]> git.sur5r.net Git - i3/i3/blobdiff - i3-config-wizard/main.c
logging: make libi3 use verboselog()/errorlog(), provide it in each caller
[i3/i3] / i3-config-wizard / main.c
index d66eda087ec5cd79b62334a9fe21d0a23d970a6e..679c5e6db5206eefb951c7d115099cb7fbf86044 100644 (file)
@@ -2,15 +2,25 @@
  * vim:ts=4:sw=4:expandtab
  *
  * i3 - an improved dynamic tiling window manager
- *
- * © 2011 Michael Stapelberg and contributors
- *
- * See file LICENSE for license information.
+ * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE)
  *
  * i3-config-wizard: Program to convert configs using keycodes to configs using
- * keysyms.
+ *                   keysyms.
  *
  */
+#if defined(__FreeBSD__)
+#include <sys/param.h>
+#endif
+
+/* For systems without getline, fall back to fgetln */
+#if defined(__APPLE__) || (defined(__FreeBSD__) && __FreeBSD_version < 800000) || defined(__OpenBSD__)
+#define USE_FGETLN
+#elif defined(__FreeBSD__)
+/* Defining this macro before including stdio.h is necessary in order to have
+ * a prototype for getline in FreeBSD. */
+#define _WITH_GETLINE
+#endif
+
 #include <ev.h>
 #include <stdio.h>
 #include <sys/types.h>
 while (0)
 
 #include "xcb.h"
-#include "ipc.h"
+#include "libi3.h"
 
 enum { STEP_WELCOME, STEP_GENERATE } current_step = STEP_WELCOME;
-enum { MOD_ALT, MOD_SUPER } modifier = MOD_SUPER;
+enum { MOD_Mod1, MOD_Mod4 } modifier = MOD_Mod4;
 
 static char *config_path;
-static xcb_connection_t *conn;
-static uint32_t font_id;
-static uint32_t font_bold_id;
+static uint32_t xcb_numlock_mask;
+xcb_connection_t *conn;
+xcb_screen_t *root_screen;
+static xcb_get_modifier_mapping_reply_t *modmap_reply;
+static i3Font font;
+static i3Font bold_font;
 static char *socket_path;
-static int font_height;
-static int font_bold_height;
 static xcb_window_t win;
 static xcb_pixmap_t pixmap;
 static xcb_gcontext_t pixmap_gc;
@@ -73,29 +84,25 @@ Display *dpy;
 char *rewrite_binding(const char *bindingline);
 static void finish();
 
-#if defined(__APPLE__)
-
 /*
- * Taken from FreeBSD
- * Returns a pointer to a new string which is a duplicate of the
- * string, but only copies at most n characters.
+ * Having verboselog() and errorlog() is necessary when using libi3.
  *
  */
-char *strndup(const char *str, size_t n) {
-    size_t len;
-    char *copy;
-
-    for (len = 0; len < n && str[len]; len++)
-        continue;
-
-    if ((copy = malloc(len + 1)) == NULL)
-        return (NULL);
-    memcpy(copy, str, len);
-    copy[len] = '\0';
-    return (copy);
+void verboselog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stdout, fmt, args);
+    va_end(args);
 }
 
-#endif
+void errorlog(char *fmt, ...) {
+    va_list args;
+
+    va_start(args, fmt);
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+}
 
 /*
  * This function resolves ~ in pathnames.
@@ -129,111 +136,70 @@ static char *resolve_tilde(const char *path) {
     return result;
 }
 
-/*
- * Try to get the socket path from X11 and return NULL if it doesn’t work.
- * As i3-msg is a short-running tool, we don’t bother with cleaning up the
- * connection and leave it up to the operating system on exit.
- *
- */
-static char *socket_path_from_x11() {
-    xcb_connection_t *conn;
-    int screen;
-    if ((conn = xcb_connect(NULL, &screen)) == NULL ||
-        xcb_connection_has_error(conn))
-        return NULL;
-    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screen);
-    xcb_window_t root = root_screen->root;
-
-    xcb_intern_atom_cookie_t atom_cookie;
-    xcb_intern_atom_reply_t *atom_reply;
-
-    atom_cookie = xcb_intern_atom(conn, 0, strlen("I3_SOCKET_PATH"), "I3_SOCKET_PATH");
-    atom_reply = xcb_intern_atom_reply(conn, atom_cookie, NULL);
-    if (atom_reply == NULL)
-        return NULL;
-
-    xcb_get_property_cookie_t prop_cookie;
-    xcb_get_property_reply_t *prop_reply;
-    prop_cookie = xcb_get_property_unchecked(conn, false, root, atom_reply->atom,
-                                             XCB_GET_PROPERTY_TYPE_ANY, 0, PATH_MAX);
-    prop_reply = xcb_get_property_reply(conn, prop_cookie, NULL);
-    if (prop_reply == NULL || xcb_get_property_value_length(prop_reply) == 0)
-        return NULL;
-    if (asprintf(&socket_path, "%.*s", xcb_get_property_value_length(prop_reply),
-                 (char*)xcb_get_property_value(prop_reply)) == -1)
-        return NULL;
-    return socket_path;
-}
-
 /*
  * Handles expose events, that is, draws the window contents.
  *
  */
 static int handle_expose() {
     /* re-draw the background */
-    xcb_rectangle_t border = {0, 0, 300, (15*font_height) + 8},
-                    inner = {2, 2, 296, (15*font_height) + 8 - 4};
-    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#285577"));
+    xcb_rectangle_t border = {0, 0, 300, (15 * font.height) + 8};
+    xcb_change_gc(conn, pixmap_gc, XCB_GC_FOREGROUND, (uint32_t[]){ get_colorpixel("#000000") });
     xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &border);
-    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#000000"));
-    xcb_poly_fill_rectangle(conn, pixmap, pixmap_gc, 1, &inner);
 
-    xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
+    set_font(&font);
 
-#define txt(x, row, text) xcb_image_text_8(conn, strlen(text), pixmap, pixmap_gc, x, (row * font_height) + 2, text)
+#define txt(x, row, text) \
+    draw_text_ascii(text, pixmap, pixmap_gc,\
+            x, (row - 1) * font.height + 4, 300 - x * 2)
 
     if (current_step == STEP_WELCOME) {
         /* restore font color */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
+        set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
 
-        txt(10, 1, "i3: first configuration");
-        txt(10, 4, "You have not configured i3 yet.");
-        txt(10, 5, "Do you want me to generate ~/.i3/config?");
-        txt(85, 8, "Yes, generate ~/.i3/config");
-        txt(85, 10, "No, I will use the defaults");
+        txt(10, 2, "You have not configured i3 yet.");
+        txt(10, 3, "Do you want me to generate ~/.i3/config?");
+        txt(85, 5, "Yes, generate ~/.i3/config");
+        txt(85, 7, "No, I will use the defaults");
 
         /* green */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#00FF00"));
-        txt(25, 8, "<Enter>");
+        set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
+        txt(25, 5, "<Enter>");
 
         /* red */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
-        txt(31, 10, "<ESC>");
+        set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
+        txt(31, 7, "<ESC>");
     }
 
     if (current_step == STEP_GENERATE) {
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
+        set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
 
-        txt(10, 1, "i3: generate config");
-
-        txt(10, 4, "Please choose either:");
-        txt(85, 6, "Win as default modifier");
-        txt(85, 7, "Alt as default modifier");
-        txt(10, 9, "Afterwards, press");
-        txt(85, 11, "to write ~/.i3/config");
-        txt(85, 12, "to abort");
+        txt(10, 2, "Please choose either:");
+        txt(85, 4, "Win as default modifier");
+        txt(85, 5, "Alt as default modifier");
+        txt(10, 7, "Afterwards, press");
+        txt(85, 9, "to write ~/.i3/config");
+        txt(85, 10, "to abort");
 
         /* the not-selected modifier */
-        if (modifier == MOD_SUPER)
-            txt(31, 7, "<Alt>");
-        else txt(31, 6, "<Win>");
+        if (modifier == MOD_Mod4)
+            txt(31, 5, "<Alt>");
+        else txt(31, 4, "<Win>");
 
         /* the selected modifier */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_bold_id);
-        if (modifier == MOD_SUPER)
-            txt(31, 6, "<Win>");
-        else txt(31, 7, "<Alt>");
+        set_font(&bold_font);
+        set_font_colors(pixmap_gc, get_colorpixel("#FFFFFF"), get_colorpixel("#000000"));
+        if (modifier == MOD_Mod4)
+            txt(10, 4, "-> <Win>");
+        else txt(10, 5, "-> <Alt>");
 
         /* green */
-        uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_FONT;
-        uint32_t values[] = { get_colorpixel(conn, "#00FF00"), font_id };
-        xcb_change_gc(conn, pixmap_gc, mask, values);
-
-        txt(25, 11, "<Enter>");
+        set_font(&font);
+        set_font_colors(pixmap_gc, get_colorpixel("#00FF00"), get_colorpixel("#000000"));
+        txt(25, 9, "<Enter>");
 
         /* red */
-        xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FF0000"));
-        txt(31, 12, "<ESC>");
+        set_font_colors(pixmap_gc, get_colorpixel("#FF0000"), get_colorpixel("#000000"));
+        txt(31, 10, "<ESC>");
     }
 
     /* Copy the contents of the pixmap to the real window */
@@ -257,8 +223,19 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
     printf("sym = %c (%d)\n", sym, sym);
 
     if (sym == XK_Return || sym == XK_KP_Enter) {
-        if (current_step == STEP_WELCOME)
+        if (current_step == STEP_WELCOME) {
             current_step = STEP_GENERATE;
+            /* Set window title */
+            xcb_change_property(conn,
+                XCB_PROP_MODE_REPLACE,
+                win,
+                A__NET_WM_NAME,
+                A_UTF8_STRING,
+                8,
+                strlen("i3: generate config"),
+                "i3: generate config");
+            xcb_flush(conn);
+        }
         else finish();
     }
 
@@ -266,16 +243,62 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
     if (sym == XK_Escape)
         exit(0);
 
-    if (sym == XK_Alt_L)
-        modifier = MOD_ALT;
+    /* Check if this is Mod1 or Mod4. The modmap contains Shift, Lock, Control,
+     * Mod1, Mod2, Mod3, Mod4, Mod5 (in that order) */
+    xcb_keycode_t *modmap = xcb_get_modifier_mapping_keycodes(modmap_reply);
+    /* Mod1? */
+    int mask = 3;
+    for (int i = 0; i < modmap_reply->keycodes_per_modifier; i++) {
+        xcb_keycode_t code = modmap[(mask * modmap_reply->keycodes_per_modifier) + i];
+        if (code == XCB_NONE)
+            continue;
+        printf("Modifier keycode for Mod1: 0x%02x\n", code);
+        if (code == event->detail) {
+            modifier = MOD_Mod1;
+            printf("This is Mod1!\n");
+        }
+    }
 
-    if (sym == XK_Super_L)
-        modifier = MOD_SUPER;
+    /* Mod4? */
+    mask = 6;
+    for (int i = 0; i < modmap_reply->keycodes_per_modifier; i++) {
+        xcb_keycode_t code = modmap[(mask * modmap_reply->keycodes_per_modifier) + i];
+        if (code == XCB_NONE)
+            continue;
+        printf("Modifier keycode for Mod4: 0x%02x\n", code);
+        if (code == event->detail) {
+            modifier = MOD_Mod4;
+            printf("This is Mod4!\n");
+        }
+    }
 
     handle_expose();
     return 1;
 }
 
+/*
+ * Handle button presses to make clicking on "<win>" and "<alt>" work
+ *
+ */
+static void handle_button_press(xcb_button_press_event_t* event) {
+    if (current_step != STEP_GENERATE)
+        return;
+
+    if (event->event_x >= 32 && event->event_x <= 68 &&
+        event->event_y >= 45 && event->event_y <= 54) {
+        modifier = MOD_Mod4;
+        handle_expose();
+    }
+
+    if (event->event_x >= 32 && event->event_x <= 68 &&
+        event->event_y >= 56 && event->event_y <= 70) {
+        modifier = MOD_Mod1;
+        handle_expose();
+    }
+
+    return;
+}
+
 /*
  * Creates the config file and tells i3 to reload.
  *
@@ -293,10 +316,11 @@ static void finish() {
     FILE *ks_config = fopen(config_path, "w");
     if (ks_config == NULL)
         err(1, "Could not open output config file \"%s\"", config_path);
+    free(config_path);
 
     char *line = NULL;
     size_t len = 0;
-#if !defined(__APPLE__)
+#ifndef USE_FGETLN
     ssize_t read;
 #endif
     bool head_of_file = true;
@@ -309,10 +333,16 @@ static void finish() {
     fputs("# this file and re-run i3-config-wizard(1).\n", ks_config);
     fputs("#\n", ks_config);
 
-#if defined(__APPLE__)
-    while ((line = fgetln(kc_config, &len)) != NULL) {
+#ifdef USE_FGETLN
+    char *buf = NULL;
+    while ((buf = fgetln(kc_config, &len)) != NULL) {
+        /* fgetln does not return null-terminated strings */
+        FREE(line);
+        sasprintf(&line, "%.*s", len, buf);
 #else
-    while ((read = getline(&line, &len, kc_config)) != -1) {
+    size_t linecap = 0;
+    while ((read = getline(&line, &linecap, kc_config)) != -1) {
+        len = strlen(line);
 #endif
         /* skip the warning block at the beginning of the input file */
         if (head_of_file &&
@@ -323,12 +353,15 @@ static void finish() {
 
         /* Skip leading whitespace */
         char *walk = line;
-        while (isspace(*walk) && walk < (line + len))
+        while (isspace(*walk) && walk < (line + len)) {
+            /* Pre-output the skipped whitespaces to keep proper indentation */
+            fputc(*walk, ks_config);
             walk++;
+        }
 
         /* Set the modifier the user chose */
         if (strncmp(walk, "set $mod ", strlen("set $mod ")) == 0) {
-            if (modifier == MOD_ALT)
+            if (modifier == MOD_Mod1)
                 fputs("set $mod Mod1\n", ks_config);
             else fputs("set $mod Mod4\n", ks_config);
             continue;
@@ -337,7 +370,7 @@ static void finish() {
         /* Check for 'bindcode'. If it’s not a bindcode line, we
          * just copy it to the output file */
         if (strncmp(walk, "bindcode", strlen("bindcode")) != 0) {
-            fputs(line, ks_config);
+            fputs(walk, ks_config);
             continue;
         }
         char *result = rewrite_binding(walk);
@@ -349,12 +382,15 @@ static void finish() {
     fflush(ks_config);
     fsync(fileno(ks_config));
 
+#ifndef USE_FGETLN
     free(line);
+#endif
+
     fclose(kc_config);
     fclose(ks_config);
 
     /* tell i3 to reload the config file */
-    int sockfd = connect_ipc(socket_path);
+    int sockfd = ipc_connect(socket_path);
     ipc_send_message(sockfd, strlen("reload"), 0, (uint8_t*)"reload");
     close(sockfd);
 
@@ -421,28 +457,90 @@ int main(int argc, char *argv[]) {
     unlink(config_path);
 
     if (socket_path == NULL)
-        socket_path = socket_path_from_x11();
+        socket_path = root_atom_contents("I3_SOCKET_PATH");
 
     if (socket_path == NULL)
         socket_path = "/tmp/i3-ipc.sock";
 
     int screens;
-    conn = xcb_connect(NULL, &screens);
-    if (xcb_connection_has_error(conn))
+    if ((conn = xcb_connect(NULL, &screens)) == NULL ||
+        xcb_connection_has_error(conn))
         errx(1, "Cannot open display\n");
 
-    xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
+    xcb_get_modifier_mapping_cookie_t modmap_cookie;
+    modmap_cookie = xcb_get_modifier_mapping(conn);
+    symbols = xcb_key_symbols_alloc(conn);
+
+    /* Place requests for the atoms we need as soon as possible */
+    #define xmacro(atom) \
+        xcb_intern_atom_cookie_t atom ## _cookie = xcb_intern_atom(conn, 0, strlen(#atom), #atom);
+    #include "atoms.xmacro"
+    #undef xmacro
+
+    root_screen = xcb_aux_get_screen(conn, screens);
     root = root_screen->root;
 
-    xcb_get_numlock_mask(conn);
+    if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL)))
+        errx(EXIT_FAILURE, "Could not get modifier mapping\n");
 
-    symbols = xcb_key_symbols_alloc(conn);
+    xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply);
 
-    font_id = get_font_id(conn, pattern, &font_height);
-    font_bold_id = get_font_id(conn, patternbold, &font_bold_height);
+    font = load_font(pattern, true);
+    bold_font = load_font(patternbold, true);
 
     /* Open an input window */
-    win = open_input_window(conn, 300, 205);
+    win = xcb_generate_id(conn);
+    xcb_create_window(
+        conn,
+        XCB_COPY_FROM_PARENT,
+        win, /* the window id */
+        root, /* parent == root */
+        490, 297, 300, 205, /* dimensions */
+        0, /* X11 border = 0, we draw our own */
+        XCB_WINDOW_CLASS_INPUT_OUTPUT,
+        XCB_WINDOW_CLASS_COPY_FROM_PARENT, /* copy visual from parent */
+        XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
+        (uint32_t[]){
+            0, /* back pixel: black */
+            XCB_EVENT_MASK_EXPOSURE |
+            XCB_EVENT_MASK_BUTTON_PRESS
+        });
+
+    /* Map the window (make it visible) */
+    xcb_map_window(conn, win);
+
+    /* Setup NetWM atoms */
+    #define xmacro(name) \
+        do { \
+            xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, name ## _cookie, NULL); \
+            if (!reply) \
+                errx(EXIT_FAILURE, "Could not get atom " # name "\n"); \
+            \
+            A_ ## name = reply->atom; \
+            free(reply); \
+        } while (0);
+    #include "atoms.xmacro"
+    #undef xmacro
+
+    /* Set dock mode */
+    xcb_change_property(conn,
+        XCB_PROP_MODE_REPLACE,
+        win,
+        A__NET_WM_WINDOW_TYPE,
+        A_ATOM,
+        32,
+        1,
+        (unsigned char*) &A__NET_WM_WINDOW_TYPE_DIALOG);
+
+    /* Set window title */
+    xcb_change_property(conn,
+        XCB_PROP_MODE_REPLACE,
+        win,
+        A__NET_WM_NAME,
+        A_UTF8_STRING,
+        8,
+        strlen("i3: first configuration"),
+        "i3: first configuration");
 
     /* Create pixmap */
     pixmap = xcb_generate_id(conn);
@@ -450,10 +548,6 @@ int main(int argc, char *argv[]) {
     xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, 500);
     xcb_create_gc(conn, pixmap_gc, pixmap, 0, 0);
 
-    /* Set input focus (we have override_redirect=1, so the wm will not do
-     * this for us) */
-    xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
-
     /* Grab the keyboard to get all input */
     xcb_flush(conn);
 
@@ -494,6 +588,10 @@ int main(int argc, char *argv[]) {
 
             /* TODO: handle mappingnotify */
 
+            case XCB_BUTTON_PRESS:
+                handle_button_press((xcb_button_press_event_t*)event);
+                break;
+
             case XCB_EXPOSE:
                 handle_expose();
                 break;