* Install gliff by default Gliff is the Hyprland remote desktop over SSH. Ship it in the base package set for new installs, with a migration for existing ones. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Show a bar indicator while a gliff remote session is active The machine being controlled should make it obvious that someone is on it. A first-party remote-session service watches Hyprland's screencast events, which gliff-server triggers through ext-image-copy-capture, and probes for the server process, picking up the ssh client address from its environment. The RemoteSession indicator in the default tray lights up in the urgent colour while a session runs, and a notification announces each session start and end. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Address review of the remote session indicator Probe only the current user's gliff-server processes, forward refresh through the third-party service proxy so cloned indicator widgets can re-probe, exercise the Hyprland screencast event path in the service test with a grim capture instead of calling refresh by hand, and describe the indicator as status only in the manual. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Drop the remote session notifications The bar indicator is the signal; a toast on every connect and disconnect is noise on top of it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Tighten remote session detection after review Hyprland reports a screencast stopped as soon as frames pause for half a second and says nothing more when the server later exits, so poll every two seconds while a session is active instead of trusting a stop event. Match only the screencast event, not its screencastv2 twin, and debounce the burst around one transition so it costs one probe. Move the probe into its own script that the service and the test both run, poll for state in the service test instead of fixed delays, and drop the unused session count. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
85 lines
3.5 KiB
QML
85 lines
3.5 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
|
|
// Drives the real remote-session service against a stub gliff-server on PATH:
|
|
// idle at start, active with the ssh peer once a server runs, idle again after
|
|
// it exits. The service is never refreshed by hand: a one-shot grim capture
|
|
// raises the same Hyprland screencast event that gliff-server does, so the
|
|
// event path is what gets exercised.
|
|
ShellRoot {
|
|
id: root
|
|
|
|
property string resultPath: Quickshell.env("OMARCHY_QML_TEST_RESULT")
|
|
property string stubPidFile: Quickshell.env("OMARCHY_QML_TEST_STUB_PID")
|
|
property var failures: []
|
|
property var service: null
|
|
|
|
function fail(message) {
|
|
failures.push(String(message))
|
|
}
|
|
|
|
function assertTrue(condition, message) {
|
|
if (!condition) fail(message)
|
|
}
|
|
|
|
function shellQuote(value) {
|
|
return "'" + String(value).replace(/'/g, "'\\''") + "'"
|
|
}
|
|
|
|
function writeResult() {
|
|
var payload = JSON.stringify({ ok: failures.length === 0, failures: failures })
|
|
Quickshell.execDetached(["bash", "-lc", "printf '%s' " + shellQuote(payload) + " > " + shellQuote(resultPath)])
|
|
}
|
|
|
|
function captureFrame() {
|
|
Quickshell.execDetached(["grim", "-g", "0,0 1x1", Quickshell.env("OMARCHY_QML_TEST_FRAME")])
|
|
}
|
|
|
|
// Polls `condition` every 100 ms for up to `timeout` ms, then continues with
|
|
// `next` either way, recording `message` as a failure on timeout. `tick`,
|
|
// when given, runs every 500 ms while waiting.
|
|
function waitFor(condition, timeout, message, next, tick) {
|
|
var timer = Qt.createQmlObject("import QtQuick; Timer { repeat: true; interval: 100 }", root)
|
|
var waited = 0
|
|
timer.triggered.connect(function() {
|
|
waited += timer.interval
|
|
if (tick && waited % 500 === 0) tick()
|
|
if (!condition() && waited < timeout) return
|
|
timer.stop()
|
|
timer.destroy()
|
|
root.assertTrue(condition(), message)
|
|
next()
|
|
})
|
|
timer.start()
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
var component = Qt.createComponent("file://" + Quickshell.env("OMARCHY_PATH") + "/shell/plugins/services/remote-session/Service.qml")
|
|
if (component.status !== Component.Ready) {
|
|
fail("remote session service failed to load: " + component.errorString())
|
|
writeResult()
|
|
return
|
|
}
|
|
service = component.createObject(root, { shell: null })
|
|
if (!service) {
|
|
fail("remote session service failed to instantiate: " + component.errorString())
|
|
writeResult()
|
|
return
|
|
}
|
|
|
|
waitFor(function() { return service.stateLoaded === true }, 3000, "service probes on startup", function() {
|
|
root.assertTrue(service.active === false, "service starts idle without a gliff server")
|
|
Quickshell.execDetached(["bash", "-c", "SSH_CONNECTION='10.0.0.5 51234 10.0.0.1 22' gliff-server --stdio & echo $! > " + root.shellQuote(root.stubPidFile)])
|
|
// Each capture raises a screencast event; the first one after the stub
|
|
// is up is what should flip the service, with no refresh() by hand.
|
|
waitFor(function() { return service.active === true }, 4000, "a screencast event makes the service report the session while gliff-server runs", function() {
|
|
root.assertTrue(JSON.stringify(service.peers) === JSON.stringify(["10.0.0.5"]), "service reports the ssh peer, got " + JSON.stringify(service.peers))
|
|
Quickshell.execDetached(["bash", "-c", "kill \"$(cat " + root.shellQuote(root.stubPidFile) + ")\""])
|
|
waitFor(function() { return service.active === false }, 5000, "service returns to idle once gliff-server exits", function() {
|
|
root.writeResult()
|
|
})
|
|
}, root.captureFrame)
|
|
})
|
|
}
|
|
}
|