diff --git a/bin/bl-reload-gtk23 b/bin/bl-reload-gtk23 new file mode 100755 index 0000000..3864d38 --- /dev/null +++ b/bin/bl-reload-gtk23 @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +# +# bl-reload-gtk23: Make GTK2/3 reload settings file changes +# Copyright (C) 2020 2ion +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from argparse import ArgumentParser, Namespace +import Xlib.display # type: ignore +import Xlib.protocol # type: ignore +import logging +import os +import psutil # type: ignore +import shutil +import signal +import subprocess +import sys + +DESCRIPTION = (""" +After changing GTK2 and GTK3 configuration files, notify running GTK2 and GTK3 +clients to apply those changes. The notification mechanism used by GTK3 requires +that xsettingsd is running. If it is installed and not running, the program will +launch it. Note that xsettingsd does not read settings from GTK3's settings.ini +- if information is changed, it must be changed in xsettingsd config files as well. + +EXIT CODES: + 0 - both gtk2 and gtk3 clients notified successfully + 1 - failed to notify gtk2 clients + 2 - failed to notify gtk3 clients + 3 - failed to notify gtk2 and gtk3 clients + +""") + +LOG_FORMAT = "%(asctime)s %(levelname)s %(module)s %(funcName)s() : %(message)s" + +def getopts() -> Namespace: + ap = ArgumentParser(description=DESCRIPTION) + ap.add_argument("-d", "--debug", action="store_true", default=False, help="print debug information") + ap.add_argument("-f", "--force", action="store_true", default=False, help="ignore all errors") + opts = ap.parse_args() + if opts.debug: + logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) + else: + logging.basicConfig(level=logging.WARN, format=LOG_FORMAT) + return opts + +def sync_gtk2() -> None: + """ Tell GTK2 X11 clients to reload the GTK RC files and update their + appearance/settings if required. This implements this process without GTK/GDK + in order to be able to drop the dependency on the obsolete pygtk library. + GTK3/pygobject does not support GTK2. + + This function will always fail on non-X11 platforms as the GTK2 client + notification mechanism is based on X. + + This implementation is based on the following resources: + * From libgtk2 2.24.18: + * gdk_event_send_client_message_to_all_recurse() + * gdk_screen_broadcast_client_message() + * From kde-gtk-config https://github.com/KDE/kde-gtk-config/blob/a5d4ddb3b1a27ec2ee4e1b6957a98a57ad56d39c/gtkproxies/reload.c + """ + display = Xlib.display.Display(display=os.getenv("DISPLAY")) + wm_state_atom = display.intern_atom("WM_STATE", False) + gtkrc_atom = display.intern_atom("_GTK_READ_RCFILES", False) + + def send_event(window) -> bool: + """ Send a _GTK_READ_RCFILES client message to the given X window. + Returns true unless an exception occurs. """ + window.send_event( + Xlib.protocol.event.ClientMessage( + window = window, + client_type = gtkrc_atom, + data = (8, b"\0" * 20), + ), + propagate = 0, + event_mask = 0 + ) + return True + + def recurse_windows(window, parents) -> bool: + """ Given a X window, recurse over all its children and selectively + apply the send_event function to them. Returns true if an event got sent + to at least one window equal or below the given one. """ + sent = False + wm_state = window.get_property(wm_state_atom, wm_state_atom, 0, 0) + name = window.get_wm_name() + level = len(parents) + if wm_state is not None: + sent = send_event(window) + else: + tree = window.query_tree() + for child in tree.children: + if not recurse_windows(child, parents + [window.id]) and level == 1: + sent = send_event(window) + logging.debug("%10s %s %s [%24s] [%s]", + hex(window.id), + "W" if not not wm_state else " ", + "S" if sent else " ", + name[:24] if name else "", + ",".join(map(hex, parents))) + return sent + + for sno in range(0, display.screen_count()): + screen = display.screen(sno) + recurse_windows(screen.root, []) + +def sync_gtk3(): + """ GTK3 applications can be notified of changes to their theming via + xsettingsd. This requires that the GTK3 theming information has been updated + in settings.ini as well as the gsettings schema, managed either by a + standalone xettingsd implementation or the gnome-settings-daemon. As for now, + we only support reloading `xsettingsd`: Send SIGHUP if we find it running, or + start it if it is installed and not running. + * https://github.com/swaywm/sway/wiki/GTK-3-settings-on-Wayland + * https://github.com/KDE/kde-gtk-config/blob/a5d4ddb3b1a27ec2ee4e1b6957a98a57ad56d39c/kded/configeditor.cpp#L285 + """ + found = False + for proc in psutil.process_iter(): + if proc.name() == "xsettingsd": + proc.send_signal(signal.SIGHUP) + found = True + logging.debug("Found xsettingsd process and sent SIGHUP: PID %d", proc.pid) + break + if not found: + xsettingsd_binary = shutil.which("xsettingsd") + if xsettingsd_binary is not None: + proc = subprocess.Popen([xsettingsd_binary], cwd="/", start_new_session=True) + logging.debug("xsettingsd not running; started from %s; PID: %d", xsettingsd_binary, proc.pid) + else: + raise RuntimeError("xsettingsd not running and not in PATH, no settings changes propagated") + +def main() -> int: + opts = getopts() + ret = 0 + try: + sync_gtk2() + except Exception as err: + logging.warning("Failed to reload GTK2 settings: %s", err) + ret |= 1<<0 + try: + sync_gtk3() + except Exception as err: + logging.warning("Failed to reload GTK3 settings: %s", err) + ret |= 1<<1 + return 0 if opts.force else ret + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bin/mb-obthemes b/bin/mb-obthemes index a36134b..1fa2799 100755 --- a/bin/mb-obthemes +++ b/bin/mb-obthemes @@ -617,6 +617,19 @@ function getMenuPanel() { echo "configdir: $CONFIGDIR" cp "$HOME"/.config/mabox/mabox.conf "$CONFIGDIR"/ } +function setXsettingsd() { + mkdir -p $HOME/.config/xsettingsd + +gtk_theme="$(grep "^[^#]*gtk-theme-name" "${HOME}/.config/gtk-3.0/settings.ini")" +gtk_theme="${gtk_theme/gtk-theme-name*=}" +echo "Net/ThemeName \"$gtk_theme\"" >> "$HOME/.config/xsettingsd/xsettingsd.conf" + +gtk_theme_icons="$(grep "^[^#]*gtk-icon-theme-name" "${HOME}/.config/gtk-3.0/settings.ini")" +gtk_theme_icons="${gtk_theme_icons/gtk-icon-theme-name*=}" +echo "Net/IconThemeName \"$gtk_theme_icons\"" > "$HOME/.config/xsettingsd/xsettingsd.conf" + +bl-reload-gtk23 +} function checkTint2(){ # kill or restart tint2s for screenshot, if necessary if [[ $1 = stop ]];then if ! cat "$SETTINGS" | grep "TINT2" &>/dev/null;then @@ -1100,6 +1113,7 @@ function restoreSettings(){ #mb-regenerate-menu restoreMenuPanel "$FPATH" restoreSettings + setXsettingsd } ### END FUNCTIONS ######################################################