File indexing completed on 2024-04-21 05:47:02

0001 /*****************************************************************************
0002  *   Copyright 2003 - 2010 Craig Drummond <craig.p.drummond@gmail.com>       *
0003  *   Copyright 2013 - 2015 Yichao Yu <yyc1992@gmail.com>                     *
0004  *                                                                           *
0005  *   This program is free software; you can redistribute it and/or modify    *
0006  *   it under the terms of the GNU Lesser General Public License as          *
0007  *   published by the Free Software Foundation; either version 2.1 of the    *
0008  *   License, or (at your option) version 3, or any later version accepted   *
0009  *   by the membership of KDE e.V. (or its successor approved by the         *
0010  *   membership of KDE e.V.), which shall act as a proxy defined in          *
0011  *   Section 6 of version 3 of the license.                                  *
0012  *                                                                           *
0013  *   This program is distributed in the hope that it will be useful,         *
0014  *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
0015  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       *
0016  *   Lesser General Public License for more details.                         *
0017  *                                                                           *
0018  *   You should have received a copy of the GNU Lesser General Public        *
0019  *   License along with this library. If not,                                *
0020  *   see <http://www.gnu.org/licenses/>.                                     *
0021  *****************************************************************************/
0022 
0023 #include "window.h"
0024 
0025 #include <qtcurve-utils/x11qtc.h>
0026 #include <qtcurve-utils/x11wrap.h>
0027 #include <qtcurve-utils/gtkprops.h>
0028 #include <qtcurve-utils/log.h>
0029 #include <qtcurve-cairo/utils.h>
0030 
0031 #include <gdk/gdkkeysyms.h>
0032 #include <gdk/gdkx.h>
0033 #include <common/common.h>
0034 #include <common/config_file.h>
0035 #include "qt_settings.h"
0036 #include "menu.h"
0037 #include "dbus.h"
0038 
0039 namespace QtCurve {
0040 namespace Window {
0041 
0042 static GtkWidget *currentActiveWindow = nullptr;
0043 
0044 typedef struct {
0045     int width;
0046     int height;
0047     int timer;
0048     GtkWidget *widget;
0049     bool locked;
0050 } QtCWindow;
0051 
0052 static GHashTable *table = nullptr;
0053 
0054 static QtCWindow*
0055 lookupHash(void *hash, bool create)
0056 {
0057     QtCWindow *rv = nullptr;
0058 
0059     if (!table)
0060         table = g_hash_table_new(g_direct_hash, g_direct_equal);
0061 
0062     rv = (QtCWindow*)g_hash_table_lookup(table, hash);
0063 
0064     if (!rv && create) {
0065         rv = qtcNew(QtCWindow);
0066         rv->width = rv->height = rv->timer = 0;
0067         rv->widget = nullptr;
0068         rv->locked = false;
0069         g_hash_table_insert(table, hash, rv);
0070         rv = (QtCWindow*)g_hash_table_lookup(table, hash);
0071     }
0072     return rv;
0073 }
0074 
0075 static void
0076 removeFromHash(void *hash)
0077 {
0078     if (table) {
0079         QtCWindow *tv = lookupHash(hash, false);
0080         if (tv) {
0081             if (tv->timer) {
0082                 g_source_remove(tv->timer);
0083                 g_object_unref(G_OBJECT(tv->widget));
0084             }
0085             g_hash_table_remove(table, hash);
0086         }
0087     }
0088 }
0089 
0090 static void
0091 cleanup(GtkWidget *widget)
0092 {
0093     if (widget) {
0094         GtkWidgetProps props(widget);
0095         if (!(qtcIsFlatBgnd(opts.bgndAppearance)) ||
0096             opts.bgndImage.type != IMG_NONE) {
0097             removeFromHash(widget);
0098             props->windowConfigure.disconn();
0099         }
0100         props->windowDestroy.disconn();
0101         props->windowStyleSet.disconn();
0102         if ((opts.menubarHiding & HIDE_KEYBOARD) ||
0103             (opts.statusbarHiding & HIDE_KEYBOARD))
0104             props->windowKeyRelease.disconn();
0105         if ((opts.menubarHiding & HIDE_KWIN) ||
0106             (opts.statusbarHiding & HIDE_KWIN))
0107             props->windowMap.disconn();
0108         if (opts.shadeMenubarOnlyWhenActive || BLEND_TITLEBAR ||
0109             opts.menubarHiding || opts.statusbarHiding)
0110             props->windowClientEvent.disconn();
0111         props->windowHacked = false;
0112     }
0113 }
0114 
0115 static gboolean
0116 styleSet(GtkWidget *widget, GtkStyle*, void*)
0117 {
0118     cleanup(widget);
0119     return false;
0120 }
0121 
0122 static bool toggleMenuBar(GtkWidget *widget);
0123 static bool toggleStatusBar(GtkWidget *widget);
0124 
0125 static gboolean
0126 clientEvent(GtkWidget *widget, GdkEventClient *event, void*)
0127 {
0128     if (gdk_x11_atom_to_xatom(event->message_type) ==
0129         qtc_x11_qtc_active_window) {
0130         if (event->data.l[0]) {
0131             currentActiveWindow = widget;
0132         } else if (currentActiveWindow == widget) {
0133             currentActiveWindow = nullptr;
0134         }
0135         gtk_widget_queue_draw(widget);
0136     } else if (gdk_x11_atom_to_xatom(event->message_type) ==
0137                qtc_x11_qtc_titlebar_size) {
0138         qtcGetWindowBorderSize(true);
0139         GtkWidget *menubar = getMenuBar(widget, 0);
0140 
0141         if (menubar) {
0142             gtk_widget_queue_draw(menubar);
0143         }
0144     } else if (gdk_x11_atom_to_xatom(event->message_type) ==
0145                qtc_x11_qtc_toggle_menubar) {
0146         if (opts.menubarHiding & HIDE_KWIN && toggleMenuBar(widget)) {
0147             gtk_widget_queue_draw(widget);
0148         }
0149     } else if (gdk_x11_atom_to_xatom(event->message_type) ==
0150                qtc_x11_qtc_toggle_statusbar) {
0151         if (opts.statusbarHiding & HIDE_KWIN &&
0152             toggleStatusBar(widget)) {
0153             gtk_widget_queue_draw(widget);
0154         }
0155     }
0156     return false;
0157 }
0158 
0159 static gboolean
0160 destroy(GtkWidget *widget, GdkEvent*, void*)
0161 {
0162     cleanup(widget);
0163     return false;
0164 }
0165 
0166 static bool
0167 sizeRequest(GtkWidget *widget)
0168 {
0169     if (widget && (!(qtcIsFlatBgnd(opts.bgndAppearance)) ||
0170                    IMG_NONE != opts.bgndImage.type)) {
0171         QtcRect alloc = Widget::getAllocation(widget);
0172         QtcRect rect = {0, 0, 0, 0};
0173         if (qtcIsFlat(opts.bgndAppearance) &&
0174             IMG_NONE != opts.bgndImage.type) {
0175             EPixPos pos = (IMG_FILE == opts.bgndImage.type ?
0176                            opts.bgndImage.pos : PP_TR);
0177             if (opts.bgndImage.type == IMG_FILE) {
0178                 qtcLoadBgndImage(&opts.bgndImage);
0179             }
0180             switch (pos) {
0181             case PP_TL:
0182                 rect.width  = opts.bgndImage.width + 1;
0183                 rect.height = opts.bgndImage.height + 1;
0184                 break;
0185             case PP_TM:
0186             case PP_TR:
0187                 rect.width = alloc.width;
0188                 rect.height = (opts.bgndImage.type == IMG_FILE ?
0189                                opts.bgndImage.height :
0190                                RINGS_HEIGHT(opts.bgndImage.type)) + 1;
0191                 break;
0192             case PP_LM:
0193             case PP_BL:
0194                 rect.width = opts.bgndImage.width + 1;
0195                 rect.height = alloc.height;
0196                 break;
0197             case PP_CENTRED:
0198             case PP_BR:
0199             case PP_BM:
0200             case PP_RM:
0201                 rect.width = alloc.width;
0202                 rect.height = alloc.height;
0203                 break;
0204             }
0205             if (alloc.width < rect.width) {
0206                 rect.width = alloc.width;
0207             }
0208             if (alloc.height < rect.height) {
0209                 rect.height = alloc.height;
0210             }
0211         } else {
0212             rect.width = alloc.width, rect.height = alloc.height;
0213         }
0214         gdk_window_invalidate_rect(gtk_widget_get_window(widget),
0215                                    (GdkRectangle*)&rect, false);
0216     }
0217     return false;
0218 }
0219 
0220 static gboolean
0221 delayedUpdate(void *user_data)
0222 {
0223     QtCWindow *window = (QtCWindow*)user_data;
0224 
0225     if (window) {
0226         if (window->locked) {
0227             window->locked = false;
0228             return true;
0229         } else {
0230             g_source_remove(window->timer);
0231             window->timer = 0;
0232             // otherwise, trigger update
0233             gdk_threads_enter();
0234             sizeRequest(window->widget);
0235             gdk_threads_leave();
0236             g_object_unref(G_OBJECT(window->widget));
0237             return false;
0238         }
0239     }
0240     return false;
0241 }
0242 
0243 static gboolean
0244 configure(GtkWidget*, GdkEventConfigure *event, void *data)
0245 {
0246     QtCWindow *window = (QtCWindow*)data;
0247 
0248     if (window && (event->width != window->width ||
0249                    event->height != window->height)) {
0250         window->width = event->width;
0251         window->height = event->height;
0252 
0253         // schedule delayed timeOut
0254         if (!window->timer) {
0255             g_object_ref(G_OBJECT(window->widget));
0256             window->timer =
0257                 g_timeout_add(50, delayedUpdate, window);
0258             window->locked = false;
0259         } else {
0260             window->locked = true;
0261         }
0262     }
0263     return false;
0264 }
0265 
0266 static bool
0267 canGetChildren(GtkWidget *widget)
0268 {
0269     return (qtSettings.app != GTK_APP_GHB ||
0270             noneOf(gTypeName(widget), "GhbCompositor") ||
0271             gtk_widget_get_realized(widget));
0272 }
0273 
0274 static bool
0275 toggleMenuBar(GtkWidget *widget)
0276 {
0277     GtkWidget *menuBar = getMenuBar(widget, 0);
0278 
0279     if (menuBar) {
0280         int size = 0;
0281         qtcSetMenuBarHidden(qtSettings.appName,
0282                             gtk_widget_get_visible(menuBar));
0283         if (gtk_widget_get_visible(menuBar)) {
0284             gtk_widget_hide(menuBar);
0285         } else {
0286             size = Widget::getAllocation(menuBar).height;
0287             gtk_widget_show(menuBar);
0288         }
0289 
0290         Menu::emitSize(menuBar, size);
0291         menuBarDBus(widget, size);
0292         return true;
0293     }
0294     return false;
0295 }
0296 
0297 static bool
0298 toggleStatusBar(GtkWidget *widget)
0299 {
0300     GtkWidget *statusBar = getStatusBar(widget, 0);
0301 
0302     if (statusBar) {
0303         bool state = gtk_widget_get_visible(statusBar);
0304         qtcSetStatusBarHidden(qtSettings.appName, state);
0305         if (state) {
0306             gtk_widget_hide(statusBar);
0307         } else {
0308             gtk_widget_show(statusBar);
0309         }
0310         statusBarDBus(widget, state);
0311         return true;
0312     }
0313     return false;
0314 }
0315 
0316 static void
0317 setProperties(GtkWidget *w, unsigned short opacity)
0318 {
0319     GtkWindow *topLevel = GTK_WINDOW(gtk_widget_get_toplevel(w));
0320     unsigned long prop = (qtcIsFlatBgnd(opts.bgndAppearance) ?
0321                           (IMG_NONE != opts.bgndImage.type ?
0322                            APPEARANCE_RAISED : APPEARANCE_FLAT) :
0323                           opts.bgndAppearance) & 0xFF;
0324     //GtkRcStyle *rcStyle=gtk_widget_get_modifier_style(w);
0325     GdkColor *bgnd = /* rcStyle ? &rcStyle->bg[GTK_STATE_NORMAL] : */
0326         &qtcPalette.background[ORIGINAL_SHADE];
0327     xcb_window_t wid =
0328         GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(topLevel)));
0329 
0330     if (opacity != 100) {
0331         qtcX11SetOpacity(wid, opacity);
0332     }
0333     prop |= (((toQtColor(bgnd->red) & 0xFF) << 24) |
0334              ((toQtColor(bgnd->green) & 0xFF) << 16) |
0335              ((toQtColor(bgnd->blue) & 0xFF) << 8));
0336     qtcX11ChangeProperty(XCB_PROP_MODE_REPLACE, wid, qtc_x11_qtc_bgnd,
0337                          XCB_ATOM_CARDINAL, 32, 1, &prop);
0338     qtcX11Flush();
0339 }
0340 
0341 static gboolean
0342 keyRelease(GtkWidget *widget, GdkEventKey *event, void*)
0343 {
0344     // Ensure only ctrl/alt/shift/capsLock are pressed...
0345     if (GDK_CONTROL_MASK & event->state && GDK_MOD1_MASK & event->state &&
0346         !event->is_modifier && 0 == (event->state & 0xFF00)) {
0347         bool toggled = false;
0348         if (opts.menubarHiding & HIDE_KEYBOARD &&
0349             (GDK_KEY_m == event->keyval || GDK_KEY_M == event->keyval)) {
0350             toggled = toggleMenuBar(widget);
0351         }
0352         if (opts.statusbarHiding & HIDE_KEYBOARD &&
0353             (GDK_KEY_s == event->keyval || GDK_KEY_S == event->keyval)) {
0354             toggled = toggleStatusBar(widget);
0355         }
0356         if (toggled) {
0357             gtk_widget_queue_draw(widget);
0358         }
0359     }
0360     return false;
0361 }
0362 
0363 static gboolean
0364 mapWindow(GtkWidget *widget, GdkEventKey*, void*)
0365 {
0366     GtkWidgetProps props(widget);
0367     setProperties(widget, props->windowOpacity);
0368 
0369     if (opts.menubarHiding & HIDE_KWIN) {
0370         GtkWidget *menuBar = getMenuBar(widget, 0);
0371 
0372         if (menuBar) {
0373             int size = (gtk_widget_get_visible(menuBar) ?
0374                         Widget::getAllocation(menuBar).height : 0);
0375 
0376             Menu::emitSize(menuBar, size);
0377             menuBarDBus(widget, size);
0378         }
0379     }
0380 
0381     if (opts.statusbarHiding & HIDE_KWIN) {
0382         GtkWidget *statusBar = getStatusBar(widget, 0);
0383 
0384         if (statusBar) {
0385             statusBarDBus(widget, !gtk_widget_get_visible(statusBar));
0386         }
0387     }
0388     return false;
0389 }
0390 
0391 bool
0392 isActive(GtkWidget *widget)
0393 {
0394     return widget && (gtk_window_is_active(GTK_WINDOW(widget)) ||
0395                       currentActiveWindow == widget);
0396 }
0397 
0398 bool
0399 setup(GtkWidget *widget, int opacity)
0400 {
0401     GtkWidgetProps props(widget);
0402     if (widget && !props->windowHacked) {
0403         props->windowHacked = true;
0404         if (!qtcIsFlatBgnd(opts.bgndAppearance) ||
0405             opts.bgndImage.type != IMG_NONE) {
0406             QtCWindow *window = lookupHash(widget, true);
0407             if (window) {
0408                 QtcRect alloc = Widget::getAllocation(widget);
0409                 props->windowConfigure.conn("configure-event",
0410                                             configure, window);
0411                 window->width = alloc.width;
0412                 window->height = alloc.height;
0413                 window->widget = widget;
0414             }
0415         }
0416         props->windowDestroy.conn("destroy-event", destroy);
0417         props->windowStyleSet.conn("style-set", styleSet);
0418         if ((opts.menubarHiding & HIDE_KEYBOARD) ||
0419             (opts.statusbarHiding & HIDE_KEYBOARD)) {
0420             props->windowKeyRelease.conn("key-release-event", keyRelease);
0421         }
0422         props->windowOpacity = (unsigned short)opacity;
0423         setProperties(widget, (unsigned short)opacity);
0424 
0425         if ((opts.menubarHiding & HIDE_KWIN) ||
0426             (opts.statusbarHiding & HIDE_KWIN) || 100 != opacity)
0427             props->windowMap.conn("map-event", mapWindow);
0428         if (opts.shadeMenubarOnlyWhenActive || BLEND_TITLEBAR ||
0429             opts.menubarHiding || opts.statusbarHiding)
0430             props->windowClientEvent.conn("client-event", clientEvent);
0431         return true;
0432     }
0433     return false;
0434 }
0435 
0436 GtkWidget*
0437 getMenuBar(GtkWidget *parent, int level)
0438 {
0439     if (level < 3 && GTK_IS_CONTAINER(parent) && canGetChildren(parent)
0440         /* && gtk_widget_get_realized(parent)*/) {
0441         GtkWidget *rv = nullptr;
0442         GList *children = gtk_container_get_children(GTK_CONTAINER(parent));
0443         for (GList *child = children;child && !rv;child = child->next) {
0444             GtkWidget *boxChild = (GtkWidget*)child->data;
0445 
0446             if (GTK_IS_MENU_BAR(boxChild)) {
0447                 rv = GTK_WIDGET(boxChild);
0448             } else if (GTK_IS_CONTAINER(boxChild)) {
0449                 rv = getMenuBar(GTK_WIDGET(boxChild), level + 1);
0450             }
0451         }
0452 
0453         if (children) {
0454             g_list_free(children);
0455         }
0456         return rv;
0457     }
0458     return nullptr;
0459 }
0460 
0461 bool
0462 setStatusBarProp(GtkWidget *w)
0463 {
0464     GtkWidgetProps props(w);
0465     if (w && !props->statusBarSet) {
0466         GtkWindow *topLevel = GTK_WINDOW(gtk_widget_get_toplevel(w));
0467         xcb_window_t wid =
0468             GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(topLevel)));
0469 
0470         props->statusBarSet = true;
0471         qtcX11SetStatusBar(wid);
0472         return true;
0473     }
0474     return false;
0475 }
0476 
0477 GtkWidget*
0478 getStatusBar(GtkWidget *parent, int level)
0479 {
0480     if (level < 3 && GTK_IS_CONTAINER(parent) && canGetChildren(parent)
0481         /* && gtk_widget_get_realized(parent)*/) {
0482         GtkWidget *rv = nullptr;
0483         GList *children = gtk_container_get_children(GTK_CONTAINER(parent));
0484         for(GList *child = children;child && !rv;child = child->next) {
0485             GtkWidget *boxChild = (GtkWidget*)child->data;
0486 
0487             if (GTK_IS_STATUSBAR(boxChild)) {
0488                 rv=GTK_WIDGET(boxChild);
0489             } else if (GTK_IS_CONTAINER(boxChild)) {
0490                 rv = getStatusBar(GTK_WIDGET(boxChild), level + 1);
0491             }
0492         }
0493         if (children) {
0494             g_list_free(children);
0495         }
0496         return rv;
0497     }
0498     return nullptr;
0499 }
0500 
0501 void
0502 statusBarDBus(GtkWidget *widget, bool state)
0503 {
0504     GtkWindow *topLevel = GTK_WINDOW(gtk_widget_get_toplevel(widget));
0505     uint32_t xid = GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(topLevel)));
0506     GDBus::callMethod("org.kde.kwin", "/QtCurve", "org.kde.QtCurve",
0507                       "statusBarState", xid, state);
0508 }
0509 
0510 void
0511 menuBarDBus(GtkWidget *widget, int32_t size)
0512 {
0513     GtkWindow *topLevel = GTK_WINDOW(gtk_widget_get_toplevel(widget));
0514     uint32_t xid = GDK_WINDOW_XID(gtk_widget_get_window(GTK_WIDGET(topLevel)));
0515     GDBus::callMethod("org.kde.kwin", "/QtCurve", "org.kde.QtCurve",
0516                       "menuBarSize", xid, size);
0517 }
0518 
0519 }
0520 }