add noctalia and fuzzel
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services.Compositor
|
||||
|
||||
/**
|
||||
* Detects which screen the cursor is currently on by creating a temporary
|
||||
* invisible PanelWindow. Use withCurrentScreen() to get the screen asynchronously.
|
||||
*
|
||||
* Usage:
|
||||
* CurrentScreenDetector {
|
||||
* id: screenDetector
|
||||
* }
|
||||
*
|
||||
* function doSomething() {
|
||||
* screenDetector.withCurrentScreen(function(screen) {
|
||||
* // screen is the ShellScreen where cursor is
|
||||
* })
|
||||
* }
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Pending callback to execute once screen is detected
|
||||
property var pendingCallback: null
|
||||
property bool pendingSkipBarCheck: false
|
||||
|
||||
// Detected screen
|
||||
property var detectedScreen: null
|
||||
|
||||
// Signal emitted when screen is detected from the PanelWindow
|
||||
signal screenDetected(var detectedScreen)
|
||||
|
||||
onScreenDetected: function (detectedScreen) {
|
||||
root.detectedScreen = detectedScreen;
|
||||
screenDetectorDebounce.restart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a fallback screen that has a bar configured.
|
||||
* Prioritizes the screen at position 0x0 (likely the primary screen).
|
||||
*/
|
||||
function findScreenWithBar(): var {
|
||||
const monitors = Settings.data.bar.monitors || [];
|
||||
let primaryCandidate = null;
|
||||
let firstWithBar = null;
|
||||
|
||||
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||
const s = Quickshell.screens[i];
|
||||
const hasBar = monitors.length === 0 || monitors.includes(s.name);
|
||||
|
||||
if (hasBar) {
|
||||
// Check if this is at 0x0 (primary position)
|
||||
if (s.x === 0 && s.y === 0) {
|
||||
primaryCandidate = s;
|
||||
}
|
||||
// Track first screen with bar as fallback
|
||||
if (!firstWithBar) {
|
||||
firstWithBar = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer primary (0x0), then first with bar, then just first screen
|
||||
return primaryCandidate || firstWithBar || Quickshell.screens[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute callback with the screen where the cursor currently is.
|
||||
* On single-monitor setups, executes immediately.
|
||||
* On multi-monitor setups, briefly opens an invisible window to detect the screen.
|
||||
*/
|
||||
function withCurrentScreen(callback: var, skipBarCheck: bool) {
|
||||
if (root.pendingCallback) {
|
||||
Logger.w("CurrentScreenDetector", "Another detection is pending, ignoring new call");
|
||||
return;
|
||||
}
|
||||
|
||||
// Single monitor setup can execute immediately
|
||||
if (Quickshell.screens.length === 1) {
|
||||
callback(Quickshell.screens[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try compositor-specific focused monitor detection first
|
||||
let screen = CompositorService.getFocusedScreen();
|
||||
|
||||
if (screen) {
|
||||
// Apply the bar check if configured (skip for overlay launcher etc.)
|
||||
if (!skipBarCheck && !Settings.data.general.allowPanelsOnScreenWithoutBar) {
|
||||
const monitors = Settings.data.bar.monitors || [];
|
||||
const hasBar = monitors.length === 0 || monitors.includes(screen.name);
|
||||
if (!hasBar) {
|
||||
screen = findScreenWithBar();
|
||||
}
|
||||
}
|
||||
Logger.d("CurrentScreenDetector", "Using compositor-detected screen:", screen.name);
|
||||
callback(screen);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: Multi-monitor setup needs async detection via invisible PanelWindow
|
||||
root.detectedScreen = null;
|
||||
root.pendingCallback = callback;
|
||||
root.pendingSkipBarCheck = !!skipBarCheck;
|
||||
screenDetectorLoader.active = true;
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: screenDetectorDebounce
|
||||
running: false
|
||||
interval: 40
|
||||
onTriggered: {
|
||||
Logger.d("CurrentScreenDetector", "Screen debounced to:", root.detectedScreen?.name || "null");
|
||||
|
||||
// Execute pending callback if any
|
||||
if (root.pendingCallback) {
|
||||
if (!root.pendingSkipBarCheck && !Settings.data.general.allowPanelsOnScreenWithoutBar) {
|
||||
// If we explicitly disabled panels on screen without bar, check if bar is configured
|
||||
// for this screen, and fallback to primary screen if necessary
|
||||
var monitors = Settings.data.bar.monitors || [];
|
||||
const hasBar = monitors.length === 0 || monitors.includes(root.detectedScreen?.name);
|
||||
if (!hasBar) {
|
||||
root.detectedScreen = findScreenWithBar();
|
||||
}
|
||||
}
|
||||
|
||||
Logger.d("CurrentScreenDetector", "Executing callback on screen:", root.detectedScreen.name);
|
||||
// Store callback locally and clear pendingCallback first to prevent deadlock
|
||||
// if the callback throws an error
|
||||
var callback = root.pendingCallback;
|
||||
root.pendingCallback = null;
|
||||
root.pendingSkipBarCheck = false;
|
||||
try {
|
||||
callback(root.detectedScreen);
|
||||
} catch (e) {
|
||||
Logger.e("CurrentScreenDetector", "Callback failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
screenDetectorLoader.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Invisible dummy PanelWindow to detect which screen should receive the action
|
||||
Loader {
|
||||
id: screenDetectorLoader
|
||||
active: false
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
implicitWidth: 0
|
||||
implicitHeight: 0
|
||||
color: "transparent"
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.namespace: "noctalia-screen-detector"
|
||||
mask: Region {}
|
||||
|
||||
onScreenChanged: root.screenDetected(screen)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Must be called at startup to force singleton instantiation,
|
||||
// which registers the IpcHandler with the IPC system.
|
||||
function init() {
|
||||
Logger.i("CustomButtonIPCService", "Service started");
|
||||
}
|
||||
|
||||
// Registry to store references to active custom buttons by their user-defined identifier
|
||||
property var customButtonRegistry: ({})
|
||||
|
||||
// Register a custom button instance
|
||||
function registerButton(button) {
|
||||
if (!button || !button.ipcIdentifier) {
|
||||
Logger.w("CustomButtonIPCService", "Cannot register button without ipcIdentifier");
|
||||
return false;
|
||||
}
|
||||
|
||||
customButtonRegistry[button.ipcIdentifier] = button;
|
||||
Logger.d("CustomButtonIPCService", `Registered button with identifier: ${button.ipcIdentifier}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unregister a custom button instance
|
||||
function unregisterButton(button) {
|
||||
if (!button || !button.ipcIdentifier) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (customButtonRegistry[button.ipcIdentifier] === button) {
|
||||
delete customButtonRegistry[button.ipcIdentifier];
|
||||
Logger.d("CustomButtonIPCService", `Unregistered button with identifier: ${button.ipcIdentifier}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find a button by identifier
|
||||
function findButton(identifier) {
|
||||
return customButtonRegistry[identifier] || null;
|
||||
}
|
||||
|
||||
// Find button config from Settings for when the live widget is not loaded
|
||||
function findButtonConfig(identifier) {
|
||||
var screens = Quickshell.screens;
|
||||
for (var i = 0; i < screens.length; i++) {
|
||||
var widgets = Settings.getBarWidgetsForScreen(screens[i].name);
|
||||
var config = _searchWidgetsForIdentifier(widgets, identifier);
|
||||
if (config)
|
||||
return config;
|
||||
}
|
||||
// Also check global widgets as a final fallback
|
||||
var globalConfig = _searchWidgetsForIdentifier(Settings.data.bar.widgets, identifier);
|
||||
if (globalConfig)
|
||||
return globalConfig;
|
||||
return null;
|
||||
}
|
||||
|
||||
function _searchWidgetsForIdentifier(widgets, identifier) {
|
||||
var sections = ["left", "center", "right"];
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
var list = widgets[sections[s]];
|
||||
if (!list)
|
||||
continue;
|
||||
for (var j = 0; j < list.length; j++) {
|
||||
var w = list[j];
|
||||
if (w.id === "CustomButton" && w.ipcIdentifier === identifier) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Resolve a command property from config with fallback to widgetMetadata defaults
|
||||
function resolveCommand(config, prop) {
|
||||
if (config[prop])
|
||||
return config[prop];
|
||||
var meta = BarWidgetRegistry.widgetMetadata["CustomButton"];
|
||||
return meta ? (meta[prop] || "") : "";
|
||||
}
|
||||
|
||||
// Substitute $delta expressions in a command string
|
||||
function substituteWheelDelta(command, delta) {
|
||||
var normalizedDelta = delta > 0 ? 1 : -1;
|
||||
return command.replace(/\$delta([+\-*/]\d+)?/g, function (match, operation) {
|
||||
if (operation) {
|
||||
try {
|
||||
var operator = operation.charAt(0);
|
||||
var operand = parseInt(operation.substring(1));
|
||||
var result;
|
||||
switch (operator) {
|
||||
case '+':
|
||||
result = normalizedDelta + operand;
|
||||
break;
|
||||
case '-':
|
||||
result = normalizedDelta - operand;
|
||||
break;
|
||||
case '*':
|
||||
result = normalizedDelta * operand;
|
||||
break;
|
||||
case '/':
|
||||
result = Math.floor(normalizedDelta / operand);
|
||||
break;
|
||||
default:
|
||||
result = normalizedDelta;
|
||||
}
|
||||
return result.toString();
|
||||
} catch (e) {
|
||||
return normalizedDelta.toString();
|
||||
}
|
||||
} else {
|
||||
return normalizedDelta.toString();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// IpcHandler for custom button commands using short alias 'cb'
|
||||
IpcHandler {
|
||||
target: "cb"
|
||||
|
||||
// Handle left click: cb left "identifier"
|
||||
function left(identifier: string) {
|
||||
const button = findButton(identifier);
|
||||
if (button) {
|
||||
if (button.leftClickExec || button.textCommand) {
|
||||
button.clicked();
|
||||
Logger.i("CustomButtonIPCService", `Triggered left click on button '${identifier}'`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no left click action configured`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: read from Settings
|
||||
const config = findButtonConfig(identifier);
|
||||
if (!config) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
const cmd = resolveCommand(config, "leftClickExec");
|
||||
if (cmd) {
|
||||
Quickshell.execDetached(["sh", "-lc", cmd]);
|
||||
Logger.i("CustomButtonIPCService", `Triggered left click on button '${identifier}' (from settings)`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no left click action configured`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle right click: cb right "identifier"
|
||||
function right(identifier: string) {
|
||||
const button = findButton(identifier);
|
||||
if (button) {
|
||||
if (button.rightClickExec) {
|
||||
button.rightClicked();
|
||||
Logger.i("CustomButtonIPCService", `Triggered right click on button '${identifier}'`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no right click action configured`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const config = findButtonConfig(identifier);
|
||||
if (!config) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
const cmd = resolveCommand(config, "rightClickExec");
|
||||
if (cmd) {
|
||||
Quickshell.execDetached(["sh", "-lc", cmd]);
|
||||
Logger.i("CustomButtonIPCService", `Triggered right click on button '${identifier}' (from settings)`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no right click action configured`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle middle click: cb middle "identifier"
|
||||
function middle(identifier: string) {
|
||||
const button = findButton(identifier);
|
||||
if (button) {
|
||||
if (button.middleClickExec) {
|
||||
button.middleClicked();
|
||||
Logger.i("CustomButtonIPCService", `Triggered middle click on button '${identifier}'`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no middle click action configured`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const config = findButtonConfig(identifier);
|
||||
if (!config) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
const cmd = resolveCommand(config, "middleClickExec");
|
||||
if (cmd) {
|
||||
Quickshell.execDetached(["sh", "-lc", cmd]);
|
||||
Logger.i("CustomButtonIPCService", `Triggered middle click on button '${identifier}' (from settings)`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no middle click action configured`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle wheel up: cb up "identifier"
|
||||
function up(identifier: string) {
|
||||
const button = findButton(identifier);
|
||||
if (button) {
|
||||
if (button.wheelMode === "separate" && button.wheelUpExec) {
|
||||
button.wheeled(1);
|
||||
Logger.i("CustomButtonIPCService", `Triggered wheel up on button '${identifier}'`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no separate wheel up action configured or is not in separate mode`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const config = findButtonConfig(identifier);
|
||||
if (!config) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
const mode = config.wheelMode || BarWidgetRegistry.widgetMetadata["CustomButton"].wheelMode;
|
||||
const cmd = resolveCommand(config, "wheelUpExec");
|
||||
if (mode === "separate" && cmd) {
|
||||
const resolved = substituteWheelDelta(cmd, 1);
|
||||
Quickshell.execDetached(["sh", "-lc", resolved]);
|
||||
Logger.i("CustomButtonIPCService", `Triggered wheel up on button '${identifier}' (from settings)`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no separate wheel up action configured or is not in separate mode`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle wheel down: cb down "identifier"
|
||||
function down(identifier: string) {
|
||||
const button = findButton(identifier);
|
||||
if (button) {
|
||||
if (button.wheelMode === "separate" && button.wheelDownExec) {
|
||||
button.wheeled(-1);
|
||||
Logger.i("CustomButtonIPCService", `Triggered wheel down on button '${identifier}'`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no separate wheel down action configured or is not in separate mode`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const config = findButtonConfig(identifier);
|
||||
if (!config) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
const mode = config.wheelMode || BarWidgetRegistry.widgetMetadata["CustomButton"].wheelMode;
|
||||
const cmd = resolveCommand(config, "wheelDownExec");
|
||||
if (mode === "separate" && cmd) {
|
||||
const resolved = substituteWheelDelta(cmd, -1);
|
||||
Quickshell.execDetached(["sh", "-lc", resolved]);
|
||||
Logger.i("CustomButtonIPCService", `Triggered wheel down on button '${identifier}' (from settings)`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no separate wheel down action configured or is not in separate mode`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle wheel action: cb wheel "identifier"
|
||||
function wheel(identifier: string) {
|
||||
const button = findButton(identifier);
|
||||
if (button) {
|
||||
if (button.wheelMode === "unified" && button.wheelExec) {
|
||||
button.wheeled(1);
|
||||
Logger.i("CustomButtonIPCService", `Triggered wheel action on button '${identifier}'`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no unified wheel action configured or is not in unified mode`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const config = findButtonConfig(identifier);
|
||||
if (!config) {
|
||||
Logger.w("CustomButtonIPCService", `Button with identifier '${identifier}' not found`);
|
||||
return;
|
||||
}
|
||||
const mode = config.wheelMode || BarWidgetRegistry.widgetMetadata["CustomButton"].wheelMode;
|
||||
const cmd = resolveCommand(config, "wheelExec");
|
||||
if (mode === "unified" && cmd) {
|
||||
const resolved = substituteWheelDelta(cmd, 1);
|
||||
Quickshell.execDetached(["sh", "-lc", resolved]);
|
||||
Logger.i("CustomButtonIPCService", `Triggered wheel action on button '${identifier}' (from settings)`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no unified wheel action configured or is not in unified mode`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle refresh: cb refresh "identifier"
|
||||
function refresh(identifier: string) {
|
||||
const button = findButton(identifier);
|
||||
if (!button) {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' is not currently loaded — refresh requires a live widget instance`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (button.textCommand && button.textCommand.length > 0 && !button.textStream) {
|
||||
button.runTextCommand();
|
||||
Logger.i("CustomButtonIPCService", `Triggered refresh (text command) on button '${identifier}'`);
|
||||
} else if (button.textStream) {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' uses streaming, manual refresh disabled`);
|
||||
} else {
|
||||
Logger.w("CustomButtonIPCService", `Button '${identifier}' has no text command to refresh`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
import qs.Services.Power
|
||||
import qs.Services.Theming
|
||||
import qs.Services.UI
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Hook connections for automatic script execution
|
||||
Connections {
|
||||
target: Settings.data.colorSchemes
|
||||
function onDarkModeChanged() {
|
||||
executeDarkModeHook(Settings.data.colorSchemes.darkMode);
|
||||
}
|
||||
}
|
||||
|
||||
// Pending wallpaper hook when waiting for color generation
|
||||
property var pendingWallpaperHook: null
|
||||
|
||||
Connections {
|
||||
target: WallpaperService
|
||||
function onWallpaperChanged(screenName, path) {
|
||||
// Check if we need to wait for color generation
|
||||
if (Settings.data.colorSchemes.useWallpaperColors) {
|
||||
var effectiveMonitor = Settings.data.colorSchemes.monitorForColors;
|
||||
if (effectiveMonitor === "" || effectiveMonitor === undefined) {
|
||||
effectiveMonitor = Screen.name;
|
||||
}
|
||||
|
||||
if (screenName === effectiveMonitor) {
|
||||
// Store pending hook and wait for colors to be generated
|
||||
root.pendingWallpaperHook = {
|
||||
path: path,
|
||||
screenName: screenName
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
// No color generation, execute immediately
|
||||
executeWallpaperHook(path, screenName);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: TemplateProcessor
|
||||
function onColorsGenerated() {
|
||||
// Execute pending wallpaper hook after colors are ready
|
||||
if (root.pendingWallpaperHook) {
|
||||
const hook = root.pendingWallpaperHook;
|
||||
root.pendingWallpaperHook = null;
|
||||
executeWallpaperHook(hook.path, hook.screenName);
|
||||
}
|
||||
executeColorGenerationHook();
|
||||
}
|
||||
}
|
||||
|
||||
// Track lock screen state for unlock hook
|
||||
property bool wasLocked: false
|
||||
|
||||
Connections {
|
||||
target: PanelService
|
||||
function onLockScreenChanged() {
|
||||
if (PanelService.lockScreen) {
|
||||
lockScreenActiveConnection.target = PanelService.lockScreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
id: lockScreenActiveConnection
|
||||
target: PanelService.lockScreen
|
||||
function onActiveChanged() {
|
||||
// Detect lock: was unlocked, now locked
|
||||
if (!wasLocked && PanelService.lockScreen.active) {
|
||||
executeLockHook();
|
||||
}
|
||||
// Detect unlock: was locked, now not locked
|
||||
if (wasLocked && !PanelService.lockScreen.active) {
|
||||
executeUnlockHook();
|
||||
}
|
||||
wasLocked = PanelService.lockScreen.active;
|
||||
}
|
||||
}
|
||||
|
||||
// Track performance mode state for hooks
|
||||
property bool wasPerformanceModeEnabled: false
|
||||
|
||||
Connections {
|
||||
target: PowerProfileService
|
||||
function onNoctaliaPerformanceModeChanged() {
|
||||
const isEnabled = PowerProfileService.noctaliaPerformanceMode;
|
||||
|
||||
// Detect enabled: was disabled, now enabled
|
||||
if (!wasPerformanceModeEnabled && isEnabled) {
|
||||
executePerformanceModeEnabledHook();
|
||||
}
|
||||
// Detect disabled: was enabled, now disabled
|
||||
if (wasPerformanceModeEnabled && !isEnabled) {
|
||||
executePerformanceModeDisabledHook();
|
||||
}
|
||||
wasPerformanceModeEnabled = isEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute wallpaper change hook
|
||||
function executeWallpaperHook(wallpaperPath, screenName) {
|
||||
if (!Settings.data.hooks?.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = Settings.data.hooks?.wallpaperChange;
|
||||
if (!script || script === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const theme = Settings.data.colorSchemes.darkMode ? "dark" : "light";
|
||||
let command = script.replace(/\$1/g, wallpaperPath);
|
||||
command = command.replace(/\$2/g, screenName || "");
|
||||
command = command.replace(/\$3/g, theme);
|
||||
Quickshell.execDetached(["sh", "-lc", command]);
|
||||
Logger.d("HooksService", `Executed wallpaper hook: ${command}`);
|
||||
} catch (e) {
|
||||
Logger.e("HooksService", `Failed to execute wallpaper hook: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute dark mode change hook
|
||||
function executeDarkModeHook(isDarkMode) {
|
||||
if (!Settings.data.hooks?.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = Settings.data.hooks?.darkModeChange;
|
||||
if (!script || script === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const command = script.replace(/\$1/g, isDarkMode ? "true" : "false");
|
||||
Quickshell.execDetached(["sh", "-lc", command]);
|
||||
Logger.d("HooksService", `Executed dark mode hook: ${command}`);
|
||||
} catch (e) {
|
||||
Logger.e("HooksService", `Failed to execute dark mode hook: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute screen lock hook
|
||||
function executeLockHook() {
|
||||
if (!Settings.data.hooks?.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = Settings.data.hooks?.screenLock;
|
||||
if (!script || script === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Pass "lock" as $1 via shell arguments so the script receives it
|
||||
Quickshell.execDetached(["sh", "-lc", script, "lock-hook", "lock"]);
|
||||
Logger.d("HooksService", `Executed screen lock hook: ${script}`);
|
||||
} catch (e) {
|
||||
Logger.e("HooksService", `Failed to execute screen lock hook: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute screen unlock hook
|
||||
function executeUnlockHook() {
|
||||
if (!Settings.data.hooks?.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = Settings.data.hooks?.screenUnlock;
|
||||
if (!script || script === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Pass "unlock" as $1 via shell arguments so the script receives it
|
||||
Quickshell.execDetached(["sh", "-lc", script, "unlock-hook", "unlock"]);
|
||||
Logger.d("HooksService", `Executed screen unlock hook: ${script}`);
|
||||
} catch (e) {
|
||||
Logger.e("HooksService", `Failed to execute screen unlock hook: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute performance mode enabled hook
|
||||
function executePerformanceModeEnabledHook() {
|
||||
if (!Settings.data.hooks?.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = Settings.data.hooks?.performanceModeEnabled;
|
||||
if (!script || script === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Quickshell.execDetached(["sh", "-lc", script]);
|
||||
} catch (e) {
|
||||
Logger.e("HooksService", `Failed to execute performance mode enabled hook: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute performance mode disabled hook
|
||||
function executePerformanceModeDisabledHook() {
|
||||
if (!Settings.data.hooks?.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = Settings.data.hooks?.performanceModeDisabled;
|
||||
if (!script || script === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Quickshell.execDetached(["sh", "-lc", script]);
|
||||
} catch (e) {
|
||||
Logger.e("HooksService", `Failed to execute performance mode disabled hook: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute color generation hook
|
||||
function executeColorGenerationHook() {
|
||||
if (!Settings.data.hooks?.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = Settings.data.hooks?.colorGeneration;
|
||||
if (!script || script === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const theme = Settings.data.colorSchemes.darkMode ? "dark" : "light";
|
||||
const command = script.replace(/\$1/g, theme);
|
||||
Quickshell.execDetached(["sh", "-lc", command]);
|
||||
Logger.d("HooksService", `Executed color generation hook: ${command}`);
|
||||
} catch (e) {
|
||||
Logger.e("HooksService", `Failed to execute color generation hook: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Blocking power hook infrastructure
|
||||
property var pendingPowerCallback: null
|
||||
|
||||
Process {
|
||||
id: powerHookProcess
|
||||
onExited: (exitCode, exitStatus) => {
|
||||
if (exitCode !== 0) {
|
||||
Logger.w("HooksService", `Power hook failed with exit code ${exitCode}`);
|
||||
}
|
||||
|
||||
if (pendingPowerCallback !== null) {
|
||||
const callback = pendingPowerCallback;
|
||||
pendingPowerCallback = null;
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runPowerHook(script, callback) {
|
||||
pendingPowerCallback = callback;
|
||||
powerHookProcess.command = ["sh", "-lc", script];
|
||||
powerHookProcess.running = true;
|
||||
}
|
||||
|
||||
function executeSessionHook(action, callback) {
|
||||
if (!Settings.data.hooks?.enabled) {
|
||||
callback();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const script = Settings.data.hooks?.session;
|
||||
if (!script) {
|
||||
callback();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.i("HooksService", `Executing session hook for ${action}`);
|
||||
runPowerHook(`${script} ${action}`, callback);
|
||||
}
|
||||
|
||||
// Execute startup hook
|
||||
function executeStartupHook() {
|
||||
if (!Settings.data.hooks?.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = Settings.data.hooks?.startup;
|
||||
if (!script || script === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Quickshell.execDetached(["sh", "-lc", script]);
|
||||
Logger.d("HooksService", `Executed startup hook: ${script}`);
|
||||
} catch (e) {
|
||||
Logger.e("HooksService", `Failed to execute startup hook: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the service
|
||||
function init() {
|
||||
Logger.i("HooksService", "Service started");
|
||||
// Initialize lock screen state tracking
|
||||
Qt.callLater(() => {
|
||||
if (PanelService.lockScreen) {
|
||||
wasLocked = PanelService.lockScreen.active;
|
||||
lockScreenActiveConnection.target = PanelService.lockScreen;
|
||||
}
|
||||
// Initialize performance mode state tracking
|
||||
wasPerformanceModeEnabled = PowerProfileService.noctaliaPerformanceMode;
|
||||
// Execute startup hook
|
||||
executeStartupHook();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,940 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
|
||||
import qs.Commons
|
||||
import qs.Modules.Panels.Settings
|
||||
import qs.Services.Compositor
|
||||
import qs.Services.Hardware
|
||||
import qs.Services.Location
|
||||
import qs.Services.Media
|
||||
import qs.Services.Networking
|
||||
import qs.Services.Noctalia
|
||||
import qs.Services.Power
|
||||
import qs.Services.System
|
||||
import qs.Services.Theming
|
||||
import qs.Services.UI
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Screen detector, set via init()
|
||||
property var screenDetector: null
|
||||
|
||||
function init(detector) {
|
||||
root.screenDetector = detector;
|
||||
Logger.i("IPCService", "Service started");
|
||||
}
|
||||
|
||||
// Helper for index-based notification lookups in IPC
|
||||
function _getNotificationByIndex(index: string, funcName: string) {
|
||||
var idx = index === "" ? 0 : parseInt(index);
|
||||
if (isNaN(idx)) {
|
||||
Logger.w("IPC", "Argument to ipc call '" + funcName + "' must be a number");
|
||||
return null;
|
||||
}
|
||||
if (idx < 0 || idx >= NotificationService.popupModel.count) {
|
||||
Logger.w("IPC", "Notification index out of range: " + idx);
|
||||
return null;
|
||||
}
|
||||
return NotificationService.popupModel.get(idx);
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "bar"
|
||||
function toggle() {
|
||||
BarService.toggleVisibility();
|
||||
}
|
||||
function hideBar() {
|
||||
BarService.hide();
|
||||
}
|
||||
function showBar() {
|
||||
BarService.show();
|
||||
}
|
||||
function peek() {
|
||||
BarService.peek();
|
||||
}
|
||||
function setDisplayMode(mode: string, screen: string) {
|
||||
if (mode === "always_visible" || mode === "non_exclusive" || mode === "auto_hide") {
|
||||
if (!screen || screen === "all") {
|
||||
Settings.data.bar.displayMode = mode;
|
||||
} else {
|
||||
Settings.setScreenOverride(screen, "displayMode", mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
function setPosition(position: string, screen: string) {
|
||||
var valid = position === "top" || position === "bottom" || position === "left" || position === "right";
|
||||
if (!valid) {
|
||||
Logger.w("IPC", "Invalid bar position: " + position + ". Valid: top, bottom, left, right");
|
||||
return;
|
||||
}
|
||||
if (!screen || screen === "all") {
|
||||
Settings.data.bar.position = position;
|
||||
} else {
|
||||
Settings.setScreenOverride(screen, "position", position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Settings IPC helpers (outside IpcHandler to avoid QVariant IPC warnings)
|
||||
readonly property var _settingsTabMap: ({
|
||||
"about": SettingsPanel.Tab.About,
|
||||
"audio": SettingsPanel.Tab.Audio,
|
||||
"bar": SettingsPanel.Tab.Bar,
|
||||
"colorscheme": SettingsPanel.Tab.ColorScheme,
|
||||
"lockscreen": SettingsPanel.Tab.LockScreen,
|
||||
"controlcenter": SettingsPanel.Tab.ControlCenter,
|
||||
"desktopwidgets": SettingsPanel.Tab.DesktopWidgets,
|
||||
"osd": SettingsPanel.Tab.OSD,
|
||||
"display": SettingsPanel.Tab.Display,
|
||||
"dock": SettingsPanel.Tab.Dock,
|
||||
"general": SettingsPanel.Tab.General,
|
||||
"hooks": SettingsPanel.Tab.Hooks,
|
||||
"launcher": SettingsPanel.Tab.Launcher,
|
||||
"location": SettingsPanel.Tab.Location,
|
||||
"connections": SettingsPanel.Tab.Connections,
|
||||
"notifications": SettingsPanel.Tab.Notifications,
|
||||
"plugins": SettingsPanel.Tab.Plugins,
|
||||
"sessionmenu": SettingsPanel.Tab.SessionMenu,
|
||||
"system": SettingsPanel.Tab.System,
|
||||
"systemmonitor": SettingsPanel.Tab.System,
|
||||
"userinterface": SettingsPanel.Tab.UserInterface,
|
||||
"wallpaper": SettingsPanel.Tab.Wallpaper
|
||||
})
|
||||
|
||||
function _parseSettingsTabArg(tabArg) {
|
||||
var parts = tabArg.split("/");
|
||||
var tabId = _resolveSettingsTab(parts[0]);
|
||||
var subTabId = parts.length > 1 ? parseInt(parts[1]) : -1;
|
||||
return {
|
||||
"tab": tabId,
|
||||
"subTab": isNaN(subTabId) ? -1 : subTabId
|
||||
};
|
||||
}
|
||||
|
||||
function _resolveSettingsTab(tabName) {
|
||||
if (!tabName)
|
||||
return SettingsPanel.Tab.General;
|
||||
var key = tabName.toLowerCase().replace(/[-_]/g, "");
|
||||
if (key in _settingsTabMap)
|
||||
return _settingsTabMap[key];
|
||||
Logger.w("IPC", "Unknown settings tab: " + tabName);
|
||||
return SettingsPanel.Tab.General;
|
||||
}
|
||||
|
||||
function _settingsToggle(tabId, subTabId) {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
SettingsPanelService.toggle(tabId, subTabId, screen);
|
||||
});
|
||||
}
|
||||
|
||||
function _settingsOpen(tabId, subTabId) {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
SettingsPanelService.openToTab(tabId, subTabId, screen);
|
||||
});
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "settings"
|
||||
|
||||
function toggle() {
|
||||
root._settingsToggle(SettingsPanel.Tab.General, -1);
|
||||
}
|
||||
|
||||
function toggleTab(tab: string) {
|
||||
var parsed = root._parseSettingsTabArg(tab);
|
||||
root._settingsToggle(parsed.tab, parsed.subTab);
|
||||
}
|
||||
|
||||
function open() {
|
||||
root._settingsOpen(SettingsPanel.Tab.General, -1);
|
||||
}
|
||||
|
||||
function openTab(tab: string) {
|
||||
var parsed = root._parseSettingsTabArg(tab);
|
||||
root._settingsOpen(parsed.tab, parsed.subTab);
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "calendar"
|
||||
function toggle() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var clockPanel = PanelService.getPanel("clockPanel", screen);
|
||||
clockPanel?.toggle(null, "Clock");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "notifications"
|
||||
function toggleHistory() {
|
||||
// Will attempt to open the panel next to the bar button if any.
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var notificationHistoryPanel = PanelService.getPanel("notificationHistoryPanel", screen);
|
||||
notificationHistoryPanel.toggle(null, "NotificationHistory");
|
||||
});
|
||||
}
|
||||
function toggleDND() {
|
||||
NotificationService.doNotDisturb = !NotificationService.doNotDisturb;
|
||||
}
|
||||
function enableDND() {
|
||||
NotificationService.doNotDisturb = true;
|
||||
}
|
||||
function disableDND() {
|
||||
NotificationService.doNotDisturb = false;
|
||||
}
|
||||
function clear() {
|
||||
NotificationService.clearHistory();
|
||||
}
|
||||
|
||||
function dismissOldest() {
|
||||
NotificationService.dismissOldestPopup();
|
||||
}
|
||||
|
||||
function removeOldestHistory() {
|
||||
NotificationService.removeOldestHistory();
|
||||
}
|
||||
|
||||
function dismissAll() {
|
||||
NotificationService.dismissAllPopups();
|
||||
}
|
||||
|
||||
function getHistory(): string {
|
||||
return JSON.stringify(NotificationService.getHistorySnapshot(), null, 2);
|
||||
}
|
||||
|
||||
function removeFromHistory(id: string): bool {
|
||||
return NotificationService.removeFromHistory(id);
|
||||
}
|
||||
|
||||
function invokeDefault(index: string): bool {
|
||||
var notif = root._getNotificationByIndex(index, "notifications invokeDefault");
|
||||
if (!notif)
|
||||
return false;
|
||||
|
||||
var actions = JSON.parse(notif.actionsJson || "[]");
|
||||
if (actions.length === 0)
|
||||
return false;
|
||||
|
||||
var actionId = actions.find(a => a.identifier === "default")?.identifier ?? actions[0].identifier;
|
||||
return NotificationService.invokeAction(notif.id, actionId);
|
||||
}
|
||||
|
||||
function invokeDefaultAndDismiss(index: string): bool {
|
||||
var notif = root._getNotificationByIndex(index, "notifications invokeDefaultAndDismiss");
|
||||
if (!notif)
|
||||
return false;
|
||||
|
||||
var actions = JSON.parse(notif.actionsJson || "[]");
|
||||
if (actions.length === 0) {
|
||||
NotificationService.dismissPopup(notif.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
var actionId = actions.find(a => a.identifier === "default")?.identifier ?? actions[0].identifier;
|
||||
var result = NotificationService.invokeAction(notif.id, actionId);
|
||||
NotificationService.dismissPopup(notif.id);
|
||||
return result;
|
||||
}
|
||||
|
||||
function invokeAction(id: string, actionId: string): bool {
|
||||
if (!id || !actionId) {
|
||||
Logger.w("IPC", "Both 'id' and 'actionId' are required for 'notifications invokeAction'");
|
||||
return false;
|
||||
}
|
||||
return NotificationService.invokeAction(id, actionId);
|
||||
}
|
||||
|
||||
function getActions(index: string): string {
|
||||
var notif = root._getNotificationByIndex(index, "notifications getActions");
|
||||
if (!notif)
|
||||
return "[]";
|
||||
return notif.actionsJson || "[]";
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "toast"
|
||||
|
||||
function send(json: string) {
|
||||
try {
|
||||
var data = JSON.parse(json);
|
||||
var title = data.title || "";
|
||||
var body = data.body || "";
|
||||
var icon = data.icon || "";
|
||||
var type = data.type || "notice";
|
||||
var duration = data.duration;
|
||||
|
||||
switch (type) {
|
||||
case "warning":
|
||||
ToastService.showWarning(title, body, duration ?? 4000);
|
||||
break;
|
||||
case "error":
|
||||
ToastService.showError(title, body, duration ?? 6000);
|
||||
break;
|
||||
default:
|
||||
ToastService.showNotice(title, body, icon, duration ?? 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.e("IPC", "Failed to parse toast JSON: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
function dismiss() {
|
||||
ToastService.dismissToast();
|
||||
}
|
||||
}
|
||||
|
||||
// Idle Inhibitor / Keep Awake
|
||||
IpcHandler {
|
||||
target: "idleInhibitor"
|
||||
function toggle() {
|
||||
IdleInhibitorService.manualToggle();
|
||||
}
|
||||
function enable() {
|
||||
IdleInhibitorService.addManualInhibitor(null);
|
||||
}
|
||||
function disable() {
|
||||
IdleInhibitorService.removeManualInhibitor();
|
||||
}
|
||||
function enableFor(seconds: string) {
|
||||
var secs = parseInt(seconds);
|
||||
if (isNaN(secs) || secs <= 0) {
|
||||
Logger.w("IPC", "Argument to 'idleInhibitor enableFor' must be a positive number");
|
||||
return;
|
||||
}
|
||||
IdleInhibitorService.addManualInhibitor(secs);
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "launcher"
|
||||
function toggle() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var searchText = PanelService.getLauncherSearchText(screen);
|
||||
var isInAppMode = !searchText.startsWith(">");
|
||||
if (!PanelService.isLauncherOpen(screen)) {
|
||||
// Closed -> open in app mode
|
||||
PanelService.openLauncherWithSearch(screen, "");
|
||||
} else if (isInAppMode) {
|
||||
// Already in app mode -> close
|
||||
PanelService.closeLauncher(screen);
|
||||
} else {
|
||||
// In another mode -> switch to app mode
|
||||
PanelService.setLauncherSearchText(screen, "");
|
||||
}
|
||||
}, Settings.data.appLauncher.overviewLayer);
|
||||
}
|
||||
function clipboard() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var searchText = PanelService.getLauncherSearchText(screen);
|
||||
var isInClipMode = searchText.startsWith(">clip");
|
||||
if (!PanelService.isLauncherOpen(screen)) {
|
||||
// Closed -> open in clipboard mode
|
||||
PanelService.openLauncherWithSearch(screen, ">clip ");
|
||||
} else if (isInClipMode) {
|
||||
// Already in clipboard mode -> close
|
||||
PanelService.closeLauncher(screen);
|
||||
} else {
|
||||
// In another mode -> switch to clipboard mode
|
||||
PanelService.setLauncherSearchText(screen, ">clip ");
|
||||
}
|
||||
}, Settings.data.appLauncher.overviewLayer);
|
||||
}
|
||||
function command() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var searchText = PanelService.getLauncherSearchText(screen);
|
||||
var isInCmdMode = searchText.startsWith(">cmd");
|
||||
if (!PanelService.isLauncherOpen(screen)) {
|
||||
PanelService.openLauncherWithSearch(screen, ">cmd ");
|
||||
} else if (isInCmdMode) {
|
||||
PanelService.closeLauncher(screen);
|
||||
} else {
|
||||
PanelService.setLauncherSearchText(screen, ">cmd ");
|
||||
}
|
||||
}, Settings.data.appLauncher.overviewLayer);
|
||||
}
|
||||
function emoji() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var searchText = PanelService.getLauncherSearchText(screen);
|
||||
var isInEmojiMode = searchText.startsWith(">emoji");
|
||||
if (!PanelService.isLauncherOpen(screen)) {
|
||||
// Closed -> open in emoji mode
|
||||
PanelService.openLauncherWithSearch(screen, ">emoji ");
|
||||
} else if (isInEmojiMode) {
|
||||
// Already in emoji mode -> close
|
||||
PanelService.closeLauncher(screen);
|
||||
} else {
|
||||
// In another mode -> switch to emoji mode
|
||||
PanelService.setLauncherSearchText(screen, ">emoji ");
|
||||
}
|
||||
}, Settings.data.appLauncher.overviewLayer);
|
||||
}
|
||||
function windows() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var searchText = PanelService.getLauncherSearchText(screen);
|
||||
var isInWindowsMode = searchText.startsWith(">win");
|
||||
if (!PanelService.isLauncherOpen(screen)) {
|
||||
PanelService.openLauncherWithSearch(screen, ">win ");
|
||||
} else if (isInWindowsMode) {
|
||||
PanelService.closeLauncher(screen);
|
||||
} else {
|
||||
PanelService.setLauncherSearchText(screen, ">win ");
|
||||
}
|
||||
}, Settings.data.appLauncher.overviewLayer);
|
||||
}
|
||||
function settings() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var searchText = PanelService.getLauncherSearchText(screen);
|
||||
var isInSettingsMode = searchText.startsWith(">settings");
|
||||
if (!PanelService.isLauncherOpen(screen)) {
|
||||
PanelService.openLauncherWithSearch(screen, ">settings ");
|
||||
} else if (isInSettingsMode) {
|
||||
PanelService.closeLauncher(screen);
|
||||
} else {
|
||||
PanelService.setLauncherSearchText(screen, ">settings ");
|
||||
}
|
||||
}, Settings.data.appLauncher.overviewLayer);
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "lockScreen"
|
||||
|
||||
// New preferred method - lock the screen
|
||||
function lock() {
|
||||
// Only lock if not already locked (prevents the red screen issue)
|
||||
if (!PanelService.lockScreen.active) {
|
||||
CompositorService.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "brightness"
|
||||
function increase() {
|
||||
BrightnessService.increaseBrightness();
|
||||
}
|
||||
function decrease() {
|
||||
BrightnessService.decreaseBrightness();
|
||||
}
|
||||
function set(value: string) {
|
||||
var val = parseFloat(value);
|
||||
if (isNaN(val))
|
||||
return;
|
||||
|
||||
// Normalize logic: heuristic handling of 0-100 vs 0-1
|
||||
if (val > 1.0)
|
||||
val = val / 100.0;
|
||||
|
||||
// Clamp
|
||||
val = Math.max(0.0, Math.min(1.0, val));
|
||||
|
||||
BrightnessService.setBrightness(val);
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "monitors"
|
||||
function on() {
|
||||
CompositorService.turnOnMonitors();
|
||||
}
|
||||
function off() {
|
||||
CompositorService.turnOffMonitors();
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "darkMode"
|
||||
function toggle() {
|
||||
Settings.data.colorSchemes.darkMode = !Settings.data.colorSchemes.darkMode;
|
||||
}
|
||||
function setDark() {
|
||||
Settings.data.colorSchemes.darkMode = true;
|
||||
}
|
||||
function setLight() {
|
||||
Settings.data.colorSchemes.darkMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "nightLight"
|
||||
function toggle() {
|
||||
if (!ProgramCheckerService.wlsunsetAvailable) {
|
||||
Logger.w("IPC", "wlsunset not available, cannot toggle night light");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Settings.data.nightLight.forced) {
|
||||
Settings.data.nightLight.forced = false;
|
||||
} else {
|
||||
if (Settings.data.nightLight.enabled) {
|
||||
Settings.data.nightLight.enabled = false;
|
||||
} else {
|
||||
Settings.data.nightLight.forced = true;
|
||||
Settings.data.nightLight.enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "colorScheme"
|
||||
function set(schemeName: string) {
|
||||
ColorSchemeService.setPredefinedScheme(schemeName);
|
||||
}
|
||||
function setGenerationMethod(method: string) {
|
||||
var valid = false;
|
||||
for (var i = 0; i < TemplateProcessor.schemeTypes.length; i++) {
|
||||
if (TemplateProcessor.schemeTypes[i].key === method) {
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
Settings.data.colorSchemes.generationMethod = method;
|
||||
} else {
|
||||
Logger.w("IPC", "Invalid generation method received: " + method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "volume"
|
||||
function increase() {
|
||||
AudioService.increaseVolume();
|
||||
}
|
||||
function decrease() {
|
||||
AudioService.decreaseVolume();
|
||||
}
|
||||
function muteOutput() {
|
||||
AudioService.setOutputMuted(!AudioService.muted);
|
||||
}
|
||||
function increaseInput() {
|
||||
AudioService.increaseInputVolume();
|
||||
}
|
||||
function decreaseInput() {
|
||||
AudioService.decreaseInputVolume();
|
||||
}
|
||||
function muteInput() {
|
||||
AudioService.setInputMuted(!AudioService.inputMuted);
|
||||
}
|
||||
function togglePanel() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var panel = PanelService.getPanel("audioPanel", screen);
|
||||
panel?.toggle(null, "Volume");
|
||||
});
|
||||
}
|
||||
function openPanel() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var panel = PanelService.getPanel("audioPanel", screen);
|
||||
panel?.open(null, "Volume");
|
||||
});
|
||||
}
|
||||
function closePanel() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var panel = PanelService.getPanel("audioPanel", screen);
|
||||
panel?.close(null, "Volume");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "sessionMenu"
|
||||
function toggle() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var sessionMenuPanel = PanelService.getPanel("sessionMenuPanel", screen);
|
||||
// Session Menu is never open near the bar
|
||||
sessionMenuPanel?.toggle();
|
||||
});
|
||||
}
|
||||
|
||||
function lock() {
|
||||
if (!PanelService.lockScreen.active) {
|
||||
CompositorService.lock();
|
||||
}
|
||||
}
|
||||
|
||||
function lockAndSuspend() {
|
||||
// Only lock and suspend if not already locked
|
||||
if (!PanelService.lockScreen.active) {
|
||||
CompositorService.lockAndSuspend();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "controlCenter"
|
||||
function toggle() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen);
|
||||
if (Settings.data.controlCenter.position === "close_to_bar_button") {
|
||||
// Will attempt to open the panel next to the bar button if any.
|
||||
controlCenterPanel?.toggle(null, "ControlCenter");
|
||||
} else {
|
||||
controlCenterPanel?.toggle();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "dock"
|
||||
function toggle() {
|
||||
Settings.data.dock.enabled = !Settings.data.dock.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
// Wallpaper IPC: trigger a new random wallpaper
|
||||
IpcHandler {
|
||||
target: "wallpaper"
|
||||
function toggle() {
|
||||
if (Settings.data.wallpaper.enabled) {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var wallpaperPanel = PanelService.getPanel("wallpaperPanel", screen);
|
||||
wallpaperPanel?.toggle();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function random(screen: string) {
|
||||
if (Settings.data.wallpaper.enabled) {
|
||||
if (!screen || screen === "all" || screen.trim().length === 0) {
|
||||
screen = undefined;
|
||||
}
|
||||
WallpaperService.setRandomWallpaper(screen);
|
||||
}
|
||||
}
|
||||
|
||||
function get(screen: string): string {
|
||||
if (screen === "all" || screen === "") {
|
||||
if (Quickshell.screens.length > 1) {
|
||||
return JSON.stringify(WallpaperService.getWallpapersEffectiveMap());
|
||||
}
|
||||
return WallpaperService.getWallpaper(Quickshell.screens[0].name) ?? "";
|
||||
} else {
|
||||
var found = Quickshell.screens.find(s => s.name === screen);
|
||||
if (!found) {
|
||||
Logger.w("IPC", "wallpaper get: unknown screen: " + screen);
|
||||
return "";
|
||||
}
|
||||
return WallpaperService.getWallpaper(screen) ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
function set(path: string, screen: string) {
|
||||
if (screen === "all" || screen === "") {
|
||||
screen = undefined;
|
||||
}
|
||||
WallpaperService.changeWallpaper(path, screen);
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
WallpaperService.refreshWallpapersList();
|
||||
}
|
||||
|
||||
function toggleAutomation() {
|
||||
Settings.data.wallpaper.automationEnabled = !Settings.data.wallpaper.automationEnabled;
|
||||
}
|
||||
function disableAutomation() {
|
||||
Settings.data.wallpaper.automationEnabled = false;
|
||||
}
|
||||
function enableAutomation() {
|
||||
Settings.data.wallpaper.automationEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "wifi"
|
||||
function toggle() {
|
||||
NetworkService.setWifiEnabled(!NetworkService.wifiEnabled);
|
||||
}
|
||||
function enable() {
|
||||
NetworkService.setWifiEnabled(true);
|
||||
}
|
||||
function disable() {
|
||||
NetworkService.setWifiEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "network"
|
||||
function togglePanel() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var networkPanel = PanelService.getPanel("networkPanel", screen);
|
||||
networkPanel?.toggle(null, "Network");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "bluetooth"
|
||||
function toggle() {
|
||||
BluetoothService.setBluetoothEnabled(!BluetoothService.enabled);
|
||||
}
|
||||
function enable() {
|
||||
BluetoothService.setBluetoothEnabled(true);
|
||||
}
|
||||
function disable() {
|
||||
BluetoothService.setBluetoothEnabled(false);
|
||||
}
|
||||
function togglePanel() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var bluetoothPanel = PanelService.getPanel("bluetoothPanel", screen);
|
||||
bluetoothPanel?.toggle(null, "Bluetooth");
|
||||
});
|
||||
}
|
||||
function toggleAutoConnect() {
|
||||
Settings.data.network.bluetoothAutoConnect = !Settings.data.network.bluetoothAutoConnect;
|
||||
}
|
||||
function enableAutoConnect() {
|
||||
Settings.data.network.bluetoothAutoConnect = true;
|
||||
}
|
||||
function disableAutoConnect() {
|
||||
Settings.data.network.bluetoothAutoConnect = false;
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "airplaneMode"
|
||||
function toggle() {
|
||||
NetworkService.setAirplaneMode(!NetworkService.airplaneModeEnabled);
|
||||
}
|
||||
function enable() {
|
||||
NetworkService.setAirplaneMode(true);
|
||||
}
|
||||
function disable() {
|
||||
NetworkService.setAirplaneMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "battery"
|
||||
function togglePanel() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var batteryPanel = PanelService.getPanel("batteryPanel", screen);
|
||||
batteryPanel?.toggle(null, "Battery");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "powerProfile"
|
||||
function cycle() {
|
||||
PowerProfileService.cycleProfile();
|
||||
}
|
||||
|
||||
function cycleReverse() {
|
||||
PowerProfileService.cycleProfileReverse();
|
||||
}
|
||||
|
||||
function set(mode: string) {
|
||||
switch (mode) {
|
||||
case "performance":
|
||||
PowerProfileService.setProfile(2);
|
||||
break;
|
||||
case "balanced":
|
||||
PowerProfileService.setProfile(1);
|
||||
break;
|
||||
case "powersaver":
|
||||
PowerProfileService.setProfile(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleNoctaliaPerformance() {
|
||||
PowerProfileService.toggleNoctaliaPerformance();
|
||||
}
|
||||
|
||||
function enableNoctaliaPerformance() {
|
||||
PowerProfileService.setNoctaliaPerformance(true);
|
||||
}
|
||||
|
||||
function disableNoctaliaPerformance() {
|
||||
PowerProfileService.setNoctaliaPerformance(false);
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "media"
|
||||
|
||||
function toggle() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var panel = PanelService.getPanel("mediaPlayerPanel", screen);
|
||||
panel?.toggle(null, "MediaMini");
|
||||
});
|
||||
}
|
||||
|
||||
function playPause() {
|
||||
MediaService.playPause();
|
||||
}
|
||||
|
||||
function play() {
|
||||
MediaService.play();
|
||||
}
|
||||
|
||||
function stop() {
|
||||
MediaService.stop();
|
||||
}
|
||||
|
||||
function pause() {
|
||||
MediaService.pause();
|
||||
}
|
||||
|
||||
function next() {
|
||||
MediaService.next();
|
||||
}
|
||||
|
||||
function previous() {
|
||||
MediaService.previous();
|
||||
}
|
||||
|
||||
function seekRelative(offset: string) {
|
||||
var offsetVal = parseFloat(offset);
|
||||
if (Number.isNaN(offsetVal)) {
|
||||
Logger.w("Media", "Argument to ipc call 'media seekRelative' must be a number");
|
||||
return;
|
||||
}
|
||||
MediaService.seekRelative(offsetVal);
|
||||
}
|
||||
|
||||
function seekByRatio(position: string) {
|
||||
var positionVal = parseFloat(position);
|
||||
if (Number.isNaN(positionVal)) {
|
||||
Logger.w("Media", "Argument to ipc call 'media seekByRatio' must be a number");
|
||||
return;
|
||||
}
|
||||
MediaService.seekByRatio(positionVal);
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "state"
|
||||
|
||||
// Returns all settings and shell state as JSON
|
||||
function all(): string {
|
||||
try {
|
||||
var snapshot = ShellState.buildStateSnapshot();
|
||||
if (!snapshot) {
|
||||
throw new Error("State snapshot unavailable");
|
||||
}
|
||||
return JSON.stringify(snapshot, null, 2);
|
||||
} catch (error) {
|
||||
Logger.e("IPC", "Failed to serialize state:", error);
|
||||
return JSON.stringify({
|
||||
"error": "Failed to serialize state: " + error
|
||||
}, null, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "desktopWidgets"
|
||||
function toggle() {
|
||||
Settings.data.desktopWidgets.enabled = !Settings.data.desktopWidgets.enabled;
|
||||
}
|
||||
function disable() {
|
||||
Settings.data.desktopWidgets.enabled = false;
|
||||
}
|
||||
function enable() {
|
||||
Settings.data.desktopWidgets.enabled = true;
|
||||
}
|
||||
function edit() {
|
||||
DesktopWidgetRegistry.editMode = !DesktopWidgetRegistry.editMode;
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "location"
|
||||
function get(): string {
|
||||
return Settings.data.location.name;
|
||||
}
|
||||
function set(name: string) {
|
||||
Settings.data.location.name = name;
|
||||
LocationService.update();
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "systemMonitor"
|
||||
function toggle() {
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var panel = PanelService.getPanel("systemStatsPanel", screen);
|
||||
panel?.toggle(null, "SystemMonitor");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "plugin"
|
||||
function openSettings(key: string) {
|
||||
var manifest = PluginRegistry.getPluginManifest(key);
|
||||
if (!manifest) {
|
||||
Logger.w("IPC", "Plugin not found:", key);
|
||||
return;
|
||||
}
|
||||
if (!manifest.entryPoints?.settings) {
|
||||
Logger.w("IPC", "Plugin has no settings entry point:", key);
|
||||
return;
|
||||
}
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
BarService.openPluginSettings(screen, manifest);
|
||||
});
|
||||
}
|
||||
|
||||
function openPanel(key: string) {
|
||||
var manifest = PluginRegistry.getPluginManifest(key);
|
||||
if (!manifest) {
|
||||
Logger.w("IPC", "Plugin not found:", key);
|
||||
return;
|
||||
}
|
||||
if (!manifest.entryPoints?.panel) {
|
||||
Logger.w("IPC", "Plugin has no panel entry point:", key);
|
||||
return;
|
||||
}
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
PluginService.openPluginPanel(key, screen, null);
|
||||
});
|
||||
}
|
||||
|
||||
function closePanel(key: string) {
|
||||
var manifest = PluginRegistry.getPluginManifest(key);
|
||||
if (!manifest) {
|
||||
Logger.w("IPC", "Plugin not found:", key);
|
||||
return;
|
||||
}
|
||||
if (!manifest.entryPoints?.panel) {
|
||||
Logger.w("IPC", "Plugin has no panel entry point:", key);
|
||||
return;
|
||||
}
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
var api = PluginService.getPluginAPI(key);
|
||||
if (api) {
|
||||
api.closePanel(screen);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function togglePanel(key: string) {
|
||||
var manifest = PluginRegistry.getPluginManifest(key);
|
||||
if (!manifest) {
|
||||
Logger.w("IPC", "Plugin not found:", key);
|
||||
return;
|
||||
}
|
||||
if (!manifest.entryPoints?.panel) {
|
||||
Logger.w("IPC", "Plugin has no panel entry point:", key);
|
||||
return;
|
||||
}
|
||||
root.screenDetector.withCurrentScreen(screen => {
|
||||
PluginService.togglePluginPanel(key, screen, null);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user