2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * Core input translation.
6 * This module transalates keystrokes and mouse events into commands.
7 * This involves tracking the current 'mode' state.
11 #define _GNU_SOURCE /* for asprintf */
18 #define PANE_DATA_TYPE struct input_mode
23 const char *mode safe;
24 const char *context safe; /* for status lines */
26 struct pane *focus, *source, *grab;
29 struct timespec last_action;
39 #include "core-pane.h"
41 /* 'head' is 1 more than the last key added. */
42 static void log_add(struct input_mode *im safe,
43 const char *type safe, const char *key safe)
45 free(im->log[im->head]);
46 im->log[im->head] = strconcat(NULL, type, key);
47 im->head = (im->head + 1) % LOGSIZE;
50 static void log_dump(struct pane *p safe)
52 struct input_mode *im = p->data;
62 line = strconcat(p, line, ", ", im->log[i]);
64 line = strconcat(p, im->log[i]);
67 LOG("Input %d: %s", cnt/10, line);
70 } while ((i = (i+1) % LOGSIZE) != im->head);
73 LOG("Input: %s", line);
82 static void report_status(struct pane *focus safe, struct input_mode *im safe)
86 if (im->num == NO_NUMERIC && im->mode[0] == 0)
89 if (im->num == NO_NUMERIC)
90 asprintf(&st, " %s", im->mode);
91 else if (im->num == -NO_NUMERIC)
92 asprintf(&st, " - %s", im->mode);
94 asprintf(&st, " %d %s", im->num, im->mode);
95 call("Message:modal", focus, 0, NULL, st);
100 struct input_mode *im = ci->home->data;
103 free((void*)im->mode);
104 im->mode = strdup(ci->str);
107 free((void*)im->context);
108 im->context = strdup(ci->str2);
109 attr_set_str(&ci->home->attrs, "display-context", im->context);
110 call("window:notify:display-context", ci->home);
112 report_status(ci->focus, im);
118 struct input_mode *im = ci->home->data;
121 report_status(ci->focus, im);
127 struct input_mode *im = ci->home->data;
135 struct input_mode *im = ci->home->data;
138 free((void*)im->mode);
139 im->mode = strdup(ci->str);
142 free((void*)im->context);
143 im->context = strdup(ci->str2);
144 attr_set_str(&ci->home->attrs, "display-context", im->context);
145 call("window:notify:display-context", ci->home);
149 report_status(ci->focus, im);
153 static const char *safe ctrl_map[][2] = {
154 { ":Backspace", ":C-H" },
155 { ":Enter", ":C-M" },
160 { ":A:Backspace",":A:C-H" },
161 { ":A:Enter", ":A:C-M" },
162 { ":A:ESC", ":A:C-[" },
163 { ":A:LF", ":A:C-J" },
164 { ":A:Tab", ":A:C-I" },
165 { ":A:Del", ":A:C-?" },
169 static const char *map_key(const char *key safe)
173 for (i = 0; i < ARRAY_SIZE(ctrl_map) ; i++) {
174 if (strcmp(key, ctrl_map[i][0]) == 0)
175 return ctrl_map[i][1];
183 struct input_mode *im = ci->home->data;
188 const char *mode = im->mode;
195 call("window:notify:Keystroke-notify", ci->home, 0, NULL, ci->str);
196 log_add(im, "K", ci->str);
198 im->mode = strdup("");
199 im->num = NO_NUMERIC;
202 if (im->source != ci->focus) {
203 im->source = ci->focus;
208 if (!im->focus || im->focus->focus) {
209 p = pane_focus(ci->focus);
211 pane_add_notify(ci->home, p, "Notify:Close");
216 im->point = call_ret(mark, "doc:point", p);
219 key = strconcat(ci->home, "K", mode, ci->str);
220 time_starts(ci->home);
221 ret = call(key, p, num, m, NULL, num2);
222 if (ret == Efallthrough && (alt = map_key(ci->str)) != NULL) {
223 key = strconcat(ci->home, "K", mode, alt);
224 ret = call(key, p, num, m, NULL, num2);
228 call("Message:default", p, 0, NULL,
229 strconcat(ci->home, "** Command ", key, " Failed **"));
230 else if (ret == Efallthrough) {
231 key = strconcat(ci->home, "K", mode, ci->str);
232 call("Message:modal", p, 0, NULL,
233 strconcat(ci->home, "** Command ", key, " not known **"));
238 DEF_CMD(keystroke_sequence)
240 struct pane *home = ci->home;
241 const char *c = ci->str;
242 const char *e, *dash;
247 while ((e = strchr(c, ' ')) != NULL) {
249 if (*c != ':' || e == c+1)
251 ret = call("Keystroke", home, 0, NULL,
252 strconcat(home, dash,
253 strnsave(home, c, e - c)));
261 if (*c != ':' || c[1] == '\0')
263 ret = call("Keystroke", home, 0, NULL, strconcat(home, dash, c));
269 static int tspec_diff_ms(struct timespec *a safe, struct timespec *b safe)
271 return ((a->tv_sec - b->tv_sec) * 1000 +
272 (a->tv_nsec - b->tv_nsec) / 1000000);
277 struct input_mode *im = ci->home->data;
288 char *mod; /* :A:C:S modifiers - optional */
289 struct mouse_state *ms = NULL;
291 clock_gettime(CLOCK_MONOTONIC, &now);
296 call("window:notify:Mouse-event-notify", ci->home,
297 ci->num, NULL, ci->str,
299 log_add(im, "M", ci->str);
305 } else if (ci->num2 == 2) {
317 ms = &im->buttons[b];
319 /* FIXME the max movement for a double-click should be
320 * about a char width - maybe something based on scale
322 if (tspec_diff_ms(&now, &ms->last_action) <= 500 &&
323 (abs(ci->x - ms->last_x) +
324 abs(ci->y - ms->last_y)) <= 2)
325 /* FIXME should I let 'release' know that
326 * it was close to the 'press'??
330 /* Too much time or space has elapsed.
331 * This cannot be a click.
338 else if (ms->click_count < 3)
339 ms->click_count += 1;
341 ms->last_action = now;
342 ms->last_x = ci->x; ms->last_y = ci->y;
348 /* FIXME is there any point in this? */
349 xy = pane_mapxy(ci->focus, focus, ci->x, ci->y, False);
351 mode = strsave(ci->home, im->mode);
352 free((void*)im->mode);
353 im->mode = strdup("");
354 im->num = NO_NUMERIC;
357 if (im->grab && !press) {
358 /* Release and Motion should go to the same
359 * place as Press went.
362 xy = pane_mapxy(focus, ci->focus, 0, 0, False);
366 struct pane *t, *chld = NULL;
368 list_for_each_entry(t, &focus->children, siblings) {
371 if (xy.x < t->x || xy.x >= t->x + t->w)
373 if (xy.y < t->y || xy.y >= t->y + t->h)
375 if (chld == NULL || t->z > chld->z)
378 /* descend into chld */
385 if (im->grab != focus) {
387 pane_add_notify(ci->home, focus, "Notify:Close");
392 key = strconcat(ci->home, "M", mode, ci->str);
393 time_starts(ci->home);
394 ret = call(key, focus, num, NULL, NULL, ex, NULL, NULL,
400 /* identify and save modifiers */
403 mod = strdup(ci->str2);
405 char *c = strrchr(ci->str, ':');
407 mod = strndup(ci->str, c - ci->str);
416 else if (ms->click_on_up)
421 /* Try :nPress :(n-1)Press ... (or :nRelease or :nClick)
422 * until something gets a result.
423 * 'n' is T (triple) or D(double) or ""(Single).
424 * If nothing got a result for a Press,, register for
425 * 'click' on release.
428 for (r = ms->click_count; r >= 1 ; r--) {
430 char *mult = "\0\0D\0T" + (r-1)*2;
435 key = strconcat(ci->home, "M", mode, ms->mod, ":", mult,
437 time_starts(ci->home);
438 ret = call(key, focus, num, NULL, NULL, ex,
439 NULL, NULL, xy.x, xy.y);
442 /* If this is 'press', then don't want
443 * click_on_up. If this is release, it
444 * don't matter what we set.
455 struct input_mode *im = ci->home->data;
457 im->grab = ci->focus;
458 pane_add_notify(ci->home, ci->focus, "Notify:Close");
462 DEF_CMD(input_cancel)
464 /* Other handlers fall-through. We catch so that
465 * it doesn't appear to be ignored
472 struct input_mode *im = ci->home->data;
482 struct input_mode *im = ci->home->data;
484 if (im->focus == ci->focus) {
490 if (im->grab == ci->focus)
495 DEF_CMD_CLOSED(input_close)
497 struct input_mode *im = ci->home->data;
500 for (i = 0; i < 3; i++) {
501 free(im->buttons[i].mod);
502 im->buttons[i].mod = NULL;
504 free((void*)im->mode);
505 free((void*)im->context);
509 static struct map *im_map;
510 static void register_map(void)
514 im_map = key_alloc();
515 key_add(im_map, "Keystroke", &keystroke);
516 key_add(im_map, "Keystroke-sequence", &keystroke_sequence);
517 key_add(im_map, "Mouse-event", &mouse_event);
518 key_add(im_map, "Mouse-grab", &mouse_grab);
519 key_add(im_map, "Mode:set-mode", &set_mode);
520 key_add(im_map, "Mode:set-num", &set_num);
521 key_add(im_map, "Mode:set-num2", &set_num2);
522 key_add(im_map, "Mode:set-all", &set_all);
523 key_add(im_map, "pane:refocus", &refocus);
524 key_add(im_map, "Notify:Close", &close_focus);
525 key_add(im_map, "input:log", &log_input);
526 key_add(im_map, "Close", &input_close);
528 key_add(im_map, "Cancel", &input_cancel);
529 key_add(im_map, "Abort", &input_cancel);
532 DEF_LOOKUP_CMD(input_handle, im_map);
533 DEF_CMD(input_attach)
535 struct input_mode *im;
540 p = pane_register(ci->focus, 0, &input_handle.c);
544 im->mode = strdup("");
545 im->context = strdup("");
546 im->num = NO_NUMERIC;
549 return comm_call(ci->comm2, "callback:attach", p);
552 void edlib_init(struct pane *ed safe)
554 call_comm("global-set-command", ed, &input_attach, 0, NULL, "attach-input");