Add Region & Language, and answer "what am I running on" in About
More GNOME Settings parity.
Region & Language is new. The locale is localectl's, and Panama stores no
copy of it -- there is exactly one system locale, so a preference here
would be a second source of truth that drifts the moment anything else
changes it. Codes are resolved against iso-codes into "Portuguese
(Brazil)" the way GNOME does, with the code kept visible because it is
what actually gets written and someone choosing between two Spanish
variants needs to see it. Changing it is privileged and only applies to
programs started afterwards, so the page says a sign-out is needed
rather than claiming the new language is in use.
The service is called SystemLocale, not Locale: QML has a built-in
Locale value type that silently shadows a singleton of that name, and
every binding then reads properties off the wrong thing. The page
rendered empty with nothing but "cannot read property of undefined" to
explain it.
About now answers what GNOME's About answers -- model, processor,
memory, disk, OS, kernel, windowing system -- where before it listed
only Panama's own component versions. Graphics is joined from
GraphicsDevices rather than read again, because two readouts of the same
hardware are two things that can disagree. Placeholder DMI strings
("To Be Filled By O.E.M.") are filtered out, and unreadable facts are
omitted rather than shown as "Unknown".
The Fedora hand-off card was one row listing five subjects that opened
the network panel regardless. Naming a panel and then not opening it
reads as a broken button rather than a deliberate hand-off. Each subject
now opens the panel that owns it, and openGnomePanel takes an optional
subpage so "Users" reaches System's users page the way GNOME's own
desktop entry does, instead of dropping the user on System's front page.
Printers and online accounts are not repeated here; they stay with the
network hardware on Network & Devices.
One bug this surfaced, caught by the hyprland write contract: the Lua
config key and the hyprctl option name genuinely differ for tap to
click. hl.config wants input.touchpad.tap_to_click; getoption answers to
input:touchpad:tap-to-click. Either spelling used for both fails -- a
hyphen is not a Lua identifier, and the underscored name is not a known
option -- which is what the schema's two separate fields are for.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
This commit is contained in:
@@ -4,9 +4,18 @@ import qs.config
|
||||
import qs.services
|
||||
|
||||
SettingsPage {
|
||||
id: root
|
||||
|
||||
title: "About Panama"
|
||||
lede: "A curated Hyprland desktop built around focus, speed, and good taste."
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!MachineInfo.scanned)
|
||||
MachineInfo.refresh();
|
||||
if (GraphicsDevices.devices.length === 0)
|
||||
GraphicsDevices.refresh();
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
title: "Panama Desktop"
|
||||
subtitle: "Tokyo Night Moon · Prism glass · native tiling"
|
||||
@@ -31,6 +40,52 @@ SettingsPage {
|
||||
}
|
||||
}
|
||||
|
||||
// What GNOME's About panel answers and this page did not: what am I running
|
||||
// on. The rows come from MachineInfo, except graphics, which is joined from
|
||||
// GraphicsDevices rather than read a second time -- two readouts of the same
|
||||
// hardware are two things that can disagree.
|
||||
SettingsCard {
|
||||
title: "This machine"
|
||||
subtitle: "Hardware and system, as the kernel reports it."
|
||||
|
||||
Repeater {
|
||||
model: MachineInfo.facts
|
||||
|
||||
TextRow {
|
||||
id: machineRow
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
label: machineRow.modelData.label
|
||||
value: machineRow.modelData.value
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: GraphicsDevices.devices
|
||||
|
||||
TextRow {
|
||||
id: gpuRow
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
label: GraphicsDevices.devices.length > 1
|
||||
? "Graphics " + (gpuRow.index + 1)
|
||||
: "Graphics"
|
||||
value: gpuRow.modelData.name
|
||||
divider: gpuRow.index < GraphicsDevices.devices.length - 1
|
||||
}
|
||||
}
|
||||
|
||||
TextRow {
|
||||
visible: MachineInfo.scanned && MachineInfo.facts.length === 0
|
||||
label: "Hardware"
|
||||
detail: "The system did not report anything readable"
|
||||
value: "Unavailable"
|
||||
divider: false
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
title: "Design principles"
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Region & Language.
|
||||
//
|
||||
// GNOME keeps language and formats under System; the setting itself is the
|
||||
// machine's locale, which localectl owns. Panama does not store a copy of it --
|
||||
// there is exactly one system locale and localectl is where it lives, so a
|
||||
// preference here would be a second source of truth that drifts the moment
|
||||
// anything else changes it.
|
||||
//
|
||||
// Keyboard layout is deliberately not repeated here even though GNOME groups it
|
||||
// with region. It is a compositor setting that applies instantly, it lives on
|
||||
// the Keyboard page with the rest of the typing settings, and showing it twice
|
||||
// invites the two views to disagree.
|
||||
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.services
|
||||
|
||||
SettingsPage {
|
||||
id: root
|
||||
|
||||
title: "Region & Language"
|
||||
lede: SystemLocale.pendingRestart
|
||||
? "Your new language applies to programs started after you sign out and back in."
|
||||
: "The language and regional formats this machine uses."
|
||||
|
||||
Component.onCompleted: if (SystemLocale.locales.length === 0) SystemLocale.refresh()
|
||||
|
||||
SettingsCard {
|
||||
title: "Language"
|
||||
subtitle: "Changing this needs your password, and takes effect for programs started afterwards."
|
||||
|
||||
TextRow {
|
||||
label: "Current language"
|
||||
detail: SystemLocale.pendingRestart
|
||||
? "Chosen, but not in use until you sign out and back in"
|
||||
: "Used by programs that ask the system what language to speak"
|
||||
value: SystemLocale.currentLabel || "Reading…"
|
||||
}
|
||||
|
||||
SearchPicker {
|
||||
width: parent.width
|
||||
items: SystemLocale.locales
|
||||
current: SystemLocale.current
|
||||
placeholder: "Search languages and regions"
|
||||
emptyText: SystemLocale.scanning ? "Reading installed locales…" : "No locales are installed"
|
||||
onPicked: value => SystemLocale.set(value)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
visible: SystemLocale.lastError !== ""
|
||||
title: "Language problem"
|
||||
subtitle: SystemLocale.lastError
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
title: "Formats"
|
||||
subtitle: "Dates, times, and numbers follow the language above. Panama's own clock formatting is on the Appearance page."
|
||||
|
||||
ActionRow {
|
||||
label: "Clock and date display"
|
||||
detail: "How Panama itself shows the time"
|
||||
action: "Open appearance"
|
||||
onTriggered: ShellState.openSettings("appearance")
|
||||
}
|
||||
|
||||
ActionRow {
|
||||
label: "Regional formats"
|
||||
detail: "Separate per-category formats (LC_TIME, LC_NUMERIC) are owned by Fedora"
|
||||
action: "Open system"
|
||||
divider: false
|
||||
onTriggered: SystemSettings.openGnomePanel("system", "region")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// A searchable list of choices, for settings with far too many values to put in
|
||||
// a dropdown and no useful preview to show.
|
||||
//
|
||||
// SearchPicker {
|
||||
// items: Locale.locales // [{ value, label, detail }]
|
||||
// current: Locale.current
|
||||
// placeholder: "Search languages"
|
||||
// onPicked: value => Locale.set(value)
|
||||
// }
|
||||
//
|
||||
// FontPicker is the same idea specialised: it draws each candidate in its own
|
||||
// family, which is the whole reason it is a list rather than a text field. This
|
||||
// one is for choices whose only useful preview is their name.
|
||||
|
||||
import QtQuick
|
||||
import qs.config
|
||||
// SearchField lives with the clipboard module, which is where it was first
|
||||
// needed; FontPicker imports it from the same place.
|
||||
import qs.modules.clipboard
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
// [{ value, label, detail }]
|
||||
property var items: []
|
||||
property string current: ""
|
||||
property string emptyText: "Nothing to choose from"
|
||||
property string placeholder: "Search"
|
||||
|
||||
signal picked(string value)
|
||||
|
||||
spacing: 0
|
||||
|
||||
// Capped and filtered rather than listing everything: 327 locales inside a
|
||||
// page that is already scrolling is worse than a search box.
|
||||
readonly property var matches: {
|
||||
const needle = filter.text.trim().toLowerCase();
|
||||
const list = root.items.filter(item =>
|
||||
needle === ""
|
||||
? true
|
||||
: (String(item.label).toLowerCase().indexOf(needle) >= 0
|
||||
|| String(item.detail).toLowerCase().indexOf(needle) >= 0));
|
||||
|
||||
// The current choice stays visible while browsing, so it is always
|
||||
// clear what would be replaced.
|
||||
const selected = list.find(item => item.value === root.current);
|
||||
if (needle === "" && selected !== undefined)
|
||||
return [selected].concat(list.filter(item => item.value !== root.current)).slice(0, 12);
|
||||
return list.slice(0, 12);
|
||||
}
|
||||
|
||||
SearchField {
|
||||
id: filter
|
||||
width: parent.width
|
||||
placeholder: root.placeholder
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.matches
|
||||
|
||||
SettingRow {
|
||||
id: candidate
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
readonly property bool selected: candidate.modelData.value === root.current
|
||||
|
||||
label: candidate.modelData.label
|
||||
detail: candidate.selected ? "Currently in use" : candidate.modelData.detail
|
||||
controlWidth: 120
|
||||
divider: candidate.index < root.matches.length - 1
|
||||
activatable: !candidate.selected
|
||||
onActivated: root.picked(candidate.modelData.value)
|
||||
|
||||
Text {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: candidate.selected
|
||||
text: "✓"
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize + 2
|
||||
color: Theme.accent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
width: parent.width
|
||||
visible: root.matches.length === 0
|
||||
label: root.emptyText
|
||||
divider: false
|
||||
}
|
||||
}
|
||||
@@ -120,14 +120,40 @@ SettingsPage {
|
||||
|
||||
SettingsCard {
|
||||
title: "Fedora system settings"
|
||||
subtitle: "These remain owned by trusted system services and GNOME's mature panels."
|
||||
subtitle: "Panels Panama does not own, because they configure system services rather than the desktop. Each row opens the panel that actually owns it. Printers and online accounts live with the rest of the network hardware, on Network & Devices."
|
||||
|
||||
// This was one row listing five subjects and opening the network panel
|
||||
// regardless. Naming a panel and then not opening it is worse than not
|
||||
// offering it: it looks like a broken button rather than a deliberate
|
||||
// hand-off, and someone looking for printers had to know to navigate
|
||||
// once GNOME Settings appeared on the wrong page.
|
||||
ActionRow {
|
||||
label: "Users"
|
||||
detail: "Accounts, passwords, and automatic login"
|
||||
action: "Open users"
|
||||
onTriggered: SystemSettings.openGnomePanel("system", "users")
|
||||
}
|
||||
|
||||
ActionRow {
|
||||
label: "Network, Bluetooth, printers, users, and accounts"
|
||||
detail: "GNOME Settings remains searchable from the launcher too"
|
||||
label: "Sharing"
|
||||
detail: "Remote desktop, media sharing, and remote login"
|
||||
action: "Open sharing"
|
||||
onTriggered: SystemSettings.openGnomePanel("sharing")
|
||||
}
|
||||
|
||||
ActionRow {
|
||||
label: "Colour profiles"
|
||||
detail: "ICC profiles for displays, printers, and scanners"
|
||||
action: "Open colour"
|
||||
onTriggered: SystemSettings.openGnomePanel("color")
|
||||
}
|
||||
|
||||
ActionRow {
|
||||
label: "Digital wellbeing"
|
||||
detail: "Screen time and break reminders"
|
||||
action: "Open wellbeing"
|
||||
divider: false
|
||||
action: "Open network"
|
||||
onTriggered: SystemSettings.openGnomePanel("network")
|
||||
onTriggered: SystemSettings.openGnomePanel("wellbeing")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ Rectangle {
|
||||
case "shortcuts": return shortcutsPage;
|
||||
case "mouse": return mousePage;
|
||||
case "privacy": return privacyPage;
|
||||
case "region": return regionPage;
|
||||
case "accessibility": return accessibilityPage;
|
||||
case "power": return powerPage;
|
||||
case "datetime": return dateTimePage;
|
||||
@@ -157,6 +158,7 @@ Rectangle {
|
||||
Component { id: shortcutsPage; ShortcutsPage {} }
|
||||
Component { id: mousePage; MousePage {} }
|
||||
Component { id: privacyPage; PrivacyPage {} }
|
||||
Component { id: regionPage; RegionPage {} }
|
||||
Component { id: servicesPage; ServicesPage {} }
|
||||
Component { id: aboutPage; AboutPage {} }
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ Rectangle {
|
||||
{ page: "shortcuts", label: "Keyboard", icon: "\u{F030C}" },
|
||||
{ page: "mouse", label: "Mouse & Touchpad", icon: "\u{F037D}" },
|
||||
{ page: "privacy", label: "Privacy & Security", icon: "\u{F0483}" },
|
||||
{ page: "region", label: "Region & Language", icon: "\u{F0AC2}" },
|
||||
{ page: "accessibility", label: "Accessibility", icon: "\u{F0208}" },
|
||||
{ page: "power", label: "Power & Lock", icon: "\u{F0425}" },
|
||||
{ page: "datetime", label: "Date & Time", icon: "\u{F0954}" },
|
||||
|
||||
@@ -49,3 +49,5 @@ FontPicker 1.0 FontPicker.qml
|
||||
MousePage 1.0 MousePage.qml
|
||||
TextEntryRow 1.0 TextEntryRow.qml
|
||||
PrivacyPage 1.0 PrivacyPage.qml
|
||||
RegionPage 1.0 RegionPage.qml
|
||||
SearchPicker 1.0 SearchPicker.qml
|
||||
|
||||
Reference in New Issue
Block a user