From 5d94c0a7d44a2255b81815a52a7056a94a39842d Mon Sep 17 00:00:00 2001 From: nippy Date: Sat, 18 Apr 2026 13:49:56 +0200 Subject: update Raveos themes --- .../quickshell/Services/CupsService.qml | 860 +++++++++++++++++++++ 1 file changed, 860 insertions(+) create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/CupsService.qml (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/CupsService.qml') diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/CupsService.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/CupsService.qml new file mode 100644 index 0000000..a88655d --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Services/CupsService.qml @@ -0,0 +1,860 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import qs.Common + +Singleton { + id: root + + property int refCount: 0 + + onRefCountChanged: { + if (refCount > 0) { + ensureSubscription(); + } else if (refCount === 0 && DMSService.activeSubscriptions.includes("cups")) { + DMSService.removeSubscription("cups"); + } + } + + function ensureSubscription() { + if (refCount <= 0) + return; + if (!DMSService.isConnected) + return; + if (DMSService.activeSubscriptions.includes("cups")) + return; + if (DMSService.activeSubscriptions.includes("all")) + return; + DMSService.addSubscription("cups"); + if (cupsAvailable) { + getState(); + } + } + + property var printerNames: [] + property var printers: [] + property string selectedPrinter: "" + property string expandedPrinter: "" + + property bool cupsAvailable: false + property bool stateInitialized: false + + property var devices: [] + property var ppds: [] + property var printerClasses: [] + + readonly property var filteredDevices: { + if (!devices || devices.length === 0) + return []; + const bareProtocols = ["ipp", "ipps", "http", "https", "lpd", "socket", "beh", "dnssd", "mdns", "smb", "file", "cups-brf"]; + + // First pass: filter out invalid/bare protocol entries + const validDevices = devices.filter(d => { + if (!d.uri) + return false; + const uriLower = d.uri.toLowerCase(); + for (let proto of bareProtocols) { + if (uriLower === proto || uriLower === proto + ":") + return false; + } + if (d.class === "network" && d.info === "Backend Error Handler") + return false; + return true; + }); + + // Second pass: prefer IPP over LPD for the same printer + // _printer._tcp (LPD) doesn't work well with driverless printing + // _ipp._tcp or _ipps._tcp (IPP) should be preferred + const ippDeviceHosts = new Set(); + for (const d of validDevices) { + if (!d.uri) + continue; + // Extract hostname from dnssd URIs like dnssd://Name%20[mac]._ipp._tcp.local + const ippMatch = d.uri.match(/dnssd:\/\/[^/]*\._ipps?\._tcp/); + if (ippMatch) { + // Extract the unique identifier (usually MAC address in brackets) + const macMatch = d.uri.match(/\[([a-f0-9]+)\]/i); + if (macMatch) + ippDeviceHosts.add(macMatch[1].toLowerCase()); + } + } + + // Filter out _printer._tcp devices when we have _ipp._tcp for the same printer + return validDevices.filter(d => { + if (!d.uri) + return true; + // If this is an LPD device, check if we have an IPP alternative + if (d.uri.includes("._printer._tcp")) { + const macMatch = d.uri.match(/\[([a-f0-9]+)\]/i); + if (macMatch && ippDeviceHosts.has(macMatch[1].toLowerCase())) { + return false; // Skip LPD device, we have IPP + } + } + return true; + }); + } + + function decodeUri(str) { + if (!str) + return ""; + try { + return decodeURIComponent(str.replace(/\+/g, " ")); + } catch (e) { + return str; + } + } + + function getDeviceDisplayName(device) { + if (!device) + return ""; + let name = ""; + if (device.info && device.info.length > 0) { + name = decodeUri(device.info); + } else if (device.makeModel && device.makeModel.length > 0) { + name = decodeUri(device.makeModel); + } else { + return decodeUri(device.uri); + } + if (device.ip) + return name + " (" + device.ip + ")"; + return name; + } + + function getDeviceSubtitle(device) { + if (!device) + return ""; + const parts = []; + switch (device.class) { + case "direct": + parts.push(I18n.tr("Local")); + break; + case "network": + parts.push(I18n.tr("Network")); + break; + case "file": + parts.push(I18n.tr("File")); + break; + default: + if (device.class) + parts.push(device.class); + } + if (device.location) + parts.push(decodeUri(device.location)); + return parts.join(" • "); + } + + function suggestPrinterName(device) { + if (!device) + return ""; + let name = device.info || device.makeModel || ""; + name = name.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, ""); + return name.substring(0, 32) || "Printer"; + } + + function getMatchingPPDs(device) { + if (!device || !ppds || ppds.length === 0) + return []; + const isDnssd = device.uri && (device.uri.startsWith("dnssd://") || device.uri.startsWith("ipp://") || device.uri.startsWith("ipps://")); + if (isDnssd) { + const driverless = ppds.filter(p => p.name === "driverless" || p.name === "everywhere" || (p.makeModel && p.makeModel.toLowerCase().includes("driverless"))); + if (driverless.length > 0) + return driverless; + } + if (!device.makeModel) + return []; + const makeModelLower = device.makeModel.toLowerCase(); + const words = makeModelLower.split(/[\s_-]+/).filter(w => w.length > 2); + return ppds.filter(p => { + if (!p.makeModel) + return false; + const ppdLower = p.makeModel.toLowerCase(); + return words.some(w => ppdLower.includes(w)); + }).slice(0, 10); + } + + property bool loadingDevices: false + property bool loadingPPDs: false + property bool loadingClasses: false + property bool creatingPrinter: false + + signal cupsStateUpdate + + readonly property string socketPath: Quickshell.env("DMS_SOCKET") + + Component.onCompleted: { + if (socketPath && socketPath.length > 0) { + checkDMSCapabilities(); + } + } + + Connections { + target: DMSService + + function onConnectionStateChanged() { + if (DMSService.isConnected) { + checkDMSCapabilities(); + ensureSubscription(); + } + } + } + + Connections { + target: DMSService + enabled: DMSService.isConnected + + function onCupsStateUpdate(data) { + console.log("CupsService: Subscription update received"); + getState(); + } + + function onCapabilitiesChanged() { + checkDMSCapabilities(); + } + } + + function checkDMSCapabilities() { + if (!DMSService.isConnected) + return; + if (DMSService.capabilities.length === 0) + return; + cupsAvailable = DMSService.capabilities.includes("cups"); + + if (cupsAvailable && !stateInitialized) { + stateInitialized = true; + getState(); + } + } + + function getState() { + if (!cupsAvailable) + return; + DMSService.sendRequest("cups.getPrinters", null, response => { + if (response.result) { + updatePrinters(response.result); + fetchAllJobs(); + } + }); + } + + function updatePrinters(printersData) { + printerNames = printersData.map(p => p.name); + + let printersObj = {}; + for (var i = 0; i < printersData.length; i++) { + let printer = printersData[i]; + printersObj[printer.name] = { + "name": printer.name, + "uri": printer.uri || "", + "state": printer.state, + "stateReason": printer.stateReason, + "location": printer.location || "", + "info": printer.info || "", + "makeModel": printer.makeModel || "", + "accepting": printer.accepting !== false, + "jobs": [] + }; + } + printers = printersObj; + + if (printerNames.length > 0) { + if (selectedPrinter.length > 0) { + if (!printerNames.includes(selectedPrinter)) { + selectedPrinter = printerNames[0]; + } + } else { + selectedPrinter = printerNames[0]; + } + } + } + + function fetchAllJobs() { + for (var i = 0; i < printerNames.length; i++) { + fetchJobsForPrinter(printerNames[i]); + } + } + + function fetchJobsForPrinter(printerName) { + const params = { + "printerName": printerName + }; + + DMSService.sendRequest("cups.getJobs", params, response => { + if (response.result && printers[printerName]) { + let updatedPrinters = Object.assign({}, printers); + updatedPrinters[printerName].jobs = response.result; + printers = updatedPrinters; + } + }); + } + + function getSelectedPrinter() { + return selectedPrinter; + } + + function setSelectedPrinter(printerName) { + if (printerNames.length > 0) { + if (printerNames.includes(printerName)) { + selectedPrinter = printerName; + } else { + selectedPrinter = printerNames[0]; + } + } + } + + function getPrintersNum() { + if (!cupsAvailable) + return 0; + + return printerNames.length; + } + + function getPrintersNames() { + if (!cupsAvailable) + return []; + + return printerNames; + } + + function getTotalJobsNum() { + if (!cupsAvailable) + return 0; + + var result = 0; + for (var i = 0; i < printerNames.length; i++) { + var printerName = printerNames[i]; + if (printers[printerName] && printers[printerName].jobs) { + result += printers[printerName].jobs.length; + } + } + return result; + } + + function getCurrentPrinterState() { + if (!cupsAvailable || !selectedPrinter) + return ""; + + var printer = printers[selectedPrinter]; + return printer.state; + } + + function getCurrentPrinterStatePrettyShort() { + if (!cupsAvailable || !selectedPrinter) + return ""; + + var printer = printers[selectedPrinter]; + return getPrinterStateTranslation(printer.state) + " (" + getPrinterStateReasonTranslation(printer.stateReason) + ")"; + } + + function getCurrentPrinterStatePretty() { + if (!cupsAvailable || !selectedPrinter) + return ""; + + var printer = printers[selectedPrinter]; + return getPrinterStateTranslation(printer.state) + " (" + I18n.tr("Reason") + ": " + getPrinterStateReasonTranslation(printer.stateReason) + ")"; + } + + function getCurrentPrinterJobs() { + if (!cupsAvailable || !selectedPrinter) + return []; + + return getJobs(selectedPrinter); + } + + function getJobs(printerName) { + if (!cupsAvailable) + return ""; + + var printer = printers[printerName]; + return printer.jobs; + } + + function getJobsNum(printerName) { + if (!cupsAvailable) + return 0; + + var printer = printers[printerName]; + return printer.jobs.length; + } + + function pausePrinter(printerName) { + if (!cupsAvailable) + return; + const params = { + "printerName": printerName + }; + + DMSService.sendRequest("cups.pausePrinter", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to pause printer"), response.error); + } else { + getState(); + } + }); + } + + function resumePrinter(printerName) { + if (!cupsAvailable) + return; + const params = { + "printerName": printerName + }; + + DMSService.sendRequest("cups.resumePrinter", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to resume printer"), response.error); + } else { + getState(); + } + }); + } + + function cancelJob(printerName, jobID) { + if (!cupsAvailable) + return; + const params = { + "printerName": printerName, + "jobID": jobID + }; + + DMSService.sendRequest("cups.cancelJob", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to cancel selected job"), response.error); + } else { + fetchJobsForPrinter(printerName); + } + }); + } + + function purgeJobs(printerName) { + if (!cupsAvailable) + return; + const params = { + "printerName": printerName + }; + + DMSService.sendRequest("cups.purgeJobs", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to cancel all jobs"), response.error); + } else { + fetchJobsForPrinter(printerName); + } + }); + } + + function getDevices() { + if (!cupsAvailable) + return; + loadingDevices = true; + DMSService.sendRequest("cups.getDevices", null, response => { + loadingDevices = false; + if (response.result) { + devices = response.result; + } + }); + } + + function getPPDs() { + if (!cupsAvailable) + return; + loadingPPDs = true; + DMSService.sendRequest("cups.getPPDs", null, response => { + loadingPPDs = false; + if (response.result) { + ppds = response.result; + } + }); + } + + function getClasses() { + if (!cupsAvailable) + return; + loadingClasses = true; + DMSService.sendRequest("cups.getClasses", null, response => { + loadingClasses = false; + if (response.result) { + printerClasses = response.result; + } + }); + } + + function testConnection(host, port, protocol, callback) { + if (!cupsAvailable) + return; + const params = { + "host": host, + "port": port, + "protocol": protocol + }; + + DMSService.sendRequest("cups.testConnection", params, response => { + if (callback) + callback(response); + }); + } + + function createPrinter(name, deviceURI, ppd, options) { + if (!cupsAvailable) + return; + creatingPrinter = true; + const params = { + "name": name, + "deviceURI": deviceURI, + "ppd": ppd + }; + if (options) { + if (options.shared !== undefined) + params.shared = options.shared; + if (options.location) + params.location = options.location; + if (options.information) + params.information = options.information; + if (options.errorPolicy) + params.errorPolicy = options.errorPolicy; + } + + DMSService.sendRequest("cups.createPrinter", params, response => { + creatingPrinter = false; + if (response.error) { + ToastService.showError(I18n.tr("Failed to create printer"), response.error); + } else { + ToastService.showInfo(I18n.tr("Printer created successfully")); + getState(); + } + }); + } + + function deletePrinter(printerName) { + if (!cupsAvailable) + return; + const params = { + "printerName": printerName + }; + + DMSService.sendRequest("cups.deletePrinter", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to delete printer"), response.error); + } else { + ToastService.showInfo(I18n.tr("Printer deleted")); + if (selectedPrinter === printerName) { + selectedPrinter = ""; + } + getState(); + } + }); + } + + function acceptJobs(printerName) { + if (!cupsAvailable) + return; + const params = { + "printerName": printerName + }; + + DMSService.sendRequest("cups.acceptJobs", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to enable job acceptance"), response.error); + } else { + getState(); + } + }); + } + + function rejectJobs(printerName) { + if (!cupsAvailable) + return; + const params = { + "printerName": printerName + }; + + DMSService.sendRequest("cups.rejectJobs", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to disable job acceptance"), response.error); + } else { + getState(); + } + }); + } + + function setPrinterShared(printerName, shared) { + if (!cupsAvailable) + return; + const params = { + "printerName": printerName, + "shared": shared + }; + + DMSService.sendRequest("cups.setPrinterShared", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to update sharing"), response.error); + } else { + getState(); + } + }); + } + + function setPrinterLocation(printerName, location) { + if (!cupsAvailable) + return; + const params = { + "printerName": printerName, + "location": location + }; + + DMSService.sendRequest("cups.setPrinterLocation", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to update location"), response.error); + } else { + getState(); + } + }); + } + + function setPrinterInfo(printerName, info) { + if (!cupsAvailable) + return; + const params = { + "printerName": printerName, + "info": info + }; + + DMSService.sendRequest("cups.setPrinterInfo", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to update description"), response.error); + } else { + getState(); + } + }); + } + + function printTestPage(printerName) { + if (!cupsAvailable) + return; + const params = { + "printerName": printerName + }; + + DMSService.sendRequest("cups.printTestPage", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to print test page"), response.error); + } else { + ToastService.showInfo(I18n.tr("Test page sent to printer")); + fetchJobsForPrinter(printerName); + } + }); + } + + function moveJob(jobID, destPrinter) { + if (!cupsAvailable) + return; + const params = { + "jobID": jobID, + "destPrinter": destPrinter + }; + + DMSService.sendRequest("cups.moveJob", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to move job"), response.error); + } else { + fetchAllJobs(); + } + }); + } + + function restartJob(jobID) { + if (!cupsAvailable) + return; + const params = { + "jobID": jobID + }; + + DMSService.sendRequest("cups.restartJob", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to restart job"), response.error); + } else { + fetchAllJobs(); + } + }); + } + + function holdJob(jobID, holdUntil) { + if (!cupsAvailable) + return; + const params = { + "jobID": jobID + }; + if (holdUntil) { + params.holdUntil = holdUntil; + } + + DMSService.sendRequest("cups.holdJob", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to hold job"), response.error); + } else { + fetchAllJobs(); + } + }); + } + + function addPrinterToClass(className, printerName) { + if (!cupsAvailable) + return; + const params = { + "className": className, + "printerName": printerName + }; + + DMSService.sendRequest("cups.addPrinterToClass", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to add printer to class"), response.error); + } else { + getClasses(); + } + }); + } + + function removePrinterFromClass(className, printerName) { + if (!cupsAvailable) + return; + const params = { + "className": className, + "printerName": printerName + }; + + DMSService.sendRequest("cups.removePrinterFromClass", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to remove printer from class"), response.error); + } else { + getClasses(); + } + }); + } + + function deleteClass(className) { + if (!cupsAvailable) + return; + const params = { + "className": className + }; + + DMSService.sendRequest("cups.deleteClass", params, response => { + if (response.error) { + ToastService.showError(I18n.tr("Failed to delete class"), response.error); + } else { + getClasses(); + } + }); + } + + function getPrinterData(printerName) { + if (!printers || !printers[printerName]) + return null; + return printers[printerName]; + } + + function getJobStateTranslation(state) { + switch (state) { + case "pending": + return I18n.tr("Pending"); + case "pending-held": + return I18n.tr("Held"); + case "processing": + return I18n.tr("Processing"); + case "processing-stopped": + return I18n.tr("Stopped"); + case "canceled": + return I18n.tr("Canceled"); + case "aborted": + return I18n.tr("Aborted"); + case "completed": + return I18n.tr("Completed"); + default: + return state; + } + } + + readonly property var states: ({ + "idle": I18n.tr("Idle"), + "processing": I18n.tr("Processing"), + "stopped": I18n.tr("Stopped") + }) + + readonly property var reasonsGeneral: ({ + "none": I18n.tr("None"), + "other": I18n.tr("Other") + }) + + readonly property var reasonsSupplies: ({ + "toner-low": I18n.tr("Toner Low"), + "toner-empty": I18n.tr("Toner Empty"), + "marker-supply-low": I18n.tr("Marker Supply Low"), + "marker-supply-empty": I18n.tr("Marker Supply Empty"), + "marker-waste-almost-full": I18n.tr("Marker Waste Almost Full"), + "marker-waste-full": I18n.tr("Marker Waste Full") + }) + + readonly property var reasonsMedia: ({ + "media-low": I18n.tr("Media Low"), + "media-empty": I18n.tr("Media Empty"), + "media-needed": I18n.tr("Media Needed"), + "media-jam": I18n.tr("Media Jam") + }) + + readonly property var reasonsParts: ({ + "cover-open": I18n.tr("Cover Open"), + "door-open": I18n.tr("Door Open"), + "interlock-open": I18n.tr("Interlock Open"), + "output-tray-missing": I18n.tr("Output Tray Missing"), + "output-area-almost-full": I18n.tr("Output Area Almost Full"), + "output-area-full": I18n.tr("Output Area Full") + }) + + readonly property var reasonsErrors: ({ + "paused": I18n.tr("Paused"), + "shutdown": I18n.tr("Shutdown"), + "connecting-to-device": I18n.tr("Connecting to Device"), + "timed-out": I18n.tr("Timed Out"), + "stopping": I18n.tr("Stopping"), + "stopped-partly": I18n.tr("Stopped Partly") + }) + + readonly property var reasonsService: ({ + "spool-area-full": I18n.tr("Spool Area Full"), + "cups-missing-filter-warning": I18n.tr("CUPS Missing Filter Warning"), + "cups-insecure-filter-warning": I18n.tr("CUPS Insecure Filter Warning") + }) + + readonly property var reasonsConnectivity: ({ + "offline-report": I18n.tr("Offline Report"), + "moving-to-paused": I18n.tr("Moving to Paused") + }) + + readonly property var severitySuffixes: ({ + "-error": I18n.tr("Error"), + "-warning": I18n.tr("Warning"), + "-report": I18n.tr("Report") + }) + + function getPrinterStateTranslation(state) { + return states[state] || state; + } + + function getPrinterStateReasonTranslation(reason) { + let allReasons = Object.assign({}, reasonsGeneral, reasonsSupplies, reasonsMedia, reasonsParts, reasonsErrors, reasonsService, reasonsConnectivity); + + let basReason = reason; + let suffix = ""; + + for (let s in severitySuffixes) { + if (reason.endsWith(s)) { + basReason = reason.slice(0, -s.length); + suffix = severitySuffixes[s]; + break; + } + } + + let translation = allReasons[basReason] || basReason; + return suffix ? translation + " (" + suffix + ")" : translation; + } +} -- cgit v1.3