1 /*
2  * This file is part of brisk-menu.
3  *
4  * Copyright © 2016-2020 Brisk Menu Developers
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  */
11 
12 #define _GNU_SOURCE
13 
14 #include "util.h"
15 
16 BRISK_BEGIN_PEDANTIC
17 #include "menu-private.h"
18 #include <gtk/gtk.h>
19 BRISK_END_PEDANTIC
20 
21 static gboolean brisk_menu_window_map(GtkWidget *widget, gpointer udata);
22 static gboolean brisk_menu_window_unmap(GtkWidget *widget, gpointer udata);
23 static void brisk_menu_window_grab_notify(GtkWidget *widget, gboolean was_grabbed, gpointer udata);
24 static gboolean brisk_menu_window_button_press(GtkWidget *widget, GdkEvent *event, gpointer udata);
25 static gboolean brisk_menu_window_grab_broken(GtkWidget *widget, GdkEvent *event, gpointer udata);
26 static void brisk_menu_window_grab(BriskMenuWindow *self);
27 static void brisk_menu_window_ungrab(BriskMenuWindow *self);
28 
29 /**
30  * Borrowed from gdkseatdefault.c
31  */
32 #define KEYBOARD_EVENTS (GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK)
33 #define POINTER_EVENTS                                                                             \
34         (GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |               \
35          GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_ENTER_NOTIFY_MASK |                        \
36          GDK_LEAVE_NOTIFY_MASK | GDK_PROXIMITY_IN_MASK | GDK_PROXIMITY_OUT_MASK)
37 
38 /**
39  * Set up grabbing support within the menu
40  */
brisk_menu_window_configure_grabs(BriskMenuWindow * self)41 void brisk_menu_window_configure_grabs(BriskMenuWindow *self)
42 {
43         g_signal_connect(self, "map-event", G_CALLBACK(brisk_menu_window_map), NULL);
44         g_signal_connect(self, "unmap-event", G_CALLBACK(brisk_menu_window_unmap), NULL);
45         g_signal_connect(self, "grab-notify", G_CALLBACK(brisk_menu_window_grab_notify), NULL);
46         g_signal_connect(self,
47                          "button-press-event",
48                          G_CALLBACK(brisk_menu_window_button_press),
49                          NULL);
50         g_signal_connect(self,
51                          "grab-broken-event",
52                          G_CALLBACK(brisk_menu_window_grab_broken),
53                          NULL);
54 }
55 
56 /**
57  * Mapped on screen, attempt a grab
58  */
brisk_menu_window_map(GtkWidget * widget,__brisk_unused__ gpointer udata)59 static gboolean brisk_menu_window_map(GtkWidget *widget, __brisk_unused__ gpointer udata)
60 {
61         GdkWindow *window = NULL;
62         BriskMenuWindow *self = NULL;
63 
64         self = BRISK_MENU_WINDOW(widget);
65 
66         /* Forcibly request focus */
67         window = gtk_widget_get_window(widget);
68         gdk_window_set_accept_focus(window, TRUE);
69         gdk_window_focus(window, GDK_CURRENT_TIME);
70         gtk_window_present(GTK_WINDOW(widget));
71         gtk_widget_grab_focus(self->search);
72 
73         brisk_menu_window_grab(self);
74 
75         return GDK_EVENT_STOP;
76 }
77 
78 /**
79  * We've been made non-visible, so drop our grab if we have it
80  */
brisk_menu_window_unmap(GtkWidget * widget,__brisk_unused__ gpointer udata)81 static gboolean brisk_menu_window_unmap(GtkWidget *widget, __brisk_unused__ gpointer udata)
82 {
83         brisk_menu_window_ungrab(BRISK_MENU_WINDOW(widget));
84         return GDK_EVENT_STOP;
85 }
86 
87 /**
88  * Grab the input events using the GdkSeat
89  */
90 #if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION < 20
91 /* Pre 3.20 grab behaviour */
brisk_menu_window_grab(BriskMenuWindow * self)92 static void brisk_menu_window_grab(BriskMenuWindow *self)
93 {
94         GdkDisplay *display = NULL;
95         GdkWindow *window = NULL;
96         GdkGrabStatus st;
97         GdkDeviceManager *manager = NULL;
98         GdkDevice *pointer, *keyboard = NULL;
99 
100         if (self->grabbed) {
101                 return;
102         }
103 
104         window = gtk_widget_get_window(GTK_WIDGET(self));
105         if (!window) {
106                 g_warning("Attempting to grab BriskMenuWindow when not realized");
107                 return;
108         }
109 
110         display = gtk_widget_get_display(GTK_WIDGET(self));
111         manager = gdk_display_get_device_manager(display);
112 
113         pointer = gdk_device_manager_get_client_pointer(manager);
114         keyboard = gdk_device_get_associated_device(pointer);
115 
116         st = gdk_device_grab(pointer,
117                              window,
118                              GDK_OWNERSHIP_NONE,
119                              TRUE,
120                              POINTER_EVENTS,
121                              NULL,
122                              GDK_CURRENT_TIME);
123 
124         if (st != GDK_GRAB_SUCCESS) {
125                 return;
126         }
127         if (keyboard) {
128                 st = gdk_device_grab(keyboard,
129                                      window,
130                                      GDK_OWNERSHIP_NONE,
131                                      TRUE,
132                                      KEYBOARD_EVENTS,
133                                      NULL,
134                                      GDK_CURRENT_TIME);
135                 if (st != GDK_GRAB_SUCCESS) {
136                         gdk_device_ungrab(keyboard, GDK_CURRENT_TIME);
137                         return;
138                 }
139         }
140 
141         self->grabbed = TRUE;
142         gtk_grab_add(GTK_WIDGET(self));
143 }
144 #else
145 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
brisk_menu_window_grab(BriskMenuWindow * self)146 static void brisk_menu_window_grab(BriskMenuWindow *self)
147 {
148         GdkDisplay *display = NULL;
149         GdkSeat *seat = NULL;
150         GdkWindow *window = NULL;
151         GdkSeatCapabilities caps = 0;
152         GdkGrabStatus st;
153 
154         if (self->grabbed) {
155                 return;
156         }
157 
158         window = gtk_widget_get_window(GTK_WIDGET(self));
159         if (!window) {
160                 g_warning("Attempting to grab BriskMenuWindow when not realized");
161                 return;
162         }
163 
164         display = gtk_widget_get_display(GTK_WIDGET(self));
165         seat = gdk_display_get_default_seat(display);
166 
167         if (gdk_seat_get_pointer(seat) != NULL) {
168                 caps |= GDK_SEAT_CAPABILITY_ALL_POINTING;
169         }
170         if (gdk_seat_get_keyboard(seat) != NULL) {
171                 caps |= GDK_SEAT_CAPABILITY_KEYBOARD;
172         }
173 
174         st = gdk_seat_grab(seat, window, caps, TRUE, NULL, NULL, NULL, NULL);
175         if (st == GDK_GRAB_SUCCESS) {
176                 self->grabbed = TRUE;
177                 gtk_grab_add(GTK_WIDGET(self));
178         }
179 }
180 G_GNUC_END_IGNORE_DEPRECATIONS
181 #endif
182 
183 /**
184  * Ungrab a previous grab by this widget
185  */
186 #if GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION < 20
187 /* Pre 3.20 behaviour */
brisk_menu_window_ungrab(BriskMenuWindow * self)188 static void brisk_menu_window_ungrab(BriskMenuWindow *self)
189 {
190         GdkDisplay *display = NULL;
191         GdkDeviceManager *manager = NULL;
192         GdkDevice *pointer, *keyboard = NULL;
193 
194         if (!self->grabbed) {
195                 return;
196         }
197 
198         display = gtk_widget_get_display(GTK_WIDGET(self));
199         manager = gdk_display_get_device_manager(display);
200 
201         pointer = gdk_device_manager_get_client_pointer(manager);
202         keyboard = gdk_device_get_associated_device(pointer);
203 
204         gtk_grab_remove(GTK_WIDGET(self));
205 
206         gdk_device_ungrab(pointer, GDK_CURRENT_TIME);
207         if (keyboard) {
208                 gdk_device_ungrab(keyboard, GDK_CURRENT_TIME);
209         }
210         self->grabbed = FALSE;
211 }
212 #else
213 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
214 static void brisk_menu_window_ungrab(BriskMenuWindow *self)
215 {
216         GdkDisplay *display = NULL;
217         GdkSeat *seat = NULL;
218 
219         if (!self->grabbed) {
220                 return;
221         }
222 
223         display = gtk_widget_get_display(GTK_WIDGET(self));
224         seat = gdk_display_get_default_seat(display);
225 
226         gtk_grab_remove(GTK_WIDGET(self));
227         gdk_seat_ungrab(seat);
228         self->grabbed = FALSE;
229 }
230 G_GNUC_END_IGNORE_DEPRECATIONS
231 #endif
232 
233 /**
234  * Grab was broken, most likely due to a window within our application
235  */
brisk_menu_window_grab_broken(GtkWidget * widget,__brisk_unused__ GdkEvent * event,__brisk_unused__ gpointer udata)236 static gboolean brisk_menu_window_grab_broken(GtkWidget *widget, __brisk_unused__ GdkEvent *event,
237                                               __brisk_unused__ gpointer udata)
238 {
239         BriskMenuWindow *self = NULL;
240 
241         self = BRISK_MENU_WINDOW(widget);
242         self->grabbed = FALSE;
243         return GDK_EVENT_PROPAGATE;
244 }
245 
246 /**
247  * Grab changed _within_ the application
248  *
249  * If our grab was broken, i.e. due to some popup menu, and we're still visible,
250  * we'll now try and grab focus once more.
251  */
brisk_menu_window_grab_notify(GtkWidget * widget,gboolean was_grabbed,__brisk_unused__ gpointer udata)252 static void brisk_menu_window_grab_notify(GtkWidget *widget, gboolean was_grabbed,
253                                           __brisk_unused__ gpointer udata)
254 {
255         BriskMenuWindow *self = NULL;
256 
257         /* Only interested in unshadowed */
258         if (!was_grabbed) {
259                 return;
260         }
261 
262         /* And being visible. ofc. */
263         if (!gtk_widget_get_visible(widget)) {
264                 return;
265         }
266 
267         self = BRISK_MENU_WINDOW(widget);
268         brisk_menu_window_grab(self);
269 }
270 
271 /**
272  * Check for clicks outside the window itself
273  */
brisk_menu_window_button_press(GtkWidget * widget,GdkEvent * event,__brisk_unused__ gpointer udata)274 static gboolean brisk_menu_window_button_press(GtkWidget *widget, GdkEvent *event,
275                                                __brisk_unused__ gpointer udata)
276 {
277         gint wx, wy = 0;
278         gint ww, wh = 0;
279 
280         gtk_window_get_size(GTK_WINDOW(widget), &ww, &wh);
281         gtk_window_get_position(GTK_WINDOW(widget), &wx, &wy);
282 
283         if ((event->button.x_root < wx || event->button.x_root > (wx + ww)) ||
284             (event->button.y_root < wy || event->button.y_root > (wy + wh))) {
285                 gtk_widget_hide(widget);
286                 return GDK_EVENT_STOP;
287         }
288         return GDK_EVENT_PROPAGATE;
289 }
290 
291 /*
292  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
293  *
294  * Local variables:
295  * c-basic-offset: 8
296  * tab-width: 8
297  * indent-tabs-mode: nil
298  * End:
299  *
300  * vi: set shiftwidth=8 tabstop=8 expandtab:
301  * :indentSize=8:tabSize=8:noTabs=true:
302  */
303