mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-06-08 20:55:16 +00:00
Merge branch 'main' into feat/wezterm
This commit is contained in:
@@ -101,10 +101,12 @@ Singleton {
|
||||
},
|
||||
"LockKeys": {
|
||||
"allowUserSettings": true,
|
||||
"indicatorStyle": "large",
|
||||
"showCapsLock": true,
|
||||
"showNumLock": true,
|
||||
"showScrollLock": true
|
||||
"showScrollLock": true,
|
||||
"capsLockIcon": "letter-c",
|
||||
"numLockIcon": "letter-n",
|
||||
"scrollLockIcon": "letter-s"
|
||||
},
|
||||
"MediaMini": {
|
||||
"allowUserSettings": true,
|
||||
|
||||
@@ -62,7 +62,7 @@ Singleton {
|
||||
} else {
|
||||
BatteryService.initialSetter = true
|
||||
ToastService.showNotice(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.uninstall-setup"))
|
||||
PanelService.getPanel("batteryPanel")?.toggle(this)
|
||||
PanelService.getPanel("batteryPanel", screen)?.toggle(this)
|
||||
uninstallerProcess.running = true
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ Singleton {
|
||||
Settings.data.battery.chargingMode = BatteryService.chargingMode
|
||||
} else if (exitCode === 2) {
|
||||
ToastService.showWarning(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.initial-setup"))
|
||||
PanelService.getPanel("batteryPanel")?.toggle(this)
|
||||
PanelService.getPanel("batteryPanel", screen)?.toggle(this)
|
||||
BatteryService.runInstaller()
|
||||
} else {
|
||||
ToastService.showError(I18n.tr("toast.battery-manager.title"), I18n.tr("toast.battery-manager.set-failed"))
|
||||
|
||||
@@ -21,7 +21,7 @@ Singleton {
|
||||
|
||||
function getAvailableMethods(): list<string> {
|
||||
var methods = []
|
||||
if (monitors.some(m => m.isDdc))
|
||||
if (Settings.data.brightness.enableDdcSupport && monitors.some(m => m.isDdc))
|
||||
methods.push("ddcutil")
|
||||
if (monitors.some(m => !m.isDdc))
|
||||
methods.push("internal")
|
||||
@@ -47,11 +47,30 @@ Singleton {
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.i("Brightness", "Service started")
|
||||
if (Settings.data.brightness.enableDdcSupport) {
|
||||
ddcProc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
onMonitorsChanged: {
|
||||
ddcMonitors = []
|
||||
ddcProc.running = true
|
||||
if (Settings.data.brightness.enableDdcSupport) {
|
||||
ddcProc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Settings.data.brightness
|
||||
function onEnableDdcSupportChanged() {
|
||||
if (Settings.data.brightness.enableDdcSupport) {
|
||||
// Re-detect DDC monitors when enabled
|
||||
ddcMonitors = []
|
||||
ddcProc.running = true
|
||||
} else {
|
||||
// Clear DDC monitors when disabled
|
||||
ddcMonitors = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
@@ -101,11 +120,21 @@ Singleton {
|
||||
id: monitor
|
||||
|
||||
required property ShellScreen modelData
|
||||
readonly property bool isDdc: root.ddcMonitors.some(m => m.model === modelData.model)
|
||||
readonly property bool isDdc: Settings.data.brightness.enableDdcSupport && root.ddcMonitors.some(m => m.model === modelData.model)
|
||||
readonly property string busNum: root.ddcMonitors.find(m => m.model === modelData.model)?.busNum ?? ""
|
||||
readonly property bool isAppleDisplay: root.appleDisplayPresent && modelData.model.startsWith("StudioDisplay")
|
||||
readonly property string method: isAppleDisplay ? "apple" : (isDdc ? "ddcutil" : "internal")
|
||||
|
||||
// Check if brightness control is available for this monitor
|
||||
readonly property bool brightnessControlAvailable: {
|
||||
if (isAppleDisplay)
|
||||
return true
|
||||
if (isDdc)
|
||||
return true
|
||||
// For internal displays, check if we have a brightness path
|
||||
return brightnessPath !== ""
|
||||
}
|
||||
|
||||
property real brightness
|
||||
property real lastBrightness: 0
|
||||
property real queuedBrightness: NaN
|
||||
|
||||
@@ -8,7 +8,15 @@ import qs.Commons
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property bool shouldRun: BarService.hasAudioVisualizer || (PanelService.getPanel("controlCenterPanel") === PanelService.openedPanel) || PanelService.lockScreen.active
|
||||
|
||||
/**
|
||||
* Cava runs if:
|
||||
* - Bar has an audio visualizer
|
||||
* - LockScreen is opened
|
||||
* - A control center is open
|
||||
*/
|
||||
property bool shouldRun: BarService.hasAudioVisualizer || PanelService.lockScreen.active || (PanelService.openedPanel && PanelService.openedPanel.objectName.startsWith("controlCenterPanel"))
|
||||
|
||||
property var values: Array(barsCount).fill(0)
|
||||
property int barsCount: 48
|
||||
property var config: ({
|
||||
|
||||
+116
-10
@@ -2,6 +2,7 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
|
||||
@@ -27,7 +28,10 @@ Item {
|
||||
IpcHandler {
|
||||
target: "settings"
|
||||
function toggle() {
|
||||
settingsPanel.toggle()
|
||||
root.withTargetScreen(screen => {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel", screen)
|
||||
settingsPanel.toggle()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +39,10 @@ Item {
|
||||
target: "notifications"
|
||||
function toggleHistory() {
|
||||
// Will attempt to open the panel next to the bar button if any.
|
||||
notificationHistoryPanel.toggle(null, "NotificationHistory")
|
||||
root.withTargetScreen(screen => {
|
||||
var notificationHistoryPanel = PanelService.getPanel("notificationHistoryPanel", screen)
|
||||
notificationHistoryPanel.toggle(null, "NotificationHistory")
|
||||
})
|
||||
}
|
||||
function toggleDND() {
|
||||
Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
|
||||
@@ -63,15 +70,24 @@ Item {
|
||||
IpcHandler {
|
||||
target: "launcher"
|
||||
function toggle() {
|
||||
launcherPanel.toggle()
|
||||
root.withTargetScreen(screen => {
|
||||
var launcherPanel = PanelService.getPanel("launcherPanel", screen)
|
||||
launcherPanel.toggle()
|
||||
})
|
||||
}
|
||||
function clipboard() {
|
||||
launcherPanel.setSearchText(">clip ")
|
||||
launcherPanel.toggle()
|
||||
root.withTargetScreen(screen => {
|
||||
var launcherPanel = PanelService.getPanel("launcherPanel", screen)
|
||||
launcherPanel.setSearchText(">clip ")
|
||||
launcherPanel.toggle()
|
||||
})
|
||||
}
|
||||
function calculator() {
|
||||
launcherPanel.setSearchText(">calc ")
|
||||
launcherPanel.toggle()
|
||||
root.withTargetScreen(screen => {
|
||||
var launcherPanel = PanelService.getPanel("launcherPanel", screen)
|
||||
launcherPanel.setSearchText(">calc ")
|
||||
launcherPanel.toggle()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +174,10 @@ Item {
|
||||
IpcHandler {
|
||||
target: "sessionMenu"
|
||||
function toggle() {
|
||||
sessionMenuPanel.toggle()
|
||||
root.withTargetScreen(screen => {
|
||||
var sessionMenuPanel = PanelService.getPanel("sessionMenuPanel", screen)
|
||||
sessionMenuPanel.toggle()
|
||||
})
|
||||
}
|
||||
|
||||
function lockAndSuspend() {
|
||||
@@ -170,7 +189,10 @@ Item {
|
||||
target: "controlCenter"
|
||||
function toggle() {
|
||||
// Will attempt to open the panel next to the bar button if any.
|
||||
controlCenterPanel.toggle(null, "ControlCenter")
|
||||
root.withTargetScreen(screen => {
|
||||
var controlCenterPanel = PanelService.getPanel("controlCenterPanel", screen)
|
||||
controlCenterPanel.toggle(null, "ControlCenter")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +201,10 @@ Item {
|
||||
target: "wallpaper"
|
||||
function toggle() {
|
||||
if (Settings.data.wallpaper.enabled) {
|
||||
wallpaperPanel.toggle()
|
||||
root.withTargetScreen(screen => {
|
||||
var wallpaperPanel = PanelService.getPanel("wallpaperPanel", screen)
|
||||
wallpaperPanel.toggle()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +253,7 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "powerProfile"
|
||||
function cycle() {
|
||||
@@ -248,6 +274,7 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "media"
|
||||
function playPause() {
|
||||
@@ -292,4 +319,83 @@ Item {
|
||||
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) {
|
||||
// Verify we have a NFullScreenWindow for this screen
|
||||
var monitors = Settings.data.bar.monitors || []
|
||||
if (!(monitors.length === 0 || monitors.includes(detectedScreen.name))) {
|
||||
// Fall back to first enabled screen as we can NOT show a panel on a screen without a Bar/NFullScreenWindow
|
||||
if (monitors.length === 0 && Quickshell.screens.length > 0) {
|
||||
detectedScreen = Quickshell.screens[0]
|
||||
} else {
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
if (monitors.includes(Quickshell.screens[i].name)) {
|
||||
detectedScreen = Quickshell.screens[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ Singleton {
|
||||
repeat: true
|
||||
running: true
|
||||
onTriggered: {
|
||||
Logger.d("MediaService", "playerStateMonitor triggered. autoSwitchingPaused: " + root.autoSwitchingPaused)
|
||||
//Logger.d("MediaService", "playerStateMonitor triggered. autoSwitchingPaused: " + root.autoSwitchingPaused)
|
||||
if (autoSwitchingPaused)
|
||||
return
|
||||
// Only update if we don't have a playing player or if current player is paused
|
||||
|
||||
@@ -14,6 +14,7 @@ Singleton {
|
||||
property var registeredPanels: ({})
|
||||
property var openedPanel: null
|
||||
signal willOpen
|
||||
signal didClose
|
||||
|
||||
// Currently opened popups, can have more than one.
|
||||
// ex: when opening an NIconPicker from a widget setting.
|
||||
@@ -21,15 +22,53 @@ Singleton {
|
||||
property bool hasOpenedPopup: false
|
||||
signal popupChanged
|
||||
|
||||
// Register this panel
|
||||
function registerPanel(panel) {
|
||||
registeredPanels[panel.objectName] = panel
|
||||
Logger.d("PanelService", "Registered:", panel.objectName)
|
||||
// Registered panel loaders (before they're loaded)
|
||||
property var registeredPanelLoaders: ({})
|
||||
|
||||
// Register a panel loader (called before panel is loaded)
|
||||
function registerPanelLoader(panelLoader, objectName) {
|
||||
registeredPanelLoaders[objectName] = panelLoader
|
||||
Logger.d("PanelService", "Registered panel loader:", objectName)
|
||||
}
|
||||
|
||||
// Returns a panel
|
||||
function getPanel(name) {
|
||||
return registeredPanels[name] || null
|
||||
// Register this panel (called after panel is loaded)
|
||||
function registerPanel(panel) {
|
||||
registeredPanels[panel.objectName] = panel
|
||||
Logger.i("PanelService", "Registered panel:", panel.objectName)
|
||||
}
|
||||
|
||||
// Returns a panel (loads it on-demand if not yet loaded)
|
||||
function getPanel(name, screen) {
|
||||
if (!screen) {
|
||||
Logger.w("PanelService", "missing screen for getPanel:", name)
|
||||
Logger.callStack()
|
||||
// If no screen specified, return the first matching panel
|
||||
for (var key in registeredPanels) {
|
||||
if (key.startsWith(name + "-")) {
|
||||
return registeredPanels[key]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
var panelKey = `${name}-${screen.name}`
|
||||
|
||||
// Check if panel is already loaded
|
||||
if (registeredPanels[panelKey]) {
|
||||
return registeredPanels[panelKey]
|
||||
}
|
||||
|
||||
// Panel not loaded yet - try to load it via the loader
|
||||
if (registeredPanelLoaders[panelKey]) {
|
||||
Logger.d("PanelService", "Loading panel on-demand:", panelKey)
|
||||
registeredPanelLoaders[panelKey].ensureLoaded()
|
||||
// After ensureLoaded(), the panel should register itself via registerPanel()
|
||||
// Return it if it registered synchronously
|
||||
return registeredPanels[panelKey] || null
|
||||
}
|
||||
|
||||
Logger.w("PanelService", "Panel not found:", panelKey)
|
||||
return null
|
||||
}
|
||||
|
||||
// Check if a panel exists
|
||||
@@ -52,6 +91,9 @@ Singleton {
|
||||
if (openedPanel && openedPanel === panel) {
|
||||
openedPanel = null
|
||||
}
|
||||
|
||||
// emit signal
|
||||
didClose()
|
||||
}
|
||||
|
||||
// Popups
|
||||
|
||||
Reference in New Issue
Block a user