Merge branch 'main' into feat/wezterm

This commit is contained in:
Lemmy
2025-11-03 15:34:41 -05:00
committed by GitHub
83 changed files with 5697 additions and 2061 deletions
+4 -2
View File
@@ -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,
+2 -2
View File
@@ -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"))
+32 -3
View File
@@ -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
+9 -1
View File
@@ -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
View File
@@ -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()
}
}
}
}
+1 -1
View File
@@ -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
+49 -7
View File
@@ -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