1 /*****************************************************************************
2 * Copyright 2003 - 2010 Craig Drummond <craig.p.drummond@gmail.com> *
3 * Copyright 2013 - 2015 Yichao Yu <yyc1992@gmail.com> *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU Lesser General Public License as *
7 * published by the Free Software Foundation; either version 2.1 of the *
8 * License, or (at your option) version 3, or any later version accepted *
9 * by the membership of KDE e.V. (or its successor approved by the *
10 * membership of KDE e.V.), which shall act as a proxy defined in *
11 * Section 6 of version 3 of the license. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
16 * Lesser General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU Lesser General Public *
19 * License along with this library. If not, *
20 * see <http://www.gnu.org/licenses/>. *
21 *****************************************************************************/
22
23 #include "window.h"
24
25 #include <qtcurve-utils/x11qtc.h>
26 #include <qtcurve-utils/x11wrap.h>
27 #include <qtcurve-utils/gtkprops.h>
28 #include <qtcurve-utils/log.h>
29 #include <qtcurve-cairo/utils.h>
30
31 #include <gdk/gdkkeysyms.h>
32 #include <gdk/gdkx.h>
33 #include <common/common.h>
34 #include <common/config_file.h>
35 #include "qt_settings.h"
36 #include "menu.h"
37 #include "dbus.h"
38
39 namespace QtCurve {
40 namespace Window {
41
42 static GtkWidget *currentActiveWindow = nullptr;
43
44 typedef struct {
45 int width;
46 int height;
47 int timer;
48 GtkWidget *widget;
49 bool locked;
50 } QtCWindow;
51
52 static GHashTable *table = nullptr;
53
54 static QtCWindow*
lookupHash(void * hash,bool create)55 lookupHash(void *hash, bool create)
56 {
57 QtCWindow *rv = nullptr;
58
59 if (!table)
60 table = g_hash_table_new(g_direct_hash, g_direct_equal);
61
62 rv = (QtCWindow*)g_hash_table_lookup(table, hash);
63
64 if (!rv && create) {
65 rv = qtcNew(QtCWindow);
66 rv->width = rv->height = rv->timer = 0;
67 rv->widget = nullptr;
68 rv->locked = false;
69 g_hash_table_insert(table, hash, rv);
70 rv = (QtCWindow*)g_hash_table_lookup(table, hash);
71 }
72 return rv;
73 }
74
75 static void
removeFromHash(void * hash)76 removeFromHash(void *hash)
77 {
78 if (table) {
79 QtCWindow *tv = lookupHash(hash, false);
80 if (tv) {
81 if (tv->timer) {
82 g_source_remove(tv->timer);
83 g_object_unref(G_OBJECT(tv->widget));
84 }
85 g_hash_table_remove(table, hash);
86 }
87 }
88 }
89
90 static void
cleanup(GtkWidget * widget)91 cleanup(GtkWidget *widget)
92 {
93 if (widget) {
94 GtkWidgetProps props(widget);
95 if (!(qtcIsFlatBgnd(opts.bgndAppearance)) ||
96 opts.bgndImage.type != IMG_NONE) {
97 removeFromHash(widget);
98 props->windowConfigure.disconn();
99 }
100 props->windowDestroy.disconn();
101 props->windowStyleSet.disconn();
102 if ((opts.menubarHiding & HIDE_KEYBOARD) ||
103 (opts.statusbarHiding & HIDE_KEYBOARD))
104 props->windowKeyRelease.disconn();
105 if ((opts.menubarHiding & HIDE_KWIN) ||
106 (opts.statusbarHiding & HIDE_KWIN))
107 props->windowMap.disconn();
108 if (opts.shadeMenubarOnlyWhenActive || BLEND_TITLEBAR ||
109 opts.menubarHiding || opts.statusbarHiding)
110 props->windowClientEvent.disconn();
111 props->windowHacked = false;
112 }
113 }
114
115 static gboolean
styleSet(GtkWidget * widget,GtkStyle *,void *)116 styleSet(GtkWidget *widget, GtkStyle*, void*)
117 {
118 cleanup(widget);
119 return false;
120 }
121
122 static bool toggleMenuBar(GtkWidget *widget);
123 static bool toggleStatusBar(GtkWidget *widget);
124
125 static gboolean
clientEvent(GtkWidget * widget,GdkEventClient * event,void *)126 clientEvent(GtkWidget *widget, GdkEventClient *event, void*)
127 {
128 if (gdk_x11_atom_to_xatom(event->message_type) ==
129 qtc_x11_qtc_active_window) {
130 if (event->data.l[0]) {
131 currentActiveWindow = widget;
132 } else if (currentActiveWindow == widget) {
133 currentActiveWindow = nullptr;
134 }
135 gtk_widget_queue_draw(widget);
136 } else if (gdk_x11_atom_to_xatom(event->message_type) ==
137 qtc_x11_qtc_titlebar_size) {
138 qtcGetWindowBorderSize(true);
139 GtkWidget *menubar = getMenuBar(widget, 0);
140
141 if (menubar) {
142 gtk_widget_queue_draw(menubar);
143 }
144 } else if (gdk_x11_atom_to_xatom(event->message_type) ==
145 qtc_x11_qtc_toggle_menubar) {
146 if (opts.menubarHiding & HIDE_KWIN && toggleMenuBar(widget)) {
147 gtk_widget_queue_draw(widget);
148 }
149 } else if (gdk_x11_atom_to_xatom(event->message_type) ==
150 qtc_x11_qtc_toggle_statusbar) {
151 if (opts.statusbarHiding & HIDE_KWIN &&
152 toggleStatusBar(widget)) {
153 gtk_widget_queue_draw(widget);
154 }
155 }
156 return false;
157 }
158
159 static gboolean
destroy(GtkWidget * widget,GdkEvent *,void *)160 destroy(GtkWidget *widget, GdkEvent*, void*)
161 {
162 cleanup(widget);
163 return false;
164 }
165
166 static bool
sizeRequest(GtkWidget * widget)167 sizeRequest(GtkWidget *widget)
168 {
169 if (widget && (!(qtcIsFlatBgnd(opts.bgndAppearance)) ||
170 IMG_NONE != opts.bgndImage.type)) {
171 QtcRect alloc = Widget::getAllocation(widget);
172 QtcRect rect = {0, 0, 0, 0};
173 if (qtcIsFlat(opts.bgndAppearance) &&
174 IMG_NONE != opts.bgndImage.type) {
175 EPixPos pos = (IMG_FILE == opts.bgndImage.type ?
176 opts.bgndImage.pos : PP_TR);
177 if (opts.bgndImage.type == IMG_FILE) {
178 qtcLoadBgndImage(&opts.bgndImage);
179 }
180 switch (pos) {
181 case PP_TL:
182 rect.width = opts.bgndImage.width + 1;
183 rect.height = opts.bgndImage.height + 1;
184 break;
185 case PP_TM:
186 case PP_TR:
187 rect.width = alloc.width;
188 rect.height = (opts.bgndImage.type == IMG_FILE ?
189 opts.bgndImage.height :
190 RINGS_HEIGHT(opts.bgndImage.type)) + 1;
191 break;
192 case PP_LM:
193 case PP_BL:
194 rect.width = opts.bgndImage.width + 1;
195 rect.height = alloc.height;
196 break;
197 case PP_CENTRED:
198 case PP_BR:
199 case PP_BM:
200 case PP_RM:
201 rect.width = alloc.width;
202 rect.height = alloc.height;
203 break;
204 }
205 if (alloc.width < rect.width) {
206 rect.width = alloc.width;
207 }
208 if (alloc.height < rect.height) {
209 rect.height = alloc.height;
210 }
211 } else {
212 rect.width = alloc.width, rect.height = alloc.height;
213 }
214 gdk_window_invalidate_rect(gtk_widget_get_window(widget),
215 (GdkRectangle*)&rect, false);
216 }
217 return false;
218 }
219
220 static gboolean
delayedUpdate(void * user_data)221 delayedUpdate(void *user_data)
222 {
223 QtCWindow *window = (QtCWindow*)user_data;
224
225 if (window) {
226 if (window->locked) {
227 window->locked = false;
228 return true;
229 } else {
230 g_source_remove(window->timer);
231 window->timer = 0;
232 // otherwise, trigger update
233 gdk_threads_enter();
234 sizeRequest(window->widget);
235 gdk_threads_leave();
236 g_object_unref(G_OBJECT(window->widget));
237 return false;
238 }
239 }
240 return false;
241 }
242
243 static gboolean
configure(GtkWidget *,GdkEventConfigure * event,void * data)244 configure(GtkWidget*, GdkEventConfigure *event, void *data)
245 {
246 QtCWindow *window = (QtCWindow*)data;
247
248 if (window && (event->width != window->width ||
249 event->height != window->height)) {
250 window->width = event->width;
251 window->height = event->height;
252
253 // schedule delayed timeOut
254 if (!window->timer) {
255 g_object_ref(G_OBJECT(window->widget));
256 window->timer =
257 g_timeout_add(50, delayedUpdate, window);
258 window->locked = false;
259 } else {
260 window->locked = true;
261 }
262 }
263 return false;
264 }
265
266 static bool
canGetChildren(GtkWidget * widget)267 canGetChildren(GtkWidget *widget)
268 {
269 return (qtSettings.app != GTK_APP_GHB ||
270 noneOf(gTypeName(widget), "GhbCompositor") ||
271 gtk_widget_get_realized(widget));
272 }
273
274 static bool
toggleMenuBar(GtkWidget * widget)275 toggleMenuBar(GtkWidget *widget)
276 {
277 GtkWidget *menuBar = getMenuBar(widget, 0);
278
279 if (menuBar) {
280 int size = 0;
281 qtcSetMenuBarHidden(qtSettings.appName,
282 gtk_widget_get_visible(menuBar));
283 if (gtk_widget_get_visible(menuBar)) {
284 gtk_widget_hide(menuBar);
285 } else {
286 size = Widget::getAllocation(menuBar).height;
287 gtk_widget_show(menuBar);
288 }
289
290 Menu::emitSize(menuBar, size);
291 menuBarDBus(widget, size);
292 return true;
293 }
294 return false;
295 }
296
297 static bool
toggleStatusBar(GtkWidget * widget)298 toggleStatusBar(GtkWidget *widget)
299 {
300 GtkWidget *statusBar = getStatusBar(widget, 0);
301
302 if (statusBar) {
303 bool state = gtk_widget_get_visible(statusBar);
304 qtcSetStatusBarHidden(qtSettings.appName, state);
305 if (state) {
306 gtk_widget_hide(statusBar);
307 } else {
308 gtk_widget_show(statusBar);
309 }
310 statusBarDBus(widget, state);
311 return true;
312 }
313 return false;
314 }
315
316 static void
setProperties(GtkWidget * w,unsigned short opacity)317 setProperties(GtkWidget *w, unsigned short opacity)
318 {
319 GtkWindow *topLevel = GTK_WINDOW(gtk_widget_get_toplevel(w));
320 unsigned long prop = (qtcIsFlatBgnd(opts.bgndAppearance) ?
321 (IMG_NONE != opts.bgndImage.type ?
322 APPEARANCE_RAISED : APPEARANCE_FLAT) :
323 opts.bgndAppearance) & 0xFF;
324 //GtkRcStyle *rcStyle=gtk_widget_get_modifier_style(w);
325 GdkColor *bgnd = /* rcStyle ? &rcStyle->bg[GTK_STATE_NORMAL] : */
326 &qtcPalette.background[ORIGINAL_SHADE];
327 xcb_window_t wid =
328 GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(topLevel)));
329
330 if (opacity != 100) {
331 qtcX11SetOpacity(wid, opacity);
332 }
333 prop |= (((toQtColor(bgnd->red) & 0xFF) << 24) |
334 ((toQtColor(bgnd->green) & 0xFF) << 16) |
335 ((toQtColor(bgnd->blue) & 0xFF) << 8));
336 qtcX11ChangeProperty(XCB_PROP_MODE_REPLACE, wid, qtc_x11_qtc_bgnd,
337 XCB_ATOM_CARDINAL, 32, 1, &prop);
338 qtcX11Flush();
339 }
340
341 static gboolean
keyRelease(GtkWidget * widget,GdkEventKey * event,void *)342 keyRelease(GtkWidget *widget, GdkEventKey *event, void*)
343 {
344 // Ensure only ctrl/alt/shift/capsLock are pressed...
345 if (GDK_CONTROL_MASK & event->state && GDK_MOD1_MASK & event->state &&
346 !event->is_modifier && 0 == (event->state & 0xFF00)) {
347 bool toggled = false;
348 if (opts.menubarHiding & HIDE_KEYBOARD &&
349 (GDK_KEY_m == event->keyval || GDK_KEY_M == event->keyval)) {
350 toggled = toggleMenuBar(widget);
351 }
352 if (opts.statusbarHiding & HIDE_KEYBOARD &&
353 (GDK_KEY_s == event->keyval || GDK_KEY_S == event->keyval)) {
354 toggled = toggleStatusBar(widget);
355 }
356 if (toggled) {
357 gtk_widget_queue_draw(widget);
358 }
359 }
360 return false;
361 }
362
363 static gboolean
mapWindow(GtkWidget * widget,GdkEventKey *,void *)364 mapWindow(GtkWidget *widget, GdkEventKey*, void*)
365 {
366 GtkWidgetProps props(widget);
367 setProperties(widget, props->windowOpacity);
368
369 if (opts.menubarHiding & HIDE_KWIN) {
370 GtkWidget *menuBar = getMenuBar(widget, 0);
371
372 if (menuBar) {
373 int size = (gtk_widget_get_visible(menuBar) ?
374 Widget::getAllocation(menuBar).height : 0);
375
376 Menu::emitSize(menuBar, size);
377 menuBarDBus(widget, size);
378 }
379 }
380
381 if (opts.statusbarHiding & HIDE_KWIN) {
382 GtkWidget *statusBar = getStatusBar(widget, 0);
383
384 if (statusBar) {
385 statusBarDBus(widget, !gtk_widget_get_visible(statusBar));
386 }
387 }
388 return false;
389 }
390
391 bool
isActive(GtkWidget * widget)392 isActive(GtkWidget *widget)
393 {
394 return widget && (gtk_window_is_active(GTK_WINDOW(widget)) ||
395 currentActiveWindow == widget);
396 }
397
398 bool
setup(GtkWidget * widget,int opacity)399 setup(GtkWidget *widget, int opacity)
400 {
401 GtkWidgetProps props(widget);
402 if (widget && !props->windowHacked) {
403 props->windowHacked = true;
404 if (!qtcIsFlatBgnd(opts.bgndAppearance) ||
405 opts.bgndImage.type != IMG_NONE) {
406 QtCWindow *window = lookupHash(widget, true);
407 if (window) {
408 QtcRect alloc = Widget::getAllocation(widget);
409 props->windowConfigure.conn("configure-event",
410 configure, window);
411 window->width = alloc.width;
412 window->height = alloc.height;
413 window->widget = widget;
414 }
415 }
416 props->windowDestroy.conn("destroy-event", destroy);
417 props->windowStyleSet.conn("style-set", styleSet);
418 if ((opts.menubarHiding & HIDE_KEYBOARD) ||
419 (opts.statusbarHiding & HIDE_KEYBOARD)) {
420 props->windowKeyRelease.conn("key-release-event", keyRelease);
421 }
422 props->windowOpacity = (unsigned short)opacity;
423 setProperties(widget, (unsigned short)opacity);
424
425 if ((opts.menubarHiding & HIDE_KWIN) ||
426 (opts.statusbarHiding & HIDE_KWIN) || 100 != opacity)
427 props->windowMap.conn("map-event", mapWindow);
428 if (opts.shadeMenubarOnlyWhenActive || BLEND_TITLEBAR ||
429 opts.menubarHiding || opts.statusbarHiding)
430 props->windowClientEvent.conn("client-event", clientEvent);
431 return true;
432 }
433 return false;
434 }
435
436 GtkWidget*
getMenuBar(GtkWidget * parent,int level)437 getMenuBar(GtkWidget *parent, int level)
438 {
439 if (level < 3 && GTK_IS_CONTAINER(parent) && canGetChildren(parent)
440 /* && gtk_widget_get_realized(parent)*/) {
441 GtkWidget *rv = nullptr;
442 GList *children = gtk_container_get_children(GTK_CONTAINER(parent));
443 for (GList *child = children;child && !rv;child = child->next) {
444 GtkWidget *boxChild = (GtkWidget*)child->data;
445
446 if (GTK_IS_MENU_BAR(boxChild)) {
447 rv = GTK_WIDGET(boxChild);
448 } else if (GTK_IS_CONTAINER(boxChild)) {
449 rv = getMenuBar(GTK_WIDGET(boxChild), level + 1);
450 }
451 }
452
453 if (children) {
454 g_list_free(children);
455 }
456 return rv;
457 }
458 return nullptr;
459 }
460
461 bool
setStatusBarProp(GtkWidget * w)462 setStatusBarProp(GtkWidget *w)
463 {
464 GtkWidgetProps props(w);
465 if (w && !props->statusBarSet) {
466 GtkWindow *topLevel = GTK_WINDOW(gtk_widget_get_toplevel(w));
467 xcb_window_t wid =
468 GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(topLevel)));
469
470 props->statusBarSet = true;
471 qtcX11SetStatusBar(wid);
472 return true;
473 }
474 return false;
475 }
476
477 GtkWidget*
getStatusBar(GtkWidget * parent,int level)478 getStatusBar(GtkWidget *parent, int level)
479 {
480 if (level < 3 && GTK_IS_CONTAINER(parent) && canGetChildren(parent)
481 /* && gtk_widget_get_realized(parent)*/) {
482 GtkWidget *rv = nullptr;
483 GList *children = gtk_container_get_children(GTK_CONTAINER(parent));
484 for(GList *child = children;child && !rv;child = child->next) {
485 GtkWidget *boxChild = (GtkWidget*)child->data;
486
487 if (GTK_IS_STATUSBAR(boxChild)) {
488 rv=GTK_WIDGET(boxChild);
489 } else if (GTK_IS_CONTAINER(boxChild)) {
490 rv = getStatusBar(GTK_WIDGET(boxChild), level + 1);
491 }
492 }
493 if (children) {
494 g_list_free(children);
495 }
496 return rv;
497 }
498 return nullptr;
499 }
500
501 void
statusBarDBus(GtkWidget * widget,bool state)502 statusBarDBus(GtkWidget *widget, bool state)
503 {
504 GtkWindow *topLevel = GTK_WINDOW(gtk_widget_get_toplevel(widget));
505 uint32_t xid = GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(topLevel)));
506 GDBus::callMethod("org.kde.kwin", "/QtCurve", "org.kde.QtCurve",
507 "statusBarState", xid, state);
508 }
509
510 void
menuBarDBus(GtkWidget * widget,int32_t size)511 menuBarDBus(GtkWidget *widget, int32_t size)
512 {
513 GtkWindow *topLevel = GTK_WINDOW(gtk_widget_get_toplevel(widget));
514 uint32_t xid = GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(topLevel)));
515 GDBus::callMethod("org.kde.kwin", "/QtCurve", "org.kde.QtCurve",
516 "menuBarSize", xid, size);
517 }
518
519 }
520 }
521