From d05f5bf9096aa291789d1312ae46403be3dbd6b6 Mon Sep 17 00:00:00 2001 From: ekklesia Date: Wed, 2 Sep 2026 20:26:12 -0500 Subject: [PATCH] feat: reserve classroom shell and restore dock interaction --- VERSION | 2 +- config/sway/config | 3 + scripts/classroom-browser | 45 ++++++- scripts/classroom-dock | 242 +++++++++++++++++++++----------------- 4 files changed, 178 insertions(+), 114 deletions(-) diff --git a/VERSION b/VERSION index 7e72641..7db2672 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.22 +0.1.26 diff --git a/config/sway/config b/config/sway/config index 1d2b62c..e4826bb 100644 --- a/config/sway/config +++ b/config/sway/config @@ -13,3 +13,6 @@ exec --no-startup-id /usr/local/bin/classroom-dock # Additional classroom controls are added explicitly by Ekklesia Classroom. include /etc/sway/config.d/* + +# Minimize focused window to scratchpad +bindsym $mod+m move container to workspace __ekklesia_hidden diff --git a/scripts/classroom-browser b/scripts/classroom-browser index 8d3d89d..2bed771 100755 --- a/scripts/classroom-browser +++ b/scripts/classroom-browser @@ -1,11 +1,44 @@ #!/bin/bash +set -e -if pgrep -x chrome >/dev/null || pgrep -x google-chrome >/dev/null; then - swaymsg '[app_id="google-chrome"] focus' >/dev/null 2>&1 || true +APP_ID='google-chrome' +HIDDEN_WS='__ekklesia_hidden' + +if ! pgrep -x chrome >/dev/null 2>&1 && + ! pgrep -x google-chrome >/dev/null 2>&1; then + exec google-chrome \ + --ozone-platform=wayland \ + --start-maximized \ + https://app.wonderink.org/ +fi + +focused_app="$( + swaymsg -t get_tree -r | + python3 -c ' +import json, sys + +tree = json.load(sys.stdin) + +def find_focused(node): + if node.get("focused"): + return node + for child in node.get("nodes", []) + node.get("floating_nodes", []): + result = find_focused(child) + if result: + return result + return None + +node = find_focused(tree) +print((node or {}).get("app_id") or "") +' +)" + +if [ "$focused_app" = "$APP_ID" ]; then + swaymsg '[app_id="google-chrome"] move container to workspace __ekklesia_hidden' >/dev/null exit 0 fi -exec google-chrome \ - --ozone-platform=wayland \ - --start-maximized \ - https://app.wonderink.org/ +swaymsg '[app_id="google-chrome"] move container to workspace current' >/dev/null 2>&1 || true +swaymsg '[app_id="google-chrome"] focus' >/dev/null 2>&1 || true +swaymsg '[app_id="google-chrome"] fullscreen disable' >/dev/null 2>&1 || true +swaymsg '[app_id="google-chrome"] floating disable' >/dev/null 2>&1 || true diff --git a/scripts/classroom-dock b/scripts/classroom-dock index 134decf..c7d5303 100755 --- a/scripts/classroom-dock +++ b/scripts/classroom-dock @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import subprocess +from datetime import datetime from pathlib import Path import gi @@ -48,10 +49,7 @@ def enabled(config, key, default=False): def launch(command): - subprocess.Popen( - command, - start_new_session=True, - ) + subprocess.Popen(command, start_new_session=True) def notify(title, message): @@ -68,7 +66,10 @@ def themed_icon(*names): for name in names: if theme.has_icon(name): - return Gtk.Image.new_from_icon_name(name, Gtk.IconSize.DIALOG) + return Gtk.Image.new_from_icon_name( + name, + Gtk.IconSize.DIALOG, + ) return Gtk.Image.new_from_icon_name( "application-x-executable", @@ -79,21 +80,23 @@ def themed_icon(*names): def custom_icon(filename, size): path = ICON_DIR / filename - if path.exists(): - pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( - str(path), - size, - size, - True, - ) - return Gtk.Image.new_from_pixbuf(pixbuf) + if not path.exists(): + return None - return None + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( + str(path), + size, + size, + True, + ) + + return Gtk.Image.new_from_pixbuf(pixbuf) -class ClassroomDock(Gtk.Window): +class ClassroomShell(Gtk.Window): def __init__(self): super().__init__() + screen = self.get_screen() visual = screen.get_rgba_visual() @@ -101,29 +104,83 @@ class ClassroomDock(Gtk.Window): self.set_visual(visual) self.set_app_paintable(True) - self.config = read_device_config() - - self.set_name("classroom-dock") + self.set_name("classroom-shell") self.set_decorated(False) self.set_resizable(False) + self.config = read_device_config() + GtkLayerShell.init_for_window(self) - GtkLayerShell.set_layer(self, GtkLayerShell.Layer.TOP) + GtkLayerShell.set_layer( + self, + GtkLayerShell.Layer.TOP, + ) GtkLayerShell.set_anchor( self, GtkLayerShell.Edge.BOTTOM, True, ) - - GtkLayerShell.set_margin( + GtkLayerShell.set_anchor( self, - GtkLayerShell.Edge.BOTTOM, - 12, + GtkLayerShell.Edge.LEFT, + True, + ) + GtkLayerShell.set_anchor( + self, + GtkLayerShell.Edge.RIGHT, + True, ) - GtkLayerShell.set_keyboard_interactivity(self, False) + GtkLayerShell.set_exclusive_zone(self, 76) + GtkLayerShell.set_keyboard_interactivity( + self, + False, + ) + layout = Gtk.Overlay() + layout.set_size_request(-1, 76) + + dock = self.build_dock() + + dock_align = Gtk.Alignment( + xalign=0.5, + yalign=0.5, + xscale=0, + yscale=0, + ) + dock_align.add(dock) + layout.add(dock_align) + + self.clock = Gtk.Label() + self.clock.get_style_context().add_class("clock") + + clock_align = Gtk.Alignment( + xalign=1.0, + yalign=0.5, + xscale=0, + yscale=0, + ) + clock_align.set_padding( + 0, + 0, + 0, + 20, + ) + clock_align.add(self.clock) + + layout.add_overlay(clock_align) + layout.set_overlay_pass_through(clock_align, True) + + self.add(layout) + + self.update_time() + GLib.timeout_add_seconds( + 15, + self.update_time, + ) + + def build_dock(self): dock = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, spacing=10, @@ -133,7 +190,6 @@ class ClassroomDock(Gtk.Window): dock.pack_start( self.make_button( self.browser_icon(), - "Browser", lambda *_: launch( ["/usr/local/bin/classroom-browser"] ), @@ -143,11 +199,14 @@ class ClassroomDock(Gtk.Window): 0, ) - if enabled(self.config, "DVD_ENABLED", False): + if enabled( + self.config, + "DVD_ENABLED", + False, + ): dock.pack_start( self.make_button( self.dvd_icon(), - "DVD", lambda *_: notify( "DVD", "DVD support is being prepared.", @@ -161,7 +220,6 @@ class ClassroomDock(Gtk.Window): dock.pack_start( self.make_button( self.sanctuary_icon(), - "Sanctuary", lambda *_: notify( "Sanctuary", "Sanctuary live view is being prepared.", @@ -172,19 +230,42 @@ class ClassroomDock(Gtk.Window): 0, ) - self.add(dock) + return dock + + def make_button(self, image, callback): + button = Gtk.Button() + button.set_relief(Gtk.ReliefStyle.NONE) + button.get_style_context().add_class( + "dock-button" + ) + + button.add(image) + button.connect("clicked", callback) + + return button def browser_icon(self): - image = custom_icon("browser.svg", 28) + image = custom_icon( + "browser.svg", + 28, + ) + if image is not None: return image - image = themed_icon("web-browser-symbolic", "internet-web-browser-symbolic") + image = themed_icon( + "web-browser-symbolic", + "internet-web-browser-symbolic", + ) image.set_pixel_size(28) + return image def dvd_icon(self): - image = custom_icon("dvd.svg", 40) + image = custom_icon( + "dvd.svg", + 40, + ) if image is not None: return image @@ -194,6 +275,7 @@ class ClassroomDock(Gtk.Window): "media-optical", ) image.set_pixel_size(28) + return image def sanctuary_icon(self): @@ -203,9 +285,16 @@ class ClassroomDock(Gtk.Window): False, ) - filename = "live.svg" if is_live else "live-off.svg" + filename = ( + "live.svg" + if is_live + else "live-off.svg" + ) - image = custom_icon(filename, 38) + image = custom_icon( + filename, + 38, + ) if image is not None: return image @@ -218,79 +307,18 @@ class ClassroomDock(Gtk.Window): return image - def make_button(self, image, tooltip, callback): - button = Gtk.Button() - button.set_relief(Gtk.ReliefStyle.NONE) - button.get_style_context().add_class("dock-button") - - button.add(image) - button.connect("clicked", callback) - - return button - -class ClassroomClock(Gtk.Window): - def __init__(self): - super().__init__() - screen = self.get_screen() - visual = screen.get_rgba_visual() - - if visual is not None: - self.set_visual(visual) - - self.set_app_paintable(True) - - self.set_name("classroom-clock") - self.set_decorated(False) - self.set_resizable(False) - - GtkLayerShell.init_for_window(self) - GtkLayerShell.set_layer(self, GtkLayerShell.Layer.TOP) - - GtkLayerShell.set_anchor( - self, - GtkLayerShell.Edge.BOTTOM, - True, - ) - - GtkLayerShell.set_anchor( - self, - GtkLayerShell.Edge.RIGHT, - True, - ) - - GtkLayerShell.set_margin( - self, - GtkLayerShell.Edge.BOTTOM, - 16, - ) - - GtkLayerShell.set_margin( - self, - GtkLayerShell.Edge.RIGHT, - 20, - ) - - GtkLayerShell.set_keyboard_interactivity(self, False) - - self.label = Gtk.Label() - self.label.get_style_context().add_class("clock") - self.add(self.label) - - self.update_time() - GLib.timeout_add_seconds(15, self.update_time) - def update_time(self): - from datetime import datetime - - self.label.set_text( + self.clock.set_text( datetime.now().strftime("%-I:%M %p") ) - return True + def load_css(): provider = Gtk.CssProvider() - provider.load_from_path(str(CSS_FILE)) + provider.load_from_path( + str(CSS_FILE) + ) screen = Gdk.Screen.get_default() @@ -304,15 +332,15 @@ def load_css(): def main(): load_css() - dock = ClassroomDock() - dock.connect("destroy", Gtk.main_quit) - dock.show_all() - - clock = ClassroomClock() - clock.show_all() + shell = ClassroomShell() + shell.connect( + "destroy", + Gtk.main_quit, + ) + shell.show_all() Gtk.main() if __name__ == "__main__": - main() \ No newline at end of file + main()