From 3ada8f326cc48ea0f12c9f1434fbb2b76ed4c0a0 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sun, 20 Sep 2009 16:54:29 +0200 Subject: [PATCH] Implement vim-like marks Commands are 'mark' and 'goto'. Both can be used either directly, like 'mark a' and 'goto a', or interactively (just 'mark'). For interactive mode, i3-input must be installed and in your PATH. --- i3-input/i3-input.h | 1 + i3-input/main.c | 96 +++++++++++++++++++++++++++-------------- i3-input/ucs2_to_utf8.c | 49 +++++++++++++++++++++ include/client.h | 7 +++ include/data.h | 3 ++ src/client.c | 28 ++++++++++++ src/commands.c | 48 +++++++++++++++++++++ 7 files changed, 200 insertions(+), 32 deletions(-) diff --git a/i3-input/i3-input.h b/i3-input/i3-input.h index c884ba02..6c982bc5 100644 --- a/i3-input/i3-input.h +++ b/i3-input/i3-input.h @@ -6,6 +6,7 @@ #define die(...) errx(EXIT_FAILURE, __VA_ARGS__); char *convert_ucs_to_utf8(char *input); +char *convert_utf8_to_ucs2(char *input, int *real_strlen); uint32_t get_colorpixel(xcb_connection_t *conn, char *hex); uint32_t get_mode_switch_mask(xcb_connection_t *conn); int connect_ipc(char *socket_path); diff --git a/i3-input/main.c b/i3-input/main.c index 588c36b7..9b856d8a 100644 --- a/i3-input/main.c +++ b/i3-input/main.c @@ -46,6 +46,9 @@ 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; /* * Concats the glyphs (either UCS-2 or UTF-8) to a single string, suitable for @@ -88,13 +91,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; } @@ -115,6 +128,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 @@ -138,24 +170,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,6 +224,9 @@ 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; } @@ -220,30 +239,43 @@ int main(int argc, char *argv[]) { 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 ] [-p ]\n"); - return 0; + switch (o) { + case 's': + socket_path = strdup(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 ] [-p ] [-l ] [-P ] [-v]\n"); + return 0; } } sockfd = connect_ipc(socket_path); + prompt = convert_utf8_to_ucs2(prompt, &prompt_len); + int screens; xcb_connection_t *conn = xcb_connect(NULL, &screens); if (xcb_connection_has_error(conn)) diff --git a/i3-input/ucs2_to_utf8.c b/i3-input/ucs2_to_utf8.c index dcd06197..4557c9da 100644 --- a/i3-input/ucs2_to_utf8.c +++ b/i3-input/ucs2_to_utf8.c @@ -10,10 +10,12 @@ */ #include #include +#include #include #include static iconv_t conversion_descriptor = 0; +static iconv_t conversion_descriptor2 = 0; /* * Returns the input string, but converted from UCS-2 to UTF-8. Memory will be @@ -53,3 +55,50 @@ char *convert_ucs_to_utf8(char *input) { return buffer; } + +/* + * Converts the given string to UCS-2 big endian for use with + * xcb_image_text_16(). The amount of real glyphs is stored in real_strlen, + * a buffer containing the UCS-2 encoded string (16 bit per glyph) is + * returned. It has to be freed when done. + * + */ +char *convert_utf8_to_ucs2(char *input, int *real_strlen) { + size_t input_size = strlen(input) + 1; + /* UCS-2 consumes exactly two bytes for each glyph */ + int buffer_size = input_size * 2; + + char *buffer = malloc(buffer_size); + if (buffer == NULL) + err(EXIT_FAILURE, "malloc() failed\n"); + size_t output_size = buffer_size; + /* We need to use an additional pointer, because iconv() modifies it */ + char *output = buffer; + + /* We convert the input into UCS-2 big endian */ + if (conversion_descriptor2 == 0) { + conversion_descriptor2 = iconv_open("UCS-2BE", "UTF-8"); + if (conversion_descriptor2 == 0) { + fprintf(stderr, "error opening the conversion context\n"); + exit(1); + } + } + + /* Get the conversion descriptor back to original state */ + iconv(conversion_descriptor2, NULL, NULL, NULL, NULL); + + /* Convert our text */ + int rc = iconv(conversion_descriptor2, (void*)&input, &input_size, &output, &output_size); + if (rc == (size_t)-1) { + perror("Converting to UCS-2 failed"); + if (real_strlen != NULL) + *real_strlen = 0; + return NULL; + } + + if (real_strlen != NULL) + *real_strlen = ((buffer_size - output_size) / 2) - 1; + + return buffer; +} + diff --git a/include/client.h b/include/client.h index e43f81b9..88335f62 100644 --- a/include/client.h +++ b/include/client.h @@ -97,6 +97,13 @@ void client_unmap(xcb_connection_t *conn, Client *client); */ void client_map(xcb_connection_t *conn, Client *client); +/** + * Set the given mark for this client. Used for jumping to the client + * afterwards (like m and ' in vim). + * + */ +void client_mark(xcb_connection_t *conn, Client *client, const char *mark); + /** * Pretty-prints the client’s information into the logfile. * diff --git a/include/data.h b/include/data.h index 6d1869c0..0f0a243d 100644 --- a/include/data.h +++ b/include/data.h @@ -376,6 +376,9 @@ struct Client { /** Holds the WM_CLASS, useful for matching the client in commands */ char *window_class; + /** Holds the client’s mark, for vim-like jumping */ + char *mark; + /** Holds the xcb_window_t (just an ID) for the leader window (logical * parent for toolwindows and similar floating windows) */ xcb_window_t leader; diff --git a/src/client.c b/src/client.c index ce115614..93f9315d 100644 --- a/src/client.c +++ b/src/client.c @@ -24,6 +24,7 @@ #include "queue.h" #include "layout.h" #include "client.h" +#include "table.h" /* * Removes the given client from the container, either because it will be inserted into another @@ -316,3 +317,30 @@ void client_map(xcb_connection_t *conn, Client *client) { xcb_map_window(conn, client->frame); } + +/* + * Set the given mark for this client. Used for jumping to the client + * afterwards (like m and ' in vim). + * + */ +void client_mark(xcb_connection_t *conn, Client *client, const char *mark) { + if (client->mark != NULL) + free(client->mark); + client->mark = sstrdup(mark); + + /* Make sure no other client has this mark set */ + Client *current; + for (int c = 0; c < 10; c++) + SLIST_FOREACH(current, &(workspaces[c].focus_stack), focus_clients) { + if (current == client || + current->mark == NULL || + strcmp(current->mark, mark) != 0) + continue; + + free(current->mark); + current->mark = NULL; + /* We can break here since there can only be one other + * client with this mark. */ + break; + } +} diff --git a/src/commands.c b/src/commands.c index 473af8dc..5ca92b31 100644 --- a/src/commands.c +++ b/src/commands.c @@ -58,6 +58,22 @@ bool focus_window_in_container(xcb_connection_t *conn, Container *container, dir typedef enum { THING_WINDOW, THING_CONTAINER, THING_SCREEN } thing_t; +static void jump_to_mark(xcb_connection_t *conn, const char *mark) { + Client *current; + LOG("Jumping to \"%s\"\n", mark); + + for (int c = 0; c < 10; c++) + SLIST_FOREACH(current, &(workspaces[c].focus_stack), focus_clients) { + if (current->mark == NULL || strcmp(current->mark, mark) != 0) + continue; + + set_focus(conn, current, true); + return; + } + + LOG("No window with this mark found\n"); +} + static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) { LOG("focusing direction %d\n", direction); @@ -817,6 +833,38 @@ void parse_command(xcb_connection_t *conn, const char *command) { return; } + if (STARTS_WITH(command, "mark")) { + if (last_focused == NULL) { + LOG("There is no window to mark\n"); + return; + } + const char *rest = command + strlen("mark"); + while (*rest == ' ') + rest++; + if (*rest == '\0') { + LOG("interactive mark starting\n"); + start_application("i3-input -p 'mark ' -l 1 -P 'Mark: '"); + } else { + LOG("mark with \"%s\"\n", rest); + client_mark(conn, last_focused, rest); + } + return; + } + + if (STARTS_WITH(command, "goto")) { + const char *rest = command + strlen("goto"); + while (*rest == ' ') + rest++; + if (*rest == '\0') { + LOG("interactive go to mark starting\n"); + start_application("i3-input -p 'goto ' -l 1 -P 'Goto: '"); + } else { + LOG("go to \"%s\"\n", rest); + jump_to_mark(conn, rest); + } + return; + } + /* Is it an ? */ if (STARTS_WITH(command, "exit")) { LOG("User issued exit-command, exiting without error.\n");