251 lines
8.9 KiB
QML
251 lines
8.9 KiB
QML
pragma Singleton
|
|
|
|
// The allow-listed machine boundary for Panama Settings. Visual pages call
|
|
// these methods; no UI text is ever interpolated into a shell command.
|
|
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import QtQuick
|
|
import qs.config
|
|
|
|
Singleton {
|
|
id: root
|
|
|
|
property string monitorName: ""
|
|
property string monitorDescription: ""
|
|
property int monitorWidth: 0
|
|
property int monitorHeight: 0
|
|
property real monitorRefreshRate: 0
|
|
property real monitorScale: 1
|
|
property string monitorFormat: ""
|
|
property string colorPreset: ""
|
|
property bool monitorVrrActive: false
|
|
|
|
property bool nextcloudActive: false
|
|
property bool rustdeskActive: false
|
|
property bool kdeconnectActive: false
|
|
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
|
|
|| 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
|
|
readonly property int directScanoutPolicy: DesktopPreferences.directScanoutPolicy
|
|
|
|
Process {
|
|
id: monitorQuery
|
|
command: ["hyprctl", "-j", "monitors"]
|
|
stdout: StdioCollector {
|
|
onStreamFinished: root.parseMonitors(this.text)
|
|
}
|
|
onExited: (exitCode, exitStatus) => {
|
|
if (exitCode !== 0)
|
|
root.lastError = "Could not read the active display.";
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: serviceQuery
|
|
command: [
|
|
"bash", "-lc",
|
|
"printf '{\"nextcloud\":%s,\"rustdesk\":%s,\"kdeconnect\":%s,\"hyprpaper\":%s,\"hypridle\":%s,\"vicinae\":%s}\\n' "
|
|
+ "$(pgrep -x nextcloud >/dev/null && printf true || printf false) "
|
|
+ "$(systemctl is-active --quiet rustdesk.service && printf true || printf false) "
|
|
+ "$(pgrep -x kdeconnectd >/dev/null && printf true || printf false) "
|
|
+ "$(systemctl --user is-active --quiet hyprpaper.service && printf true || printf false) "
|
|
+ "$(systemctl --user is-active --quiet hypridle.service && printf true || printf false) "
|
|
+ "$(systemctl --user is-active --quiet vicinae.service && printf true || printf false)"
|
|
]
|
|
stdout: StdioCollector {
|
|
onStreamFinished: root.parseServices(this.text)
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: versionQuery
|
|
command: ["Hyprland", "--version"]
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
const match = this.text.match(/Hyprland\s+([0-9.]+)/);
|
|
root.hyprlandVersion = match ? match[1] : this.text.trim().split("\n")[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: bluebubblesQuery
|
|
command: ["flatpak", "info", "app.bluebubbles.BlueBubbles"]
|
|
onExited: (exitCode, exitStatus) => root.bluebubblesDetected = exitCode === 0
|
|
}
|
|
|
|
Process {
|
|
id: autoHdrWrite
|
|
property bool requested: true
|
|
onExited: (exitCode, exitStatus) => {
|
|
if (exitCode === 0) {
|
|
DesktopPreferences.autoHdr = requested;
|
|
root.lastError = "";
|
|
} else {
|
|
root.lastError = "Hyprland rejected the HDR policy.";
|
|
}
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: vrrWrite
|
|
property int requested: 3
|
|
onExited: (exitCode, exitStatus) => {
|
|
if (exitCode === 0) {
|
|
DesktopPreferences.vrrPolicy = requested;
|
|
root.lastError = "";
|
|
} else {
|
|
root.lastError = "Hyprland rejected the VRR policy.";
|
|
}
|
|
}
|
|
}
|
|
|
|
Process {
|
|
id: directScanoutWrite
|
|
property int requested: 2
|
|
onExited: (exitCode, exitStatus) => {
|
|
if (exitCode === 0) {
|
|
DesktopPreferences.directScanoutPolicy = requested;
|
|
root.lastError = "";
|
|
} else {
|
|
root.lastError = "Hyprland rejected the direct-scanout policy.";
|
|
}
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
// Preferences load asynchronously from disk. Applying after one quiet
|
|
// second avoids racing their restore and runs only once per shell start.
|
|
interval: 1000
|
|
running: true
|
|
onTriggered: root.applyPersistedDisplayPolicy()
|
|
}
|
|
|
|
Component.onCompleted: root.refresh()
|
|
|
|
function refresh(): void {
|
|
root.lastError = "";
|
|
if (!monitorQuery.running)
|
|
monitorQuery.running = true;
|
|
if (!serviceQuery.running)
|
|
serviceQuery.running = true;
|
|
if (!versionQuery.running && !root.hyprlandVersion)
|
|
versionQuery.running = true;
|
|
if (!bluebubblesQuery.running)
|
|
bluebubblesQuery.running = true;
|
|
}
|
|
|
|
function parseMonitors(text: string): void {
|
|
try {
|
|
const monitors = JSON.parse(text);
|
|
const monitor = monitors.find(item => item.focused) ?? monitors[0];
|
|
if (!monitor)
|
|
throw new Error("No active monitor");
|
|
root.monitorName = monitor.name ?? "";
|
|
root.monitorDescription = monitor.description ?? monitor.model ?? "Display";
|
|
root.monitorWidth = monitor.width ?? 0;
|
|
root.monitorHeight = monitor.height ?? 0;
|
|
root.monitorRefreshRate = monitor.refreshRate ?? 0;
|
|
root.monitorScale = monitor.scale ?? 1;
|
|
root.monitorFormat = monitor.currentFormat ?? "";
|
|
root.colorPreset = monitor.colorManagementPreset ?? "";
|
|
root.monitorVrrActive = monitor.vrr ?? false;
|
|
} catch (error) {
|
|
root.lastError = "The display response could not be read.";
|
|
}
|
|
}
|
|
|
|
function parseServices(text: string): void {
|
|
try {
|
|
const state = JSON.parse(text);
|
|
root.nextcloudActive = state.nextcloud === true;
|
|
root.rustdeskActive = state.rustdesk === true;
|
|
root.kdeconnectActive = state.kdeconnect === true;
|
|
root.hyprpaperActive = state.hyprpaper === true;
|
|
root.hypridleActive = state.hypridle === true;
|
|
root.vicinaeActive = state.vicinae === true;
|
|
} catch (error) {
|
|
root.lastError = "Startup-service status could not be read.";
|
|
}
|
|
}
|
|
|
|
function setAutoHdr(enabled: bool): void {
|
|
autoHdrWrite.requested = enabled;
|
|
autoHdrWrite.exec(["hyprctl", "keyword", "render:cm_auto_hdr", enabled ? "1" : "0"]);
|
|
}
|
|
|
|
function setVrrPolicy(policy: int): void {
|
|
if (policy !== 0 && policy !== 3) {
|
|
root.lastError = "Unsupported VRR policy.";
|
|
return;
|
|
}
|
|
vrrWrite.requested = policy;
|
|
vrrWrite.exec(["hyprctl", "keyword", "misc:vrr", String(policy)]);
|
|
}
|
|
|
|
function setDirectScanoutPolicy(policy: int): void {
|
|
if (policy !== 0 && policy !== 2) {
|
|
root.lastError = "Unsupported direct-scanout policy.";
|
|
return;
|
|
}
|
|
directScanoutWrite.requested = policy;
|
|
directScanoutWrite.exec(["hyprctl", "keyword", "render:direct_scanout", String(policy)]);
|
|
}
|
|
|
|
function applyPersistedDisplayPolicy(): void {
|
|
root.setAutoHdr(DesktopPreferences.autoHdr);
|
|
root.setVrrPolicy(DesktopPreferences.vrrPolicy);
|
|
root.setDirectScanoutPolicy(DesktopPreferences.directScanoutPolicy);
|
|
}
|
|
|
|
function isGnomePanelAllowed(panel: string): bool {
|
|
return [
|
|
"wifi", "network", "bluetooth", "sound", "power", "printers",
|
|
"online-accounts", "users", "mouse", "keyboard", "sharing"
|
|
].indexOf(panel) >= 0;
|
|
}
|
|
|
|
function openGnomePanel(panel: string): bool {
|
|
if (!root.isGnomePanelAllowed(panel)) {
|
|
root.lastError = "That GNOME Settings panel is not available.";
|
|
return false;
|
|
}
|
|
Quickshell.execDetached({
|
|
command: ["gnome-control-center", panel],
|
|
environment: { "XDG_CURRENT_DESKTOP": "GNOME" }
|
|
});
|
|
return true;
|
|
}
|
|
|
|
function openApplication(id: string): bool {
|
|
const commands = {
|
|
"nextcloud": ["nextcloud"],
|
|
"rustdesk": ["rustdesk"],
|
|
"kdeconnect": ["kdeconnect-app"],
|
|
"mission-center": ["flatpak", "run", "io.missioncenter.MissionCenter"],
|
|
"bluebubbles": ["flatpak", "run", "app.bluebubbles.BlueBubbles"]
|
|
};
|
|
const command = commands[id];
|
|
if (!command) {
|
|
root.lastError = "That application is not managed by Panama Settings.";
|
|
return false;
|
|
}
|
|
Quickshell.execDetached(command);
|
|
return true;
|
|
}
|
|
}
|