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