Add display identification overlays

This commit is contained in:
Gabriel Brown
2026-08-18 15:00:18 -04:00
parent 70a9d27c98
commit 0c3935f818
7 changed files with 142 additions and 1 deletions
@@ -3,8 +3,11 @@ import Quickshell.Io
import QtQuick
import qs.modules.settings
import qs.services
ShellRoot {
DisplayIdentify {}
QtObject {
id: fixtureService
@@ -71,5 +74,8 @@ ShellRoot {
name: record.name, x: record.x, y: record.y, primary: record.primary
})));
}
function identify(): void { Displays.identify(); }
function identifying(): bool { return Displays.identifying; }
}
}
@@ -279,7 +279,8 @@ Item {
spacing: 8
Text {
width: Math.max(0, parent.width - primaryButton.width - applyButton.width - 16)
width: Math.max(0, parent.width - identifyButton.width
- primaryButton.width - applyButton.width - 24)
anchors.verticalCenter: parent.verticalCenter
text: root.width >= 600
? "Drag to arrange · arrows move 10 px · Shift moves 100 px"
@@ -290,6 +291,13 @@ Item {
elide: Text.ElideRight
}
SettingsButton {
id: identifyButton
text: "Identify"
enabled: root.interactionEnabled
onClicked: root.displayService.identify()
}
SettingsButton {
id: primaryButton
text: "Make primary"
@@ -0,0 +1,85 @@
import Quickshell
import Quickshell.Wayland
import QtQuick
import qs.config
import qs.services
import qs.widgets
Variants {
model: Quickshell.screens
PanelWindow {
id: win
property var modelData: null
readonly property string connector: win.modelData?.name ?? "Display"
readonly property int number: Math.max(1,
Displays.monitors.findIndex(monitor => monitor.name === win.connector) + 1)
readonly property string description: Displays.monitorNamed(win.connector)?.description ?? "Connected display"
screen: win.modelData
visible: Displays.identifying
implicitWidth: 260
implicitHeight: 172
color: "transparent"
exclusiveZone: 0
exclusionMode: ExclusionMode.Ignore
mask: Region {}
WlrLayershell.namespace: "qs-display-identify"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
Rectangle {
anchors.fill: parent
radius: Theme.popoverRadius
color: Theme.alpha(Theme.bgPopover, 0.96)
border.width: 1
border.color: Theme.alpha(Theme.fg, 0.14)
PrismEdge {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
inset: parent.radius
}
Column {
anchors.centerIn: parent
width: parent.width - 32
spacing: 4
Text {
width: parent.width
horizontalAlignment: Text.AlignHCenter
text: String(win.number)
color: Theme.fg
font.family: Theme.fontFamily
font.pixelSize: 68
font.weight: Font.DemiBold
}
Text {
width: parent.width
horizontalAlignment: Text.AlignHCenter
text: win.connector
color: Theme.accentAlt
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.DemiBold
elide: Text.ElideRight
}
Text {
width: parent.width
horizontalAlignment: Text.AlignHCenter
text: win.description
color: Theme.fgDim
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
elide: Text.ElideRight
}
}
}
}
}
@@ -43,6 +43,7 @@ ShortcutCapture 1.0 ShortcutCapture.qml
ChoiceGrid 1.0 ChoiceGrid.qml
DisplayModePicker 1.0 DisplayModePicker.qml
DisplayArrangement 1.0 DisplayArrangement.qml
DisplayIdentify 1.0 DisplayIdentify.qml
WifiPanel 1.0 WifiPanel.qml
BluetoothPanel 1.0 BluetoothPanel.qml
PasswordField 1.0 PasswordField.qml
@@ -43,6 +43,7 @@ Singleton {
property int revertGeneration: -1
property bool externalChangeBlocked: false
property int secondsLeft: 0
property bool identifying: false
readonly property bool awaitingConfirmation: root.pendingRequestedLayout !== null
readonly property bool canConfirm: root.awaitingConfirmation
@@ -123,6 +124,11 @@ Singleton {
return false;
}
function identify(): void {
root.identifying = true;
identifyTimer.restart();
}
function parse(text: string, generation: int): void {
try {
const raw = JSON.parse(text);
@@ -520,6 +526,13 @@ Singleton {
root.lastError = "The previous display setting could not be verified. Open Displays and restore it manually.";
}
Timer {
id: identifyTimer
interval: 3000
repeat: false
onTriggered: root.identifying = false
}
Timer {
id: verifyTimer
property int attempts: 0
+2
View File
@@ -86,6 +86,8 @@ ShellRoot {
Osd {}
}
DisplayIdentify {}
// ── Single-instance overlays ────────────────────────────────────────────
// These are always constructed but only *visible* when ShellState says so.
// They're cheap while hidden, and keeping them alive means opening the
@@ -6,6 +6,9 @@ repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
component="$repo_dir/config/dot/quickshell/modules/settings/DisplayArrangement.qml"
page="$repo_dir/config/dot/quickshell/modules/settings/DisplaysPage.qml"
harness="$repo_dir/config/dot/quickshell/display-arrangement-harness.qml"
identify="$repo_dir/config/dot/quickshell/modules/settings/DisplayIdentify.qml"
service="$repo_dir/config/dot/quickshell/services/Displays.qml"
shell="$repo_dir/config/dot/quickshell/shell.qml"
fail() {
printf 'display arrangement contract: %s\n' "$1" >&2
@@ -30,6 +33,22 @@ rg -Fq 'DisplayArrangement {' "$page" \
|| fail 'Displays page does not expose the arrangement canvas'
rg -Fq 'visible: Displays.monitors.length > 1' "$page" \
|| fail 'arrangement is shown for a single display'
[[ -f "$identify" ]] || fail 'DisplayIdentify.qml is missing'
for contract in \
'model: Quickshell.screens' \
'WlrLayershell.keyboardFocus: WlrKeyboardFocus.None' \
'mask: Region {}' \
'visible: Displays.identifying' \
'text: String(win.number)' \
'text: win.connector'; do
rg -Fq "$contract" "$identify" \
|| fail "display identification overlay is incomplete: $contract"
done
rg -Fq 'DisplayIdentify {}' "$shell" \
|| fail 'display identification overlays are not shell-owned'
rg -Fq 'function identify()' "$service" \
&& rg -Fq 'interval: 3000' "$service" \
|| fail 'display identification does not use one three-second service timer'
state_home="$(mktemp -d /tmp/panama-display-arrangement-state.XXXXXX)"
harness_pid=""
@@ -82,4 +101,11 @@ jq -e '. == [
]' <<<"$primary" >/dev/null \
|| fail "Make primary did not normalize the selected output to 0,0: $primary"
ipc identify >/dev/null
[[ "$(ipc identifying)" == "true" ]] \
|| fail 'identify did not reveal the overlays'
sleep 3.2
[[ "$(ipc identifying)" == "false" ]] \
|| fail 'identify overlays did not disappear after one three-second timer'
printf 'display arrangement contract: PASS\n'