Files
noctalia-shell/Services/System/ProgramCheckerService.qml
T
2026-03-13 01:30:55 +01:00

338 lines
10 KiB
QML

pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services.Theming
// Service to check if various programs are available on the system
Singleton {
id: root
// Program availability properties
property bool nmcliAvailable: false
property bool bluetoothctlAvailable: false
property bool wlsunsetAvailable: false
property bool gnomeCalendarAvailable: false
property bool pythonAvailable: false
property bool wtypeAvailable: false
// Programs to check - maps property names to commands
readonly property var programsToCheck: ({
"bluetoothctlAvailable": ["sh", "-c", "command -v bluetoothctl"],
"nmcliAvailable": ["sh", "-c", "command -v nmcli"],
"wlsunsetAvailable": ["sh", "-c", "command -v wlsunset"],
"gnomeCalendarAvailable": ["sh", "-c", "command -v gnome-calendar"],
"wtypeAvailable": ["sh", "-c", "command -v wtype"],
"pythonAvailable": ["sh", "-c", "command -v python3"]
})
// Discord client auto-detection
property var availableDiscordClients: []
// Code client auto-detection
property var availableCodeClients: []
// Emacs client auto-detection
property var availableEmacsClients: []
// Signal emitted when all checks are complete
signal checksCompleted
// disable Night Light in settings if wlsunset is not available
onChecksCompleted: {
if (!wlsunsetAvailable && Settings.data.nightLight.enabled) {
Settings.data.nightLight.enabled = false;
}
}
onWlsunsetAvailableChanged: {
if (!wlsunsetAvailable && Settings.data.nightLight.enabled) {
Settings.data.nightLight.enabled = false;
}
}
// Function to detect Discord client by checking config directories
function detectDiscordClient() {
// Build shell script to check each client
var scriptParts = ["available_clients=\"\";"];
for (var i = 0; i < TemplateRegistry.discordClients.length; i++) {
var client = TemplateRegistry.discordClients[i];
var clientName = client.name;
var configPath = client.configPath;
// Use the actual config path from the client, removing ~ prefix
var checkPath = configPath.startsWith("~") ? configPath.substring(2) : configPath.substring(1);
scriptParts.push("if [ -d \"$HOME/" + checkPath + "\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;");
}
scriptParts.push("echo \"$available_clients\"");
// Use a Process to check directory existence for all clients
discordDetector.command = ["sh", "-c", scriptParts.join(" ")];
discordDetector.running = true;
}
// Process to detect Discord client directories
Process {
id: discordDetector
running: false
onExited: function (exitCode) {
availableDiscordClients = [];
if (exitCode === 0) {
var detectedClients = stdout.text.trim().split(/\s+/).filter(function (client) {
return client.length > 0;
});
if (detectedClients.length > 0) {
// Build list of available clients
for (var i = 0; i < detectedClients.length; i++) {
var clientName = detectedClients[i];
for (var j = 0; j < TemplateRegistry.discordClients.length; j++) {
var client = TemplateRegistry.discordClients[j];
if (client.name === clientName) {
availableDiscordClients.push(client);
break;
}
}
}
Logger.d("ProgramChecker", "Detected Discord clients:", detectedClients.join(", "));
}
}
if (availableDiscordClients.length === 0) {
Logger.d("ProgramChecker", "No Discord clients detected");
}
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
// Function to detect Code client by checking config directories
function detectCodeClient() {
// Build shell script to check each client
var scriptParts = ["available_clients=\"\";"];
for (var i = 0; i < TemplateRegistry.codeClients.length; i++) {
var client = TemplateRegistry.codeClients[i];
var clientName = client.name;
var configPath = client.configPath;
// Check if the config directory exists
scriptParts.push("if [ -d \"$HOME" + configPath.substring(1) + "\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;");
}
scriptParts.push("echo \"$available_clients\"");
// Use a Process to check directory existence for all clients
codeDetector.command = ["sh", "-c", scriptParts.join(" ")];
codeDetector.running = true;
}
// Process to detect Code client directories
Process {
id: codeDetector
running: false
onExited: function (exitCode) {
availableCodeClients = [];
if (exitCode === 0) {
var detectedClients = stdout.text.trim().split(/\s+/).filter(function (client) {
return client.length > 0;
});
if (detectedClients.length > 0) {
// Build list of available clients
for (var i = 0; i < detectedClients.length; i++) {
var clientName = detectedClients[i];
for (var j = 0; j < TemplateRegistry.codeClients.length; j++) {
var client = TemplateRegistry.codeClients[j];
if (client.name === clientName) {
availableCodeClients.push(client);
break;
}
}
}
Logger.d("ProgramChecker", "Detected Code clients:", detectedClients.join(", "));
}
}
if (availableCodeClients.length === 0) {
Logger.d("ProgramChecker", "No Code clients detected");
}
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
// Function to detect Emacs client by checking config directories
function detectEmacsClient() {
// Build shell script to check each client
var scriptParts = ["available_clients=\"\";"];
for (var i = 0; i < TemplateRegistry.emacsClients.length; i++) {
var client = TemplateRegistry.emacsClients[i];
var clientName = client.name;
var configPath = client.path;
// Check if the config directory exists
scriptParts.push("if [ -d \"$HOME" + configPath.substring(1) + "\" ]; then available_clients=\"$available_clients " + clientName + "\"; fi;");
}
scriptParts.push("echo \"$available_clients\"");
// Use a Process to check directory existence for all clients
emacsDetector.command = ["sh", "-c", scriptParts.join(" ")];
emacsDetector.running = true;
}
// Process to detect Emacs client directories
Process {
id: emacsDetector
running: false
onExited: function (exitCode) {
availableEmacsClients = [];
if (exitCode === 0) {
var detectedClients = stdout.text.trim().split(/\s+/).filter(function (client) {
return client.length > 0;
});
if (detectedClients.length > 0) {
// Build list of available clients
for (var i = 0; i < detectedClients.length; i++) {
var clientName = detectedClients[i];
for (var j = 0; j < TemplateRegistry.emacsClients.length; j++) {
var client = TemplateRegistry.emacsClients[j];
if (client.name === clientName) {
availableEmacsClients.push(client);
break;
}
}
}
Logger.d("ProgramChecker", "Detected Emacs clients:", detectedClients.join(", "));
}
}
if (availableEmacsClients.length === 0) {
Logger.d("ProgramChecker", "No Emacs clients detected");
}
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
// Internal tracking
property int completedChecks: 0
property int totalChecks: Object.keys(programsToCheck).length
// Single reusable Process object
Process {
id: checker
running: false
property string currentProperty: ""
onExited: function (exitCode) {
// Set the availability property
root[currentProperty] = (exitCode === 0);
// Stop the process to free resources
running = false;
// Track completion
root.completedChecks++;
// Check next program or emit completion signal
if (root.completedChecks >= root.totalChecks) {
// Run Discord, Code and Emacs client detection after all checks are complete
root.detectDiscordClient();
root.detectCodeClient();
root.detectEmacsClient();
root.checksCompleted();
} else {
root.checkNextProgram();
}
}
stdout: StdioCollector {}
stderr: StdioCollector {}
}
// Queue of programs to check
property var checkQueue: []
property int currentCheckIndex: 0
// Function to check the next program in the queue
function checkNextProgram() {
if (currentCheckIndex >= checkQueue.length)
return;
var propertyName = checkQueue[currentCheckIndex];
var command = programsToCheck[propertyName];
checker.currentProperty = propertyName;
checker.command = command;
checker.running = true;
currentCheckIndex++;
}
// Function to run all program checks
function checkAllPrograms() {
// Reset state
completedChecks = 0;
currentCheckIndex = 0;
checkQueue = Object.keys(programsToCheck);
// Start first check
if (checkQueue.length > 0) {
checkNextProgram();
}
}
// Function to check a specific program
function checkProgram(programProperty) {
if (!programsToCheck.hasOwnProperty(programProperty)) {
Logger.w("ProgramChecker", "Unknown program property:", programProperty);
return;
}
checker.currentProperty = programProperty;
checker.command = programsToCheck[programProperty];
checker.running = true;
}
// Manual function to test Discord detection (for debugging)
function testDiscordDetection() {
Logger.d("ProgramChecker", "Testing Discord detection...");
Logger.d("ProgramChecker", "HOME:", Quickshell.env("HOME"));
// Test each client directory
for (var i = 0; i < TemplateRegistry.discordClients.length; i++) {
var client = TemplateRegistry.discordClients[i];
var configDir = client.configPath.replace("~", Quickshell.env("HOME"));
Logger.d("ProgramChecker", "Checking:", configDir);
}
detectDiscordClient();
}
// Initialize checks when service is created
Component.onCompleted: {
checkAllPrograms();
}
}