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.
This commit is contained in:
parent
6510b0e14f
commit
3ada8f326c
|
@ -6,6 +6,7 @@
|
||||||
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
|
#define die(...) errx(EXIT_FAILURE, __VA_ARGS__);
|
||||||
|
|
||||||
char *convert_ucs_to_utf8(char *input);
|
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_colorpixel(xcb_connection_t *conn, char *hex);
|
||||||
uint32_t get_mode_switch_mask(xcb_connection_t *conn);
|
uint32_t get_mode_switch_mask(xcb_connection_t *conn);
|
||||||
int connect_ipc(char *socket_path);
|
int connect_ipc(char *socket_path);
|
||||||
|
|
|
@ -46,6 +46,9 @@ static char *glyphs_utf8[512];
|
||||||
static int input_position;
|
static int input_position;
|
||||||
static int font_height;
|
static int font_height;
|
||||||
static char *command_prefix;
|
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
|
* 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 */
|
/* restore font color */
|
||||||
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
|
xcb_change_gc_single(conn, pixmap_gc, XCB_GC_FOREGROUND, get_colorpixel(conn, "#FFFFFF"));
|
||||||
uint8_t *con = concat_strings(glyphs_ucs, input_position);
|
uint8_t *con = concat_strings(glyphs_ucs, input_position);
|
||||||
xcb_image_text_16(conn, input_position, pixmap, pixmap_gc, 4 /* X */,
|
char *full_text = (char*)con;
|
||||||
font_height + 2 /* Y = baseline of font */, (xcb_char2b_t*)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 */
|
/* 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_copy_area(conn, pixmap, win, pixmap_gc, 0, 0, 0, 0, /* */ 500, font_height + 8);
|
||||||
xcb_flush(conn);
|
xcb_flush(conn);
|
||||||
free(con);
|
free(con);
|
||||||
|
if (prompt != NULL)
|
||||||
|
free(full_text);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -115,6 +128,25 @@ static int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_rel
|
||||||
return 1;
|
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
|
* Handles keypresses by converting the keycodes to keysymbols, then the
|
||||||
* keysymbols to UCS-2. If the conversion succeeded, the glyph is saved in 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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sym == XK_Return) {
|
if (sym == XK_Return)
|
||||||
uint8_t *command = concat_strings(glyphs_utf8, input_position);
|
finish_input();
|
||||||
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_BackSpace) {
|
if (sym == XK_BackSpace) {
|
||||||
if (input_position == 0)
|
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);
|
glyphs_utf8[input_position] = strdup(out);
|
||||||
input_position++;
|
input_position++;
|
||||||
|
|
||||||
|
if (input_position == limit)
|
||||||
|
finish_input();
|
||||||
|
|
||||||
handle_expose(NULL, conn, NULL);
|
handle_expose(NULL, conn, NULL);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -220,30 +239,43 @@ int main(int argc, char *argv[]) {
|
||||||
static struct option long_options[] = {
|
static struct option long_options[] = {
|
||||||
{"socket", required_argument, 0, 's'},
|
{"socket", required_argument, 0, 's'},
|
||||||
{"version", no_argument, 0, 'v'},
|
{"version", no_argument, 0, 'v'},
|
||||||
|
{"limit", required_argument, 0, 'l'},
|
||||||
|
{"prompt", required_argument, 0, 'P'},
|
||||||
{"prefix", required_argument, 0, 'p'},
|
{"prefix", required_argument, 0, 'p'},
|
||||||
{"help", no_argument, 0, 'h'},
|
{"help", no_argument, 0, 'h'},
|
||||||
{0, 0, 0, 0}
|
{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) {
|
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
|
||||||
if (o == 's') {
|
switch (o) {
|
||||||
|
case 's':
|
||||||
socket_path = strdup(optarg);
|
socket_path = strdup(optarg);
|
||||||
} else if (o == 'v') {
|
break;
|
||||||
|
case 'v':
|
||||||
printf("i3-input " I3_VERSION);
|
printf("i3-input " I3_VERSION);
|
||||||
return 0;
|
return 0;
|
||||||
} else if (o == 'p') {
|
case 'p':
|
||||||
command_prefix = strdup(optarg);
|
command_prefix = strdup(optarg);
|
||||||
} else if (o == 'h') {
|
break;
|
||||||
|
case 'l':
|
||||||
|
limit = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
prompt = strdup(optarg);
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
printf("i3-input " I3_VERSION);
|
printf("i3-input " I3_VERSION);
|
||||||
printf("i3-input [-s <socket>] [-p <prefix>]\n");
|
printf("i3-input [-s <socket>] [-p <prefix>] [-l <limit>] [-P <prompt>] [-v]\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sockfd = connect_ipc(socket_path);
|
sockfd = connect_ipc(socket_path);
|
||||||
|
|
||||||
|
prompt = convert_utf8_to_ucs2(prompt, &prompt_len);
|
||||||
|
|
||||||
int screens;
|
int screens;
|
||||||
xcb_connection_t *conn = xcb_connect(NULL, &screens);
|
xcb_connection_t *conn = xcb_connect(NULL, &screens);
|
||||||
if (xcb_connection_has_error(conn))
|
if (xcb_connection_has_error(conn))
|
||||||
|
|
|
@ -10,10 +10,12 @@
|
||||||
*/
|
*/
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
#include <iconv.h>
|
#include <iconv.h>
|
||||||
|
|
||||||
static iconv_t conversion_descriptor = 0;
|
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
|
* 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;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,13 @@ void client_unmap(xcb_connection_t *conn, Client *client);
|
||||||
*/
|
*/
|
||||||
void client_map(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.
|
* Pretty-prints the client’s information into the logfile.
|
||||||
*
|
*
|
||||||
|
|
|
@ -376,6 +376,9 @@ struct Client {
|
||||||
/** Holds the WM_CLASS, useful for matching the client in commands */
|
/** Holds the WM_CLASS, useful for matching the client in commands */
|
||||||
char *window_class;
|
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
|
/** Holds the xcb_window_t (just an ID) for the leader window (logical
|
||||||
* parent for toolwindows and similar floating windows) */
|
* parent for toolwindows and similar floating windows) */
|
||||||
xcb_window_t leader;
|
xcb_window_t leader;
|
||||||
|
|
28
src/client.c
28
src/client.c
|
@ -24,6 +24,7 @@
|
||||||
#include "queue.h"
|
#include "queue.h"
|
||||||
#include "layout.h"
|
#include "layout.h"
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
|
#include "table.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Removes the given client from the container, either because it will be inserted into another
|
* 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);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
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) {
|
static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) {
|
||||||
LOG("focusing direction %d\n", direction);
|
LOG("focusing direction %d\n", direction);
|
||||||
|
|
||||||
|
@ -817,6 +833,38 @@ void parse_command(xcb_connection_t *conn, const char *command) {
|
||||||
return;
|
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>? */
|
/* Is it an <exit>? */
|
||||||
if (STARTS_WITH(command, "exit")) {
|
if (STARTS_WITH(command, "exit")) {
|
||||||
LOG("User issued exit-command, exiting without error.\n");
|
LOG("User issued exit-command, exiting without error.\n");
|
||||||
|
|
Loading…
Reference in New Issue