]> git.neil.brown.name Git - edlib.git/blob - lib-input.c
Send warning message when nested notification is prohibited.
[edlib.git] / lib-input.c
1 /*
2  * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * Core input translation.
6  * This module transalates keystrokes and mouse events into commands.
7  * This involves tracking the current 'mode' state.
8  *
9  */
10
11 #define _GNU_SOURCE /*  for asprintf */
12 #include <unistd.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <time.h>
16 #include <stdio.h>
17
18 #define PANE_DATA_TYPE struct input_mode
19 #include "core.h"
20
21 #define LOGSIZE 128
22 struct input_mode {
23         const char      *mode safe;
24         const char      *context safe; /* for status lines */
25         int             num, num2;
26         struct pane     *focus, *source, *grab;
27         struct mark     *point;
28         struct mouse_state {
29                 struct timespec last_action;
30                 int             last_x, last_y;
31                 int             click_count;
32                 int             click_on_up;
33                 char            *mod;
34         } buttons[3];
35
36         char            *log[LOGSIZE];
37         int             head;
38 };
39 #include "core-pane.h"
40
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)
44 {
45         free(im->log[im->head]);
46         im->log[im->head] = strconcat(NULL, type, key);
47         im->head = (im->head + 1) % LOGSIZE;
48 }
49
50 static void log_dump(struct pane *p safe)
51 {
52         struct input_mode *im = p->data;
53         int i;
54         int cnt = 0;
55         char *line = NULL;
56
57         i = im->head;
58         do {
59                 if (!im->log[i])
60                         continue;
61                 if (line)
62                         line = strconcat(p, line, ", ", im->log[i]);
63                 else
64                         line = strconcat(p, im->log[i]);
65                 cnt += 1;
66                 if (cnt % 10 == 0) {
67                         LOG("Input %d: %s", cnt/10, line);
68                         line = NULL;
69                 }
70         } while ((i = (i+1) % LOGSIZE) != im->head);
71
72         if (line)
73                 LOG("Input: %s", line);
74 }
75
76 DEF_CMD(log_input)
77 {
78         log_dump(ci->home);
79         return 1;
80 }
81
82 static void report_status(struct pane *focus safe, struct input_mode *im safe)
83 {
84         char *st = NULL;
85
86         if (im->num == NO_NUMERIC && im->mode[0] == 0)
87                 return;
88
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);
93         else
94                 asprintf(&st, " %d %s", im->num, im->mode);
95         call("Message:modal", focus, 0, NULL, st);
96 }
97
98 DEF_CMD(set_mode)
99 {
100         struct input_mode *im = ci->home->data;
101
102         if (ci->str) {
103                 free((void*)im->mode);
104                 im->mode = strdup(ci->str);
105         }
106         if (ci->str2) {
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);
111         }
112         report_status(ci->focus, im);
113         return 1;
114 }
115
116 DEF_CMD(set_num)
117 {
118         struct input_mode *im = ci->home->data;
119
120         im->num = ci->num;
121         report_status(ci->focus, im);
122         return 1;
123 }
124
125 DEF_CMD(set_num2)
126 {
127         struct input_mode *im = ci->home->data;
128
129         im->num2 = ci->num;
130         return 1;
131 }
132
133 DEF_CMD(set_all)
134 {
135         struct input_mode *im = ci->home->data;
136
137         if (ci->str) {
138                 free((void*)im->mode);
139                 im->mode = strdup(ci->str);
140         }
141         if (ci->str2) {
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);
146         }
147         im->num = ci->num;
148         im->num2 = ci->num;
149         report_status(ci->focus, im);
150         return 1;
151 }
152
153 static const char *safe ctrl_map[][2] = {
154         { ":Backspace", ":C-H" },
155         { ":Enter",     ":C-M" },
156         { ":ESC",       ":C-[" },
157         { ":LF",        ":C-J" },
158         { ":Tab",       ":C-I" },
159         { ":Del",       ":C-?" },
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-?" },
166         { ":SPC",       "- " },
167 };
168
169 static const char *map_key(const char *key safe)
170 {
171         unsigned int i;
172
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];
176         }
177         return NULL;
178 }
179
180 DEF_CMD(keystroke)
181 {
182         const char *key;
183         struct input_mode *im = ci->home->data;
184         struct pane *p;
185         int ret;
186         int num = im->num;
187         int num2 = im->num2;
188         const char *mode = im->mode;
189         const char *alt;
190         struct mark *m;
191
192         if (!ci->str)
193                 return Enoarg;
194
195         call("window:notify:Keystroke-notify", ci->home, 0, NULL, ci->str);
196         log_add(im, "K", ci->str);
197
198         im->mode = strdup("");
199         im->num = NO_NUMERIC;
200         im->num2 = 0;
201
202         if (im->source != ci->focus) {
203                 im->source = ci->focus;
204                 im->focus = NULL;
205                 im->point = NULL;
206         }
207
208         if (!im->focus || im->focus->focus) {
209                 p = pane_focus(ci->focus);
210                 im->focus = p;
211                 pane_add_notify(ci->home, p, "Notify:Close");
212         }
213         p = im->focus;
214
215         if (!im->point)
216                 im->point = call_ret(mark, "doc:point", p);
217         m = im->point;
218
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);
225         }
226         time_ends(ci->home);
227         if (ret <= Efail)
228                 call("Message:default", ci->focus, 0, NULL,
229                      "** Command Failed **");
230         return Efallthrough;
231 }
232
233 DEF_CMD(keystroke_sequence)
234 {
235         struct pane *home = ci->home;
236         const char *c = ci->str;
237         const char *e, *dash;
238         int ret;
239
240         if (!c)
241                 return Enoarg;
242         while ((e = strchr(c, ' ')) != NULL) {
243                 dash = "";
244                 if (*c != ':' || e == c+1)
245                         dash = "-";
246                 ret = call("Keystroke", home, 0, NULL,
247                            strconcat(home, dash,
248                                      strnsave(home, c, e - c)));
249                 if (ret < 0)
250                         return Efail;
251                 c = e+1;
252         }
253         dash = "";
254         if (*c != ':' || c[1] == '\0')
255                 dash = "-";
256         ret = call("Keystroke", home, 0, NULL, strconcat(home, dash, c));
257         if (ret < 0)
258                 return Efail;
259         return 1;
260 }
261
262 static int tspec_diff_ms(struct timespec *a safe, struct timespec *b safe)
263 {
264         return ((a->tv_sec - b->tv_sec) * 1000 +
265                 (a->tv_nsec - b->tv_nsec) / 1000000);
266 }
267
268 DEF_CMD(mouse_event)
269 {
270         struct input_mode *im = ci->home->data;
271         struct xy xy;
272         int num, ex;
273         struct pane *focus;
274         char *key;
275         struct timespec now;
276         unsigned int b;
277         int press;
278         int r;
279         const char *mode;
280         const char *cmd;
281         char *mod; /* :A:C:S modifiers - optional */
282         struct mouse_state *ms = NULL;
283
284         clock_gettime(CLOCK_MONOTONIC, &now);
285
286         if (!ci->str)
287                 return Enoarg;
288
289         call("window:notify:Mouse-event-notify", ci->home,
290              ci->num, NULL, ci->str,
291              ci->num2);
292         log_add(im, "M", ci->str);
293
294         if (ci->num2 == 1) {
295                 /* Press */
296                 press = 1;
297                 b = ci->num - 1;
298         } else if (ci->num2 == 2) {
299                 /* Release */
300                 press = 0;
301                 b = ci->num - 1;
302         } else {
303                 /* 3 is Motion */
304                 press = 0;
305                 b = 100;
306         }
307         if (b < 3) {
308                 bool repeat = False;
309
310                 ms = &im->buttons[b];
311
312                 /* FIXME the max movement for a double-click should be
313                  * about a char width - maybe something based on scale
314                  */
315                 if (tspec_diff_ms(&now, &ms->last_action) <= 500 &&
316                     (abs(ci->x - ms->last_x) +
317                      abs(ci->y - ms->last_y)) <= 2)
318                         /* FIXME should I let 'release' know that
319                          * it was close to the 'press'??
320                          */
321                         repeat = True;
322                 else
323                         /* Too much time or space has elapsed.
324                          * This cannot be a click.
325                          */
326                         ms->click_on_up = 0;
327
328                 if (press) {
329                         if (!repeat)
330                                 ms->click_count = 1;
331                         else if (ms->click_count < 3)
332                                 ms->click_count += 1;
333                 }
334                 ms->last_action = now;
335                 ms->last_x = ci->x; ms->last_y = ci->y;
336         }
337
338         focus = ci->focus;
339         num = im->num;
340         ex = im->num2;
341         /* FIXME is there any point in this? */
342         xy = pane_mapxy(ci->focus, focus, ci->x, ci->y, False);
343
344         mode = strsave(ci->home, im->mode);
345         free((void*)im->mode);
346         im->mode = strdup("");
347         im->num = NO_NUMERIC;
348         im->num2 = 0;
349
350         if (im->grab && !press) {
351                 /* Release and Motion should go to the same
352                  * place as Press went.
353                  */
354                 focus = im->grab;
355                 xy = pane_mapxy(focus, ci->focus, 0, 0, False);
356                 xy.x = ci->x - xy.x;
357                 xy.y = ci->y - xy.y;
358         } else while (1) {
359                         struct pane *t, *chld = NULL;
360
361                         list_for_each_entry(t, &focus->children, siblings) {
362                                 if (t->z < 0)
363                                         continue;
364                                 if (xy.x < t->x || xy.x >= t->x + t->w)
365                                         continue;
366                                 if (xy.y < t->y || xy.y >= t->y + t->h)
367                                         continue;
368                                 if (chld == NULL || t->z > chld->z)
369                                         chld = t;
370                         }
371                         /* descend into chld */
372                         if (!chld)
373                                 break;
374                         xy.x -= chld->x;
375                         xy.y -= chld->y;
376                         focus = chld;
377                 }
378         if (im->grab != focus) {
379                 im->grab = focus;
380                 pane_add_notify(ci->home, focus, "Notify:Close");
381         }
382
383         if (!ms) {
384                 int ret;
385                 key = strconcat(ci->home, "M", mode, ci->str);
386                 time_starts(ci->home);
387                 ret = call(key, focus, num, NULL, NULL, ex, NULL, NULL,
388                             xy.x, xy.y);
389                 time_ends(ci->home);
390                 return ret;
391         }
392         if (press) {
393                 /* identify and save modifiers */
394                 free(ms->mod);
395                 if (ci->str2)
396                         mod = strdup(ci->str2);
397                 else {
398                         char *c = strrchr(ci->str, ':');
399                         if (c)
400                                 mod = strndup(ci->str, c - ci->str);
401                         else
402                                 mod = strdup("");
403                 }
404                 ms->mod = mod;
405         }
406
407         if (press)
408                 cmd = "Press-";
409         else if (ms->click_on_up)
410                 cmd = "Click-";
411         else
412                 cmd = "Release-";
413
414         /* Try :nPress :(n-1)Press ... (or :nRelease or :nClick)
415          * until something gets a result.
416          * 'n' is T (triple) or D(double) or ""(Single).
417          * If nothing got a result for a Press,, register for
418          * 'click' on release.
419          */
420         ms->click_on_up = 1;
421         for (r = ms->click_count; r >= 1 ; r--) {
422                 int ret;
423                 char *mult = "\0\0D\0T" + (r-1)*2;
424                 char n[2];
425
426                 n[0] = '1' + b;
427                 n[1] = 0;
428                 key = strconcat(ci->home, "M", mode, ms->mod, ":", mult,
429                                 cmd, n);
430                 time_starts(ci->home);
431                 ret = call(key, focus, num, NULL, NULL, ex,
432                            NULL, NULL, xy.x, xy.y);
433                 time_ends(ci->home);
434                 if (ret > 0) {
435                         /* If this is 'press', then don't want
436                          * click_on_up.  If this is release, it
437                          * don't matter what we set.
438                          */
439                         ms->click_on_up = 0;
440                         return ret;
441                 }
442         }
443         return Efalse;
444 }
445
446 DEF_CMD(mouse_grab)
447 {
448         struct input_mode *im = ci->home->data;
449
450         im->grab = ci->focus;
451         pane_add_notify(ci->home, ci->focus, "Notify:Close");
452         return 1;
453 }
454
455 DEF_CMD(refocus)
456 {
457         struct input_mode *im = ci->home->data;
458
459         im->focus = NULL;
460         im->point = NULL;
461         im->source = NULL;
462         return Efallthrough;
463 }
464
465 DEF_CMD(close_focus)
466 {
467         struct input_mode *im = ci->home->data;
468
469         if (im->focus == ci->focus) {
470                 im->focus = NULL;
471                 im->point = NULL;
472                 im->source = NULL;
473         }
474
475         if (im->grab == ci->focus)
476                 im->grab = NULL;
477         return 1;
478 }
479
480 DEF_CMD(input_close)
481 {
482         struct input_mode *im = ci->home->data;
483         int i;
484
485         for (i = 0; i < 3; i++) {
486                 free(im->buttons[i].mod);
487                 im->buttons[i].mod = NULL;
488         }
489         free((void*)im->mode);
490         free((void*)im->context);
491         return 1;
492 }
493
494 static struct map *im_map;
495 static void register_map(void)
496 {
497         if (im_map)
498                 return;
499         im_map = key_alloc();
500         key_add(im_map, "Keystroke", &keystroke);
501         key_add(im_map, "Keystroke-sequence", &keystroke_sequence);
502         key_add(im_map, "Mouse-event", &mouse_event);
503         key_add(im_map, "Mouse-grab", &mouse_grab);
504         key_add(im_map, "Mode:set-mode", &set_mode);
505         key_add(im_map, "Mode:set-num", &set_num);
506         key_add(im_map, "Mode:set-num2", &set_num2);
507         key_add(im_map, "Mode:set-all", &set_all);
508         key_add(im_map, "pane:refocus", &refocus);
509         key_add(im_map, "Notify:Close", &close_focus);
510         key_add(im_map, "input:log", &log_input);
511         key_add(im_map, "Close", &input_close);
512 }
513
514 DEF_LOOKUP_CMD(input_handle, im_map);
515 DEF_CMD(input_attach)
516 {
517         struct input_mode *im;
518         struct pane *p;
519
520         register_map();
521
522         p = pane_register(ci->focus, 0, &input_handle.c);
523         if (!p)
524                 return Efail;
525         im = p->data;
526         im->mode = strdup("");
527         im->context = strdup("");
528         im->num = NO_NUMERIC;
529         im->num2 = 0;
530
531         return comm_call(ci->comm2, "callback:attach", p);
532 }
533
534 void edlib_init(struct pane *ed safe)
535 {
536         call_comm("global-set-command", ed, &input_attach, 0, NULL, "attach-input");
537 }