diff --git a/config/dock/style.css b/config/dock/style.css new file mode 100644 index 0000000..e4769d5 --- /dev/null +++ b/config/dock/style.css @@ -0,0 +1,85 @@ +window#classroom-dock, +window#classroom-clock { + background-color: transparent; + background-image: none; + border: none; + box-shadow: none; +} + +/* Main floating dock */ +.dock { + background-image: none; + background-color: rgba(34, 38, 50, 0.62); + + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 24px; + + padding: 8px 10px; + + /* Important: no shadow here. + GTK was clipping it against the rectangular window edge. */ + box-shadow: none; +} + +button.dock-button { + color: white; + + background-image: none; + background-color: rgba(255, 255, 255, 0.05); + + border: none; + border-radius: 16px; + + padding: 6px; + margin: 0; + + min-width: 44px; + min-height: 44px; + + box-shadow: none; + outline: none; +} + +button.dock-button:hover { + background-image: none; + background-color: rgba(70, 75, 90, 0.85); + box-shadow: none; +} + +button.dock-button:active { + background-image: none; + background-color: rgba(85, 90, 108, 0.90); + box-shadow: none; +} + +button.dock-button:focus { + background-image: none; + background-color: rgba(255, 255, 255, 0.08); + box-shadow: none; + outline: none; +} + +button.dock-button image { + color: white; + -gtk-icon-effect: none; +} + + +/* Clock */ + +.clock { + color: white; + + background-image: none; + background-color: rgba(34, 38, 50, 0.62); + + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 16px; + + padding: 7px 12px; + + font-size: 17px; + font-weight: 600; + + box-shadow: none; +} diff --git a/config/icons/browser.svg b/config/icons/browser.svg new file mode 100644 index 0000000..51c93ed --- /dev/null +++ b/config/icons/browser.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/config/icons/dvd.svg b/config/icons/dvd.svg new file mode 100644 index 0000000..b571d06 --- /dev/null +++ b/config/icons/dvd.svg @@ -0,0 +1,67 @@ + + + + + + + 08dvdlogo- + + + + + + + + + diff --git a/config/icons/live-off.svg b/config/icons/live-off.svg new file mode 100644 index 0000000..538ae88 --- /dev/null +++ b/config/icons/live-off.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/config/icons/live.svg b/config/icons/live.svg new file mode 100644 index 0000000..942dfe6 --- /dev/null +++ b/config/icons/live.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/config/sway/config b/config/sway/config index 03b4ea3..1d2b62c 100644 --- a/config/sway/config +++ b/config/sway/config @@ -4,10 +4,10 @@ set $mod Mod4 # Wallpaper -output * bg #202020 solid_color +output * bg /usr/share/backgrounds/ekklesia/classroom-default.png fill -# Waybar -exec --no-startup-id waybar -c /etc/ekklesia-classroom/waybar/config.jsonc -s /etc/ekklesia-classroom/waybar/style.css +# Ekklesia Classroom dock +exec --no-startup-id /usr/local/bin/classroom-dock # Keep the interface intentionally minimal. # Additional classroom controls are added explicitly by Ekklesia Classroom. diff --git a/config/wallpapers/classroom-default.png b/config/wallpapers/classroom-default.png new file mode 100644 index 0000000..3cce003 Binary files /dev/null and b/config/wallpapers/classroom-default.png differ diff --git a/packaging/debian/control.template b/packaging/debian/control.template index c9a640b..7d38f88 100644 --- a/packaging/debian/control.template +++ b/packaging/debian/control.template @@ -4,6 +4,6 @@ Section: misc Priority: optional Architecture: amd64 Maintainer: Ekklesia -Depends: sway, swaybg, waybar, mako-notifier, dbus-user-session, xwayland, fonts-dejavu, fonts-noto, network-manager, pipewire, pipewire-audio, wireplumber, cups +Depends: sway, swaybg, waybar, mako-notifier, dbus-user-session, xwayland, fonts-dejavu, fonts-noto, network-manager, pipewire, pipewire-audio, wireplumber, cups, python3-gi, gir1.2-gtk-3.0, gir1.2-gtklayershell-0.1, adwaita-icon-theme, libnotify-bin Description: Ekklesia managed classroom environment A minimal Wayland classroom environment for Ekklesia-managed church computers. diff --git a/scripts/classroom-dock b/scripts/classroom-dock new file mode 100755 index 0000000..134decf --- /dev/null +++ b/scripts/classroom-dock @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 + +import subprocess +from pathlib import Path + +import gi + +gi.require_version("Gtk", "3.0") +gi.require_version("GtkLayerShell", "0.1") + +from gi.repository import Gtk, Gdk, GdkPixbuf, GLib, GtkLayerShell + + +CONFIG_FILE = Path("/etc/ekklesia-classroom/device.conf") +CSS_FILE = Path("/etc/ekklesia-classroom/dock/style.css") +ICON_DIR = Path("/etc/ekklesia-classroom/icons") + + +def read_device_config(): + config = {} + + if not CONFIG_FILE.exists(): + return config + + for raw_line in CONFIG_FILE.read_text().splitlines(): + line = raw_line.strip() + + if not line or line.startswith("#") or "=" not in line: + continue + + key, value = line.split("=", 1) + config[key.strip()] = value.strip() + + return config + + +def enabled(config, key, default=False): + if key not in config: + return default + + return config[key].lower() in { + "1", + "true", + "yes", + "on", + "enabled", + } + + +def launch(command): + subprocess.Popen( + command, + start_new_session=True, + ) + + +def notify(title, message): + launch([ + "notify-send", + "--app-name=Ekklesia Classroom", + title, + message, + ]) + + +def themed_icon(*names): + theme = Gtk.IconTheme.get_default() + + 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( + "application-x-executable", + Gtk.IconSize.DIALOG, + ) + + +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) + + return None + + +class ClassroomDock(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.config = read_device_config() + + self.set_name("classroom-dock") + 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_margin( + self, + GtkLayerShell.Edge.BOTTOM, + 12, + ) + + GtkLayerShell.set_keyboard_interactivity(self, False) + + dock = Gtk.Box( + orientation=Gtk.Orientation.HORIZONTAL, + spacing=10, + ) + dock.get_style_context().add_class("dock") + + dock.pack_start( + self.make_button( + self.browser_icon(), + "Browser", + lambda *_: launch( + ["/usr/local/bin/classroom-browser"] + ), + ), + False, + False, + 0, + ) + + 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.", + ), + ), + False, + False, + 0, + ) + + dock.pack_start( + self.make_button( + self.sanctuary_icon(), + "Sanctuary", + lambda *_: notify( + "Sanctuary", + "Sanctuary live view is being prepared.", + ), + ), + False, + False, + 0, + ) + + self.add(dock) + + def browser_icon(self): + image = custom_icon("browser.svg", 28) + if image is not None: + return image + + 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) + + if image is not None: + return image + + image = themed_icon( + "media-optical-symbolic", + "media-optical", + ) + image.set_pixel_size(28) + return image + + def sanctuary_icon(self): + is_live = enabled( + self.config, + "SANCTUARY_LIVE", + False, + ) + + filename = "live.svg" if is_live else "live-off.svg" + + image = custom_icon(filename, 38) + + if image is not None: + return image + + image = themed_icon( + "video-display-symbolic", + "video-display", + ) + image.set_pixel_size(28) + + 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( + datetime.now().strftime("%-I:%M %p") + ) + + return True + +def load_css(): + provider = Gtk.CssProvider() + provider.load_from_path(str(CSS_FILE)) + + screen = Gdk.Screen.get_default() + + Gtk.StyleContext.add_provider_for_screen( + screen, + provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, + ) + + +def main(): + load_css() + + dock = ClassroomDock() + dock.connect("destroy", Gtk.main_quit) + dock.show_all() + + clock = ClassroomClock() + clock.show_all() + + Gtk.main() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tools/build-release.sh b/tools/build-release.sh index a8bf95f..7453fef 100755 --- a/tools/build-release.sh +++ b/tools/build-release.sh @@ -54,6 +54,18 @@ install -Dm755 \ "$ROOT/scripts/classroom-browser" \ "$STAGE/usr/local/bin/classroom-browser" +install -Dm644 \ + "$ROOT/config/wallpapers/classroom-default.png" \ + "$STAGE/usr/share/backgrounds/ekklesia/classroom-default.png" + +install -Dm755 \ + "$ROOT/scripts/classroom-dock" \ + "$STAGE/usr/local/bin/classroom-dock" + +install -Dm644 \ + "$ROOT/config/dock/style.css" \ + "$STAGE/etc/ekklesia-classroom/dock/style.css" + install -Dm755 \ "$ROOT/scripts/ekklesia-classroom-update" \ "$STAGE/usr/local/bin/ekklesia-classroom-update" @@ -71,6 +83,22 @@ install -Dm644 \ "$ROOT/systemd/updater/ekklesia-classroom-update.timer" \ "$STAGE/etc/systemd/system/ekklesia-classroom-update.timer" +install -Dm644 \ + "$ROOT/config/icons/dvd.svg" \ + "$STAGE/etc/ekklesia-classroom/icons/dvd.svg" + +install -Dm644 \ + "$ROOT/config/icons/live.svg" \ + "$STAGE/etc/ekklesia-classroom/icons/live.svg" + +install -Dm644 \ + "$ROOT/config/icons/live-off.svg" \ + "$STAGE/etc/ekklesia-classroom/icons/live-off.svg" + +install -Dm644 \ + "$ROOT/config/icons/browser.svg" \ + "$STAGE/etc/ekklesia-classroom/icons/browser.svg" + printf '%s\n' "$VERSION" > "$ROOT/VERSION" dpkg-deb \