Skip to content

Commit

Permalink
Implement vim-like marks
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
stapelberg committed Sep 20, 2009
1 parent 6510b0e commit 3ada8f3
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 32 deletions.
1 change: 1 addition & 0 deletions i3-input/i3-input.h
Expand Up @@ -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);
Expand Down
96 changes: 64 additions & 32 deletions i3-input/main.c
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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;
}
Expand All @@ -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 <socket>] [-p <prefix>]\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 <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-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))
Expand Down
49 changes: 49 additions & 0 deletions i3-input/ucs2_to_utf8.c
Expand Up @@ -10,10 +10,12 @@
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
#include <iconv.h>

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
Expand Down Expand Up @@ -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;
}

7 changes: 7 additions & 0 deletions include/client.h
Expand Up @@ -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<mark> and '<mark> in vim).
*
*/
void client_mark(xcb_connection_t *conn, Client *client, const char *mark);

/**
* Pretty-prints the client’s information into the logfile.
*
Expand Down
3 changes: 3 additions & 0 deletions include/data.h
Expand Up @@ -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;
Expand Down
28 changes: 28 additions & 0 deletions src/client.c
Expand Up @@ -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
Expand Down Expand Up @@ -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<mark> and '<mark> 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;
}
}
48 changes: 48 additions & 0 deletions src/commands.c
Expand Up @@ -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);

Expand Down Expand Up @@ -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 <exit>? */
if (STARTS_WITH(command, "exit")) {
LOG("User issued exit-command, exiting without error.\n");
Expand Down

0 comments on commit 3ada8f3

Please sign in to comment.