Files
dotwryn/config/user/quickshell/DisplayPicker.qml
T

213 lines
5.4 KiB
QML
Raw Normal View History

import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import Quickshell.Io
// monitor layout picker for $DOTWRYN/config/desktop/hyprland/config.lua.d/02-monitor-layouts.lua;
// shown with 'qs ipc call -- displays show' (without '--', 'show' parses as 'qs ipc show')
Scope {
id: picker
property bool shown: false
property var screen: null
property var layouts: []
property var resolutions: []
property int selectedLayout: 0
property int selectedResolution: -1 // -1 : the modes configured in the layout
function show(): void {
selectedLayout = 0
selectedResolution = -1
screen = Quickshell.screens.find(screen => screen.name === Hyprland.focusedMonitor?.name) ?? null
Quickshell.execDetached(["hyprctl", "eval", "dotwryn_publish_monitor_layouts()"])
shown = true
}
function moveLayout(offset: int): void {
if (layouts.length > 0) selectedLayout = (selectedLayout + offset + layouts.length) % layouts.length
}
function moveResolution(offset: int): void {
const count = resolutions.length + 1
selectedResolution = ((selectedResolution + 1 + offset + count) % count) - 1
}
// hyprland 0.56.2 segfaults if a focused layer surface is destroyed by a
// monitor change, so the picker must be gone before the layout is applied
function applyLayout(): void {
if (selectedLayout >= layouts.length) return
const resolution = selectedResolution < 0 ? "nil" : JSON.stringify(resolutions[selectedResolution])
evaluation.command = ["hyprctl", "eval", `dotwryn_set_monitor_layout(${JSON.stringify(layouts[selectedLayout])}, ${resolution})`]
shown = false
applyAfterHidden.restart()
}
Timer {
id: applyAfterHidden
interval: 250
onTriggered: evaluation.running = true
}
Process {
id: evaluation
stdout: StdioCollector {
onStreamFinished: {
const result = text.trim()
if (result !== "ok") Quickshell.execDetached(["notify-send", "displays", result.replace(/^.*:\d+: /, "")])
}
}
}
IpcHandler {
target: "displays"
function show(): void { picker.show() }
function hide(): void { picker.shown = false }
}
Connections {
target: Hyprland
function onRawEvent(event) {
const prefix = "dotwryn-monitor-layouts "
if (event.name !== "custom" || !event.data.startsWith(prefix)) return
const published = JSON.parse(event.data.slice(prefix.length))
picker.layouts = published.layouts
picker.resolutions = published.resolutions
}
}
component PickerText: Text {
color: Colors.foreground
font.family: Colors.font
font.pointSize: Colors.fontSize
}
PanelWindow {
visible: picker.shown
screen: picker.screen
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
exclusionMode: ExclusionMode.Ignore
implicitWidth: content.implicitWidth + 24
implicitHeight: content.implicitHeight + 24
color: "transparent"
Rectangle {
anchors.fill: parent
color: Colors.background
border.width: 1
border.color: Colors.primary
focus: true
Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_J: case Qt.Key_Down: picker.moveLayout(1); break
case Qt.Key_K: case Qt.Key_Up: picker.moveLayout(-1); break
case Qt.Key_L: case Qt.Key_Right: picker.moveResolution(1); break
case Qt.Key_H: case Qt.Key_Left: picker.moveResolution(-1); break
case Qt.Key_Return: case Qt.Key_Enter: picker.applyLayout(); break
case Qt.Key_Escape: case Qt.Key_Q: picker.shown = false; break
default: return
}
event.accepted = true
}
Column {
id: content
anchors.centerIn: parent
spacing: 4
PickerText {
text: "monitor layout"
font.bold: true
color: Colors.secondary
}
PickerText {
visible: picker.layouts.length === 0
text: "no monitor layouts for this host"
color: Colors.disabled
}
Repeater {
model: picker.layouts
Rectangle {
id: layoutRow
required property int index
required property string modelData
readonly property bool selected: picker.selectedLayout === index
implicitWidth: Math.max(layoutLabel.implicitWidth + 24, 280)
implicitHeight: layoutLabel.implicitHeight + 8
color: selected ? Colors.primary : "transparent"
PickerText {
id: layoutLabel
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: layoutRow.modelData
font.bold: layoutRow.selected
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: picker.selectedLayout = layoutRow.index
onClicked: picker.applyLayout()
}
}
}
Row {
visible: picker.layouts.length > 0
spacing: 8
topPadding: 8
PickerText {
text: "resolution"
color: Colors.secondary
}
Repeater {
model: ["configured"].concat(picker.resolutions)
PickerText {
required property int index
required property string modelData
readonly property bool selected: picker.selectedResolution === index - 1
text: selected ? `[${modelData}]` : ` ${modelData} `
color: selected ? Colors.foreground : Colors.disabled
font.bold: selected
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: picker.selectedResolution = parent.index - 1
}
}
}
}
}
}
}
}