feat: take the last of sway out of the classroom
The DVD player was the only thing still speaking a compositor the room no longer runs. It asked sway's tree whether VLC's window had really gone fullscreen, because VLC reports playing while its window is still the black startup one the loading cover exists to hide. VLC's own status was measured as a replacement and is not one: it reports fullscreen true in the same 250ms poll as playing, because the flag is the option it was started with and not a report about its window. So the cover is held for a floor and capped by the deadline that was already there, and the compositor is not asked anything. Focus-or-launch went the same way. The session already takes an exclusive lock, so a second one cannot start, and bringing an existing one to the front is the panel's job. classroom-window, classroom_sway and classroom_chrome are gone with it — the last of them was reachable from nothing at all. DVD also moves out of the status row, where it sat beside the clock as though a disc were a machine reading. It is an application: it is in the row with Wonder Ink and the browser, and classroom-provision removes it from the layout on a machine with no optical drive, which retires DVD_ENABLED. A computer that moves between rooms takes its drive with it; nobody remembers to edit a file when it does. The panel is rebuilt from the package at every login now. Plasma keeps whatever panel it first built, so an update could change the bar in the package and never change it in a room — measured, with an icon that outlived the file it came from. Editing the live panel configuration was tried instead and destroyed the panel, because the shell owns that file. Removing it before the shell starts is safe and makes the desktop stateless. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
18cf0249dd
commit
d2d30d6b0c
Executable
+96
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Match the classroom's panel to the machine it is on.
|
||||
|
||||
There is exactly one thing about the bar that depends on hardware: a room
|
||||
with no optical drive should not be offered a disc player. This edits the
|
||||
one marked line in the installed layout, and nothing else.
|
||||
|
||||
It writes the *layout*, not the running panel. Editing
|
||||
plasma-org.kde.plasma.desktop-appletsrc underneath a live plasmashell was
|
||||
tried and it destroyed the panel: the shell owns that file and rewrites
|
||||
it, so an edit races with it. The layout is a file the package owns and
|
||||
the shell only reads, and classroom-desktop-reset makes the shell read it
|
||||
again at the next login.
|
||||
|
||||
DVD_ENABLED is gone. A drive is something the machine can be asked about,
|
||||
and asking is more reliable than remembering to edit a file when a
|
||||
computer moves between rooms.
|
||||
"""
|
||||
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
LAYOUT = (
|
||||
"/usr/share/plasma/look-and-feel/org.ekklesia.classroom.desktop"
|
||||
"/contents/layouts/org.kde.plasma.desktop-layout.js"
|
||||
)
|
||||
|
||||
MARKER = "/* CLASSROOM-DVD-LAUNCHER */"
|
||||
|
||||
# The kernel names optical drives sr0, sr1, ... regardless of bus.
|
||||
DRIVE_GLOB = "/dev/sr[0-9]*"
|
||||
|
||||
|
||||
def has_optical_drive():
|
||||
return bool(glob.glob(DRIVE_GLOB))
|
||||
|
||||
|
||||
def rewrite(text, wanted):
|
||||
"""Comment the DVD launcher in or out, idempotently."""
|
||||
line = re.compile(
|
||||
r"^(\s*)(//\s*)?(" + re.escape(MARKER) + r'\s*"applications:ekklesia-dvd\.desktop")',
|
||||
re.M,
|
||||
)
|
||||
|
||||
def swap(match):
|
||||
indent, _, body = match.groups()
|
||||
return f"{indent}{'' if wanted else '// '}{body}"
|
||||
|
||||
return line.sub(swap, text)
|
||||
|
||||
|
||||
def main():
|
||||
path = sys.argv[1] if len(sys.argv) > 1 else LAYOUT
|
||||
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as handle:
|
||||
before = handle.read()
|
||||
except OSError as error:
|
||||
print(f"classroom-desktop-layout: {error}", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
if MARKER not in before:
|
||||
print("classroom-desktop-layout: no DVD marker in the layout",
|
||||
file=sys.stderr)
|
||||
return 0
|
||||
|
||||
wanted = has_optical_drive()
|
||||
after = rewrite(before, wanted)
|
||||
|
||||
if after == before:
|
||||
print(f"classroom-desktop-layout: DVD launcher already "
|
||||
f"{'present' if wanted else 'absent'}")
|
||||
return 0
|
||||
|
||||
# Replace the file rather than edit in place, so a full disk or a
|
||||
# crash leaves the old layout intact rather than half of a new one.
|
||||
temporary = f"{path}.new"
|
||||
|
||||
try:
|
||||
with open(temporary, "w", encoding="utf-8") as handle:
|
||||
handle.write(after)
|
||||
os.replace(temporary, path)
|
||||
except OSError as error:
|
||||
print(f"classroom-desktop-layout: {error}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"classroom-desktop-layout: DVD launcher "
|
||||
f"{'added' if wanted else 'removed'}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Make the panel come from the package, every time.
|
||||
#
|
||||
# Plasma builds the panel from the classroom's layout script whenever it
|
||||
# starts with no panel configuration, and keeps whatever it built after
|
||||
# that. That means an update could change the bar in the package and
|
||||
# never change it in a room — which is exactly what happened with an icon
|
||||
# that outlived the file it came from.
|
||||
#
|
||||
# So the configuration is removed before the shell starts and the bar is
|
||||
# rebuilt from the layout at every login. The desktop becomes stateless:
|
||||
# there is nothing in a teacher's home that an update cannot reach, and a
|
||||
# panel damaged somehow is repaired by the next login rather than by a
|
||||
# technician.
|
||||
#
|
||||
# Nothing is lost by this. The panel is immutable to teachers by Kiosk
|
||||
# policy, so there is never anything of theirs in that file.
|
||||
|
||||
CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/plasma-org.kde.plasma.desktop-appletsrc"
|
||||
|
||||
if [[ -e "$CONFIG" ]]; then
|
||||
rm -f "$CONFIG"
|
||||
echo "classroom-desktop-reset: panel will be rebuilt from the layout"
|
||||
else
|
||||
echo "classroom-desktop-reset: no panel configuration to clear"
|
||||
fi
|
||||
|
||||
# The icon cache remembers a name it once failed to find, which outlives
|
||||
# the package that later supplies it.
|
||||
rm -f "${XDG_CACHE_HOME:-$HOME/.cache}/icon-cache.kcache" 2>/dev/null || true
|
||||
+9
-15
@@ -22,7 +22,6 @@ sys.path.insert(
|
||||
import classroom_optical as optical # noqa: E402
|
||||
from classroom_vlc import PLAYER # noqa: E402
|
||||
|
||||
WINDOW_TOOL = "/usr/local/bin/classroom-window"
|
||||
SESSION_TOOL = "/usr/local/bin/classroom-dvd-session"
|
||||
|
||||
|
||||
@@ -44,13 +43,17 @@ def notify(title, message, urgency="normal"):
|
||||
|
||||
|
||||
def play(device):
|
||||
"""Hand the disc to DVD mode through the normal window toggle.
|
||||
"""Hand the disc to DVD mode.
|
||||
|
||||
classroom-dvd-session owns the player from here: it puts a black
|
||||
surface up immediately, starts VLC behind it, and provides the
|
||||
classroom's own controls. Going through classroom-window keeps the
|
||||
dock button behaving like every other one — focus if it is already
|
||||
up, rather than starting a second session.
|
||||
classroom's own controls.
|
||||
|
||||
Started directly, with nothing arranging windows on the way. A second
|
||||
session cannot happen — the session takes an exclusive lock on
|
||||
dvd.lock and exits if it cannot get it — and bringing an existing one
|
||||
back to the front is the panel's job, not this command's. That is
|
||||
what replaced the window helper.
|
||||
"""
|
||||
if shutil.which(PLAYER) is None:
|
||||
notify(
|
||||
@@ -59,16 +62,7 @@ def play(device):
|
||||
)
|
||||
return 1
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
WINDOW_TOOL,
|
||||
"toggle",
|
||||
"--match",
|
||||
"(?i)vlc",
|
||||
"--",
|
||||
SESSION_TOOL,
|
||||
]
|
||||
)
|
||||
subprocess.Popen([SESSION_TOOL], start_new_session=True)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ any of this.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
@@ -356,7 +357,7 @@ def status():
|
||||
print(f"libdvdcss: {'installed' if libdvdcss_installed() else 'missing'}")
|
||||
|
||||
config = read_device_config()
|
||||
print(f"DVD_ENABLED: {enabled(config, 'DVD_ENABLED', False)}")
|
||||
print(f"optical drive: {', '.join(sorted(glob.glob('/dev/sr[0-9]*'))) or 'none'}")
|
||||
|
||||
return 0 if libdvdcss_installed() else 1
|
||||
|
||||
@@ -378,7 +379,7 @@ def main():
|
||||
parser.add_argument(
|
||||
"--if-enabled",
|
||||
action="store_true",
|
||||
help="Do nothing unless DVD_ENABLED is set for this classroom",
|
||||
help="Do nothing unless this machine has an optical drive",
|
||||
)
|
||||
arguments = parser.parse_args()
|
||||
|
||||
@@ -386,8 +387,11 @@ def main():
|
||||
return status()
|
||||
|
||||
if arguments.if_enabled:
|
||||
if not enabled(read_device_config(), "DVD_ENABLED", False):
|
||||
log("DVD support is not enabled on this classroom; nothing to do")
|
||||
# Ask the machine rather than a setting. A computer that moves
|
||||
# between rooms takes its drive with it, and nobody remembers to
|
||||
# edit a file when it does.
|
||||
if not glob.glob("/dev/sr[0-9]*"):
|
||||
log("this classroom has no optical drive; nothing to do")
|
||||
return 0
|
||||
|
||||
# Only the real system needs privilege. Pointed at a fixture tree
|
||||
|
||||
@@ -13,7 +13,7 @@ Layout, bottom to top:
|
||||
loading surface layer-shell OVERLAY, covers the whole display
|
||||
including the dock while the disc loads, removed
|
||||
once playback is actually on screen
|
||||
VLC fullscreen, drawn above the top layer by sway
|
||||
VLC fullscreen, an ordinary window below both layers
|
||||
control bar layer-shell OVERLAY, drawn above fullscreen video
|
||||
"""
|
||||
|
||||
@@ -45,7 +45,6 @@ sys.path.insert(
|
||||
|
||||
import classroom_optical as optical # noqa: E402
|
||||
import classroom_style as style # noqa: E402
|
||||
import classroom_sway as sway # noqa: E402
|
||||
import classroom_vlc as vlc # noqa: E402
|
||||
from classroom_config import SHELL_SCALE, read_device_config, scaled # noqa: E402
|
||||
from classroom_icons import ScalableIcon # noqa: E402
|
||||
@@ -78,9 +77,21 @@ HOVER_STRIP = 14
|
||||
IDLE_HIDE_SECONDS = 4
|
||||
LEAVE_HIDE_SECONDS = 1
|
||||
|
||||
# How VLC is recognised in the sway tree. Qt5 puts it on XWayland, so
|
||||
# the X11 class is what usually matches.
|
||||
VLC_WINDOW = (r"vlc", r"vlc-.*")
|
||||
# How long the cover stays up at the very least.
|
||||
#
|
||||
# This replaced a query to the window manager. Under sway the session
|
||||
# walked the tree until VLC's window was really fullscreen, because VLC
|
||||
# reports playing while its window is still the ordinary one — the black
|
||||
# startup window the cover exists to hide.
|
||||
#
|
||||
# VLC's own status was measured as a replacement and is not one: it
|
||||
# reports <fullscreen>true</fullscreen> in the same 250ms poll as
|
||||
# <state>playing</state>, because the flag is the setting it was started
|
||||
# with and not the state of its window. So the cover is held for a floor
|
||||
# instead, and LOADING_DEADLINE_SECONDS still caps the wait. On real
|
||||
# hardware the disc takes far longer than this to open, so the floor is
|
||||
# almost never what decides.
|
||||
MINIMUM_COVER_SECONDS = 1.2
|
||||
|
||||
# The loading surface goes away only once VLC has genuinely taken the
|
||||
# screen. The deadline is a safety net: whatever happens, DVD mode must
|
||||
@@ -188,15 +199,29 @@ class VlcControl:
|
||||
|
||||
def state(self):
|
||||
"""playing, paused, stopped, or None while VLC is still coming up."""
|
||||
return self.field(r"<state>(\w+)</state>")
|
||||
|
||||
def field(self, pattern):
|
||||
body = self.request()
|
||||
|
||||
if not body:
|
||||
return None
|
||||
|
||||
found = re.search(r"<state>(\w+)</state>", body)
|
||||
found = re.search(pattern, body)
|
||||
|
||||
return found.group(1) if found else None
|
||||
|
||||
def wants_fullscreen(self):
|
||||
"""Whether VLC believes it is fullscreen.
|
||||
|
||||
Deliberately named for what it is. VLC reports this true from the
|
||||
moment it starts, because it is the option it was given, not a
|
||||
report about its window — measured at 250ms resolution against a
|
||||
real VLC. It is a precondition worth checking and not evidence
|
||||
that anything is on screen.
|
||||
"""
|
||||
return self.field(r"<fullscreen>(\w+)</fullscreen>") == "true"
|
||||
|
||||
|
||||
class LoadingSurface(Gtk.Window):
|
||||
"""The whole display, while the disc spins up.
|
||||
@@ -221,9 +246,9 @@ class LoadingSurface(Gtk.Window):
|
||||
|
||||
GtkLayerShell.init_for_window(self)
|
||||
|
||||
# OVERLAY rather than TOP. The dock is a TOP layer surface, and
|
||||
# OVERLAY rather than TOP. The panel is a TOP layer surface and
|
||||
# DVD mode has to cover it; OVERLAY is above it without depending
|
||||
# on which surface sway happens to have created first. It is
|
||||
# on which surface the compositor happened to create first. It is
|
||||
# taken down the moment playback is on screen, so it never sits
|
||||
# over the video.
|
||||
GtkLayerShell.set_layer(self, GtkLayerShell.Layer.OVERLAY)
|
||||
@@ -290,7 +315,8 @@ class ControlBar(Gtk.Window):
|
||||
|
||||
GtkLayerShell.init_for_window(self)
|
||||
|
||||
# OVERLAY is the one layer sway draws above a fullscreen window.
|
||||
# OVERLAY is the one layer a compositor draws above a fullscreen
|
||||
# window, so the classroom's controls stay over the video.
|
||||
GtkLayerShell.set_layer(self, GtkLayerShell.Layer.OVERLAY)
|
||||
|
||||
for edge in (
|
||||
@@ -564,25 +590,23 @@ class DvdSession:
|
||||
self.pid = None
|
||||
Gtk.main_quit()
|
||||
|
||||
def player_is_fullscreen(self):
|
||||
"""True once VLC's window exists and has taken the whole output.
|
||||
def player_is_ready(self):
|
||||
"""True once there is something worth uncovering.
|
||||
|
||||
Playback state alone is not enough. VLC reports playing while its
|
||||
window is still the ordinary tiled one sway gave it, which is the
|
||||
black startup window the cover exists to hide. It only takes the
|
||||
display when it goes fullscreen, and that is what the tree says.
|
||||
Three things, none of which asks the window manager anything:
|
||||
VLC is playing or paused, VLC was told to be fullscreen and still
|
||||
thinks it is, and the cover has been up for its minimum.
|
||||
"""
|
||||
for window in sway.windows():
|
||||
if sway.matches(window, VLC_WINDOW) and window["fullscreen"]:
|
||||
return True
|
||||
if not self.control.wants_fullscreen():
|
||||
return False
|
||||
|
||||
return False
|
||||
return self.elapsed >= MINIMUM_COVER_SECONDS
|
||||
|
||||
def watch_startup(self):
|
||||
"""Hold the cover until VLC is genuinely ready to replace it.
|
||||
|
||||
Three things have to line up: VLC says it is playing, its window
|
||||
is fullscreen, and a moment has passed for the disc menu to draw.
|
||||
Three things have to line up: VLC says it is playing, it is still
|
||||
set fullscreen, and a moment has passed for the disc menu to draw.
|
||||
The deadline is the safety net: DVD mode must never leave a cover
|
||||
over a screen with nothing behind it, even if the disc never
|
||||
opens.
|
||||
@@ -592,7 +616,7 @@ class DvdSession:
|
||||
# The cheap local check first; the tree is only worth walking
|
||||
# once VLC claims to be playing something.
|
||||
playing = self.control.state() in ("playing", "paused")
|
||||
ready = playing and self.player_is_fullscreen()
|
||||
ready = playing and self.player_is_ready()
|
||||
|
||||
if ready or self.elapsed >= LOADING_DEADLINE_SECONDS:
|
||||
GLib.timeout_add(STABILISE_MS, self.drop_loading)
|
||||
|
||||
@@ -49,6 +49,15 @@ else
|
||||
log "WARNING: sudo classroom-printer-setup"
|
||||
fi
|
||||
|
||||
# The bar is tailored to the machine before the session is started: a
|
||||
# room with no optical drive is not offered a disc player. This writes the
|
||||
# layout only; the shell rebuilds the panel from it at the next login.
|
||||
if /usr/local/bin/classroom-desktop-layout; then
|
||||
:
|
||||
else
|
||||
log "WARNING: the desktop layout was not matched to this machine."
|
||||
fi
|
||||
|
||||
log "finished"
|
||||
|
||||
# Provisioning is best-effort by design.
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Classroom window control.
|
||||
|
||||
Sway has no real minimise concept, so the classroom uses a dedicated
|
||||
hidden workspace. A minimised window is parked there as an ordinary
|
||||
tiled container and moved back to the visible workspace on restore,
|
||||
which is why it returns maximised instead of floating the way the old
|
||||
scratchpad approach did.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "/usr/local/lib/ekklesia-classroom")
|
||||
sys.path.insert(
|
||||
0,
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "lib"),
|
||||
)
|
||||
|
||||
from classroom_sway import ( # noqa: E402
|
||||
HIDDEN_WORKSPACE,
|
||||
command,
|
||||
matches,
|
||||
visible_workspace,
|
||||
windows,
|
||||
)
|
||||
|
||||
STATE_DIR = os.path.join(
|
||||
os.environ.get("XDG_RUNTIME_DIR") or "/tmp",
|
||||
"ekklesia-classroom",
|
||||
)
|
||||
STATE_FILE = os.path.join(STATE_DIR, "minimized")
|
||||
|
||||
|
||||
def read_state():
|
||||
try:
|
||||
with open(STATE_FILE, "r", encoding="utf-8") as handle:
|
||||
return [int(line) for line in handle.read().split() if line.strip()]
|
||||
except (OSError, ValueError):
|
||||
return []
|
||||
|
||||
|
||||
def write_state(identifiers):
|
||||
try:
|
||||
os.makedirs(STATE_DIR, exist_ok=True)
|
||||
|
||||
with open(STATE_FILE, "w", encoding="utf-8") as handle:
|
||||
handle.write("\n".join(str(item) for item in identifiers))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def remember(identifier):
|
||||
"""Track minimise order so restore-last picks the newest window."""
|
||||
state = [item for item in read_state() if item != identifier]
|
||||
state.append(identifier)
|
||||
write_state(state[-32:])
|
||||
|
||||
|
||||
def forget(identifier):
|
||||
write_state([item for item in read_state() if item != identifier])
|
||||
|
||||
|
||||
def minimize(window):
|
||||
# A fullscreen container keeps rendering over everything else if it
|
||||
# is parked while still fullscreen, so always drop out of it first.
|
||||
command(f'[con_id={window["id"]}] fullscreen disable')
|
||||
command(f'[con_id={window["id"]}] move container to workspace {HIDDEN_WORKSPACE}')
|
||||
|
||||
remember(window["id"])
|
||||
|
||||
|
||||
def restore(window):
|
||||
workspace = visible_workspace()
|
||||
|
||||
command(f'[con_id={window["id"]}] move container to workspace {workspace}')
|
||||
|
||||
# Windows parked by older classroom builds came back floating because
|
||||
# they had been through the scratchpad. Force them back into the tiling
|
||||
# layout so a restored browser is maximised like the user expects.
|
||||
command(f'[con_id={window["id"]}] floating disable')
|
||||
command(f'[con_id={window["id"]}] focus')
|
||||
|
||||
forget(window["id"])
|
||||
|
||||
|
||||
def launch(argv):
|
||||
if not argv:
|
||||
return
|
||||
|
||||
try:
|
||||
subprocess.Popen(argv, start_new_session=True)
|
||||
except OSError as error:
|
||||
print(f"classroom-window: unable to launch {argv[0]}: {error}", file=sys.stderr)
|
||||
|
||||
|
||||
def action_toggle(patterns, argv):
|
||||
"""Launch, focus, minimise or restore, in the order a user expects."""
|
||||
candidates = [window for window in windows() if matches(window, patterns)]
|
||||
|
||||
if not candidates:
|
||||
launch(argv)
|
||||
return 0
|
||||
|
||||
hidden = [window for window in candidates if window["hidden"]]
|
||||
|
||||
if hidden:
|
||||
for window in hidden:
|
||||
restore(window)
|
||||
return 0
|
||||
|
||||
if any(window["focused"] for window in candidates):
|
||||
for window in candidates:
|
||||
minimize(window)
|
||||
return 0
|
||||
|
||||
command(f'[con_id={candidates[-1]["id"]}] focus')
|
||||
return 0
|
||||
|
||||
|
||||
def action_minimize():
|
||||
for window in windows():
|
||||
if window["focused"] and not window["hidden"]:
|
||||
minimize(window)
|
||||
return 0
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def action_restore_last():
|
||||
hidden = {window["id"]: window for window in windows() if window["hidden"]}
|
||||
|
||||
if not hidden:
|
||||
return 0
|
||||
|
||||
for identifier in reversed(read_state()):
|
||||
if identifier in hidden:
|
||||
restore(hidden[identifier])
|
||||
return 0
|
||||
|
||||
restore(list(hidden.values())[-1])
|
||||
return 0
|
||||
|
||||
|
||||
def action_list():
|
||||
for window in windows():
|
||||
state = "minimized" if window["hidden"] else "visible"
|
||||
state = "focused" if window["focused"] else state
|
||||
|
||||
print(
|
||||
f'{window["id"]}\t{state}\t'
|
||||
f'{window["app_id"] or window["class"]}\t{window["workspace"]}'
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Minimise, restore and toggle classroom windows.",
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="action", required=True)
|
||||
|
||||
toggle = subparsers.add_parser(
|
||||
"toggle",
|
||||
help="Launch, focus, minimise or restore an application",
|
||||
)
|
||||
toggle.add_argument(
|
||||
"--match",
|
||||
action="append",
|
||||
required=True,
|
||||
metavar="REGEX",
|
||||
help="app_id or X11 class to match (may be repeated)",
|
||||
)
|
||||
toggle.add_argument(
|
||||
"launch",
|
||||
nargs=argparse.REMAINDER,
|
||||
help="Command to run when the application is not running",
|
||||
)
|
||||
|
||||
subparsers.add_parser("minimize", help="Minimise the focused window")
|
||||
subparsers.add_parser("restore-last", help="Restore the newest minimised window")
|
||||
subparsers.add_parser("list", help="List classroom windows and their state")
|
||||
|
||||
arguments = parser.parse_args()
|
||||
|
||||
if arguments.action == "toggle":
|
||||
argv = arguments.launch
|
||||
|
||||
if argv and argv[0] == "--":
|
||||
argv = argv[1:]
|
||||
|
||||
return action_toggle(arguments.match, argv)
|
||||
|
||||
if arguments.action == "minimize":
|
||||
return action_minimize()
|
||||
|
||||
if arguments.action == "restore-last":
|
||||
return action_restore_last()
|
||||
|
||||
return action_list()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user