Add Home and Phone settings
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import QtQuick
|
||||
import qs.config
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var entity
|
||||
signal addRequested(string id)
|
||||
|
||||
width: parent ? parent.width : 620
|
||||
implicitHeight: 62
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: stateCopy.left
|
||||
anchors.rightMargin: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 3
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.entity.sourceName
|
||||
color: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.entity.id
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: stateCopy
|
||||
anchors.right: addButton.left
|
||||
anchors.rightMargin: 14
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 92
|
||||
text: root.entity.available === false ? "Unavailable" : String(root.entity.state || "Unknown")
|
||||
color: root.entity.available === false ? Theme.fgMuted : Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
horizontalAlignment: Text.AlignRight
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
id: addButton
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Add"
|
||||
tone: "accent"
|
||||
activeFocusOnTab: true
|
||||
border.width: activeFocus ? 2 : 0
|
||||
border.color: activeFocus ? Theme.fg : Theme.alpha(Theme.fg, 0)
|
||||
onClicked: root.addRequested(root.entity.id)
|
||||
Keys.onReturnPressed: root.addRequested(root.entity.id)
|
||||
Keys.onSpacePressed: root.addRequested(root.entity.id)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
height: 1
|
||||
color: Theme.alpha(Theme.fg, 0.06)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
required property var favorite
|
||||
required property string sourceName
|
||||
required property int index
|
||||
required property bool featured
|
||||
|
||||
signal aliasCommitted(string id, string alias)
|
||||
signal removeRequested(string id)
|
||||
signal moveRequested(string id, int targetIndex)
|
||||
|
||||
readonly property bool dragging: dragHandler.active
|
||||
|
||||
implicitHeight: 108
|
||||
radius: Theme.cardRadius
|
||||
color: root.dragging
|
||||
? Theme.mix(Theme.bgDark, Theme.accent, 0.09)
|
||||
: Theme.alpha(Theme.bgDark, 0.7)
|
||||
border.width: root.dragging ? 2 : 1
|
||||
border.color: root.dragging
|
||||
? Theme.alpha(Theme.accent, 0.82)
|
||||
: Theme.alpha(Theme.fg, 0.07)
|
||||
z: root.dragging ? 10 : 0
|
||||
|
||||
transform: Translate {
|
||||
x: root.dragging ? dragHandler.translation.x : 0
|
||||
y: root.dragging ? dragHandler.translation.y : 0
|
||||
}
|
||||
|
||||
PrismEdge {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
inset: root.radius
|
||||
opacity: root.dragging ? 0.82 : 0.2
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dragHandle
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 11
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 30
|
||||
height: 42
|
||||
radius: 9
|
||||
activeFocusOnTab: true
|
||||
color: root.dragging || activeFocus
|
||||
? Theme.alpha(Theme.accent, 0.14)
|
||||
: (handleMouse.containsMouse ? Theme.alpha(Theme.fg, 0.09) : Theme.alpha(Theme.fg, 0.045))
|
||||
border.width: activeFocus ? 2 : 1
|
||||
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.06)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "⠿"
|
||||
color: root.dragging ? Theme.accent : Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 16
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: handleMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: Qt.SizeAllCursor
|
||||
}
|
||||
|
||||
DragHandler {
|
||||
id: dragHandler
|
||||
target: null
|
||||
onActiveChanged: {
|
||||
if (!active)
|
||||
root.commitDrag();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Left || event.key === Qt.Key_Up) {
|
||||
root.moveRequested(root.favorite.id, Math.max(0, root.index - 1));
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Right || event.key === Qt.Key_Down) {
|
||||
const grid = root.GridView.view;
|
||||
const lastIndex = grid ? grid.count - 1 : root.index;
|
||||
root.moveRequested(root.favorite.id, Math.min(lastIndex, root.index + 1));
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: aliasFrame
|
||||
anchors.left: dragHandle.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: removeButton.left
|
||||
anchors.rightMargin: 12
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 12
|
||||
height: 34
|
||||
radius: 8
|
||||
color: Theme.alpha(Theme.fg, aliasInput.activeFocus ? 0.075 : 0.045)
|
||||
border.width: aliasInput.activeFocus ? 2 : 1
|
||||
border.color: aliasInput.activeFocus
|
||||
? Theme.alpha(Theme.accent, 0.78)
|
||||
: Theme.alpha(Theme.fg, 0.065)
|
||||
|
||||
TextInput {
|
||||
id: aliasInput
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
activeFocusOnTab: true
|
||||
text: String(root.favorite.alias || "")
|
||||
color: Theme.fg
|
||||
selectionColor: Theme.accent
|
||||
selectedTextColor: Theme.bgDark
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.weight: Font.Medium
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
selectByMouse: true
|
||||
clip: true
|
||||
onEditingFinished: root.aliasCommitted(root.favorite.id, text)
|
||||
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
visible: aliasInput.text === "" && !aliasInput.activeFocus
|
||||
text: root.sourceName
|
||||
color: Theme.fgDim
|
||||
font: aliasInput.font
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.left: aliasFrame.left
|
||||
anchors.right: removeButton.left
|
||||
anchors.rightMargin: 12
|
||||
anchors.top: aliasFrame.bottom
|
||||
anchors.topMargin: 7
|
||||
text: root.sourceName
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: aliasFrame.left
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 10
|
||||
width: badgeCopy.implicitWidth + 14
|
||||
height: 21
|
||||
radius: Theme.pillRadius
|
||||
visible: root.featured
|
||||
color: Theme.alpha(Theme.accent, 0.1)
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.accent, 0.2)
|
||||
|
||||
Text {
|
||||
id: badgeCopy
|
||||
anchors.centerIn: parent
|
||||
text: "Control Center"
|
||||
color: Theme.accent
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 9
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
id: removeButton
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 11
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Remove"
|
||||
activeFocusOnTab: true
|
||||
border.width: activeFocus ? 2 : 1
|
||||
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08)
|
||||
onClicked: root.removeRequested(root.favorite.id)
|
||||
Keys.onReturnPressed: root.removeRequested(root.favorite.id)
|
||||
Keys.onSpacePressed: root.removeRequested(root.favorite.id)
|
||||
}
|
||||
|
||||
function commitDrag(): void {
|
||||
const grid = root.GridView.view;
|
||||
if (!grid || grid.count <= 0)
|
||||
return;
|
||||
const centerX = root.x + dragHandler.translation.x + root.width / 2;
|
||||
const centerY = root.y + dragHandler.translation.y + root.height / 2;
|
||||
const modelCount = grid.count;
|
||||
const column = Math.max(0, Math.min(1, Math.floor(centerX / grid.cellWidth)));
|
||||
const row = Math.max(0, Math.floor(centerY / grid.cellHeight));
|
||||
root.moveRequested(root.favorite.id, Math.min(modelCount - 1, row * 2 + column));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string lightQuery: searchInput.text.trim().toLowerCase()
|
||||
|
||||
readonly property var availableLights: HomeAssistant.catalog.filter(entity => {
|
||||
if (HomePreferences.isSelected(entity.id))
|
||||
return false;
|
||||
const haystack = (entity.sourceName + " " + entity.id).toLowerCase();
|
||||
return root.lightQuery === "" || haystack.includes(root.lightQuery);
|
||||
})
|
||||
|
||||
function homeStatus(): string {
|
||||
if (HomeAssistant.phase === "ready")
|
||||
return `Connected · ${HomeAssistant.discoveredCount} lights discovered`;
|
||||
if (HomeAssistant.phase === "degraded")
|
||||
return "Last update unavailable · showing saved controls";
|
||||
if (HomeAssistant.lastError === "authentication-required")
|
||||
return "Authentication required";
|
||||
if (HomeAssistant.lastError === "not-configured")
|
||||
return "Home Assistant is not configured";
|
||||
if (HomeAssistant.phase === "loading")
|
||||
return "Connecting to Home Assistant";
|
||||
return "Home Assistant is unavailable";
|
||||
}
|
||||
|
||||
Flickable {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
contentWidth: width
|
||||
contentHeight: content.implicitHeight + 64
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
Column {
|
||||
id: content
|
||||
width: parent.width - 68
|
||||
x: 34
|
||||
y: 30
|
||||
spacing: 16
|
||||
|
||||
Text {
|
||||
text: "Home & Phone"
|
||||
color: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 27
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Choose what appears in Control Center and keep phone continuity close at hand."
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
bottomPadding: 6
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
title: "Home Assistant"
|
||||
subtitle: root.homeStatus()
|
||||
|
||||
SettingRow {
|
||||
label: "Light catalog"
|
||||
detail: "Panama reads light state through the Home Assistant helper."
|
||||
divider: false
|
||||
controlWidth: 176
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 8
|
||||
|
||||
SettingsButton {
|
||||
id: refreshButton
|
||||
text: "Refresh"
|
||||
activeFocusOnTab: true
|
||||
border.width: activeFocus ? 2 : 1
|
||||
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08)
|
||||
onClicked: HomeAssistant.refresh()
|
||||
Keys.onReturnPressed: HomeAssistant.refresh()
|
||||
Keys.onSpacePressed: HomeAssistant.refresh()
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
id: openHomeButton
|
||||
text: "Open"
|
||||
activeFocusOnTab: true
|
||||
border.width: activeFocus ? 2 : 1
|
||||
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08)
|
||||
onClicked: HomeAssistant.open()
|
||||
Keys.onReturnPressed: HomeAssistant.open()
|
||||
Keys.onSpacePressed: HomeAssistant.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
title: "Control Center lights"
|
||||
subtitle: HomeAssistant.selectedEntities.length === 0
|
||||
? "Select the lights that belong on your shelf."
|
||||
: `${Math.min(4, HomeAssistant.selectedEntities.length)} in Control Center · ${HomeAssistant.selectedEntities.length} selected`
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: HomeAssistant.selectedEntities.length === 0
|
||||
text: "Choose lights below to build your Control Center shelf."
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
wrapMode: Text.WordWrap
|
||||
topPadding: 3
|
||||
bottomPadding: 13
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: favoritesGrid
|
||||
width: parent.width
|
||||
height: Math.ceil(count / 2) * cellHeight
|
||||
visible: count > 0
|
||||
interactive: false
|
||||
clip: false
|
||||
cellWidth: width / 2
|
||||
cellHeight: 116
|
||||
model: HomeAssistant.selectedEntities
|
||||
|
||||
delegate: HomeFavoriteCard {
|
||||
required property var modelData
|
||||
width: GridView.view.cellWidth - 6
|
||||
height: 108
|
||||
favorite: ({
|
||||
id: modelData.id,
|
||||
alias: modelData.name === modelData.sourceName ? "" : modelData.name
|
||||
})
|
||||
sourceName: modelData.sourceName
|
||||
featured: index < 4
|
||||
onAliasCommitted: (id, alias) => HomePreferences.setAlias(id, alias)
|
||||
onMoveRequested: (id, targetIndex) => HomePreferences.move(id, targetIndex)
|
||||
onRemoveRequested: id => HomePreferences.remove(id)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 46
|
||||
visible: HomePreferences.saveError !== ""
|
||||
radius: 9
|
||||
color: Theme.alpha(Theme.warn, 0.09)
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.warn, 0.24)
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 12
|
||||
anchors.right: retryButton.left
|
||||
anchors.rightMargin: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: HomePreferences.saveError
|
||||
color: Theme.warn
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
id: retryButton
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Retry"
|
||||
activeFocusOnTab: true
|
||||
border.width: activeFocus ? 2 : 1
|
||||
border.color: activeFocus ? Theme.warn : Theme.alpha(Theme.fg, 0.08)
|
||||
onClicked: HomePreferences.retrySave()
|
||||
Keys.onReturnPressed: HomePreferences.retrySave()
|
||||
Keys.onSpacePressed: HomePreferences.retrySave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
title: "Available lights"
|
||||
subtitle: "Search the Home Assistant catalog by source name or entity ID."
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 38
|
||||
radius: 10
|
||||
color: Theme.alpha(Theme.fg, searchInput.activeFocus ? 0.08 : 0.05)
|
||||
border.width: searchInput.activeFocus ? 2 : 1
|
||||
border.color: searchInput.activeFocus
|
||||
? Theme.alpha(Theme.accent, 0.72)
|
||||
: Theme.alpha(Theme.fg, 0.065)
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 11
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "\u{F0349}"
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: 14
|
||||
}
|
||||
|
||||
TextInput {
|
||||
id: searchInput
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 36
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 11
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
activeFocusOnTab: true
|
||||
color: Theme.fg
|
||||
selectionColor: Theme.accent
|
||||
selectedTextColor: Theme.bgDark
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
clip: true
|
||||
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
visible: searchInput.text === "" && !searchInput.activeFocus
|
||||
text: "Search available lights"
|
||||
color: Theme.fgMuted
|
||||
font: searchInput.font
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
visible: root.availableLights.length > 0
|
||||
|
||||
Repeater {
|
||||
model: root.availableLights
|
||||
|
||||
AvailableLightRow {
|
||||
required property var modelData
|
||||
width: parent.width
|
||||
entity: modelData
|
||||
onAddRequested: id => HomePreferences.add(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: root.availableLights.length === 0
|
||||
text: root.lightQuery === "" && HomeAssistant.catalog.length > 0
|
||||
? "All discovered lights are already selected"
|
||||
: "No lights match that search"
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.WordWrap
|
||||
topPadding: 18
|
||||
bottomPadding: 10
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
title: "Phone continuity"
|
||||
subtitle: "Keep the Messages handoff independent from phone connectivity."
|
||||
|
||||
SettingRow {
|
||||
label: "Messages"
|
||||
detail: "Opens BlueBubbles"
|
||||
divider: false
|
||||
controlWidth: 204
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 12
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: SystemSettings.bluebubblesAvailable ? "Installed" : "Unavailable"
|
||||
color: SystemSettings.bluebubblesAvailable ? Theme.ok : Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
id: openBlueBubblesButton
|
||||
text: "Open"
|
||||
enabled: SystemSettings.bluebubblesAvailable
|
||||
activeFocusOnTab: enabled
|
||||
border.width: activeFocus ? 2 : 1
|
||||
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08)
|
||||
onClicked: SystemSettings.openApplication("bluebubbles")
|
||||
Keys.onReturnPressed: if (enabled) SystemSettings.openApplication("bluebubbles")
|
||||
Keys.onSpacePressed: if (enabled) SystemSettings.openApplication("bluebubbles")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,7 @@ Rectangle {
|
||||
case "appearance": return appearancePage;
|
||||
case "displays": return displaysPage;
|
||||
case "connectivity": return connectivityPage;
|
||||
case "home-phone": return homePhonePage;
|
||||
case "desktop": return desktopPage;
|
||||
case "sound": return soundPage;
|
||||
case "notifications": return notificationsPage;
|
||||
@@ -133,6 +134,7 @@ Rectangle {
|
||||
Component { id: appearancePage; AppearancePage {} }
|
||||
Component { id: displaysPage; DisplaysPage {} }
|
||||
Component { id: connectivityPage; ConnectivityPage {} }
|
||||
Component { id: homePhonePage; HomePhonePage {} }
|
||||
Component { id: desktopPage; DesktopPage {} }
|
||||
Component { id: soundPage; SoundPage {} }
|
||||
Component { id: notificationsPage; NotificationsPage {} }
|
||||
|
||||
@@ -13,6 +13,7 @@ Rectangle {
|
||||
{ page: "appearance", label: "Appearance", icon: "\u{F0E0D}" },
|
||||
{ page: "displays", label: "Displays", icon: "\u{F0379}" },
|
||||
{ page: "connectivity", label: "Network & Devices", icon: "\u{F08D4}" },
|
||||
{ page: "home-phone", label: "Home & Phone", icon: "\u{F02DC}" },
|
||||
{ page: "desktop", label: "Desktop & Dock", icon: "\u{F04A4}" },
|
||||
{ page: "sound", label: "Sound", icon: "\u{F057E}" },
|
||||
{ page: "notifications", label: "Notifications & Focus", icon: "\u{F009A}" },
|
||||
|
||||
@@ -2,6 +2,9 @@ module qs.modules.settings
|
||||
AboutPage 1.0 AboutPage.qml
|
||||
AppearancePage 1.0 AppearancePage.qml
|
||||
ConnectivityPage 1.0 ConnectivityPage.qml
|
||||
HomePhonePage 1.0 HomePhonePage.qml
|
||||
HomeFavoriteCard 1.0 HomeFavoriteCard.qml
|
||||
AvailableLightRow 1.0 AvailableLightRow.qml
|
||||
DesktopPage 1.0 DesktopPage.qml
|
||||
DisplaysPage 1.0 DisplaysPage.qml
|
||||
HomePage 1.0 HomePage.qml
|
||||
|
||||
@@ -92,7 +92,7 @@ Singleton {
|
||||
}
|
||||
|
||||
function openSettings(page: string): void {
|
||||
const allowed = ["home", "appearance", "displays", "connectivity", "desktop", "sound", "notifications", "screen-intelligence", "shortcuts", "services", "about"];
|
||||
const allowed = ["home", "appearance", "displays", "connectivity", "home-phone", "desktop", "sound", "notifications", "screen-intelligence", "shortcuts", "services", "about"];
|
||||
root.settingsPage = allowed.indexOf(page) >= 0 ? page : "home";
|
||||
DesktopPreferences.lastPage = root.settingsPage;
|
||||
root.settingsOpen = true;
|
||||
|
||||
@@ -27,13 +27,16 @@ Singleton {
|
||||
property bool hyprpaperActive: false
|
||||
property bool hypridleActive: false
|
||||
property bool vicinaeActive: false
|
||||
property bool bluebubblesDetected: false
|
||||
|
||||
property string hyprlandVersion: ""
|
||||
property string quickshellVersion: "0.3.0"
|
||||
property string lastError: ""
|
||||
|
||||
readonly property bool busy: monitorQuery.running || serviceQuery.running || versionQuery.running
|
||||
|| autoHdrWrite.running || vrrWrite.running || directScanoutWrite.running
|
||||
|| bluebubblesQuery.running || autoHdrWrite.running || vrrWrite.running || directScanoutWrite.running
|
||||
|
||||
readonly property bool bluebubblesAvailable: root.bluebubblesDetected
|
||||
|
||||
readonly property bool autoHdr: DesktopPreferences.autoHdr
|
||||
readonly property int vrrPolicy: DesktopPreferences.vrrPolicy
|
||||
@@ -79,6 +82,12 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: bluebubblesQuery
|
||||
command: ["flatpak", "info", "app.bluebubbles.BlueBubbles"]
|
||||
onExited: (exitCode, exitStatus) => root.bluebubblesDetected = exitCode === 0
|
||||
}
|
||||
|
||||
Process {
|
||||
id: autoHdrWrite
|
||||
property bool requested: true
|
||||
@@ -136,6 +145,8 @@ Singleton {
|
||||
serviceQuery.running = true;
|
||||
if (!versionQuery.running && !root.hyprlandVersion)
|
||||
versionQuery.running = true;
|
||||
if (!bluebubblesQuery.running)
|
||||
bluebubblesQuery.running = true;
|
||||
}
|
||||
|
||||
function parseMonitors(text: string): void {
|
||||
@@ -225,7 +236,8 @@ Singleton {
|
||||
"nextcloud": ["nextcloud"],
|
||||
"rustdesk": ["rustdesk"],
|
||||
"kdeconnect": ["kdeconnect-app"],
|
||||
"mission-center": ["flatpak", "run", "io.missioncenter.MissionCenter"]
|
||||
"mission-center": ["flatpak", "run", "io.missioncenter.MissionCenter"],
|
||||
"bluebubbles": ["flatpak", "run", "app.bluebubbles.BlueBubbles"]
|
||||
};
|
||||
const command = commands[id];
|
||||
if (!command) {
|
||||
|
||||
@@ -291,7 +291,12 @@ ShellRoot {
|
||||
function close(): void { ShellState.closeSettings(); }
|
||||
function page(name: string): void { ShellState.openSettings(name); }
|
||||
function status(): string {
|
||||
return JSON.stringify({ open: ShellState.settingsOpen, page: ShellState.settingsPage });
|
||||
return JSON.stringify({
|
||||
open: ShellState.settingsOpen,
|
||||
page: ShellState.settingsPage,
|
||||
discoveredCount: HomeAssistant.discoveredCount,
|
||||
selectedCount: HomeAssistant.configuredCount
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,6 +311,7 @@ ShellRoot {
|
||||
autoHdr: SystemSettings.autoHdr,
|
||||
vrrPolicy: SystemSettings.vrrPolicy,
|
||||
directScanoutPolicy: SystemSettings.directScanoutPolicy,
|
||||
bluebubblesAvailable: SystemSettings.bluebubblesAvailable,
|
||||
busy: SystemSettings.busy,
|
||||
lastError: SystemSettings.lastError
|
||||
});
|
||||
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
state_home="$(mktemp -d /tmp/panama-home-phone-settings-state.XXXXXX)"
|
||||
source_config_path="$repo_dir/config/dot/quickshell"
|
||||
config_path="$state_home/quickshell"
|
||||
test_bin="$state_home/bin"
|
||||
shell_log="$state_home/quickshell.log"
|
||||
flatpak_log="$state_home/flatpak.log"
|
||||
|
||||
cleanup_bootstrap() {
|
||||
rm -rf "$state_home"
|
||||
}
|
||||
trap cleanup_bootstrap EXIT
|
||||
|
||||
mkdir -p "$test_bin"
|
||||
cp -a "$source_config_path" "$config_path"
|
||||
: >"$flatpak_log"
|
||||
|
||||
cat >"$config_path/scripts/panama-home-assistant" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
case "${1:-}" in
|
||||
catalog)
|
||||
printf '%s\n' '{"ok":false,"error":"test-helper"}'
|
||||
;;
|
||||
toggle|brightness)
|
||||
printf '%s\n' '{"ok":true}'
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$config_path/scripts/panama-home-assistant"
|
||||
|
||||
cat >"$test_bin/hyprctl" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${1:-}" == "-j" && "${2:-}" == "monitors" ]]; then
|
||||
printf '%s\n' '[{"focused":true,"name":"TEST-1","description":"Home and Phone contract","width":1920,"height":1080,"refreshRate":60,"scale":1,"currentFormat":"XRGB8888","colorManagementPreset":"srgb","vrr":false}]'
|
||||
exit 0
|
||||
fi
|
||||
if [[ "${1:-}" == "keyword" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
exec /usr/sbin/hyprctl "$@"
|
||||
EOF
|
||||
chmod +x "$test_bin/hyprctl"
|
||||
|
||||
cat >"$test_bin/flatpak" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
printf '%s\n' "$*" >>"$PANAMA_FLATPAK_LOG"
|
||||
if [[ "${1:-}" == "info" && "${2:-}" == "app.bluebubbles.BlueBubbles" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
exit 97
|
||||
EOF
|
||||
chmod +x "$test_bin/flatpak"
|
||||
|
||||
fail() {
|
||||
printf 'home phone settings contract: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local needle="$1"
|
||||
local file="$2"
|
||||
rg -Fq "$needle" "$file" || fail "$file is missing: $needle"
|
||||
}
|
||||
|
||||
home_page="$repo_dir/config/dot/quickshell/modules/settings/HomePhonePage.qml"
|
||||
favorite_card="$repo_dir/config/dot/quickshell/modules/settings/HomeFavoriteCard.qml"
|
||||
available_row="$repo_dir/config/dot/quickshell/modules/settings/AvailableLightRow.qml"
|
||||
system_settings="$repo_dir/config/dot/quickshell/services/SystemSettings.qml"
|
||||
|
||||
[[ -f "$home_page" ]] || fail 'HomePhonePage.qml is missing'
|
||||
[[ -f "$favorite_card" ]] || fail 'HomeFavoriteCard.qml is missing'
|
||||
[[ -f "$available_row" ]] || fail 'AvailableLightRow.qml is missing'
|
||||
assert_contains 'text: "Home & Phone"' "$home_page"
|
||||
assert_contains 'text: "Choose what appears in Control Center and keep phone continuity close at hand."' "$home_page"
|
||||
assert_contains 'Connected · ' "$home_page"
|
||||
assert_contains 'Last update unavailable · showing saved controls' "$home_page"
|
||||
assert_contains 'Authentication required' "$home_page"
|
||||
assert_contains 'Home Assistant is not configured' "$home_page"
|
||||
assert_contains 'HomePreferences.setAlias' "$home_page"
|
||||
assert_contains 'HomePreferences.move' "$home_page"
|
||||
assert_contains 'HomePreferences.remove' "$home_page"
|
||||
assert_contains 'HomePreferences.add' "$home_page"
|
||||
assert_contains 'HomePreferences.retrySave' "$home_page"
|
||||
assert_contains 'Choose lights below to build your Control Center shelf.' "$home_page"
|
||||
assert_contains 'All discovered lights are already selected' "$home_page"
|
||||
assert_contains 'No lights match that search' "$home_page"
|
||||
assert_contains 'Opens BlueBubbles' "$home_page"
|
||||
[[ "$(rg -Fc 'required property var modelData' "$home_page")" -ge 2 ]] \
|
||||
|| fail 'HomePhonePage.qml does not bind both reusable delegates to modelData'
|
||||
if rg -Fq 'index: model.index' "$home_page"; then
|
||||
fail 'HomePhonePage.qml reads an undefined model.index instead of the delegate index'
|
||||
fi
|
||||
if rg -qi 'token|bearer|api/states' "$home_page"; then
|
||||
fail 'HomePhonePage.qml crosses the credential or REST privacy boundary'
|
||||
fi
|
||||
assert_contains 'signal aliasCommitted(string id, string alias)' "$favorite_card"
|
||||
assert_contains 'signal removeRequested(string id)' "$favorite_card"
|
||||
assert_contains 'signal moveRequested(string id, int targetIndex)' "$favorite_card"
|
||||
assert_contains 'DragHandler {' "$favorite_card"
|
||||
assert_contains 'onEditingFinished:' "$favorite_card"
|
||||
assert_contains 'text: "Control Center"' "$favorite_card"
|
||||
assert_contains 'activeFocusOnTab: true' "$favorite_card"
|
||||
assert_contains 'signal addRequested(string id)' "$available_row"
|
||||
assert_contains 'activeFocusOnTab: true' "$available_row"
|
||||
assert_contains 'readonly property bool bluebubblesAvailable: root.bluebubblesDetected' "$system_settings"
|
||||
assert_contains 'command: ["flatpak", "info", "app.bluebubbles.BlueBubbles"]' "$system_settings"
|
||||
assert_contains '"bluebubbles": ["flatpak", "run", "app.bluebubbles.BlueBubbles"]' "$system_settings"
|
||||
|
||||
qs_for_test() {
|
||||
PATH="$test_bin:$PATH" QS_CONFIG_PATH="$config_path" XDG_STATE_HOME="$state_home" \
|
||||
PANAMA_FLATPAK_LOG="$flatpak_log" qs -p "$config_path" "$@"
|
||||
}
|
||||
|
||||
stop_test_shell() {
|
||||
qs_for_test kill >/dev/null 2>&1 || true
|
||||
for _ in $(seq 1 80); do
|
||||
if ! qs_for_test list 2>/dev/null | rg '^Instance ' >/dev/null \
|
||||
&& ! qs_for_test ipc show >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
qs_for_test ipc call settings close >/dev/null 2>&1 || true
|
||||
if stop_test_shell; then
|
||||
rm -rf "$state_home"
|
||||
else
|
||||
printf 'home phone settings contract: branch shell did not stop; retained %s\n' \
|
||||
"$state_home" >&2
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
start_test_shell() {
|
||||
stop_test_shell || fail 'pre-existing branch shell did not stop cleanly'
|
||||
for _attempt in 1 2; do
|
||||
qs_for_test --daemonize >"$shell_log" 2>&1
|
||||
for _ in $(seq 1 80); do
|
||||
if qs_for_test ipc show 2>/dev/null | rg '^target settings-system$' >/dev/null; then
|
||||
return
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
stop_test_shell || fail 'failed branch-shell attempt did not stop cleanly'
|
||||
done
|
||||
sed -n '1,240p' "$shell_log" >&2
|
||||
fail 'isolated branch shell did not start'
|
||||
}
|
||||
|
||||
start_test_shell
|
||||
qs_for_test ipc call home-assistant fixture ready >/dev/null
|
||||
qs_for_test ipc call settings page home-phone >/dev/null
|
||||
|
||||
status='{}'
|
||||
for _ in $(seq 1 40); do
|
||||
status="$(qs_for_test ipc call settings status | jq -c .)"
|
||||
if jq -e '.page == "home-phone" and .discoveredCount == 7 and .selectedCount == 7' \
|
||||
<<<"$status" >/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
jq -e '.open == true and .page == "home-phone" and .discoveredCount == 7 and .selectedCount == 7' \
|
||||
<<<"$status" >/dev/null || fail "Home & Phone diagnostics are incomplete: $status"
|
||||
|
||||
system_status="$(qs_for_test ipc call settings-system status | jq -c .)"
|
||||
jq -e '.bluebubblesAvailable == true' <<<"$system_status" >/dev/null \
|
||||
|| fail "BlueBubbles availability was not exposed: $system_status"
|
||||
|
||||
shell_pid="$(qs_for_test list | awk '/Process ID:/ { print $3; exit }')"
|
||||
[[ "$shell_pid" =~ ^[0-9]+$ ]] || fail 'could not identify the branch shell process'
|
||||
for _ in $(seq 1 40); do
|
||||
if /usr/sbin/hyprctl -j clients | jq -e --argjson pid "$shell_pid" \
|
||||
'[.[] | select(.pid == $pid and .title == "Panama Settings" and .floating == false)] | length == 1' >/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
/usr/sbin/hyprctl -j clients | jq -e --argjson pid "$shell_pid" \
|
||||
'[.[] | select(.pid == $pid and .title == "Panama Settings" and .floating == false)] | length == 1' >/dev/null \
|
||||
|| fail 'the branch shell did not own exactly one tiled Panama Settings client'
|
||||
|
||||
if [[ -n "${PANAMA_TEST_SCREENSHOT_PATH:-}" ]]; then
|
||||
geometry="$(/usr/sbin/hyprctl -j clients | jq -r --argjson pid "$shell_pid" \
|
||||
'.[] | select(.pid == $pid and .title == "Panama Settings") | "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"')"
|
||||
[[ -n "$geometry" ]] || fail 'could not resolve the Settings client geometry'
|
||||
grim -g "$geometry" "$PANAMA_TEST_SCREENSHOT_PATH"
|
||||
fi
|
||||
|
||||
rg -Fxq 'info app.bluebubbles.BlueBubbles' "$flatpak_log" \
|
||||
|| fail 'the fixed BlueBubbles availability probe did not run'
|
||||
if rg -q '^run ' "$flatpak_log"; then
|
||||
fail 'the contract started BlueBubbles'
|
||||
fi
|
||||
if find "$state_home" -name panama-home.json -print -quit | rg -q .; then
|
||||
fail 'the read-only fixture route wrote Home preferences'
|
||||
fi
|
||||
|
||||
trap - EXIT
|
||||
cleanup
|
||||
[[ ! -e "$state_home" ]] || fail 'temporary Home & Phone state was not removed after shell exit'
|
||||
printf 'home phone settings contract: PASS\n'
|
||||
@@ -2,34 +2,135 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
state_home="$(mktemp -d /tmp/panama-settings-pages-state.XXXXXX)"
|
||||
source_config_path="$repo_dir/config/dot/quickshell"
|
||||
config_path="$state_home/quickshell"
|
||||
test_bin="$state_home/bin"
|
||||
shell_log="$state_home/quickshell.log"
|
||||
|
||||
cleanup_bootstrap() {
|
||||
rm -rf "$state_home"
|
||||
}
|
||||
trap cleanup_bootstrap EXIT
|
||||
|
||||
mkdir -p "$test_bin"
|
||||
cp -a "$source_config_path" "$config_path"
|
||||
|
||||
cat >"$config_path/scripts/panama-home-assistant" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
case "${1:-}" in
|
||||
catalog)
|
||||
printf '%s\n' '{"ok":false,"error":"test-helper"}'
|
||||
;;
|
||||
toggle|brightness)
|
||||
printf '%s\n' '{"ok":true}'
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$config_path/scripts/panama-home-assistant"
|
||||
|
||||
cat >"$test_bin/hyprctl" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${1:-}" == "-j" && "${2:-}" == "monitors" ]]; then
|
||||
printf '%s\n' '[{"focused":true,"name":"TEST-1","description":"Settings contract","width":1920,"height":1080,"refreshRate":60,"scale":1,"currentFormat":"XRGB8888","colorManagementPreset":"srgb","vrr":false}]'
|
||||
exit 0
|
||||
fi
|
||||
if [[ "${1:-}" == "keyword" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
exec /usr/sbin/hyprctl "$@"
|
||||
EOF
|
||||
chmod +x "$test_bin/hyprctl"
|
||||
|
||||
cat >"$test_bin/flatpak" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${1:-}" == "info" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 97
|
||||
EOF
|
||||
chmod +x "$test_bin/flatpak"
|
||||
|
||||
fail() {
|
||||
printf 'settings pages contract: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
qs_for_test() {
|
||||
PATH="$test_bin:$PATH" QS_CONFIG_PATH="$config_path" XDG_STATE_HOME="$state_home" \
|
||||
qs -p "$config_path" "$@"
|
||||
}
|
||||
|
||||
stop_test_shell() {
|
||||
qs_for_test kill >/dev/null 2>&1 || true
|
||||
for _ in $(seq 1 80); do
|
||||
if ! qs_for_test list 2>/dev/null | rg '^Instance ' >/dev/null \
|
||||
&& ! qs_for_test ipc show >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
qs ipc call settings close >/dev/null 2>&1 || true
|
||||
qs_for_test ipc call settings close >/dev/null 2>&1 || true
|
||||
if stop_test_shell; then
|
||||
rm -rf "$state_home"
|
||||
else
|
||||
printf 'settings pages contract: branch shell did not stop; retained %s\n' \
|
||||
"$state_home" >&2
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
pages=(home appearance displays connectivity desktop sound notifications screen-intelligence shortcuts services about)
|
||||
start_test_shell() {
|
||||
stop_test_shell || fail 'pre-existing branch shell did not stop cleanly'
|
||||
for _attempt in 1 2; do
|
||||
qs_for_test --daemonize >"$shell_log" 2>&1
|
||||
for _ in $(seq 1 80); do
|
||||
if qs_for_test ipc show 2>/dev/null | rg '^target settings$' >/dev/null; then
|
||||
return
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
stop_test_shell || fail 'failed branch-shell attempt did not stop cleanly'
|
||||
done
|
||||
sed -n '1,200p' "$shell_log" >&2
|
||||
fail 'isolated branch shell did not start'
|
||||
}
|
||||
|
||||
start_test_shell
|
||||
qs_for_test ipc call home-assistant fixture ready >/dev/null
|
||||
shell_pid="$(qs_for_test list | awk '/Process ID:/ { print $3; exit }')"
|
||||
[[ "$shell_pid" =~ ^[0-9]+$ ]] || fail 'could not identify the branch shell process'
|
||||
|
||||
pages=(home appearance displays connectivity home-phone desktop sound notifications screen-intelligence shortcuts services about)
|
||||
for page in "${pages[@]}"; do
|
||||
qs ipc call settings page "$page" >/dev/null
|
||||
qs_for_test ipc call settings page "$page" >/dev/null
|
||||
for _ in $(seq 1 20); do
|
||||
[[ "$(qs ipc call settings status | jq -r .page)" == "$page" ]] && break
|
||||
[[ "$(qs_for_test ipc call settings status | jq -r .page)" == "$page" ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
[[ "$(qs ipc call settings status | jq -r .page)" == "$page" ]] || fail "$page did not route"
|
||||
hyprctl -j clients | jq -e '[.[] | select(.title == "Panama Settings")] | length == 1' >/dev/null \
|
||||
|| fail "$page created a missing or duplicate Settings window"
|
||||
[[ "$(qs_for_test ipc call settings status | jq -r .page)" == "$page" ]] || fail "$page did not route"
|
||||
/usr/sbin/hyprctl -j clients | jq -e --argjson pid "$shell_pid" \
|
||||
'[.[] | select(.pid == $pid and .title == "Panama Settings" and .floating == false)] | length == 1' >/dev/null \
|
||||
|| fail "$page created a missing, floating, or duplicate Settings window"
|
||||
done
|
||||
|
||||
qs ipc call settings page '__unsupported__' >/dev/null
|
||||
[[ "$(qs ipc call settings status | jq -r .page)" == "home" ]] || fail 'unsupported page did not fall back to Home'
|
||||
qs_for_test ipc call settings page '__unsupported__' >/dev/null
|
||||
[[ "$(qs_for_test ipc call settings status | jq -r .page)" == "home" ]] || fail 'unsupported page did not fall back to Home'
|
||||
|
||||
hyprctl -j binds | jq -e '.[] | select(.description == "Panama Settings" and .key == "I" and .modmask == 64)' >/dev/null \
|
||||
/usr/sbin/hyprctl -j binds | jq -e '.[] | select(.description == "Panama Settings" and .key == "I" and .modmask == 64)' >/dev/null \
|
||||
|| fail 'Super+I is not registered as Panama Settings'
|
||||
hyprctl -j binds | jq -e '.[] | select(.description == "Screen Intelligence" and .key == "S" and .modmask == 65)' >/dev/null \
|
||||
/usr/sbin/hyprctl -j binds | jq -e '.[] | select(.description == "Screen Intelligence" and .key == "S" and .modmask == 65)' >/dev/null \
|
||||
|| fail 'Super+Shift+S is not registered as Screen Intelligence'
|
||||
|
||||
desktop_file="$HOME/.local/share/applications/panama-settings.desktop"
|
||||
@@ -44,4 +145,5 @@ desktop-file-validate "$intelligence_desktop_file" >/dev/null || fail 'Screen In
|
||||
|
||||
trap - EXIT
|
||||
cleanup
|
||||
[[ ! -e "$state_home" ]] || fail 'temporary Settings state was not removed after shell exit'
|
||||
printf 'settings pages contract: PASS\n'
|
||||
|
||||
Reference in New Issue
Block a user