Complete Panama settings controls
This commit is contained in:
@@ -28,13 +28,13 @@ Singleton {
|
|||||||
// label deliberately general rather than exposing precise coordinates in
|
// label deliberately general rather than exposing precise coordinates in
|
||||||
// the UI or guessing at a city from them.
|
// the UI or guessing at a city from them.
|
||||||
readonly property string weatherLocation: "Local weather"
|
readonly property string weatherLocation: "Local weather"
|
||||||
readonly property string temperatureUnit: "fahrenheit"
|
readonly property string temperatureUnit: DesktopPreferences.get("temperatureUnit")
|
||||||
readonly property int weatherRefreshMinutes: 20
|
readonly property int weatherRefreshMinutes: DesktopPreferences.get("weatherRefreshMinutes")
|
||||||
|
|
||||||
// ── Vitals ──────────────────────────────────────────────────────────────
|
// ── Vitals ──────────────────────────────────────────────────────────────
|
||||||
// The GNOME Vitals extension showed processor usage, memory usage and GPU
|
// The GNOME Vitals extension showed processor usage, memory usage and GPU
|
||||||
// usage, in that order. Same here.
|
// usage, in that order. Same here.
|
||||||
readonly property int vitalsIntervalMs: 2000
|
readonly property int vitalsIntervalMs: DesktopPreferences.get("vitalsIntervalMs")
|
||||||
readonly property bool showCpu: DesktopPreferences.get("showCpu")
|
readonly property bool showCpu: DesktopPreferences.get("showCpu")
|
||||||
readonly property bool showMemory: DesktopPreferences.get("showMemory")
|
readonly property bool showMemory: DesktopPreferences.get("showMemory")
|
||||||
readonly property bool showGpu: DesktopPreferences.get("showGpu")
|
readonly property bool showGpu: DesktopPreferences.get("showGpu")
|
||||||
@@ -51,10 +51,10 @@ Singleton {
|
|||||||
readonly property bool nightLightEnabledByDefault: DesktopPreferences.get("nightLightEnabled")
|
readonly property bool nightLightEnabledByDefault: DesktopPreferences.get("nightLightEnabled")
|
||||||
|
|
||||||
// ── Notifications ───────────────────────────────────────────────────────
|
// ── Notifications ───────────────────────────────────────────────────────
|
||||||
readonly property int notificationTimeoutMs: 5000
|
readonly property int notificationTimeoutMs: DesktopPreferences.get("notificationTimeoutMs")
|
||||||
readonly property int notificationTimeoutCriticalMs: 0 // 0 = never auto-expire
|
readonly property int notificationTimeoutCriticalMs: DesktopPreferences.get("notificationTimeoutCriticalMs") // 0 = never auto-expire
|
||||||
readonly property int notificationHistoryLimit: 100
|
readonly property int notificationHistoryLimit: DesktopPreferences.get("notificationHistoryLimit")
|
||||||
readonly property int maxVisibleToasts: 4
|
readonly property int maxVisibleToasts: DesktopPreferences.get("maxVisibleToasts")
|
||||||
|
|
||||||
// ── Focus ──────────────────────────────────────────────────────────────
|
// ── Focus ──────────────────────────────────────────────────────────────
|
||||||
// One deliberate default rather than a preset picker: quick settings and
|
// One deliberate default rather than a preset picker: quick settings and
|
||||||
@@ -79,9 +79,9 @@ Singleton {
|
|||||||
readonly property int dockHideDelayMs: DesktopPreferences.get("dockHideDelayMs")
|
readonly property int dockHideDelayMs: DesktopPreferences.get("dockHideDelayMs")
|
||||||
|
|
||||||
// ── Capture ─────────────────────────────────────────────────────────────
|
// ── Capture ─────────────────────────────────────────────────────────────
|
||||||
readonly property string screenshotDir: "Pictures/Screenshots"
|
readonly property string screenshotDir: DesktopPreferences.get("screenshotDir")
|
||||||
readonly property string recordingDir: "Videos/Recordings"
|
readonly property string recordingDir: DesktopPreferences.get("recordingDir")
|
||||||
// Passed to wf-recorder. Uses VAAPI on the AMD card so recording does not
|
// Passed to wf-recorder. Uses VAAPI on the AMD card so recording does not
|
||||||
// cost CPU while gaming.
|
// cost CPU while gaming.
|
||||||
readonly property string recorderArgs: "-c h264_vaapi -d /dev/dri/renderD128"
|
readonly property string recorderArgs: DesktopPreferences.get("recorderArgs")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,39 +3,49 @@ import Quickshell
|
|||||||
import qs.config
|
import qs.config
|
||||||
import qs.services
|
import qs.services
|
||||||
|
|
||||||
Item {
|
SettingsPage {
|
||||||
Flickable {
|
title: "About Panama"
|
||||||
anchors.fill: parent
|
lede: "A curated Hyprland desktop built around focus, speed, and good taste."
|
||||||
clip: true
|
|
||||||
contentWidth: width
|
|
||||||
contentHeight: content.implicitHeight + 64
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
Column {
|
SettingsCard {
|
||||||
id: content
|
title: "Panama Desktop"
|
||||||
width: parent.width - 68
|
subtitle: "Tokyo Night Moon · Prism glass · native tiling"
|
||||||
x: 34
|
|
||||||
y: 30
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
Text { text: "About Panama"; color: Theme.fg; font.family: Theme.fontFamily; font.pixelSize: 27; font.weight: Font.DemiBold }
|
TextRow {
|
||||||
Text { text: "A curated Hyprland desktop built around focus, speed, and good taste."; color: Theme.fgDim; font.family: Theme.fontFamily; font.pixelSize: Theme.fontSize; bottomPadding: 6 }
|
label: "Hyprland"
|
||||||
|
value: SystemSettings.hyprlandVersion || "Detecting…"
|
||||||
|
}
|
||||||
|
TextRow {
|
||||||
|
label: "Quickshell"
|
||||||
|
value: SystemSettings.quickshellVersion
|
||||||
|
}
|
||||||
|
TextRow {
|
||||||
|
label: "Display"
|
||||||
|
value: SystemSettings.monitorName || "Detecting…"
|
||||||
|
}
|
||||||
|
TextRow {
|
||||||
|
label: "Configuration"
|
||||||
|
detail: Quickshell.shellDir
|
||||||
|
value: "Local"
|
||||||
|
divider: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
title: "Panama Desktop"
|
title: "Design principles"
|
||||||
subtitle: "Tokyo Night Moon · Prism glass · native tiling"
|
|
||||||
SettingRow { label: "Hyprland"; value: SystemSettings.hyprlandVersion || "Detecting…" }
|
|
||||||
SettingRow { label: "Quickshell"; value: SystemSettings.quickshellVersion }
|
|
||||||
SettingRow { label: "Display"; value: SystemSettings.monitorName || "Detecting…" }
|
|
||||||
SettingRow { label: "Configuration"; detail: Quickshell.shellDir; value: "Local"; divider: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsCard {
|
TextRow {
|
||||||
title: "Design principles"
|
label: "Curated by default"
|
||||||
SettingRow { label: "Curated by default"; detail: "Strong choices instead of an incoherent matrix of switches" }
|
detail: "Strong choices instead of an incoherent matrix of switches"
|
||||||
SettingRow { label: "Quiet while idle"; detail: "No continuous decorative repaint loops" }
|
}
|
||||||
SettingRow { label: "Real system boundaries"; detail: "Every control either works or clearly hands off to its owner"; divider: false }
|
TextRow {
|
||||||
}
|
label: "Quiet while idle"
|
||||||
|
detail: "No continuous decorative repaint loops"
|
||||||
|
}
|
||||||
|
TextRow {
|
||||||
|
label: "Real system boundaries"
|
||||||
|
detail: "Every control either works or clearly hands off to its owner"
|
||||||
|
divider: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ import qs.config
|
|||||||
import qs.services
|
import qs.services
|
||||||
import qs.modules.quicksettings
|
import qs.modules.quicksettings
|
||||||
|
|
||||||
Item {
|
SettingsPage {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
title: "Network & Devices"
|
||||||
|
lede: "Connect graphically—no terminal workflow required."
|
||||||
|
|
||||||
readonly property var wifiDevice: {
|
readonly property var wifiDevice: {
|
||||||
for (const device of Networking.devices.values) {
|
for (const device of Networking.devices.values) {
|
||||||
if (device.type === DeviceType.Wifi)
|
if (device.type === DeviceType.Wifi)
|
||||||
@@ -17,72 +20,73 @@ Item {
|
|||||||
}
|
}
|
||||||
readonly property var bluetoothAdapter: Bluetooth.defaultAdapter
|
readonly property var bluetoothAdapter: Bluetooth.defaultAdapter
|
||||||
|
|
||||||
Flickable {
|
SettingsCard {
|
||||||
anchors.fill: parent
|
title: "Wi‑Fi"
|
||||||
clip: true
|
subtitle: Networking.wifiEnabled ? "Available networks" : "Wireless networking is off"
|
||||||
contentWidth: width
|
|
||||||
contentHeight: content.implicitHeight + 64
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
Column {
|
SettingRow {
|
||||||
id: content
|
label: "Wi‑Fi"
|
||||||
width: parent.width - 68
|
detail: root.wifiDevice ? "Managed by NetworkManager" : "No wireless adapter found"
|
||||||
x: 34
|
controlWidth: 48
|
||||||
y: 30
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
Text { text: "Network & Devices"; color: Theme.fg; font.family: Theme.fontFamily; font.pixelSize: 27; font.weight: Font.DemiBold }
|
SettingsToggle {
|
||||||
Text { text: "Connect graphically—no terminal workflow required."; color: Theme.fgDim; font.family: Theme.fontFamily; font.pixelSize: Theme.fontSize; bottomPadding: 6 }
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
SettingsCard {
|
checked: Networking.wifiEnabled
|
||||||
title: "Wi‑Fi"
|
enabled: Networking.wifiHardwareEnabled
|
||||||
subtitle: Networking.wifiEnabled ? "Available networks" : "Wireless networking is off"
|
onToggled: value => Networking.wifiEnabled = value
|
||||||
SettingRow {
|
|
||||||
label: "Wi‑Fi"
|
|
||||||
detail: root.wifiDevice ? "Managed by NetworkManager" : "No wireless adapter found"
|
|
||||||
controlWidth: 48
|
|
||||||
SettingsToggle {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
checked: Networking.wifiEnabled
|
|
||||||
enabled: Networking.wifiHardwareEnabled
|
|
||||||
onToggled: value => Networking.wifiEnabled = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WifiList { width: parent.width; device: root.wifiDevice; active: true; maxHeight: 240 }
|
|
||||||
SettingRow {
|
|
||||||
label: "Advanced network settings"
|
|
||||||
detail: "VPN, wired profiles, DNS, and connection details"
|
|
||||||
divider: false
|
|
||||||
controlWidth: 104
|
|
||||||
SettingsButton { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; text: "Open panel"; onClicked: SystemSettings.openGnomePanel("network") }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
WifiList {
|
||||||
title: "Bluetooth"
|
width: parent.width
|
||||||
subtitle: root.bluetoothAdapter?.enabled ? "Nearby and paired devices" : "Bluetooth is off"
|
device: root.wifiDevice
|
||||||
SettingRow {
|
active: true
|
||||||
label: "Bluetooth"
|
maxHeight: 240
|
||||||
detail: root.bluetoothAdapter ? "Pair and reconnect without leaving Settings" : "No Bluetooth adapter found"
|
}
|
||||||
controlWidth: 48
|
|
||||||
SettingsToggle {
|
ActionRow {
|
||||||
anchors.right: parent.right
|
label: "Advanced network settings"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
detail: "VPN, wired profiles, DNS, and connection details"
|
||||||
checked: root.bluetoothAdapter?.enabled ?? false
|
divider: false
|
||||||
enabled: root.bluetoothAdapter !== null
|
action: "Open panel"
|
||||||
onToggled: value => { if (root.bluetoothAdapter) root.bluetoothAdapter.enabled = value; }
|
onTriggered: SystemSettings.openGnomePanel("network")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BluetoothList { width: parent.width; active: true; maxHeight: 220 }
|
|
||||||
SettingRow {
|
SettingsCard {
|
||||||
label: "Advanced Bluetooth settings"
|
title: "Bluetooth"
|
||||||
detail: "Device details and system-level options"
|
subtitle: root.bluetoothAdapter?.enabled ? "Nearby and paired devices" : "Bluetooth is off"
|
||||||
divider: false
|
|
||||||
controlWidth: 104
|
SettingRow {
|
||||||
SettingsButton { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; text: "Open panel"; onClicked: SystemSettings.openGnomePanel("bluetooth") }
|
label: "Bluetooth"
|
||||||
|
detail: root.bluetoothAdapter ? "Pair and reconnect without leaving Settings" : "No Bluetooth adapter found"
|
||||||
|
controlWidth: 48
|
||||||
|
|
||||||
|
SettingsToggle {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
checked: root.bluetoothAdapter?.enabled ?? false
|
||||||
|
enabled: root.bluetoothAdapter !== null
|
||||||
|
onToggled: value => {
|
||||||
|
if (root.bluetoothAdapter)
|
||||||
|
root.bluetoothAdapter.enabled = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BluetoothList {
|
||||||
|
width: parent.width
|
||||||
|
active: true
|
||||||
|
maxHeight: 220
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionRow {
|
||||||
|
label: "Advanced Bluetooth settings"
|
||||||
|
detail: "Device details and system-level options"
|
||||||
|
divider: false
|
||||||
|
action: "Open panel"
|
||||||
|
onTriggered: SystemSettings.openGnomePanel("bluetooth")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,64 +3,50 @@ import qs.config
|
|||||||
import qs.services
|
import qs.services
|
||||||
import qs.widgets
|
import qs.widgets
|
||||||
|
|
||||||
Item {
|
SettingsPage {
|
||||||
Flickable {
|
title: "Displays"
|
||||||
anchors.fill: parent
|
lede: SystemSettings.monitorDescription || "Reading the active display…"
|
||||||
clip: true
|
|
||||||
contentWidth: width
|
|
||||||
contentHeight: content.implicitHeight + 64
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
Column {
|
SettingsCard {
|
||||||
id: content
|
title: SystemSettings.monitorName || "Active display"
|
||||||
width: parent.width - 68
|
subtitle: `${SystemSettings.monitorWidth} × ${SystemSettings.monitorHeight} at ${SystemSettings.monitorRefreshRate.toFixed(0)} Hz · ${SystemSettings.monitorScale.toFixed(1)}× scale · ${SystemSettings.monitorFormat}`
|
||||||
x: 34
|
TextRow { label: "Color mode"; detail: "Wide-gamut SDR desktop at 10-bit"; value: SystemSettings.colorPreset || "wide" }
|
||||||
y: 30
|
TextRow { label: "Variable refresh"; detail: SystemSettings.monitorVrrActive ? "Active for current fullscreen content" : "Ready when game or video content requests it"; value: SystemSettings.monitorVrrActive ? "Active" : "Standby"; divider: false }
|
||||||
spacing: 16
|
}
|
||||||
|
|
||||||
Text { text: "Displays"; color: Theme.fg; font.family: Theme.fontFamily; font.pixelSize: 27; font.weight: Font.DemiBold }
|
SettingsCard {
|
||||||
Text { text: SystemSettings.monitorDescription || "Reading the active display…"; color: Theme.fgDim; font.family: Theme.fontFamily; font.pixelSize: Theme.fontSize; bottomPadding: 6 }
|
title: "Gaming display policy"
|
||||||
|
subtitle: "These values apply immediately and are restored when Panama starts."
|
||||||
SettingsCard {
|
SettingRow {
|
||||||
title: SystemSettings.monitorName || "Active display"
|
|
||||||
subtitle: `${SystemSettings.monitorWidth} × ${SystemSettings.monitorHeight} at ${SystemSettings.monitorRefreshRate.toFixed(0)} Hz · ${SystemSettings.monitorScale.toFixed(1)}× scale · ${SystemSettings.monitorFormat}`
|
|
||||||
SettingRow { label: "Color mode"; detail: "Wide-gamut SDR desktop at 10-bit"; value: SystemSettings.colorPreset || "wide" }
|
|
||||||
SettingRow { label: "Variable refresh"; detail: SystemSettings.monitorVrrActive ? "Active for current fullscreen content" : "Ready when game or video content requests it"; value: SystemSettings.monitorVrrActive ? "Active" : "Standby"; divider: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsCard {
|
|
||||||
title: "Gaming display policy"
|
|
||||||
subtitle: "These values apply immediately and are restored when Panama starts."
|
|
||||||
SettingRow {
|
|
||||||
label: "Game-aware HDR"
|
label: "Game-aware HDR"
|
||||||
detail: "Enter HDR only for fullscreen content that requests it"
|
detail: "Enter HDR only for fullscreen content that requests it"
|
||||||
controlWidth: 48
|
controlWidth: 48
|
||||||
SettingsToggle { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; checked: SystemSettings.autoHdr; onToggled: value => SystemSettings.setAutoHdr(value) }
|
SettingsToggle { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; checked: SystemSettings.autoHdr; onToggled: value => SystemSettings.setAutoHdr(value) }
|
||||||
}
|
}
|
||||||
SettingRow {
|
SettingRow {
|
||||||
label: "Content-aware VRR"
|
label: "Content-aware VRR"
|
||||||
detail: "Enable variable refresh only for game and video content"
|
detail: "Enable variable refresh only for game and video content"
|
||||||
controlWidth: 48
|
controlWidth: 48
|
||||||
SettingsToggle { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; checked: SystemSettings.vrrPolicy === 3; onToggled: value => SystemSettings.setVrrPolicy(value ? 3 : 0) }
|
SettingsToggle { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; checked: SystemSettings.vrrPolicy === 3; onToggled: value => SystemSettings.setVrrPolicy(value ? 3 : 0) }
|
||||||
}
|
}
|
||||||
SettingRow {
|
SettingRow {
|
||||||
label: "Direct scanout for games"
|
label: "Direct scanout for games"
|
||||||
detail: "Bypass compositing only for windows classified as games"
|
detail: "Bypass compositing only for windows classified as games"
|
||||||
divider: false
|
divider: false
|
||||||
controlWidth: 48
|
controlWidth: 48
|
||||||
SettingsToggle { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; checked: SystemSettings.directScanoutPolicy === 2; onToggled: value => SystemSettings.setDirectScanoutPolicy(value ? 2 : 0) }
|
SettingsToggle { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; checked: SystemSettings.directScanoutPolicy === 2; onToggled: value => SystemSettings.setDirectScanoutPolicy(value ? 2 : 0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
title: "Night Light"
|
title: "Night Light"
|
||||||
SettingRow {
|
SettingRow {
|
||||||
label: "Warm display colors"
|
label: "Warm display colors"
|
||||||
detail: NightLight.automatic ? "Following the evening schedule" : "Manual control"
|
detail: NightLight.automatic ? "Following the evening schedule" : "Manual control"
|
||||||
controlWidth: 48
|
controlWidth: 48
|
||||||
SettingsToggle { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; checked: NightLight.active; onToggled: NightLight.toggle() }
|
SettingsToggle { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; checked: NightLight.active; onToggled: NightLight.toggle() }
|
||||||
}
|
}
|
||||||
SettingRow {
|
SettingRow {
|
||||||
label: "Color temperature"
|
label: "Color temperature"
|
||||||
detail: `${NightLight.temperature} K`
|
detail: `${NightLight.temperature} K`
|
||||||
divider: false
|
divider: false
|
||||||
@@ -71,27 +57,25 @@ Item {
|
|||||||
icon: "weather-clear-night-symbolic"
|
icon: "weather-clear-night-symbolic"
|
||||||
onMoved: value => NightLight.temperature = Math.round((6500 - value * 4000) / 50) * 50
|
onMoved: value => NightLight.temperature = Math.round((6500 - value * 4000) / 50) * 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: warningText.implicitHeight + 30
|
height: warningText.implicitHeight + 30
|
||||||
radius: Theme.cardRadius
|
radius: Theme.cardRadius
|
||||||
color: Theme.alpha(Theme.warn, 0.085)
|
color: Theme.alpha(Theme.warn, 0.085)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: Theme.alpha(Theme.warn, 0.22)
|
border.color: Theme.alpha(Theme.warn, 0.22)
|
||||||
Text {
|
Text {
|
||||||
id: warningText
|
id: warningText
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 15
|
anchors.margins: 15
|
||||||
text: "Full-time desktop HDR stays unavailable here because the current compositor path can break screenshots, OBS, Sunshine, and lock-screen capture. Game-aware HDR keeps the desktop dependable without giving up HDR games."
|
text: "Full-time desktop HDR stays unavailable here because the current compositor path can break screenshots, OBS, Sunshine, and lock-screen capture. Game-aware HDR keeps the desktop dependable without giving up HDR games."
|
||||||
color: Theme.mix(Theme.fg, Theme.warn, 0.25)
|
color: Theme.mix(Theme.fg, Theme.warn, 0.25)
|
||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,160 +2,159 @@ import QtQuick
|
|||||||
import qs.config
|
import qs.config
|
||||||
import qs.services
|
import qs.services
|
||||||
|
|
||||||
Item {
|
SettingsPage {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property int openedHour: new Date().getHours()
|
readonly property int openedHour: new Date().getHours()
|
||||||
readonly property string greeting: openedHour < 12 ? "Good morning" : (openedHour < 18 ? "Good afternoon" : "Good evening")
|
readonly property string greeting: openedHour < 12 ? "Good morning" : (openedHour < 18 ? "Good afternoon" : "Good evening")
|
||||||
|
|
||||||
Flickable {
|
title: `${root.greeting}, Gabriel`
|
||||||
anchors.fill: parent
|
lede: "Your Panama desktop is configured and ready."
|
||||||
clip: true
|
|
||||||
contentWidth: width
|
|
||||||
contentHeight: content.implicitHeight + 64
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
Column {
|
SettingsCard {
|
||||||
id: content
|
title: SystemSettings.monitorDescription || "Active display"
|
||||||
width: parent.width - 68
|
subtitle: SystemSettings.monitorName || "Detecting your display…"
|
||||||
x: 34
|
|
||||||
y: 30
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
Text {
|
Grid {
|
||||||
text: `${root.greeting}, Gabriel`
|
id: monitorLayout
|
||||||
color: Theme.fg
|
|
||||||
font.family: Theme.fontFamily
|
|
||||||
font.pixelSize: 27
|
|
||||||
font.weight: Font.DemiBold
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
width: parent.width
|
||||||
text: "Your Panama desktop is configured and ready."
|
columns: width >= 620 ? 2 : 1
|
||||||
color: Theme.fgDim
|
columnSpacing: 28
|
||||||
font.family: Theme.fontFamily
|
rowSpacing: 16
|
||||||
font.pixelSize: Theme.fontSize
|
|
||||||
bottomPadding: 6
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsCard {
|
Item {
|
||||||
title: SystemSettings.monitorDescription || "Active display"
|
width: monitorLayout.columns === 2
|
||||||
subtitle: SystemSettings.monitorName || "Detecting your display…"
|
? (monitorLayout.width - monitorLayout.columnSpacing) * 0.47
|
||||||
|
: monitorLayout.width
|
||||||
|
height: 164
|
||||||
|
|
||||||
Row {
|
Rectangle {
|
||||||
width: parent.width
|
width: Math.min(parent.width - 24, 260)
|
||||||
height: 164
|
height: width * 0.64
|
||||||
spacing: 28
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 5
|
||||||
|
radius: 12
|
||||||
|
color: Theme.alpha(Theme.bgDark, 0.94)
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.alpha(Theme.accent, 0.34)
|
||||||
|
|
||||||
Item {
|
Rectangle {
|
||||||
width: parent.width * 0.47
|
anchors.fill: parent
|
||||||
height: parent.height
|
anchors.margins: 10
|
||||||
|
radius: 7
|
||||||
Rectangle {
|
gradient: Gradient {
|
||||||
width: Math.min(parent.width - 24, 260)
|
orientation: Gradient.Horizontal
|
||||||
height: width * 0.64
|
GradientStop { position: 0; color: Theme.mix(Theme.bg, Theme.accent, 0.08) }
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
GradientStop { position: 1; color: Theme.mix(Theme.bg, Theme.accentSecondary, 0.08) }
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: 5
|
|
||||||
radius: 12
|
|
||||||
color: Theme.alpha(Theme.bgDark, 0.94)
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.alpha(Theme.accent, 0.34)
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 10
|
|
||||||
radius: 7
|
|
||||||
gradient: Gradient {
|
|
||||||
orientation: Gradient.Horizontal
|
|
||||||
GradientStop { position: 0; color: Theme.mix(Theme.bg, Theme.accent, 0.08) }
|
|
||||||
GradientStop { position: 1; color: Theme.mix(Theme.bg, Theme.accentSecondary, 0.08) }
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: SystemSettings.monitorName || "DISPLAY"
|
|
||||||
color: Theme.fgMuted
|
|
||||||
font.family: Theme.fontFamily
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.letterSpacing: 1.5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width * 0.47
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 13
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: `${SystemSettings.monitorWidth} × ${SystemSettings.monitorHeight}`
|
anchors.centerIn: parent
|
||||||
color: Theme.fg
|
text: SystemSettings.monitorName || "DISPLAY"
|
||||||
|
color: Theme.fgMuted
|
||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
font.features: Theme.tabularFigures
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
font.letterSpacing: 1.5
|
||||||
font.weight: Font.DemiBold
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
text: `${SystemSettings.monitorRefreshRate.toFixed(0)} Hz · ${SystemSettings.monitorScale.toFixed(1)}× scale`
|
|
||||||
color: Theme.fgDim
|
|
||||||
font.family: Theme.fontFamily
|
|
||||||
font.features: Theme.tabularFigures
|
|
||||||
font.pixelSize: Theme.fontSize
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
text: `${SystemSettings.monitorFormat || "Detecting format"} · ${SystemSettings.colorPreset || "standard color"}`
|
|
||||||
color: Theme.fgDim
|
|
||||||
font.family: Theme.fontFamily
|
|
||||||
font.pixelSize: Theme.fontSize
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
text: SystemSettings.autoHdr ? "Game-aware HDR is ready" : "Game-aware HDR is off"
|
|
||||||
color: SystemSettings.autoHdr ? Theme.ok : Theme.warn
|
|
||||||
font.family: Theme.fontFamily
|
|
||||||
font.pixelSize: Theme.fontSize
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Column {
|
||||||
width: parent.width
|
width: monitorLayout.columns === 2
|
||||||
spacing: 16
|
? (monitorLayout.width - monitorLayout.columnSpacing) * 0.47
|
||||||
|
: monitorLayout.width
|
||||||
|
height: monitorLayout.columns === 2 ? 164 : implicitHeight
|
||||||
|
spacing: 13
|
||||||
|
|
||||||
SettingsCard {
|
Text {
|
||||||
width: (parent.width - parent.spacing) / 2
|
text: `${SystemSettings.monitorWidth} × ${SystemSettings.monitorHeight}`
|
||||||
title: "Quiet focus"
|
color: Theme.fg
|
||||||
subtitle: "Notifications and focused work"
|
font.family: Theme.fontFamily
|
||||||
|
font.features: Theme.tabularFigures
|
||||||
SettingRow {
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
label: "Do Not Disturb"
|
font.weight: Font.DemiBold
|
||||||
detail: Notifs.doNotDisturb ? "Banners are currently quiet" : "Notification banners are visible"
|
|
||||||
divider: false
|
|
||||||
controlWidth: 48
|
|
||||||
SettingsToggle {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
checked: Notifs.doNotDisturb
|
|
||||||
onToggled: value => Notifs.doNotDisturb = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Text {
|
||||||
SettingsCard {
|
text: `${SystemSettings.monitorRefreshRate.toFixed(0)} Hz · ${SystemSettings.monitorScale.toFixed(1)}× scale`
|
||||||
width: (parent.width - parent.spacing) / 2
|
color: Theme.fgDim
|
||||||
title: "Desktop services"
|
font.family: Theme.fontFamily
|
||||||
subtitle: "The essentials are running"
|
font.features: Theme.tabularFigures
|
||||||
|
font.pixelSize: Theme.fontSize
|
||||||
SettingRow {
|
}
|
||||||
label: "Sync & remote access"
|
Text {
|
||||||
detail: `${SystemSettings.nextcloudActive ? "Nextcloud ready" : "Nextcloud stopped"} · ${SystemSettings.rustdeskActive ? "RustDesk ready" : "RustDesk stopped"}`
|
text: `${SystemSettings.monitorFormat || "Detecting format"} · ${SystemSettings.colorPreset || "standard color"}`
|
||||||
divider: false
|
color: Theme.fgDim
|
||||||
value: SystemSettings.nextcloudActive && SystemSettings.rustdeskActive ? "Healthy" : "Review"
|
font.family: Theme.fontFamily
|
||||||
}
|
font.pixelSize: Theme.fontSize
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: SystemSettings.autoHdr ? "Game-aware HDR is ready" : "Game-aware HDR is off"
|
||||||
|
color: SystemSettings.autoHdr ? Theme.ok : Theme.warn
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
title: "Weather"
|
||||||
|
subtitle: "Local conditions in the date menu"
|
||||||
|
ChoiceRow { setting: "temperatureUnit" }
|
||||||
|
SliderRow { setting: "weatherRefreshMinutes"; divider: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
title: "System vitals"
|
||||||
|
subtitle: "Processor, memory, and graphics activity in the bar"
|
||||||
|
SliderRow { setting: "vitalsIntervalMs"; divider: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
Grid {
|
||||||
|
id: summaryCards
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
columns: width >= 720 ? 2 : 1
|
||||||
|
columnSpacing: 16
|
||||||
|
rowSpacing: 16
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
width: summaryCards.columns === 2
|
||||||
|
? (summaryCards.width - summaryCards.columnSpacing) / 2
|
||||||
|
: summaryCards.width
|
||||||
|
title: "Quiet focus"
|
||||||
|
subtitle: "Notifications and focused work"
|
||||||
|
|
||||||
|
SettingRow {
|
||||||
|
label: "Do Not Disturb"
|
||||||
|
detail: Notifs.doNotDisturb ? "Banners are currently quiet" : "Notification banners are visible"
|
||||||
|
divider: false
|
||||||
|
controlWidth: 48
|
||||||
|
SettingsToggle {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
checked: Notifs.doNotDisturb
|
||||||
|
onToggled: value => Notifs.doNotDisturb = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
width: summaryCards.columns === 2
|
||||||
|
? (summaryCards.width - summaryCards.columnSpacing) / 2
|
||||||
|
: summaryCards.width
|
||||||
|
title: "Desktop services"
|
||||||
|
subtitle: "The essentials are running"
|
||||||
|
|
||||||
|
TextRow {
|
||||||
|
label: "Sync & remote access"
|
||||||
|
detail: `${SystemSettings.nextcloudActive ? "Nextcloud ready" : "Nextcloud stopped"} · ${SystemSettings.rustdeskActive ? "RustDesk ready" : "RustDesk stopped"}`
|
||||||
|
divider: false
|
||||||
|
value: SystemSettings.nextcloudActive && SystemSettings.rustdeskActive ? "Healthy" : "Review"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,78 +2,97 @@ import QtQuick
|
|||||||
import qs.config
|
import qs.config
|
||||||
import qs.services
|
import qs.services
|
||||||
|
|
||||||
Item {
|
SettingsPage {
|
||||||
Flickable {
|
title: "Notifications & Focus"
|
||||||
anchors.fill: parent
|
lede: "Control interruptions without losing useful history."
|
||||||
clip: true
|
|
||||||
contentWidth: width
|
|
||||||
contentHeight: content.implicitHeight + 64
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
Column {
|
SettingsCard {
|
||||||
id: content
|
title: "Notifications"
|
||||||
width: parent.width - 68
|
|
||||||
x: 34
|
|
||||||
y: 30
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
Text { text: "Notifications & Focus"; color: Theme.fg; font.family: Theme.fontFamily; font.pixelSize: 27; font.weight: Font.DemiBold }
|
SettingRow {
|
||||||
Text { text: "Control interruptions without losing useful history."; color: Theme.fgDim; font.family: Theme.fontFamily; font.pixelSize: Theme.fontSize; bottomPadding: 6 }
|
label: "Do Not Disturb"
|
||||||
|
detail: "Keep notifications in the center but suppress banners"
|
||||||
|
controlWidth: 48
|
||||||
|
|
||||||
SettingsCard {
|
SettingsToggle {
|
||||||
title: "Notifications"
|
anchors.right: parent.right
|
||||||
SettingRow {
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
label: "Do Not Disturb"
|
checked: Notifs.doNotDisturb
|
||||||
detail: "Keep notifications in the center but suppress banners"
|
onToggled: value => Notifs.doNotDisturb = value
|
||||||
controlWidth: 48
|
|
||||||
SettingsToggle { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; checked: Notifs.doNotDisturb; onToggled: value => Notifs.doNotDisturb = value }
|
|
||||||
}
|
|
||||||
SettingRow { label: "Notification history"; detail: "Live notifications retained by Panama"; value: `${Notifs.history.length} items` }
|
|
||||||
SettingRow {
|
|
||||||
label: "Clear notification history"
|
|
||||||
detail: "Dismiss every item currently in the notification center"
|
|
||||||
divider: false
|
|
||||||
controlWidth: 94
|
|
||||||
SettingsButton { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; text: "Clear all"; enabled: Notifs.history.length > 0; onClicked: Notifs.dismissAll() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextRow {
|
||||||
|
label: "Notification history"
|
||||||
|
detail: "Live notifications retained by Panama"
|
||||||
|
value: `${Notifs.history.length} items`
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionRow {
|
||||||
|
label: "Clear notification history"
|
||||||
|
detail: "Dismiss every item currently in the notification center"
|
||||||
|
divider: false
|
||||||
|
action: "Clear all"
|
||||||
|
enabled: Notifs.history.length > 0
|
||||||
|
onTriggered: Notifs.dismissAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
title: "Banner behavior"
|
||||||
|
|
||||||
|
SliderRow { setting: "notificationTimeoutMs" }
|
||||||
|
SliderRow {
|
||||||
|
setting: "notificationTimeoutCriticalMs"
|
||||||
|
zeroLabel: "Never"
|
||||||
|
}
|
||||||
|
SliderRow { setting: "notificationHistoryLimit" }
|
||||||
|
SliderRow { setting: "maxVisibleToasts"; divider: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
title: "Focus sessions"
|
||||||
|
subtitle: "A focus session binds quiet mode and Caffeine to the current workspace."
|
||||||
|
|
||||||
|
SettingRow {
|
||||||
|
id: durationRow
|
||||||
|
|
||||||
|
label: "Default duration"
|
||||||
|
detail: "Used by Super+Shift+F and Quick Settings"
|
||||||
|
controlWidth: 264
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 6
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: [25, 45, 60, 90]
|
||||||
|
|
||||||
SettingsCard {
|
|
||||||
title: "Focus sessions"
|
|
||||||
subtitle: "A focus session binds quiet mode and Caffeine to the current workspace."
|
|
||||||
SettingRow {
|
|
||||||
label: "Default duration"
|
|
||||||
detail: "Used by Super+Shift+F and Quick Settings"
|
|
||||||
controlWidth: 264
|
|
||||||
Row {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 6
|
|
||||||
Repeater {
|
|
||||||
model: [25, 45, 60, 90]
|
|
||||||
SettingsButton {
|
|
||||||
required property int modelData
|
|
||||||
text: `${modelData}m`
|
|
||||||
tone: DesktopPreferences.get("focusDurationMinutes") === modelData ? "accent" : "normal"
|
|
||||||
onClicked: DesktopPreferences.set("focusDurationMinutes", modelData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SettingRow {
|
|
||||||
label: FocusSession.active ? `Active on ${FocusSession.workspaceLabel}` : "No active focus session"
|
|
||||||
detail: FocusSession.active ? `${FocusSession.remainingText} remaining` : "Start one without leaving Settings"
|
|
||||||
divider: false
|
|
||||||
controlWidth: 120
|
|
||||||
SettingsButton {
|
SettingsButton {
|
||||||
anchors.right: parent.right
|
required property int modelData
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: FocusSession.active ? "Show controls" : "Start focus"
|
text: `${modelData}m`
|
||||||
tone: FocusSession.active ? "normal" : "accent"
|
tone: DesktopPreferences.get("focusDurationMinutes") === modelData ? "accent" : "normal"
|
||||||
onClicked: FocusSession.reveal()
|
onClicked: SystemSettings.commitPreference("focusDurationMinutes", modelData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingRow {
|
||||||
|
label: FocusSession.active ? `Active on ${FocusSession.workspaceLabel}` : "No active focus session"
|
||||||
|
detail: FocusSession.active ? `${FocusSession.remainingText} remaining` : "Start one without leaving Settings"
|
||||||
|
divider: false
|
||||||
|
controlWidth: 120
|
||||||
|
|
||||||
|
SettingsButton {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: FocusSession.active ? "Show controls" : "Start focus"
|
||||||
|
tone: FocusSession.active ? "normal" : "accent"
|
||||||
|
onClicked: FocusSession.reveal()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import QtQuick
|
|||||||
import qs.config
|
import qs.config
|
||||||
import qs.services
|
import qs.services
|
||||||
|
|
||||||
Item {
|
SettingsPage {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
title: "Screen Intelligence"
|
||||||
|
lede: "Turn text and codes on screen into content you can use."
|
||||||
|
|
||||||
Component.onCompleted: ScreenIntelligence.refresh()
|
Component.onCompleted: ScreenIntelligence.refresh()
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@@ -13,98 +16,78 @@ Item {
|
|||||||
onTriggered: Capture.openIntelligence()
|
onTriggered: Capture.openIntelligence()
|
||||||
}
|
}
|
||||||
|
|
||||||
Flickable {
|
SettingsCard {
|
||||||
anchors.fill: parent
|
title: "Read anything on screen"
|
||||||
clip: true
|
subtitle: "Select a region, window, or display. Panama recognizes it locally and gives you clean follow-up actions."
|
||||||
contentWidth: width
|
|
||||||
contentHeight: content.implicitHeight + 64
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
Column {
|
SettingRow {
|
||||||
id: content
|
icon: ""
|
||||||
width: parent.width - 68
|
label: "Read screen text"
|
||||||
x: 34
|
detail: "Copy, search, translate, or open detected links"
|
||||||
y: 30
|
controlWidth: 160
|
||||||
spacing: 16
|
divider: false
|
||||||
|
|
||||||
Text {
|
SettingsButton {
|
||||||
text: "Screen Intelligence"
|
anchors.right: parent.right
|
||||||
color: Theme.fg
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
font.family: Theme.fontFamily
|
text: "Start reading"
|
||||||
font.pixelSize: 27
|
tone: "accent"
|
||||||
font.weight: Font.DemiBold
|
enabled: ScreenIntelligence.ocrReady && ScreenIntelligence.englishReady
|
||||||
}
|
onClicked: {
|
||||||
Text {
|
ShellState.closeSettings();
|
||||||
text: "Turn text and codes on screen into content you can use."
|
launchDelay.restart();
|
||||||
color: Theme.fgDim
|
|
||||||
font.family: Theme.fontFamily
|
|
||||||
font.pixelSize: Theme.fontSize
|
|
||||||
bottomPadding: 6
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsCard {
|
|
||||||
title: "Read anything on screen"
|
|
||||||
subtitle: "Select a region, window, or display. Panama recognizes it locally and gives you clean follow-up actions."
|
|
||||||
|
|
||||||
SettingRow {
|
|
||||||
icon: ""
|
|
||||||
label: "Read screen text"
|
|
||||||
detail: "Copy, search, translate, or open detected links"
|
|
||||||
controlWidth: 160
|
|
||||||
divider: false
|
|
||||||
SettingsButton {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: "Start reading"
|
|
||||||
tone: "accent"
|
|
||||||
enabled: ScreenIntelligence.ocrReady && ScreenIntelligence.englishReady
|
|
||||||
onClicked: {
|
|
||||||
ShellState.closeSettings();
|
|
||||||
launchDelay.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsCard {
|
|
||||||
title: "Local recognition"
|
|
||||||
subtitle: "The screen image stays in Panama's cache and is deleted when you dismiss the result."
|
|
||||||
|
|
||||||
SettingRow {
|
|
||||||
label: "Text recognition"
|
|
||||||
detail: "Tesseract with the English language model"
|
|
||||||
value: ScreenIntelligence.ocrReady && ScreenIntelligence.englishReady ? "Ready" : "Needs install"
|
|
||||||
}
|
|
||||||
SettingRow {
|
|
||||||
label: "QR & barcodes"
|
|
||||||
detail: "ZBar recognizes codes alongside ordinary text"
|
|
||||||
value: ScreenIntelligence.codeReady ? "Ready" : "Needs install"
|
|
||||||
}
|
|
||||||
SettingRow {
|
|
||||||
label: "Privacy"
|
|
||||||
detail: "Only Search, Translate, and Open send the selected result to another application or service"
|
|
||||||
value: "Local first"
|
|
||||||
divider: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsCard {
|
|
||||||
title: "Shortcut"
|
|
||||||
SettingRow {
|
|
||||||
label: "Read a screen selection"
|
|
||||||
detail: "Also available as Read in the Print-screen picker"
|
|
||||||
value: "Super + Shift + S"
|
|
||||||
controlWidth: 190
|
|
||||||
divider: ScreenIntelligence.ocrReady && ScreenIntelligence.codeReady && ScreenIntelligence.englishReady
|
|
||||||
}
|
|
||||||
SettingRow {
|
|
||||||
visible: !ScreenIntelligence.ocrReady || !ScreenIntelligence.codeReady || !ScreenIntelligence.englishReady
|
|
||||||
label: "Install recognition engines"
|
|
||||||
detail: "sudo dnf install -y tesseract zbar"
|
|
||||||
value: "Required once"
|
|
||||||
divider: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
title: "Capture preferences"
|
||||||
|
subtitle: "Choose where captures go and how recordings are encoded."
|
||||||
|
|
||||||
|
ChoiceRow { setting: "screenshotDir" }
|
||||||
|
ChoiceRow { setting: "recordingDir" }
|
||||||
|
ChoiceRow { setting: "recorderArgs"; divider: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
title: "Local recognition"
|
||||||
|
subtitle: "The screen image stays in Panama's cache and is deleted when you dismiss the result."
|
||||||
|
|
||||||
|
TextRow {
|
||||||
|
label: "Text recognition"
|
||||||
|
detail: "Tesseract with the English language model"
|
||||||
|
value: ScreenIntelligence.ocrReady && ScreenIntelligence.englishReady ? "Ready" : "Needs install"
|
||||||
|
}
|
||||||
|
TextRow {
|
||||||
|
label: "QR & barcodes"
|
||||||
|
detail: "ZBar recognizes codes alongside ordinary text"
|
||||||
|
value: ScreenIntelligence.codeReady ? "Ready" : "Needs install"
|
||||||
|
}
|
||||||
|
TextRow {
|
||||||
|
label: "Privacy"
|
||||||
|
detail: "Only Search, Translate, and Open send the selected result to another application or service"
|
||||||
|
value: "Local first"
|
||||||
|
divider: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
title: "Shortcut"
|
||||||
|
|
||||||
|
TextRow {
|
||||||
|
label: "Read a screen selection"
|
||||||
|
detail: "Also available as Read in the Print-screen picker"
|
||||||
|
value: "Super + Shift + S"
|
||||||
|
controlWidth: 190
|
||||||
|
divider: ScreenIntelligence.ocrReady && ScreenIntelligence.codeReady && ScreenIntelligence.englishReady
|
||||||
|
}
|
||||||
|
TextRow {
|
||||||
|
visible: !ScreenIntelligence.ocrReady || !ScreenIntelligence.codeReady || !ScreenIntelligence.englishReady
|
||||||
|
label: "Install recognition engines"
|
||||||
|
detail: "sudo dnf install -y tesseract zbar"
|
||||||
|
value: "Required once"
|
||||||
|
divider: false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,89 +2,132 @@ import QtQuick
|
|||||||
import qs.config
|
import qs.config
|
||||||
import qs.services
|
import qs.services
|
||||||
|
|
||||||
Item {
|
SettingsPage {
|
||||||
function status(active: bool): string { return active ? "Running" : "Stopped"; }
|
title: "Startup & Services"
|
||||||
|
lede: "A clear view of the background tools that make the desktop feel complete."
|
||||||
|
|
||||||
Flickable {
|
function status(active: bool): string {
|
||||||
anchors.fill: parent
|
return active ? "Running" : "Stopped";
|
||||||
clip: true
|
}
|
||||||
contentWidth: width
|
|
||||||
contentHeight: content.implicitHeight + 64
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
Column {
|
Item {
|
||||||
id: content
|
width: parent.width
|
||||||
width: parent.width - 68
|
implicitHeight: refresh.implicitHeight
|
||||||
x: 34
|
|
||||||
y: 30
|
SettingsButton {
|
||||||
spacing: 16
|
id: refresh
|
||||||
|
anchors.right: parent.right
|
||||||
|
text: SystemSettings.busy ? "Refreshing…" : "Refresh"
|
||||||
|
enabled: !SystemSettings.busy
|
||||||
|
onClicked: SystemSettings.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
title: "Your services"
|
||||||
|
|
||||||
|
SettingRow {
|
||||||
|
label: "Nextcloud"
|
||||||
|
detail: "File synchronization and tray status"
|
||||||
|
controlWidth: 190
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
width: parent.width
|
anchors.right: parent.right
|
||||||
Text { width: parent.width - refresh.width; text: "Startup & Services"; color: Theme.fg; font.family: Theme.fontFamily; font.pixelSize: 27; font.weight: Font.DemiBold }
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
SettingsButton { id: refresh; text: SystemSettings.busy ? "Refreshing…" : "Refresh"; enabled: !SystemSettings.busy; onClicked: SystemSettings.refresh() }
|
spacing: 10
|
||||||
}
|
Text {
|
||||||
Text { text: "A clear view of the background tools that make the desktop feel complete."; color: Theme.fgDim; font.family: Theme.fontFamily; font.pixelSize: Theme.fontSize; bottomPadding: 6 }
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: status(SystemSettings.nextcloudActive)
|
||||||
SettingsCard {
|
color: Theme.fgDim
|
||||||
title: "Your services"
|
font.family: Theme.fontFamily
|
||||||
SettingRow {
|
font.pixelSize: Theme.fontSize
|
||||||
label: "Nextcloud"
|
|
||||||
detail: "File synchronization and tray status"
|
|
||||||
controlWidth: 190
|
|
||||||
Row {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 10
|
|
||||||
Text { anchors.verticalCenter: parent.verticalCenter; text: status(SystemSettings.nextcloudActive); color: Theme.fgDim; font.family: Theme.fontFamily; font.pixelSize: Theme.fontSize }
|
|
||||||
SettingsButton { text: "Open"; onClicked: SystemSettings.openApplication("nextcloud") }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SettingRow {
|
SettingsButton {
|
||||||
label: "RustDesk"
|
text: "Open"
|
||||||
detail: "Remote access through the enabled system service"
|
onClicked: SystemSettings.openApplication("nextcloud")
|
||||||
controlWidth: 190
|
|
||||||
Row {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 10
|
|
||||||
Text { anchors.verticalCenter: parent.verticalCenter; text: status(SystemSettings.rustdeskActive); color: Theme.fgDim; font.family: Theme.fontFamily; font.pixelSize: Theme.fontSize }
|
|
||||||
SettingsButton { text: "Open"; onClicked: SystemSettings.openApplication("rustdesk") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SettingRow {
|
|
||||||
label: "KDE Connect"
|
|
||||||
detail: "Phone pairing, clipboard, files, and remote controls"
|
|
||||||
divider: false
|
|
||||||
controlWidth: 190
|
|
||||||
Row {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 10
|
|
||||||
Text { anchors.verticalCenter: parent.verticalCenter; text: status(SystemSettings.kdeconnectActive); color: Theme.fgDim; font.family: Theme.fontFamily; font.pixelSize: Theme.fontSize }
|
|
||||||
SettingsButton { text: "Open"; onClicked: SystemSettings.openApplication("kdeconnect") }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingRow {
|
||||||
title: "Desktop foundation"
|
label: "RustDesk"
|
||||||
SettingRow { label: "Hyprpaper"; detail: "Wallpaper service"; value: status(SystemSettings.hyprpaperActive) }
|
detail: "Remote access through the enabled system service"
|
||||||
SettingRow { label: "Hypridle"; detail: "Idle and lock policy"; value: status(SystemSettings.hypridleActive) }
|
controlWidth: 190
|
||||||
SettingRow { label: "Vicinae"; detail: "Spotlight-style launcher daemon"; value: status(SystemSettings.vicinaeActive); divider: false }
|
|
||||||
|
Row {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 10
|
||||||
|
Text {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: status(SystemSettings.rustdeskActive)
|
||||||
|
color: Theme.fgDim
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSize
|
||||||
|
}
|
||||||
|
SettingsButton {
|
||||||
|
text: "Open"
|
||||||
|
onClicked: SystemSettings.openApplication("rustdesk")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingRow {
|
||||||
title: "Fedora system settings"
|
label: "KDE Connect"
|
||||||
subtitle: "These remain owned by trusted system services and GNOME's mature panels."
|
detail: "Phone pairing, clipboard, files, and remote controls"
|
||||||
SettingRow {
|
divider: false
|
||||||
label: "Network, Bluetooth, printers, users, and accounts"
|
controlWidth: 190
|
||||||
detail: "GNOME Settings remains searchable from the launcher too"
|
|
||||||
divider: false
|
Row {
|
||||||
controlWidth: 122
|
anchors.right: parent.right
|
||||||
SettingsButton { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; text: "Open network"; onClicked: SystemSettings.openGnomePanel("network") }
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 10
|
||||||
|
Text {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: status(SystemSettings.kdeconnectActive)
|
||||||
|
color: Theme.fgDim
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSize
|
||||||
|
}
|
||||||
|
SettingsButton {
|
||||||
|
text: "Open"
|
||||||
|
onClicked: SystemSettings.openApplication("kdeconnect")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
title: "Desktop foundation"
|
||||||
|
|
||||||
|
TextRow {
|
||||||
|
label: "Hyprpaper"
|
||||||
|
detail: "Wallpaper service"
|
||||||
|
value: status(SystemSettings.hyprpaperActive)
|
||||||
|
}
|
||||||
|
TextRow {
|
||||||
|
label: "Hypridle"
|
||||||
|
detail: "Idle and lock policy"
|
||||||
|
value: status(SystemSettings.hypridleActive)
|
||||||
|
}
|
||||||
|
TextRow {
|
||||||
|
label: "Vicinae"
|
||||||
|
detail: "Spotlight-style launcher daemon"
|
||||||
|
value: status(SystemSettings.vicinaeActive)
|
||||||
|
divider: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
title: "Fedora system settings"
|
||||||
|
subtitle: "These remain owned by trusted system services and GNOME's mature panels."
|
||||||
|
|
||||||
|
ActionRow {
|
||||||
|
label: "Network, Bluetooth, printers, users, and accounts"
|
||||||
|
detail: "GNOME Settings remains searchable from the launcher too"
|
||||||
|
divider: false
|
||||||
|
action: "Open network"
|
||||||
|
onTriggered: SystemSettings.openGnomePanel("network")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,50 +4,61 @@ import qs.config
|
|||||||
import qs.services
|
import qs.services
|
||||||
import qs.modules.quicksettings
|
import qs.modules.quicksettings
|
||||||
|
|
||||||
Item {
|
SettingsPage {
|
||||||
Flickable {
|
title: "Sound"
|
||||||
anchors.fill: parent
|
lede: "Live PipeWire output, input, and device selection."
|
||||||
clip: true
|
|
||||||
contentWidth: width
|
|
||||||
contentHeight: content.implicitHeight + 64
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
Column {
|
SettingsCard {
|
||||||
id: content
|
title: "Output"
|
||||||
width: parent.width - 68
|
subtitle: Pipewire.defaultAudioSink?.description ?? "No output device"
|
||||||
x: 34
|
|
||||||
y: 30
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
Text { text: "Sound"; color: Theme.fg; font.family: Theme.fontFamily; font.pixelSize: 27; font.weight: Font.DemiBold }
|
AudioSlider {
|
||||||
Text { text: "Live PipeWire output, input, and device selection."; color: Theme.fgDim; font.family: Theme.fontFamily; font.pixelSize: Theme.fontSize; bottomPadding: 6 }
|
width: parent.width
|
||||||
|
node: Pipewire.defaultAudioSink
|
||||||
|
output: true
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.alpha(Theme.fg, 0.06)
|
||||||
|
}
|
||||||
|
AudioDeviceList {
|
||||||
|
width: parent.width
|
||||||
|
output: true
|
||||||
|
maxHeight: 190
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
title: "Output"
|
title: "Input"
|
||||||
subtitle: Pipewire.defaultAudioSink?.description ?? "No output device"
|
subtitle: Pipewire.defaultAudioSource?.description ?? "No input device"
|
||||||
AudioSlider { width: parent.width; node: Pipewire.defaultAudioSink; output: true }
|
|
||||||
Rectangle { width: parent.width; height: 1; color: Theme.alpha(Theme.fg, 0.06) }
|
|
||||||
AudioDeviceList { width: parent.width; output: true; maxHeight: 190 }
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsCard {
|
AudioSlider {
|
||||||
title: "Input"
|
width: parent.width
|
||||||
subtitle: Pipewire.defaultAudioSource?.description ?? "No input device"
|
node: Pipewire.defaultAudioSource
|
||||||
AudioSlider { width: parent.width; node: Pipewire.defaultAudioSource; output: false }
|
output: false
|
||||||
Rectangle { width: parent.width; height: 1; color: Theme.alpha(Theme.fg, 0.06) }
|
}
|
||||||
AudioDeviceList { width: parent.width; output: false; maxHeight: 160 }
|
Rectangle {
|
||||||
}
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.alpha(Theme.fg, 0.06)
|
||||||
|
}
|
||||||
|
AudioDeviceList {
|
||||||
|
width: parent.width
|
||||||
|
output: false
|
||||||
|
maxHeight: 160
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
title: "Advanced sound"
|
title: "Advanced sound"
|
||||||
SettingRow {
|
|
||||||
label: "Application volumes and profiles"
|
ActionRow {
|
||||||
detail: "Open Fedora's complete sound panel"
|
label: "Application volumes and profiles"
|
||||||
divider: false
|
detail: "Open Fedora's complete sound panel"
|
||||||
controlWidth: 104
|
divider: false
|
||||||
SettingsButton { anchors.right: parent.right; anchors.verticalCenter: parent.verticalCenter; text: "Open panel"; onClicked: SystemSettings.openGnomePanel("sound") }
|
action: "Open panel"
|
||||||
}
|
onTriggered: SystemSettings.openGnomePanel("sound")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Public Settings.qml values are the compatibility surface consumed throughout
|
||||||
|
# the shell. Once a value becomes user-configurable, this file must read it from
|
||||||
|
# DesktopPreferences rather than keeping a second hardcoded source of truth.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
settings="$repo_dir/config/dot/quickshell/config/Settings.qml"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'settings hardcoded values contract: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
properties=(
|
||||||
|
temperatureUnit
|
||||||
|
weatherRefreshMinutes
|
||||||
|
vitalsIntervalMs
|
||||||
|
notificationTimeoutMs
|
||||||
|
notificationTimeoutCriticalMs
|
||||||
|
notificationHistoryLimit
|
||||||
|
maxVisibleToasts
|
||||||
|
screenshotDir
|
||||||
|
recordingDir
|
||||||
|
recorderArgs
|
||||||
|
)
|
||||||
|
|
||||||
|
for property in "${properties[@]}"; do
|
||||||
|
count="$(rg -c \
|
||||||
|
"^[[:space:]]*readonly property [A-Za-z]+ ${property}: DesktopPreferences\\.get\\(\"${property}\"\\)[[:space:]]*(//.*)?$" \
|
||||||
|
"$settings" || true)"
|
||||||
|
[[ "$count" == "1" ]] \
|
||||||
|
|| fail "$property must use DesktopPreferences.get(\"$property\") exactly once"
|
||||||
|
done
|
||||||
|
|
||||||
|
# dockPinned was already migrated on the shared branch. Pinning it here keeps a
|
||||||
|
# later bulk edit from accidentally restoring the old hardcoded app list.
|
||||||
|
rg -q '^[[:space:]]*readonly property var dockPinned: DesktopPreferences\.get\("dockPinned"\)[[:space:]]*$' "$settings" \
|
||||||
|
|| fail 'dockPinned no longer reads DesktopPreferences exactly'
|
||||||
|
|
||||||
|
printf 'settings hardcoded values contract: PASS\n'
|
||||||
@@ -3,6 +3,85 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
printf 'settings pages contract: %s\n' "$1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pages=(Home Displays Connectivity Sound Notifications ScreenIntelligence Services About)
|
||||||
|
for page in "${pages[@]}"; do
|
||||||
|
page_file="$repo_dir/config/dot/quickshell/modules/settings/${page}Page.qml"
|
||||||
|
[[ -f "$page_file" ]] || fail "${page}Page.qml is missing"
|
||||||
|
|
||||||
|
root_type="$(awk '
|
||||||
|
/^import / { next }
|
||||||
|
/^[[:space:]]*\/\// { next }
|
||||||
|
/^[[:space:]]*$/ { next }
|
||||||
|
match($0, /^[[:space:]]*([A-Za-z][A-Za-z0-9]*)[[:space:]]*\{/, found) {
|
||||||
|
print found[1]
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' "$page_file")"
|
||||||
|
[[ "$root_type" == "SettingsPage" ]] \
|
||||||
|
|| fail "${page}Page.qml root is ${root_type:-unknown}, expected SettingsPage"
|
||||||
|
! rg -q '^[[:space:]]*Flickable[[:space:]]*\{' "$page_file" \
|
||||||
|
|| fail "${page}Page.qml still copies the page Flickable scaffold"
|
||||||
|
done
|
||||||
|
|
||||||
|
require_row() {
|
||||||
|
local file="$1"
|
||||||
|
local row_type="$2"
|
||||||
|
local setting="$3"
|
||||||
|
|
||||||
|
python3 - "$file" "$row_type" "$setting" <<'PY' || \
|
||||||
|
fail "$(basename "$file") is missing $row_type for $setting"
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
text = open(sys.argv[1], encoding="utf-8").read()
|
||||||
|
row_type = re.escape(sys.argv[2])
|
||||||
|
setting = re.escape(sys.argv[3])
|
||||||
|
pattern = rf"{row_type}\s*\{{(?:(?!\n\s*[A-Z][A-Za-z0-9]*\s*\{{).)*?setting\s*:\s*\"{setting}\""
|
||||||
|
raise SystemExit(0 if re.search(pattern, text, re.S) else 1)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
home_page="$repo_dir/config/dot/quickshell/modules/settings/HomePage.qml"
|
||||||
|
require_row "$home_page" ChoiceRow temperatureUnit
|
||||||
|
require_row "$home_page" SliderRow weatherRefreshMinutes
|
||||||
|
require_row "$home_page" SliderRow vitalsIntervalMs
|
||||||
|
|
||||||
|
notifications_page="$repo_dir/config/dot/quickshell/modules/settings/NotificationsPage.qml"
|
||||||
|
for setting in notificationTimeoutMs notificationTimeoutCriticalMs notificationHistoryLimit maxVisibleToasts; do
|
||||||
|
require_row "$notifications_page" SliderRow "$setting"
|
||||||
|
done
|
||||||
|
python3 - "$notifications_page" <<'PY' || fail 'critical notification timeout does not render zero as Never'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
text = open(sys.argv[1], encoding="utf-8").read()
|
||||||
|
block = re.search(
|
||||||
|
r'SliderRow\s*\{(?:(?!\n\s*[A-Z][A-Za-z0-9]*\s*\{).)*?'
|
||||||
|
r'setting\s*:\s*"notificationTimeoutCriticalMs"(?P<tail>.*?)\n\s*\}',
|
||||||
|
text,
|
||||||
|
re.S,
|
||||||
|
)
|
||||||
|
raise SystemExit(0 if block and re.search(r'zeroLabel\s*:\s*"Never"', block.group(0)) else 1)
|
||||||
|
PY
|
||||||
|
|
||||||
|
intelligence_page="$repo_dir/config/dot/quickshell/modules/settings/ScreenIntelligencePage.qml"
|
||||||
|
require_row "$intelligence_page" ChoiceRow screenshotDir
|
||||||
|
require_row "$intelligence_page" ChoiceRow recordingDir
|
||||||
|
require_row "$intelligence_page" ChoiceRow recorderArgs
|
||||||
|
|
||||||
|
# The source-only contract is safe during a shared Quickshell quiet window.
|
||||||
|
# The existing compositor integration checks remain available explicitly.
|
||||||
|
if [[ "${PANAMA_SETTINGS_STATIC_ONLY:-0}" == "1" ]]; then
|
||||||
|
printf 'settings pages contract: PASS (static)\n'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
state_home="$(mktemp -d /tmp/panama-settings-pages-state.XXXXXX)"
|
state_home="$(mktemp -d /tmp/panama-settings-pages-state.XXXXXX)"
|
||||||
source_config_path="$repo_dir/config/dot/quickshell"
|
source_config_path="$repo_dir/config/dot/quickshell"
|
||||||
config_path="$state_home/quickshell"
|
config_path="$state_home/quickshell"
|
||||||
@@ -58,11 +137,6 @@ exit 97
|
|||||||
EOF
|
EOF
|
||||||
chmod +x "$test_bin/flatpak"
|
chmod +x "$test_bin/flatpak"
|
||||||
|
|
||||||
fail() {
|
|
||||||
printf 'settings pages contract: %s\n' "$1" >&2
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
qs_for_test() {
|
qs_for_test() {
|
||||||
PATH="$test_bin:$PATH" QS_CONFIG_PATH="$config_path" XDG_STATE_HOME="$state_home" \
|
PATH="$test_bin:$PATH" QS_CONFIG_PATH="$config_path" XDG_STATE_HOME="$state_home" \
|
||||||
qs -p "$config_path" "$@"
|
qs -p "$config_path" "$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user