318 lines
7.0 KiB
Python
Executable File
318 lines
7.0 KiB
Python
Executable File
#!/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() |