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