]> git.sur5r.net Git - i3/i3/blobdiff - i3-input/main.c
Make i3 compatible with the very latest xcb
[i3/i3] / i3-input / main.c
index 588c36b795443273e3a05dee2206d5fe9356d8b8..fc9afd30baf0f3c566e82d82800bf34db0316cd7 100644 (file)
@@ -3,7 +3,7 @@
  *
  * i3 - an improved dynamic tiling window manager
  *
- * © 2009 Michael Stapelberg and contributors
+ * © 2009-2010 Michael Stapelberg and contributors
  *
  * See file LICENSE for license information.
  *
@@ -22,6 +22,7 @@
 #include <err.h>
 #include <stdint.h>
 #include <getopt.h>
+#include <glob.h>
 
 #include <xcb/xcb.h>
 #include <xcb/xcb_aux.h>
@@ -37,6 +38,7 @@
 static int sockfd;
 static xcb_key_symbols_t *symbols;
 static int modeswitchmask;
+static int numlockmask;
 static bool modeswitch_active = false;
 static xcb_window_t win;
 static xcb_pixmap_t pixmap;
@@ -46,6 +48,24 @@ static char *glyphs_utf8[512];
 static int input_position;
 static int font_height;
 static char *command_prefix;
+static char *prompt;
+static int prompt_len;
+static int limit;
+
+/*
+ * This function resolves ~ in pathnames (and more, see glob(3)).
+ *
+ */
+static char *glob_path(const char *path) {
+        static glob_t globbuf;
+        if (glob(path, GLOB_NOCHECK | GLOB_TILDE, NULL, &globbuf) < 0)
+                errx(EXIT_FAILURE, "glob() failed");
+        char *result = strdup(globbuf.gl_pathc > 0 ? globbuf.gl_pathv[0] : path);
+        if (result == NULL)
+                err(EXIT_FAILURE, "malloc() failed");
+        globfree(&globbuf);
+        return result;
+}
 
 /*
  * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for
@@ -53,7 +73,7 @@ static char *command_prefix;
  *
  */
 static uint8_t *concat_strings(char **glyphs, int max) {
-        uint8_t *output = calloc(max, 4);
+        uint8_t *output = calloc(max+1, 4);
         uint8_t *walk = output;
         for (int c = 0; c < max; c++) {
                 printf("at %c\n", glyphs[c][0]);
@@ -88,13 +108,23 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
         /* restore font color */
         xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
         uint8_t *con = concat_strings(glyphs_ucs, input_position);
-        xcb_image_text_16(conn, input_position, pixmap, pixmap_gc, 4 /* X */,
-                          font_height + 2 /* Y = baseline of font */, (xcb_char2b_t*)con);
+        char *full_text = (char*)con;
+        if (prompt != NULL) {
+                full_text = malloc((prompt_len + input_position) * 2 + 1);
+                if (full_text == NULL)
+                        err(EXIT_FAILURE, "malloc() failed\n");
+                memcpy(full_text, prompt, prompt_len * 2);
+                memcpy(full_text + (prompt_len * 2), con, input_position * 2);
+        }
+        xcb_image_text_16(conn, input_position + prompt_len, pixmap, pixmap_gc, 4 /* X */,
+                          font_height + 2 /* Y = baseline of font */, (xcb_char2b_t*)full_text);
 
         /* Copy the contents of the pixmap to the real window */
         xcb_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font_height + 8);
         xcb_flush(conn);
         free(con);
+        if (prompt != NULL)
+                free(full_text);
 
         return 1;
 }
@@ -106,6 +136,9 @@ static int handle_expose(void *data, xcb_connection_t *conn, xcb_expose_event_t
 static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_event_t *event) {
         printf("releasing %d, state raw = %d\n", event->detail, event->state);
 
+        /* fix state */
+        event->state &= ~numlockmask;
+
         xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
         if (sym == XK_Mode_switch) {
                 printf("Mode switch disabled\n");
@@ -115,6 +148,25 @@ static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_rel
         return 1;
 }
 
+static void finish_input() {
+        uint8_t *command = concat_strings(glyphs_utf8, input_position);
+        char *full_command = (char*)command;
+        /* prefix the command if a prefix was specified on commandline */
+        if (command_prefix != NULL) {
+                if (asprintf(&full_command, "%s%s", command_prefix, command) == -1)
+                        err(EXIT_FAILURE, "asprintf() failed\n");
+        }
+        printf("command = %s\n", full_command);
+
+        ipc_send_message(sockfd, strlen(full_command), 0, (uint8_t*)full_command);
+
+#if 0
+        free(command);
+        return 1;
+#endif
+        exit(0);
+}
+
 /*
  * Handles keypresses by converting the keycodes to keysymbols, then the
  * keysymbols to UCS-2. If the conversion succeeded, the glyph is saved in the
@@ -131,6 +183,11 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
         if (modeswitch_active)
                 event->state |= modeswitchmask;
 
+        /* Apparantly, after activating numlock once, the numlock modifier
+         * stays turned on (use xev(1) to verify). So, to resolve useful
+         * keysyms, we remove the numlock flag from the event state */
+        event->state &= ~numlockmask;
+
         xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, event->state);
         if (sym == XK_Mode_switch) {
                 printf("Mode switch enabled\n");
@@ -138,24 +195,8 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
                 return 1;
         }
 
-        if (sym == XK_Return) {
-                uint8_t *command = concat_strings(glyphs_utf8, input_position);
-                char *full_command = (char*)command;
-                /* prefix the command if a prefix was specified on commandline */
-                if (command_prefix != NULL) {
-                        if (asprintf(&full_command, "%s%s", command_prefix, command) == -1)
-                                err(EXIT_FAILURE, "asprintf() failed\n");
-                }
-                printf("command = %s\n", full_command);
-
-                ipc_send_message(sockfd, strlen(full_command), 0, (uint8_t*)full_command);
-
-#if 0
-                free(command);
-                return 1;
-#endif
-                exit(0);
-        }
+        if (sym == XK_Return)
+                finish_input();
 
         if (sym == XK_BackSpace) {
                 if (input_position == 0)
@@ -208,56 +249,67 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
         glyphs_utf8[input_position] = strdup(out);
         input_position++;
 
+        if (input_position == limit)
+                finish_input();
+
         handle_expose(NULL, conn, NULL);
         return 1;
 }
 
 int main(int argc, char *argv[]) {
-        char *socket_path = "/tmp/i3-ipc.sock";
+        char *socket_path = glob_path("~/.i3/ipc.sock");
         char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
         int o, option_index = 0;
 
         static struct option long_options[] = {
                 {"socket", required_argument, 0, 's'},
                 {"version", no_argument, 0, 'v'},
+                {"limit", required_argument, 0, 'l'},
+                {"prompt", required_argument, 0, 'P'},
                 {"prefix", required_argument, 0, 'p'},
                 {"help", no_argument, 0, 'h'},
                 {0, 0, 0, 0}
         };
 
-        char *options_string = "s:p:vh";
+        char *options_string = "s:p:P:l:vh";
 
         while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
-                if (o == 's') {
-                        socket_path = strdup(optarg);
-                } else if (o == 'v') {
-                        printf("i3-input " I3_VERSION);
-                        return 0;
-                } else if (o == 'p') {
-                        command_prefix = strdup(optarg);
-                } else if (o == 'h') {
-                        printf("i3-input " I3_VERSION);
-                        printf("i3-input [-s <socket>] [-p <prefix>]\n");
-                        return 0;
+                switch (o) {
+                        case 's':
+                                socket_path = glob_path(optarg);
+                                break;
+                        case 'v':
+                                printf("i3-input " I3_VERSION);
+                                return 0;
+                        case 'p':
+                                command_prefix = strdup(optarg);
+                                break;
+                        case 'l':
+                                limit = atoi(optarg);
+                                break;
+                        case 'P':
+                                prompt = strdup(optarg);
+                                break;
+                        case 'h':
+                                printf("i3-input " I3_VERSION);
+                                printf("i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-v]\n");
+                                return 0;
                 }
         }
 
         sockfd = connect_ipc(socket_path);
 
+        if (prompt != NULL)
+                prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
+
         int screens;
         xcb_connection_t *conn = xcb_connect(NULL, &screens);
         if (xcb_connection_has_error(conn))
                 die("Cannot open display\n");
 
         /* Set up event handlers for key press and key release */
-        xcb_event_handlers_t evenths;
-        memset(&evenths, 0, sizeof(xcb_event_handlers_t));
-        xcb_event_handlers_init(conn, &evenths);
-        xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL);
-        xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL);
-        xcb_event_set_expose_handler(&evenths, handle_expose, NULL);
-
-        modeswitchmask = get_mode_switch_mask(conn);
+        modeswitchmask = get_mod_mask(conn, XK_Mode_switch);
+        numlockmask = get_mod_mask(conn, XK_Num_Lock);
        symbols = xcb_key_symbols_alloc(conn);
 
         uint32_t font_id = get_font_id(conn, pattern, &font_height);
@@ -273,15 +325,63 @@ int main(int argc, char *argv[]) {
         xcb_create_pixmap(conn, root_screen->root_depth, pixmap, win, 500, font_height + 8);
         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);
+
         /* Create graphics context */
         xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FONT, font_id);
 
         /* Grab the keyboard to get all input */
-        xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+        xcb_flush(conn);
+
+        /* Try (repeatedly, if necessary) to grab the keyboard. We might not
+         * get the keyboard at the first attempt because of the keybinding
+         * still being active when started via a wm’s keybinding. */
+        xcb_grab_keyboard_cookie_t cookie;
+        xcb_grab_keyboard_reply_t *reply = NULL;
+
+        int count = 0;
+        while ((reply == NULL || reply->status != XCB_GRAB_STATUS_SUCCESS) && (count++ < 500)) {
+                cookie = xcb_grab_keyboard(conn, false, win, XCB_CURRENT_TIME, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+                reply = xcb_grab_keyboard_reply(conn, cookie, NULL);
+                usleep(1000);
+        }
+
+        if (reply->status != XCB_GRAB_STATUS_SUCCESS) {
+                fprintf(stderr, "Could not grab keyboard, status = %d\n", reply->status);
+                exit(-1);
+        }
 
         xcb_flush(conn);
 
-        xcb_event_wait_for_event_loop(&evenths);
+        xcb_generic_event_t *event;
+        while ((event = xcb_wait_for_event(conn)) != NULL) {
+                if (event->response_type == 0) {
+                        fprintf(stderr, "X11 Error received! sequence %x\n", event->sequence);
+                        continue;
+                }
+
+                /* Strip off the highest bit (set if the event is generated) */
+                int type = (event->response_type & 0x7F);
+
+                switch (type) {
+                        case XCB_KEY_PRESS:
+                                handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
+                                break;
+
+                        case XCB_KEY_RELEASE:
+                                handle_key_release(NULL, conn, (xcb_key_release_event_t*)event);
+                                break;
+
+                        case XCB_EXPOSE:
+                                handle_expose(NULL, conn, (xcb_expose_event_t*)event);
+                                break;
+                }
+
+                free(event);
+        }
+
 
         return 0;
 }