forked from trip-zip/somewm
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinput.c
More file actions
1935 lines (1696 loc) · 67.9 KB
/
Copy pathinput.c
File metadata and controls
1935 lines (1696 loc) · 67.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* input.c - Input handling for somewm compositor */
#include <libinput.h>
#include <linux/input-event-codes.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <time.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/libinput.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_cursor_shape_v1.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_fractional_scale_v1.h>
#include <wlr/types/wlr_idle_notify_v1.h>
#include <wlr/types/wlr_input_device.h>
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_keyboard_group.h>
#include <wlr/types/wlr_layer_shell_v1.h>
#include <wlr/types/wlr_pointer.h>
#include <wlr/types/wlr_pointer_constraints_v1.h>
#include <wlr/types/wlr_pointer_gestures_v1.h>
#include <wlr/types/wlr_primary_selection.h>
#include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_switch.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/util/log.h>
#include <wlr/util/region.h>
#include <xkbcommon/xkbcommon.h>
#include "somewm.h"
#include "somewm_api.h"
#include "input.h"
#include "event.h"
#include "event_queue.h"
#include "monitor.h"
#include "globalconf.h"
#include "client.h"
#include "common/luaobject.h"
#include "common/util.h"
#include "objects/client.h"
#include "objects/drawin.h"
#include "objects/drawable.h"
#include "objects/keybinding.h"
#include "objects/keygrabber.h"
#include "objects/mousegrabber.h"
#include "objects/gesture.h"
#include "objects/layer_surface.h"
#include "objects/button.h"
#include "objects/root.h"
#include "objects/signal.h"
#include "event.h"
#include "xkb.h"
#include <wlr/types/wlr_virtual_keyboard_v1.h>
#include <wlr/types/wlr_virtual_pointer_v1.h>
/* macros */
#define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS & ~WLR_MODIFIER_MOD2)
#include "focus.h"
#include "window.h"
#include "somewm_internal.h"
#include "bench.h"
/* Module-private state */
static unsigned int cursor_mode;
static bool gesture_swipe_consumed = false;
static bool gesture_pinch_consumed = false;
static bool gesture_hold_consumed = false;
/* Forward declarations */
void axisnotify(struct wl_listener *listener, void *data);
void buttonpress(struct wl_listener *listener, void *data);
void cursorframe(struct wl_listener *listener, void *data);
void motionrelative(struct wl_listener *listener, void *data);
void motionabsolute(struct wl_listener *listener, void *data);
void inputdevice(struct wl_listener *listener, void *data);
void virtualkeyboard(struct wl_listener *listener, void *data);
void virtualpointer(struct wl_listener *listener, void *data);
void createpointerconstraint(struct wl_listener *listener, void *data);
static void createswitch(struct wlr_switch *sw);
void gestureswipebegin(struct wl_listener *listener, void *data);
void gestureswipeupdate(struct wl_listener *listener, void *data);
void gestureswipeend(struct wl_listener *listener, void *data);
void gesturepinchbegin(struct wl_listener *listener, void *data);
void gesturepinchupdate(struct wl_listener *listener, void *data);
void gesturepinchend(struct wl_listener *listener, void *data);
void gestureholdbegin(struct wl_listener *listener, void *data);
void gestureholdend(struct wl_listener *listener, void *data);
void destroypointerconstraint(struct wl_listener *listener, void *data);
static void destroytrackedpointer(struct wl_listener *listener, void *data);
static void destroyswitchtracker(struct wl_listener *listener, void *data);
static void switchevent(struct wl_listener *listener, void *data);
void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel);
void pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy,
uint32_t time);
int keybinding(uint32_t mods, uint32_t keycode, xkb_keysym_t sym, xkb_keysym_t base_sym, bool is_keypress);
void createkeyboard(struct wlr_keyboard *keyboard);
KeyboardGroup *createkeyboardgroup(void);
void createpointer(struct wlr_pointer *pointer);
void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint);
void cursorwarptohint(void);
void apply_input_settings_to_device(struct libinput_device *device);
void mouse_emit_leave(lua_State *L);
void mouse_emit_client_enter(lua_State *L, client_t *c);
void mouse_emit_drawin_enter(lua_State *L, drawin_t *d);
void setcursor(struct wl_listener *listener, void *data);
void setcursorshape(struct wl_listener *listener, void *data);
void keypress(struct wl_listener *listener, void *data);
void keypressmod(struct wl_listener *listener, void *data);
int keyrepeat(void *data);
bool drawin_accepts_input_at(drawin_t *d, double local_x, double local_y);
void setpsel(struct wl_listener *listener, void *data);
void setsel(struct wl_listener *listener, void *data);
void requeststartdrag(struct wl_listener *listener, void *data);
void startdrag(struct wl_listener *listener, void *data);
void destroydrag(struct wl_listener *listener, void *data);
void destroydragicon(struct wl_listener *listener, void *data);
void xytonode(double x, double y, struct wlr_surface **psurface,
Client **pc, LayerSurface **pl, drawin_t **pd, drawable_t **pdrawable,
double *nx, double *ny);
/* Tracked pointer device for runtime libinput configuration */
typedef struct {
struct libinput_device *libinput_dev;
struct wl_listener destroy;
struct wl_list link;
} TrackedPointer;
typedef struct {
struct wlr_switch *switch_dev;
struct wl_listener toggle;
struct wl_listener destroy;
} TrackedSwitch;
/* Listener structs */
struct wl_listener cursor_axis = {.notify = axisnotify};
struct wl_listener cursor_button = {.notify = buttonpress};
struct wl_listener cursor_frame = {.notify = cursorframe};
struct wl_listener cursor_motion = {.notify = motionrelative};
struct wl_listener cursor_motion_absolute = {.notify = motionabsolute};
struct wl_listener new_input_device = {.notify = inputdevice};
struct wl_listener new_virtual_keyboard = {.notify = virtualkeyboard};
struct wl_listener new_virtual_pointer = {.notify = virtualpointer};
struct wl_listener new_pointer_constraint = {.notify = createpointerconstraint};
struct wl_listener gesture_swipe_begin = {.notify = gestureswipebegin};
struct wl_listener gesture_swipe_update = {.notify = gestureswipeupdate};
struct wl_listener gesture_swipe_end = {.notify = gestureswipeend};
struct wl_listener gesture_pinch_begin = {.notify = gesturepinchbegin};
struct wl_listener gesture_pinch_update = {.notify = gesturepinchupdate};
struct wl_listener gesture_pinch_end = {.notify = gesturepinchend};
struct wl_listener gesture_hold_begin = {.notify = gestureholdbegin};
struct wl_listener gesture_hold_end = {.notify = gestureholdend};
struct wl_listener request_cursor = {.notify = setcursor};
struct wl_listener request_set_psel = {.notify = setpsel};
struct wl_listener request_set_sel = {.notify = setsel};
struct wl_listener request_set_cursor_shape = {.notify = setcursorshape};
struct wl_listener request_start_drag = {.notify = requeststartdrag};
struct wl_listener start_drag = {.notify = startdrag};
static void
switchevent(struct wl_listener *listener, void *data)
{
TrackedSwitch *ts = wl_container_of(listener, ts, toggle);
struct wlr_switch_toggle_event *event = data;
char* type;
switch (event->switch_type) {
case WLR_SWITCH_TYPE_LID:
type = "lid";
break;
case WLR_SWITCH_TYPE_TABLET_MODE:
type = "tablet_mode";
break;
case WLR_SWITCH_TYPE_KEYPAD_SLIDE:
type = "keypad_slide";
break;
default:
type = "unknown";
break;
}
some_event_queue_global_with_table(SIG_SWITCH_TOGGLE, 6,
"device_name", ts->switch_dev && ts->switch_dev->base.name
? ts->switch_dev->base.name : "",
"type", type,
"state", event->switch_state == WLR_SWITCH_STATE_ON ? "on" : "off");
}
void
gestureswipebegin(struct wl_listener *listener, void *data)
{
struct wlr_pointer_swipe_begin_event *event = data;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
gesture_swipe_consumed = luaA_gesture_check_swipe_begin(
event->time_msec, event->fingers);
if (!gesture_swipe_consumed)
wlr_pointer_gestures_v1_send_swipe_begin(pointer_gestures,
seat, event->time_msec, event->fingers);
}
void
gestureswipeupdate(struct wl_listener *listener, void *data)
{
struct wlr_pointer_swipe_update_event *event = data;
luaA_gesture_check_swipe_update(
event->time_msec, event->fingers, event->dx, event->dy);
if (!gesture_swipe_consumed)
wlr_pointer_gestures_v1_send_swipe_update(pointer_gestures,
seat, event->time_msec, event->dx, event->dy);
}
void
gestureswipeend(struct wl_listener *listener, void *data)
{
struct wlr_pointer_swipe_end_event *event = data;
luaA_gesture_check_swipe_end(event->time_msec, event->cancelled);
if (!gesture_swipe_consumed)
wlr_pointer_gestures_v1_send_swipe_end(pointer_gestures,
seat, event->time_msec, event->cancelled);
gesture_swipe_consumed = false;
}
void
gesturepinchbegin(struct wl_listener *listener, void *data)
{
struct wlr_pointer_pinch_begin_event *event = data;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
gesture_pinch_consumed = luaA_gesture_check_pinch_begin(
event->time_msec, event->fingers);
if (!gesture_pinch_consumed)
wlr_pointer_gestures_v1_send_pinch_begin(pointer_gestures,
seat, event->time_msec, event->fingers);
}
void
gesturepinchupdate(struct wl_listener *listener, void *data)
{
struct wlr_pointer_pinch_update_event *event = data;
luaA_gesture_check_pinch_update(
event->time_msec, event->fingers,
event->dx, event->dy, event->scale, event->rotation);
if (!gesture_pinch_consumed)
wlr_pointer_gestures_v1_send_pinch_update(pointer_gestures,
seat, event->time_msec,
event->dx, event->dy, event->scale, event->rotation);
}
void
gesturepinchend(struct wl_listener *listener, void *data)
{
struct wlr_pointer_pinch_end_event *event = data;
luaA_gesture_check_pinch_end(event->time_msec, event->cancelled);
if (!gesture_pinch_consumed)
wlr_pointer_gestures_v1_send_pinch_end(pointer_gestures,
seat, event->time_msec, event->cancelled);
gesture_pinch_consumed = false;
}
void
gestureholdbegin(struct wl_listener *listener, void *data)
{
struct wlr_pointer_hold_begin_event *event = data;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
gesture_hold_consumed = luaA_gesture_check_hold_begin(
event->time_msec, event->fingers);
if (!gesture_hold_consumed)
wlr_pointer_gestures_v1_send_hold_begin(pointer_gestures,
seat, event->time_msec, event->fingers);
}
void
gestureholdend(struct wl_listener *listener, void *data)
{
struct wlr_pointer_hold_end_event *event = data;
luaA_gesture_check_hold_end(event->time_msec, event->cancelled);
if (!gesture_hold_consumed)
wlr_pointer_gestures_v1_send_hold_end(pointer_gestures,
seat, event->time_msec, event->cancelled);
gesture_hold_consumed = false;
}
void
axisnotify(struct wl_listener *listener, void *data)
{
/* This event is forwarded by the cursor when a pointer emits an axis event,
* for example when you move the scroll wheel. */
struct wlr_pointer_axis_event *event = data;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
some_notify_activity();
/* Handle scroll wheel for mousebindings and the mousegrabber
* (AwesomeWM compatibility).
* Convert axis events to X11-style button 4/5/6/7 press+release events.
* In X11, each scroll tick generates a button press+release pair.
*
* Wayland uses "value120" for discrete scroll: one standard wheel click
* sends delta_discrete=±120. High-resolution mice send smaller steps
* (e.g. ±15 or ±30). We accumulate until a full step (±120) is reached
* to avoid multiple tag switches per physical wheel click. */
if (!session_is_locked() && event->delta != 0) {
/* NOTE: process-global accumulators shared across all pointer devices.
* Multi-mouse interleaving is possible but negligible in practice. */
static int32_t scroll_acc_v = 0;
static int32_t scroll_acc_h = 0;
int32_t *acc;
int ticks = 0;
uint32_t button;
acc = (event->orientation == WL_POINTER_AXIS_VERTICAL_SCROLL)
? &scroll_acc_v : &scroll_acc_h;
if (event->delta_discrete != 0) {
/* Discrete scroll (wheel): accumulate value120 steps */
*acc += event->delta_discrete;
/* Count full steps (±120 each) */
while (*acc >= 120) { ticks++; *acc -= 120; }
while (*acc <= -120) { ticks++; *acc += 120; }
} else {
/* Continuous scroll (touchpad/smooth): use delta directly,
* one button event per axis event (no accumulation) */
ticks = 1;
}
/* Reset accumulator on direction change to prevent stale state */
if ((event->delta > 0 && *acc < 0) || (event->delta < 0 && *acc > 0))
*acc = 0;
/* Determine button number based on axis orientation and direction */
if (event->orientation == WL_POINTER_AXIS_VERTICAL_SCROLL)
button = (event->delta < 0) ? 4 : 5;
else
button = (event->delta < 0) ? 6 : 7;
if (mousegrabber_isrunning()) {
event_handle_mousegrabber_scroll(cursor->x, cursor->y,
button, ticks);
return; /* Don't process event further */
}
for (int tick = 0; tick < ticks; tick++) {
lua_State *L = globalconf_get_lua_State();
struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat);
uint32_t mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
Client *c = NULL;
drawin_t *drawin = NULL;
drawable_t *titlebar_drawable = NULL;
int rel_x, rel_y;
/* Find what's under the cursor */
xytonode(cursor->x, cursor->y, NULL, &c, NULL, &drawin, &titlebar_drawable, NULL, NULL);
if (drawin) {
/* Scroll on drawin (wibox) */
rel_x = (int)cursor->x - drawin->x;
rel_y = (int)cursor->y - drawin->y;
/* Emit press then release (scroll is instantaneous) */
luaA_drawin_button_check(drawin, rel_x, rel_y, button, CLEANMASK(mods), true);
luaA_drawin_button_check(drawin, rel_x, rel_y, button, CLEANMASK(mods), false);
} else if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) {
/* Scroll on client */
rel_x = (int)cursor->x - c->geometry.x;
rel_y = (int)cursor->y - c->geometry.y;
/* Emit on titlebar drawable if applicable */
if (titlebar_drawable) {
luaA_drawable_button_emit(c, titlebar_drawable, rel_x, rel_y, button,
CLEANMASK(mods), true);
luaA_drawable_button_emit(c, titlebar_drawable, rel_x, rel_y, button,
CLEANMASK(mods), false);
}
/* Emit on client (press + release) */
luaA_client_button_check(c, rel_x, rel_y, button, CLEANMASK(mods), true);
luaA_client_button_check(c, rel_x, rel_y, button, CLEANMASK(mods), false);
} else {
/* Scroll on root/empty space */
luaA_root_button_check(L, button, CLEANMASK(mods), cursor->x, cursor->y, true);
luaA_root_button_check(L, button, CLEANMASK(mods), cursor->x, cursor->y, false);
}
}
}
/* An active grabber consumes the remaining axis events: axis-stop
* (delta == 0) frames, and a grab started inside a scroll-binding
* callback above (matching the equivalent buttonpress check). */
if (mousegrabber_isrunning())
return;
/* Notify the client with pointer focus of the axis event. */
wlr_seat_pointer_notify_axis(seat,
event->time_msec, event->orientation, event->delta,
event->delta_discrete, event->source, event->relative_direction);
}
void
buttonpress(struct wl_listener *listener, void *data)
{
#ifdef SOMEWM_BENCH
bench_input_event_record();
#endif
struct wlr_pointer_button_event *event = data;
struct wlr_keyboard *keyboard;
uint32_t mods;
Client *c;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
some_notify_activity();
/* Update globalconf button state tracking FIRST, before any early returns.
* This ensures mousegrabber callbacks receive accurate button states.
* Only buttons 1-3 have a slot: slots 4/5 of the X11 state mask mean
* scroll up/down (synthesized from axis events, never held), and
* BTN_SIDE/BTN_EXTRA are X11 buttons 8/9, which have no state mask bit.
*/
{
int idx = -1;
switch (event->button) {
case 0x110: idx = 0; break; /* BTN_LEFT -> button 1 */
case 0x112: idx = 1; break; /* BTN_MIDDLE -> button 2 */
case 0x111: idx = 2; break; /* BTN_RIGHT -> button 3 */
}
if (idx >= 0) {
globalconf.button_state.buttons[idx] =
(event->state == WL_POINTER_BUTTON_STATE_PRESSED);
}
}
/* If mousegrabber is active, route event to Lua callback */
if (event_handle_mousegrabber(cursor->x, cursor->y, 0))
return; /* Don't process event further */
switch (event->state) {
case WL_POINTER_BUTTON_STATE_PRESSED: {
Monitor *mon;
LayerSurface *l = NULL;
drawin_t *drawin = NULL;
drawable_t *titlebar_drawable = NULL;
int rel_x, rel_y;
cursor_mode = CurPressed;
if (locked)
break;
/* Change focus if the button was _pressed_ over a client or layer surface */
xytonode(cursor->x, cursor->y, NULL, &c, &l, &drawin, &titlebar_drawable, NULL, NULL);
/* For Lua lock, only allow interaction with the lock surface */
if (some_is_lua_locked() && drawin != some_get_lua_lock_surface())
return;
/* Get keyboard modifiers */
keyboard = wlr_seat_get_keyboard(seat);
mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
/* Check if a drawin was clicked */
if (drawin) {
/* Calculate drawin-relative coordinates */
rel_x = (int)cursor->x - drawin->x;
rel_y = (int)cursor->y - drawin->y;
/* Check drawin-specific button array (AwesomeWM-compatible two-stage emission) */
if (luaA_drawin_button_check(drawin, rel_x, rel_y, event->button,
CLEANMASK(mods), true)) {
return;
}
} else if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) {
/* Calculate client-relative coordinates */
rel_x = (int)cursor->x - c->geometry.x;
rel_y = (int)cursor->y - c->geometry.y;
/* If click is on a titlebar drawable, emit button signal on it
* This matches AwesomeWM's event.c:76-84 where they call event_emit_button on titlebar drawable */
if (titlebar_drawable) {
luaA_drawable_button_emit(c, titlebar_drawable, rel_x, rel_y, event->button,
CLEANMASK(mods), true);
}
/* Check client button array (AwesomeWM-compatible two-stage emission)
* This enables awful.mouse.client.move() and resize() to work via
* client button bindings defined in rc.lua
* Note: Unlike root/drawin handlers, we don't return early here.
* AwesomeWM always passes clicks through to clients (XCB_ALLOW_REPLAY_POINTER),
* so button bindings act as transparent observers, not consumers. */
luaA_client_button_check(c, rel_x, rel_y, event->button,
CLEANMASK(mods), true);
} else if (l && l->lua_object) {
/* Layer surface was clicked - grant keyboard focus if it wants it */
uint32_t kb_mode = l->layer_surface->current.keyboard_interactive;
if (kb_mode == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE ||
kb_mode == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND) {
/* Emit signal for Lua to observe, then grant focus */
layer_surface_emit_request_keyboard(l->lua_object, "click");
/* Set focus state and emit property signal */
if (!l->lua_object->has_keyboard_focus) {
lua_State *Ls = globalconf_get_lua_State();
l->lua_object->has_keyboard_focus = true;
luaA_object_push(Ls, l->lua_object);
luaA_object_emit_signal(Ls, -1, "property::has_keyboard_focus", 0);
lua_pop(Ls, 1);
}
layer_surface_grant_keyboard(l);
}
}
/* Check root button bindings (only over empty desktop). A click on a
* wibar belongs to that wibar whether or not a widget consumed it:
* AwesomeWM grabs root buttons on the root window, which a wibox click
* never reaches. */
if (!c && !l && !drawin) {
lua_State *L = globalconf_get_lua_State();
/* Check root button bindings */
if (luaA_root_button_check(L, event->button, CLEANMASK(mods),
cursor->x, cursor->y, true) > 0) {
/* Root button binding matched and handled */
return;
}
/* No root binding matched - update selmon based on cursor position */
mon = xytomon(cursor->x, cursor->y);
if (mon && mon != selmon) {
selmon = mon;
/* Queue signal so Lua knows monitor changed */
some_event_queue_global(SIG_SCREEN_FOCUS);
}
}
break;
}
case WL_POINTER_BUTTON_STATE_RELEASED: {
drawin_t *drawin = NULL;
drawable_t *titlebar_drawable = NULL;
/* NOTE: C-level move/resize exit handling removed - Lua mousegrabber handles this now */
cursor_mode = CurNormal;
/* Check if a drawin was released over */
if (!session_is_locked()) {
xytonode(cursor->x, cursor->y, NULL, &c, NULL, &drawin, &titlebar_drawable, NULL, NULL);
/* Get keyboard modifiers */
keyboard = wlr_seat_get_keyboard(seat);
mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
if (drawin) {
/* Calculate drawin-relative coordinates */
int rel_x = (int)cursor->x - drawin->x;
int rel_y = (int)cursor->y - drawin->y;
/* Emit button release signals (AwesomeWM-compatible) */
if (luaA_drawin_button_check(drawin, rel_x, rel_y, event->button,
CLEANMASK(mods), false)) {
return;
}
} else if (c) {
/* Released on client - check client button bindings */
int rel_x = (int)cursor->x - c->geometry.x;
int rel_y = (int)cursor->y - c->geometry.y;
/* If release is on a titlebar drawable, emit button signal on it */
if (titlebar_drawable) {
luaA_drawable_button_emit(c, titlebar_drawable, rel_x, rel_y, event->button,
CLEANMASK(mods), false);
}
/* Emit button release signals on client (AwesomeWM-compatible)
* Like press events, releases are passed through to the client. */
luaA_client_button_check(c, rel_x, rel_y, event->button,
CLEANMASK(mods), false);
} else {
/* Released on empty space - check root button bindings */
lua_State *L = globalconf_get_lua_State();
/* Check root button bindings for release event */
if (luaA_root_button_check(L, event->button, CLEANMASK(mods),
cursor->x, cursor->y, false) > 0) {
/* Root button release binding matched and handled */
return;
}
}
}
break;
}
}
/* Don't forward button event to client if mousegrabber started during
* Lua callback processing (e.g., awful.mouse.client.move() grabbed it).
* This ensures symmetric handling: if the press was consumed by starting
* a mousegrabber, the client never sees either press or release. */
if (mousegrabber_isrunning()) {
return;
}
/* If the event wasn't handled by the compositor, notify the client with
* pointer focus that a button press has occurred */
wlr_seat_pointer_notify_button(seat,
event->time_msec, event->button, event->state);
}
void
cursorframe(struct wl_listener *listener, void *data)
{
/* This event is forwarded by the cursor when a pointer emits a frame
* event. Frame events are sent after regular pointer events to group
* multiple events together. For instance, two axis events may happen at the
* same time, in which case a frame event won't be sent in between. */
/* Notify the client with pointer focus of the frame event. */
wlr_seat_pointer_notify_frame(seat);
}
void
motionrelative(struct wl_listener *listener, void *data)
{
/* This event is forwarded by the cursor when a pointer emits a _relative_
* pointer motion event (i.e. a delta) */
struct wlr_pointer_motion_event *event = data;
/* The cursor doesn't move unless we tell it to. The cursor automatically
* handles constraining the motion to the output layout, as well as any
* special configuration applied for the specific input device which
* generated the event. You can pass NULL for the device if you want to move
* the cursor around without any input. */
motionnotify(event->time_msec, &event->pointer->base, event->delta_x, event->delta_y,
event->unaccel_dx, event->unaccel_dy);
}
void
motionabsolute(struct wl_listener *listener, void *data)
{
/* This event is forwarded by the cursor when a pointer emits an _absolute_
* motion event, from 0..1 on each axis. This happens, for example, when
* wlroots is running under a Wayland window rather than KMS+DRM, and you
* move the mouse over the window. You could enter the window from any edge,
* so we have to warp the mouse there. Also, some hardware emits these events. */
struct wlr_pointer_motion_absolute_event *event = data;
double lx, ly, dx, dy;
if (!event->time_msec) /* this is 0 with virtual pointers */
wlr_cursor_warp_absolute(cursor, &event->pointer->base, event->x, event->y);
wlr_cursor_absolute_to_layout_coords(cursor, &event->pointer->base, event->x, event->y, &lx, &ly);
dx = lx - cursor->x;
dy = ly - cursor->y;
motionnotify(event->time_msec, &event->pointer->base, dx, dy, dx, dy);
}
/* Helper function to emit mouse::leave on the object that previously had the mouse.
* Also clears drawable_under_mouse tracking to emit leave on the drawable. */
void
mouse_emit_leave(lua_State *L)
{
if (globalconf.mouse_under.type == UNDER_CLIENT) {
client_t *c = globalconf.mouse_under.ptr.client;
luaA_object_push(L, c);
some_event_queue_signal0(L, -1, SIG_MOUSE_LEAVE);
lua_pop(L, 1);
} else if (globalconf.mouse_under.type == UNDER_DRAWIN) {
drawin_t *d = globalconf.mouse_under.ptr.drawin;
luaA_object_push(L, d);
if (lua_isnil(L, -1)) {
warn("mouse::leave on unregistered drawin %p", (void*)d);
}
some_event_queue_signal0(L, -1, SIG_MOUSE_LEAVE);
lua_pop(L, 1);
}
globalconf.mouse_under.type = UNDER_NONE;
/* Also clear drawable tracking - emit leave on drawable if any */
if (globalconf.drawable_under_mouse != NULL) {
luaA_object_push(L, globalconf.drawable_under_mouse);
some_event_queue_signal0(L, -1, SIG_MOUSE_LEAVE);
lua_pop(L, 1);
luaA_object_unref(L, globalconf.drawable_under_mouse);
globalconf.drawable_under_mouse = NULL;
}
}
/* Helper function to emit mouse::enter on a client */
void
mouse_emit_client_enter(lua_State *L, client_t *c)
{
luaA_object_push(L, c);
some_event_queue_signal0(L, -1, SIG_MOUSE_ENTER);
lua_pop(L, 1);
globalconf.mouse_under.type = UNDER_CLIENT;
globalconf.mouse_under.ptr.client = c;
}
/* Helper function to emit mouse::enter on a drawin */
void
mouse_emit_drawin_enter(lua_State *L, drawin_t *d)
{
luaA_object_push(L, d);
if (lua_isnil(L, -1)) {
warn("mouse::enter on unregistered drawin %p", (void*)d);
}
some_event_queue_signal0(L, -1, SIG_MOUSE_ENTER);
lua_pop(L, 1);
globalconf.mouse_under.type = UNDER_DRAWIN;
globalconf.mouse_under.ptr.drawin = d;
}
void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel)
{
#ifdef SOMEWM_BENCH
bench_input_event_record();
#endif
double sx = 0, sy = 0, sx_confined, sy_confined;
Client *c = NULL, *w = NULL;
LayerSurface *l = NULL;
struct wlr_surface *surface = NULL;
/* Find the client under the pointer and send the event along. */
xytonode(cursor->x, cursor->y, &surface, &c, NULL, NULL, NULL, &sx, &sy);
if (seat->pointer_state.button_count > 0 && !seat->drag
&& surface != seat->pointer_state.focused_surface
&& toplevel_from_wlr_surface(seat->pointer_state.focused_surface, &w, &l) >= 0) {
c = w;
surface = seat->pointer_state.focused_surface;
sx = cursor->x - (l ? l->scene->node.x : w->geometry.x);
sy = cursor->y - (l ? l->scene->node.y : w->geometry.y);
}
/* time is 0 in internal calls meant to restore pointer focus. */
if (time) {
wlr_relative_pointer_manager_v1_send_relative_motion(
relative_pointer_mgr, seat, (uint64_t)time * 1000,
dx, dy, dx_unaccel, dy_unaccel);
/* Note: Constraint selection is done in focusclient(), not here.
* dwl/somewm previously iterated all constraints here which caused
* the "last constraint wins" bug breaking games like Minecraft. */
if (active_constraint) {
toplevel_from_wlr_surface(active_constraint->surface, &c, NULL);
if (c && active_constraint->surface == seat->pointer_state.focused_surface) {
sx = cursor->x - c->geometry.x - c->bw;
sy = cursor->y - c->geometry.y - c->bw;
if (wlr_region_confine(&active_constraint->region, sx, sy,
sx + dx, sy + dy, &sx_confined, &sy_confined)) {
dx = sx_confined - sx;
dy = sy_confined - sy;
}
if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED)
return;
}
}
wlr_cursor_move(cursor, device, dx, dy);
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
some_notify_activity();
/* Update selected monitor when cursor crosses monitor boundaries.
* Without this, layer-shell clients (rofi, etc.) that don't specify
* an output get assigned to the stale selmon in createlayersurface(). */
{
Monitor *mon = xytomon(cursor->x, cursor->y);
if (mon && mon != selmon)
selmon = mon;
}
}
/* Update drag icon's position */
wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y));
/* If drag source became invalid, clear it. */
if (seat->drag && drag_source_client) {
bool valid = false;
foreach(elem, globalconf.clients)
if (*elem == drag_source_client) { valid = true; break; }
if (!valid)
drag_source_client = NULL;
}
/* During active drag over compositor surfaces (wibars), don't clear
* drag focus - that would cancel the drag on drop. Instead, keep
* sending motion events with coordinates projected onto the focused
* surface so the source app knows the pointer is outside its window
* (e.g., Firefox uses out-of-bounds coords to trigger tab detach). */
if (seat->drag && drag_source_client && !surface) {
struct wlr_surface *source_surface = client_surface(drag_source_client);
if (source_surface) {
double fx, fy;
cursor_to_client_coordinates(drag_source_client, &fx, &fy);
if (seat->pointer_state.focused_surface != source_surface)
wlr_seat_pointer_notify_enter(seat, source_surface, fx, fy);
wlr_seat_pointer_notify_motion(seat, time, fx, fy);
}
return;
}
/* If mousegrabber is active, route event to Lua callback (AwesomeWM behavior:
* check mousegrabber BEFORE enter/leave signals to filter them during grabs) */
if (event_handle_mousegrabber(cursor->x, cursor->y, 0))
return; /* Don't process event further (skip enter/leave signals, pointerfocus) */
/* Track which object is under the cursor and emit enter/leave/move signals
* (only when mousegrabber is NOT active - filtered above) */
if (time && !globalconf.mouse_under.ignore_next_enter_leave) {
lua_State *L = globalconf_get_lua_State();
Client *current_client = NULL;
drawin_t *current_drawin = NULL;
drawable_t *titlebar_drawable = NULL;
bool client_valid = false;
/* Find what's under cursor */
xytonode(cursor->x, cursor->y, NULL, ¤t_client, NULL, ¤t_drawin, &titlebar_drawable, NULL, NULL);
/* Validate client pointer - xytonode can return stale pointers from scene graph
* if a node's data field wasn't cleared when the client was destroyed */
if (current_client) {
foreach(elem, globalconf.clients) {
if (*elem == current_client) {
client_valid = true;
break;
}
}
if (!client_valid) {
current_client = NULL; /* Ignore stale/invalid client pointer */
}
}
if (current_client) {
/* Mouse is over a client */
if (globalconf.mouse_under.type != UNDER_CLIENT ||
globalconf.mouse_under.ptr.client != current_client) {
/* Different object - emit leave on old, enter on new */
mouse_emit_leave(L);
mouse_emit_client_enter(L, current_client);
}
/* Always emit mouse::move on current client (coalesced per frame) */
luaA_object_push(L, current_client);
if (lua_isnil(L, -1)) {
warn("mouse::move on unregistered client %p", (void*)current_client);
}
some_event_queue_move(L, -1,
(int)(cursor->x - current_client->geometry.x),
(int)(cursor->y - current_client->geometry.y));
lua_pop(L, 1);
/* Check if mouse is over a titlebar drawable - emit signals for widget hover */
if (titlebar_drawable) {
luaA_object_push(L, current_client);
luaA_object_push_item(L, -1, titlebar_drawable);
if (lua_isnil(L, -1)) {
lua_pop(L, 2);
} else {
event_drawable_under_mouse(L, -1);
/* Emit mouse::move on titlebar drawable with local coordinates */
int tb_x = (int)(cursor->x - current_client->geometry.x);
int tb_y = (int)(cursor->y - current_client->geometry.y);
client_get_drawable_offset(current_client, &tb_x, &tb_y);
some_event_queue_move(L, -1, tb_x, tb_y);
lua_pop(L, 2); /* pop drawable and client */
}
} else if (globalconf.drawable_under_mouse) {
/* Left titlebar area - emit leave on previous drawable */
lua_pushnil(L);
event_drawable_under_mouse(L, -1);
lua_pop(L, 1);
}
} else if (current_drawin) {
/* Mouse over drawin - emit signals on drawable for widget hover */
if (globalconf.mouse_under.type != UNDER_DRAWIN ||
globalconf.mouse_under.ptr.drawin != current_drawin) {
mouse_emit_leave(L);
mouse_emit_drawin_enter(L, current_drawin);
}
luaA_object_push(L, current_drawin);
if (lua_isnil(L, -1)) {
warn("mouse event on unregistered drawin %p", (void*)current_drawin);
lua_pop(L, 1);
} else {
luaA_object_push_item(L, -1, current_drawin->drawable);
event_drawable_under_mouse(L, -1);
some_event_queue_move(L, -1,
(int)cursor->x - current_drawin->x,
(int)cursor->y - current_drawin->y);
lua_pop(L, 2);
}
} else {
/* Mouse is over empty space - emit leave if we were over something */
if (globalconf.mouse_under.type != UNDER_NONE) {
mouse_emit_leave(L);
}
}
}
/* Reset the ignore flag after processing */
if (globalconf.mouse_under.ignore_next_enter_leave) {
globalconf.mouse_under.ignore_next_enter_leave = false;
}
/* If there's no client surface under the cursor, set the cursor image.
* Check if pointer is over a drawin with a custom cursor first. */
if (!surface && !seat->drag) {
drawin_t *hover_drawin = NULL;
xytonode(cursor->x, cursor->y, NULL, NULL, NULL, &hover_drawin, NULL, NULL, NULL);
if (hover_drawin && hover_drawin->cursor)
wlr_cursor_set_xcursor(cursor, cursor_mgr, hover_drawin->cursor);
else
wlr_cursor_set_xcursor(cursor, cursor_mgr, selected_root_cursor ? selected_root_cursor : "default");
}
pointerfocus(c, surface, sx, sy, time);
}
void
pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy,
uint32_t time)
{
struct timespec now;
/* A NULL surface means the cursor is over compositor-drawn chrome
* (titlebar/border), not the client. Clear pointer focus so the client gets
* wl_pointer.leave. Do NOT fall back to the client's main surface here: the
* cursor is above the content, so translating it yields an in-bounds top-row
* coordinate that leaks hover/clicks onto the client (issue: titlebar event
* propagation). */
if (!surface) {
wlr_seat_pointer_notify_clear_focus(seat);
return;
}
/* Don't give pointer focus to clients when Lua-locked */
if (some_is_lua_locked()) {
wlr_seat_pointer_notify_clear_focus(seat);
return;
}
/* Workaround for wlroots pointer enter race condition:
* wlr_seat_pointer_enter() caches focused_surface and returns early if
* it matches. But if the initial enter was called before the client bound
* wl_pointer (pointers list empty), the wl_pointer.enter event was never
* sent even though focused_surface was set. Clear the stale state so
* wlr_seat_pointer_enter() re-delivers the enter event. */
if (seat->pointer_state.focused_surface == surface) {
struct wlr_seat_client *sc = seat->pointer_state.focused_client;
if (!sc || wl_list_empty(&sc->pointers)) {
wlr_log(WLR_DEBUG, "[POINTER-REENTER] clearing stale pointer focus on %s "
"(client had no wl_pointer resources)",
c ? client_get_appid(c) : "?");
wlr_seat_pointer_notify_clear_focus(seat);
}
}
if (!time) {
clock_gettime(CLOCK_MONOTONIC, &now);
time = now.tv_sec * 1000 + now.tv_nsec / 1000000;
}
/* Deliver pointer enter + motion to the surface.
* wlr_seat_pointer_enter is a no-op if surface is already focused AND
* the client has pointer resources (normal case). */
wlr_seat_pointer_notify_enter(seat, surface, sx, sy);
wlr_seat_pointer_notify_motion(seat, time, sx, sy);
}
int
keybinding(uint32_t mods, uint32_t keycode, xkb_keysym_t sym, xkb_keysym_t base_sym, bool is_keypress)
{
client_t *focused;
struct wlr_surface *surface;
/*
* Here we handle compositor keybindings. This is when the compositor is
* processing keys, rather than passing them on to the client for its own
* processing.
*
* AwesomeWM pattern: check client-specific keybindings first (they receive
* the client as argument), then check global keybindings.
*/
/* Get the client that has keyboard focus from the Wayland seat.
* This matches AwesomeWM's pattern of using the X11 event window
* (event.c:781: client_getbywin(ev->event)) rather than internal
* focus state. We don't modify globalconf.focus.client here to
* avoid emitting focus signals during keybinding dispatch. */