Install gliff by default and show when a remote session is active (#14712)
* 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>
This commit is contained in:
1 parent
54fc12db76
commit
9d48fc402e
17 files changed
+483
-4
No files matched your search
@@ -34,6 +34,7 @@ User-installed plugins live alongside these conceptually but on disk under
|
||||
| Battery | `omarchy.battery` | `service` | `services/battery/Service.qml` |
|
||||
| Idle | `omarchy.idle` | `service` | `services/idle/Service.qml` |
|
||||
| Night light | `omarchy.nightlight` | `service` | `services/nightlight/Service.qml` |
|
||||
| Remote session | `omarchy.remote-session` | `service` | `services/remote-session/Service.qml` |
|
||||
| Lock screen | `omarchy.lock` | `service` | `lock/Service.qml` |
|
||||
| OSD | `omarchy.osd` | `panel` | `osd/Osd.qml` |
|
||||
| Polkit agent | `omarchy.polkit` | `service` | `polkit/PolkitAgent.qml` |
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import QtQuick
|
||||
import qs.Commons as Commons
|
||||
import qs.Ui
|
||||
|
||||
BarIndicator {
|
||||
id: root
|
||||
|
||||
readonly property var remoteSessionService: bar?.shell?.firstPartyServiceFor("omarchy.remote-session")
|
||||
readonly property var peers: remoteSessionService && Array.isArray(remoteSessionService.peers) ? remoteSessionService.peers : []
|
||||
|
||||
active: remoteSessionService ? remoteSessionService.active : false
|
||||
activeText: ""
|
||||
inactiveText: ""
|
||||
activeTooltipText: peers.length > 0 ? "Remote session from " + peers.join(", ") : "Remote session"
|
||||
inactiveTooltipText: "Remote Session"
|
||||
useActiveColor: true
|
||||
activeColor: Commons.Color.urgent
|
||||
|
||||
Connections {
|
||||
target: root.indicatorHost
|
||||
ignoreUnknownSignals: true
|
||||
function onRefreshRequested() {
|
||||
if (root.remoteSessionService) root.remoteSessionService.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,11 @@
|
||||
"label": "Screen recording",
|
||||
"description": "GPU screen recorder status"
|
||||
},
|
||||
{
|
||||
"value": "RemoteSession",
|
||||
"label": "Remote session",
|
||||
"description": "Warning while someone is connected through gliff"
|
||||
},
|
||||
{
|
||||
"value": "Reminder",
|
||||
"label": "Reminder",
|
||||
|
||||
@@ -8,7 +8,7 @@ BarWidget {
|
||||
id: root
|
||||
moduleName: "omarchy.indicators"
|
||||
|
||||
readonly property var defaultIndicatorEntries: [ "PasswordlessSudo", "ScreenRecording", "Reminder", "NightLight", "Dnd", "StayAwake" ]
|
||||
readonly property var defaultIndicatorEntries: [ "PasswordlessSudo", "ScreenRecording", "RemoteSession", "Reminder", "NightLight", "Dnd", "StayAwake" ]
|
||||
readonly property var indicatorEntries: indicatorEntriesFromSettings(settings)
|
||||
property var activeIndicatorIds: []
|
||||
property var indicatorActiveStates: ({})
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Hyprland emits this whenever a screencopy session starts or stops, which
|
||||
// covers gliff-server (ext-image-copy-capture) as well as screen recorders
|
||||
// and browser screen shares, so the event is only a cue to re-probe. The
|
||||
// matching screencastv2 event always accompanies it and is left alone so one
|
||||
// transition costs one probe.
|
||||
function isCaptureEvent(name) {
|
||||
return String(name || "") === "screencast"
|
||||
}
|
||||
|
||||
// Parses the probe output: one "<pid> [peer]" line per running gliff-server,
|
||||
// where peer is the ssh client address when the server was spawned over ssh.
|
||||
function stateFromOutput(text) {
|
||||
var lines = String(text || "").split("\n")
|
||||
var active = false
|
||||
var peers = []
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var parts = lines[i].trim().split(/\s+/)
|
||||
if (parts[0] === "") continue
|
||||
active = true
|
||||
var peer = parts[1] || ""
|
||||
if (peer !== "" && peers.indexOf(peer) === -1) peers.push(peer)
|
||||
}
|
||||
return { active: active, peers: peers }
|
||||
}
|
||||
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = {
|
||||
isCaptureEvent: isCaptureEvent,
|
||||
stateFromOutput: stateFromOutput
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import "RemoteSessionModel.js" as RemoteSessionModel
|
||||
|
||||
// Watches for gliff-server, the Hyprland remote desktop, so the machine being
|
||||
// controlled can see that someone is on it: the RemoteSession bar indicator
|
||||
// reads `active` and `peers`.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Injected by omarchy-shell (the first-party service loader).
|
||||
property var shell: null
|
||||
|
||||
property bool stateLoaded: false
|
||||
property bool active: false
|
||||
property var peers: []
|
||||
|
||||
property bool refreshPending: false
|
||||
|
||||
function refresh() {
|
||||
if (statusProbe.running) {
|
||||
root.refreshPending = true
|
||||
return
|
||||
}
|
||||
root.refreshPending = false
|
||||
statusProbe.running = true
|
||||
}
|
||||
|
||||
function applyProbe(text) {
|
||||
var state = RemoteSessionModel.stateFromOutput(text)
|
||||
root.peers = state.peers
|
||||
root.active = state.active
|
||||
root.stateLoaded = true
|
||||
}
|
||||
|
||||
Component.onCompleted: refresh()
|
||||
|
||||
// gliff-server captures the screen through ext-image-copy-capture, so a
|
||||
// session start surfaces as a Hyprland screencast event. The probe decides
|
||||
// whether it was gliff or another screencopy client, and a short debounce
|
||||
// folds the event bursts Hyprland sends around one transition into one probe.
|
||||
Connections {
|
||||
target: Hyprland
|
||||
function onRawEvent(event) {
|
||||
if (RemoteSessionModel.isCaptureEvent(event ? event.name : "")) eventDebounce.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: eventDebounce
|
||||
interval: 150
|
||||
repeat: false
|
||||
onTriggered: root.refresh()
|
||||
}
|
||||
|
||||
// The end of a session often has no event: Hyprland reports the screencast
|
||||
// stopped as soon as frames pause for half a second, and says nothing more
|
||||
// when the server later exits. So poll while a session is active.
|
||||
Timer {
|
||||
interval: 2000
|
||||
repeat: true
|
||||
running: root.active
|
||||
onTriggered: root.refresh()
|
||||
}
|
||||
|
||||
Process {
|
||||
id: statusProbe
|
||||
command: ["bash", Quickshell.env("OMARCHY_PATH") + "/shell/plugins/services/remote-session/probe.sh"]
|
||||
stdout: StdioCollector {
|
||||
waitForEnd: true
|
||||
onStreamFinished: root.applyProbe(text)
|
||||
}
|
||||
onExited: function() {
|
||||
if (root.refreshPending) Qt.callLater(root.refresh)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "omarchy.remote-session",
|
||||
"name": "Remote Session",
|
||||
"version": "1.0.0",
|
||||
"author": "Omarchy",
|
||||
"description": "Detects a gliff remote-desktop session on this machine for the bar indicator.",
|
||||
"kinds": [
|
||||
"service"
|
||||
],
|
||||
"entryPoints": {
|
||||
"service": "Service.qml"
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Lists this user's running gliff-server processes, one "<pid> [peer]" line
|
||||
# each. The peer is the ssh client address when the server was spawned over
|
||||
# ssh (the real path); a local --listen server has none. Another account's
|
||||
# server captures its own desktop, not this one, so other users are skipped.
|
||||
|
||||
for pid in $(pgrep -x -u "$(id -u)" gliff-server); do
|
||||
peer=$(tr '\0' '\n' <"/proc/$pid/environ" 2>/dev/null | sed -n 's/^SSH_CONNECTION=\([^ ]*\).*/\1/p')
|
||||
printf '%s %s\n' "$pid" "$peer"
|
||||
done
|
||||
Reference in new issue
Block a user