Initial commit: hypr-rdp Control bar widget
Toggles the hypr-rdp systemd --user service, isolates its headless monitor's workspace via Omarchy's toggle-flag convention, shows what's running on that workspace, and manages hypr-rdp's connection credentials. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MLfSWFEMyY85ZDWEYicdaF
This commit is contained in:
308
Panel.qml
Normal file
308
Panel.qml
Normal file
@@ -0,0 +1,308 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
|
||||
Panel {
|
||||
id: root
|
||||
moduleName: "jandieman.hypr-rdp"
|
||||
ipcTarget: "jandieman.hypr-rdp"
|
||||
|
||||
readonly property color fg: root.bar ? root.bar.foreground : Color.foreground
|
||||
readonly property color dim: Qt.darker(fg, 1.45)
|
||||
readonly property color good: "#22c55e"
|
||||
readonly property color bad: "#ef4444"
|
||||
readonly property string fontFamily: root.bar ? root.bar.fontFamily : "JetBrainsMono Nerd Font"
|
||||
|
||||
property string editUsername: ""
|
||||
property string editPassword: ""
|
||||
property string editBind: ""
|
||||
property bool showPassword: false
|
||||
|
||||
function syncEditFields() {
|
||||
editUsername = service.username
|
||||
editPassword = service.password
|
||||
editBind = service.bindAddress
|
||||
}
|
||||
|
||||
function triggerPress(button) {
|
||||
if (button === Qt.MiddleButton) { service.refresh(); return }
|
||||
if (opened) close()
|
||||
else { open(); service.refresh() }
|
||||
}
|
||||
|
||||
implicitWidth: button.implicitWidth
|
||||
implicitHeight: button.implicitHeight
|
||||
|
||||
Service {
|
||||
id: service
|
||||
settings: root.settings
|
||||
}
|
||||
|
||||
onOpenedChanged: if (opened) syncEditFields()
|
||||
|
||||
WidgetButton {
|
||||
id: button
|
||||
anchors.fill: parent
|
||||
bar: root.bar
|
||||
text: "RDP"
|
||||
fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(30)
|
||||
fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1
|
||||
foreground: !service.installed ? root.dim : (service.active ? root.good : root.dim)
|
||||
tooltipText: !service.installed ? "hypr-rdp: not installed" : ("hypr-rdp: " + service.statusText)
|
||||
onPressed: function(b) { root.triggerPress(b) }
|
||||
}
|
||||
|
||||
KeyboardPanel {
|
||||
id: panel
|
||||
anchorItem: button
|
||||
owner: root
|
||||
bar: root.bar
|
||||
open: root.opened
|
||||
focusTarget: keyCatcher
|
||||
contentWidth: panel.fittedContentWidth(Style.space(400))
|
||||
contentHeight: panel.fittedContentHeight(contentColumn.implicitHeight)
|
||||
|
||||
PanelKeyCatcher {
|
||||
id: keyCatcher
|
||||
anchors.fill: parent
|
||||
onCloseRequested: root.close()
|
||||
|
||||
ColumnLayout {
|
||||
id: contentColumn
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
spacing: Style.space(10)
|
||||
|
||||
// ── Header ──
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "hypr-rdp"
|
||||
color: root.fg
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.title
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
Text {
|
||||
text: service.statusText
|
||||
color: !service.installed ? root.dim : (service.active ? root.good : root.dim)
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.caption
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Refresh"
|
||||
foreground: root.fg
|
||||
fontFamily: root.fontFamily
|
||||
fontSize: Style.font.caption
|
||||
horizontalPadding: Style.spacing.controlPaddingX
|
||||
verticalPadding: Style.spacing.controlPaddingY
|
||||
onClicked: service.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: service.actionStatus !== ""
|
||||
Layout.fillWidth: true
|
||||
text: service.actionStatus
|
||||
color: root.dim
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.caption
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: service.lastError !== ""
|
||||
Layout.fillWidth: true
|
||||
text: service.lastError
|
||||
color: root.bad
|
||||
wrapMode: Text.WordWrap
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.caption
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
PanelSeparator { Layout.fillWidth: true; foreground: root.fg }
|
||||
|
||||
// ── Not installed ──
|
||||
ColumnLayout {
|
||||
visible: !service.installed
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.space(8)
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: "hypr-rdp isn't installed on this machine."
|
||||
color: root.dim
|
||||
wrapMode: Text.WordWrap
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.body
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Install hypr-rdp (opens a terminal)"
|
||||
foreground: root.fg
|
||||
fontFamily: root.fontFamily
|
||||
fontSize: Style.font.body
|
||||
horizontalPadding: Style.spacing.controlPaddingX
|
||||
verticalPadding: Style.spacing.controlPaddingY
|
||||
onClicked: service.installHyprRdp()
|
||||
}
|
||||
}
|
||||
|
||||
// ── Installed: full UI ──
|
||||
ColumnLayout {
|
||||
visible: service.installed
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.space(12)
|
||||
|
||||
// Service
|
||||
PanelSectionHeader { text: "SERVICE"; foreground: root.fg }
|
||||
|
||||
Toggle {
|
||||
Layout.fillWidth: true
|
||||
label: "RDP server"
|
||||
description: service.bindAddress !== "" ? ("Listening on " + service.bindAddress) : "systemd --user service"
|
||||
checked: service.active
|
||||
foreground: root.fg
|
||||
onClicked: service.toggleService()
|
||||
}
|
||||
|
||||
// Isolation
|
||||
PanelSectionHeader { text: "MONITOR ISOLATION"; foreground: root.fg }
|
||||
|
||||
Toggle {
|
||||
Layout.fillWidth: true
|
||||
label: "Isolate RDP monitor"
|
||||
description: "Keeps it off workspace " + service.isolatedWorkspace +
|
||||
" only, out of normal auto-assignment. Turn off to let Hyprland treat it" +
|
||||
" like a real second display. Takes effect next time hypr-rdp (re)starts."
|
||||
checked: service.isolated
|
||||
foreground: root.fg
|
||||
onClicked: service.toggleIsolation()
|
||||
}
|
||||
|
||||
// Workspace view
|
||||
PanelSectionHeader { text: "WORKSPACE " + service.isolatedWorkspace; foreground: root.fg }
|
||||
|
||||
Text {
|
||||
visible: service.windowsOnIsolated.length === 0
|
||||
Layout.fillWidth: true
|
||||
text: service.running ? "No windows" : "hypr-rdp isn't running"
|
||||
color: root.dim
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.caption
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: service.windowsOnIsolated.length > 0
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.space(4)
|
||||
|
||||
Repeater {
|
||||
model: service.windowsOnIsolated
|
||||
delegate: Text {
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
text: modelData.cls !== "" ? (modelData.cls + " — " + modelData.title) : modelData.title
|
||||
color: root.fg
|
||||
elide: Text.ElideRight
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.caption
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
visible: service.running
|
||||
text: service.peeking ? "Back to your workspace" : "Peek at RDP workspace"
|
||||
foreground: root.fg
|
||||
fontFamily: root.fontFamily
|
||||
fontSize: Style.font.caption
|
||||
horizontalPadding: Style.spacing.controlPaddingX
|
||||
verticalPadding: Style.spacing.controlPaddingY
|
||||
onClicked: service.peeking ? service.returnFromPeek() : service.peekIsolatedWorkspace()
|
||||
}
|
||||
|
||||
// Credentials
|
||||
PanelSectionHeader { text: "CREDENTIALS"; foreground: root.fg }
|
||||
|
||||
TextField {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "Username"
|
||||
text: root.editUsername
|
||||
foreground: root.fg
|
||||
onTextEdited: root.editUsername = text
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.space(6)
|
||||
|
||||
TextField {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "Password"
|
||||
text: root.editPassword
|
||||
password: !root.showPassword
|
||||
foreground: root.fg
|
||||
onTextEdited: root.editPassword = text
|
||||
}
|
||||
|
||||
Button {
|
||||
text: root.showPassword ? "Hide" : "Show"
|
||||
foreground: root.fg
|
||||
fontFamily: root.fontFamily
|
||||
fontSize: Style.font.caption
|
||||
horizontalPadding: Style.spacing.controlPaddingX
|
||||
verticalPadding: Style.spacing.controlPaddingY
|
||||
onClicked: root.showPassword = !root.showPassword
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "Bind address (e.g. 0.0.0.0:3389)"
|
||||
text: root.editBind
|
||||
foreground: root.fg
|
||||
onTextEdited: root.editBind = text
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Save credentials"
|
||||
foreground: root.fg
|
||||
fontFamily: root.fontFamily
|
||||
fontSize: Style.font.body
|
||||
horizontalPadding: Style.spacing.controlPaddingX
|
||||
verticalPadding: Style.spacing.controlPaddingY
|
||||
onClicked: service.saveCredentials(root.editUsername, root.editPassword, root.editBind)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Footer ──
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: "esc closes"
|
||||
color: Qt.rgba(root.fg.r, root.fg.g, root.fg.b, 0.3)
|
||||
font.family: root.fontFamily
|
||||
font.pixelSize: Style.font.caption
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user