Files
noctalia-shell/Services/Control/IPCService.qml
T
ItsLemmy dc08751ec2 Multi-Monitors: new setting to explicitely disable panels to appear on screen without bar.
On my setup this saves about 130MB per 1080p monitor where the bar is
not shown. I only interact with noctalia on my main screen.
2025-11-16 20:49:56 -05:00

435 lines
12 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import qs.Commons
import qs.Services.Compositor
import qs.Services.Hardware
import qs.Services.Media
import qs.Services.Power
import qs.Services.System
import qs.Services.Theming
import qs.Services.UI
Item {
id: root
IpcHandler {
target: "bar"
function toggle() {
BarService.isVisible = !BarService.isVisible;
}
}
IpcHandler {
target: "screenRecorder"
function toggle() {
if (ScreenRecorderService.isAvailable) {
ScreenRecorderService.toggleRecording();
}
}
}
IpcHandler {
target: "settings"
function toggle() {
root.withTargetScreen(screen => {
var settingsPanel = PanelService.getPanel("settingsPanel", screen);
settingsPanel?.toggle();
});
}
}
IpcHandler {
target: "calendar"
function toggle() {
root.withTargetScreen(screen => {
var calendarPanel = PanelService.getPanel("calendarPanel", screen);
calendarPanel?.toggle(null, "Clock");
});
}
}
IpcHandler {
target: "notifications"
function toggleHistory() {
// Will attempt to open the panel next to the bar button if any.
root.withTargetScreen(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.dismissOldestActive();
}
function dismissAll() {
NotificationService.dismissAllActive();
}
}
IpcHandler {
target: "idleInhibitor"
function toggle() {
return IdleInhibitorService.manualToggle();
}
}
IpcHandler {
target: "launcher"
function toggle() {
root.withTargetScreen(screen => {
var launcherPanel = PanelService.getPanel("launcherPanel", screen);
launcherPanel?.toggle();
});
}
function clipboard() {
root.withTargetScreen(screen => {
var launcherPanel = PanelService.getPanel("launcherPanel", screen);
launcherPanel?.setSearchText(">clip ");
launcherPanel?.toggle();
});
}
function calculator() {
root.withTargetScreen(screen => {
var launcherPanel = PanelService.getPanel("launcherPanel", screen);
launcherPanel?.setSearchText(">calc ");
launcherPanel?.toggle();
});
}
}
IpcHandler {
target: "lockScreen"
// New preferred method - lock the screen
function lock() {
// Only lock if not already locked (prevents the red screen issue)
// Note: No unlock via IPC for security reasons
if (!lockScreen.active) {
lockScreen.triggeredViaDeprecatedCall = false;
lockScreen.active = true;
}
}
// Deprecated: Use 'lockScreen lock' instead
function toggle() {
// Mark as triggered via deprecated call - warning will show in lock screen
lockScreen.triggeredViaDeprecatedCall = true;
// Log deprecation warning for users checking logs
Logger.w("IPC", "The 'lockScreen toggle' IPC call is deprecated. Use 'lockScreen lock' instead.");
// Still functional for backward compatibility
if (!lockScreen.active) {
lockScreen.active = true;
}
}
}
IpcHandler {
target: "brightness"
function increase() {
BrightnessService.increaseBrightness();
}
function decrease() {
BrightnessService.decreaseBrightness();
}
}
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: "colorScheme"
function set(schemeName: string) {
ColorSchemeService.setPredefinedScheme(schemeName);
}
}
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);
}
}
IpcHandler {
target: "sessionMenu"
function toggle() {
root.withTargetScreen(screen => {
var sessionMenuPanel = PanelService.getPanel("sessionMenuPanel", screen);
sessionMenuPanel?.toggle();
});
}
function lockAndSuspend() {
CompositorService.lockAndSuspend();
}
}
IpcHandler {
target: "controlCenter"
function toggle() {
root.withTargetScreen(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();
}
});
}
}
// Wallpaper IPC: trigger a new random wallpaper
IpcHandler {
target: "wallpaper"
function toggle() {
if (Settings.data.wallpaper.enabled) {
root.withTargetScreen(screen => {
var wallpaperPanel = PanelService.getPanel("wallpaperPanel", screen);
wallpaperPanel?.toggle();
});
}
}
function random() {
if (Settings.data.wallpaper.enabled) {
WallpaperService.setRandomWallpaper();
}
}
function set(path: string, screen: string) {
if (screen === "all" || screen === "") {
screen = undefined;
}
WallpaperService.changeWallpaper(path, screen);
}
function toggleAutomation() {
Settings.data.wallpaper.randomEnabled = !Settings.data.wallpaper.randomEnabled;
}
function disableAutomation() {
Settings.data.wallpaper.randomEnabled = false;
}
function enableAutomation() {
Settings.data.wallpaper.randomEnabled = true;
}
}
IpcHandler {
target: "batteryManager"
function cycle() {
BatteryService.cycleModes();
}
function set(mode: string) {
switch (mode) {
case "full":
BatteryService.setChargingMode(BatteryService.ChargingMode.Full);
break;
case "balanced":
BatteryService.setChargingMode(BatteryService.ChargingMode.Balanced);
break;
case "lifespan":
BatteryService.setChargingMode(BatteryService.ChargingMode.Lifespan);
break;
}
}
}
IpcHandler {
target: "powerProfile"
function cycle() {
PowerProfileService.cycleProfile();
}
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 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);
}
}
// Queue an IPC panel operation - will execute when screen is detected
function withTargetScreen(callback) {
if (pendingCallback) {
Logger.w("IPC", "Another IPC call is pending, ignoring new call");
return;
}
// Single monitor setup can execute immediately
if (Quickshell.screens.length === 1) {
callback(Quickshell.screens[0]);
} else {
// Multi-monitors setup needs to start async detection
detectedScreen = null;
pendingCallback = callback;
screenDetectorLoader.active = true;
}
}
/**
* For IPC calls on multi-monitors setup that will open panels on screen,
* we need to open a QS PanelWindow and wait for it's "screen" property to stabilize.
*/
property ShellScreen detectedScreen: null
property var pendingCallback: null
Timer {
id: screenDetectorDebounce
running: false
interval: 20
onTriggered: {
Logger.d("IPC", "Screen debounced to:", detectedScreen?.name || "null");
// Execute pending callback if any
if (pendingCallback) {
if (!Settings.data.general.allowPanelsOnScreenWithoutBar) {
// If we explicitely 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(detectedScreen?.name);
if (!hasBar) {
detectedScreen = Quickshell.screens[0];
}
}
Logger.d("IPC", "Executing pending IPC callback on screen:", detectedScreen.name);
pendingCallback(detectedScreen);
pendingCallback = null;
}
// Clean up
screenDetectorLoader.active = false;
}
}
// Invisible dummy PanelWindow to detect which screen should receive IPC calls
Loader {
id: screenDetectorLoader
active: false
sourceComponent: PanelWindow {
implicitWidth: 0
implicitHeight: 0
color: Color.transparent
WlrLayershell.exclusionMode: ExclusionMode.Ignore
mask: Region {}
onScreenChanged: {
detectedScreen = screen;
screenDetectorDebounce.restart();
}
}
}
}