mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 18:43:59 +00:00
Merge upstream/main into feat/taskbar-workspace-numbers
- Resolved file location conflicts by moving TaskbarGroupedSettings.qml to Modules/Panels/Settings/Bar/WidgetSettings/ - Fixed translation conflicts in de.json, es.json, fr.json, pt.json, and zh-CN.json by adding taskbar-grouped translation sections - Maintained all existing functionality while adapting to new project structure - Preserved workspace number feature functionality across all supported languages Conflicts resolved: - Translation files: Added taskbar-grouped sections after existing taskbar sections - File location: Moved settings file to new Panels directory structure
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property bool hasAudioVisualizer: false
|
||||
property bool isVisible: true
|
||||
property var readyBars: ({})
|
||||
|
||||
// Registry to store actual widget instances
|
||||
// Key format: "screenName|section|widgetId|index"
|
||||
property var widgetInstances: ({})
|
||||
|
||||
signal activeWidgetsChanged
|
||||
signal barReadyChanged(string screenName)
|
||||
|
||||
// onHasAudioVisualizerChanged: {
|
||||
// Logger.d("BarService", "hasAudioVisualizer", hasAudioVisualizer)
|
||||
// }
|
||||
|
||||
// Simple timer that run once when the widget structure has changed
|
||||
// and determine if any MediaMini widget has the visualizer on
|
||||
Timer {
|
||||
id: timerCheckVisualizer
|
||||
interval: 100
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
hasAudioVisualizer = false
|
||||
if (getAllWidgetInstances("AudioVisualizer").length > 0) {
|
||||
hasAudioVisualizer = true
|
||||
return
|
||||
}
|
||||
const widgets = getAllWidgetInstances("MediaMini")
|
||||
for (var i = 0; i < widgets.length; i++) {
|
||||
const widget = widgets[i]
|
||||
if (widget.showVisualizer) {
|
||||
hasAudioVisualizer = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.i("BarService", "Service started")
|
||||
}
|
||||
|
||||
// Function for the Bar to call when it's ready
|
||||
function registerBar(screenName) {
|
||||
if (!readyBars[screenName]) {
|
||||
readyBars[screenName] = true
|
||||
Logger.d("BarService", "Bar is ready on screen:", screenName)
|
||||
barReadyChanged(screenName)
|
||||
}
|
||||
}
|
||||
|
||||
// Function for the Dock to check if the bar is ready
|
||||
function isBarReady(screenName) {
|
||||
return readyBars[screenName] || false
|
||||
}
|
||||
|
||||
// Register a widget instance
|
||||
function registerWidget(screenName, section, widgetId, index, instance) {
|
||||
const key = [screenName, section, widgetId, index].join("|")
|
||||
widgetInstances[key] = {
|
||||
"key": key,
|
||||
"screenName": screenName,
|
||||
"section": section,
|
||||
"widgetId": widgetId,
|
||||
"index": index,
|
||||
"instance": instance
|
||||
}
|
||||
|
||||
timerCheckVisualizer.restart()
|
||||
|
||||
Logger.d("BarService", "Registered widget:", key)
|
||||
root.activeWidgetsChanged()
|
||||
}
|
||||
|
||||
// Unregister a widget instance
|
||||
function unregisterWidget(screenName, section, widgetId, index) {
|
||||
const key = [screenName, section, widgetId, index].join("|")
|
||||
delete widgetInstances[key]
|
||||
Logger.d("BarService", "Unregistered widget:", key)
|
||||
root.activeWidgetsChanged()
|
||||
}
|
||||
|
||||
// Lookup a specific widget instance (returns the actual QML instance)
|
||||
function lookupWidget(widgetId, screenName = null, section = null, index = null) {
|
||||
// If looking for a specific instance
|
||||
if (screenName && section !== null) {
|
||||
for (var key in widgetInstances) {
|
||||
var widget = widgetInstances[key]
|
||||
if (widget.widgetId === widgetId && widget.screenName === screenName && widget.section === section) {
|
||||
if (index === null) {
|
||||
return widget.instance
|
||||
} else if (widget.index == index) {
|
||||
return widget.instance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return first match if no specific screen/section specified
|
||||
for (var key in widgetInstances) {
|
||||
var widget = widgetInstances[key]
|
||||
if (widget.widgetId === widgetId) {
|
||||
if (!screenName || widget.screenName === screenName) {
|
||||
if (section === null || widget.section === section) {
|
||||
return widget.instance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Get all instances of a widget type
|
||||
function getAllWidgetInstances(widgetId = null, screenName = null, section = null) {
|
||||
var instances = []
|
||||
|
||||
for (var key in widgetInstances) {
|
||||
var widget = widgetInstances[key]
|
||||
|
||||
var matches = true
|
||||
if (widgetId && widget.widgetId !== widgetId)
|
||||
matches = false
|
||||
if (screenName && widget.screenName !== screenName)
|
||||
matches = false
|
||||
if (section !== null && widget.section !== section)
|
||||
matches = false
|
||||
|
||||
if (matches) {
|
||||
instances.push(widget.instance)
|
||||
}
|
||||
}
|
||||
|
||||
return instances
|
||||
}
|
||||
|
||||
// Get widget with full metadata
|
||||
function getWidgetWithMetadata(widgetId, screenName = null, section = null) {
|
||||
for (var key in widgetInstances) {
|
||||
var widget = widgetInstances[key]
|
||||
if (widget.widgetId === widgetId) {
|
||||
if (!screenName || widget.screenName === screenName) {
|
||||
if (section === null || widget.section === section) {
|
||||
return widget
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Get all widgets in a specific section
|
||||
function getWidgetsBySection(section, screenName = null) {
|
||||
var widgets = []
|
||||
|
||||
for (var key in widgetInstances) {
|
||||
var widget = widgetInstances[key]
|
||||
if (widget.section === section) {
|
||||
if (!screenName || widget.screenName === screenName) {
|
||||
widgets.push(widget.instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by index to maintain order
|
||||
widgets.sort(function (a, b) {
|
||||
var aWidget = getWidgetWithMetadata(a.widgetId, a.screen?.name, a.section)
|
||||
var bWidget = getWidgetWithMetadata(b.widgetId, b.screen?.name, b.section)
|
||||
return (aWidget?.index || 0) - (bWidget?.index || 0)
|
||||
})
|
||||
|
||||
return widgets
|
||||
}
|
||||
|
||||
// Get all registered widgets (for debugging)
|
||||
function getAllRegisteredWidgets() {
|
||||
var result = []
|
||||
for (var key in widgetInstances) {
|
||||
result.push({
|
||||
"key": key,
|
||||
"widgetId": widgetInstances[key].widgetId,
|
||||
"section": widgetInstances[key].section,
|
||||
"screenName": widgetInstances[key].screenName,
|
||||
"index": widgetInstances[key].index
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Check if a widget type exists in a section
|
||||
function hasWidget(widgetId, section = null, screenName = null) {
|
||||
for (var key in widgetInstances) {
|
||||
var widget = widgetInstances[key]
|
||||
if (widget.widgetId === widgetId) {
|
||||
if (section === null || widget.section === section) {
|
||||
if (!screenName || widget.screenName === screenName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get pill direction for a widget instance
|
||||
function getPillDirection(widgetInstance) {
|
||||
try {
|
||||
if (widgetInstance.section === "left") {
|
||||
return true
|
||||
} else if (widgetInstance.section === "right") {
|
||||
return false
|
||||
} else {
|
||||
// middle section
|
||||
if (widgetInstance.sectionWidgetIndex < widgetInstance.sectionWidgetsCount / 2) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.e(e)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function getTooltipDirection() {
|
||||
switch (Settings.data.bar.position) {
|
||||
case "right":
|
||||
return "left"
|
||||
case "left":
|
||||
return "right"
|
||||
case "bottom":
|
||||
return "top"
|
||||
default:
|
||||
return "bottom"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Bar.Widgets
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Widget registry object mapping widget names to components
|
||||
property var widgets: ({
|
||||
"ActiveWindow": activeWindowComponent,
|
||||
"AudioVisualizer": audioVisualizerComponent,
|
||||
"Battery"// HEAVY
|
||||
: batteryComponent,
|
||||
"Bluetooth": bluetoothComponent,
|
||||
"Brightness": brightnessComponent,
|
||||
"Clock": clockComponent,
|
||||
"ControlCenter": controlCenterComponent,
|
||||
"CustomButton": customButtonComponent,
|
||||
"DarkMode": darkModeComponent,
|
||||
"KeepAwake": keepAwakeComponent,
|
||||
"KeyboardLayout": keyboardLayoutComponent,
|
||||
"LockKeys": lockKeysComponent,
|
||||
"MediaMini": mediaMiniComponent,
|
||||
"Microphone": microphoneComponent,
|
||||
"NightLight": nightLightComponent,
|
||||
"NoctaliaPerformance": noctaliaPerformanceComponent,
|
||||
"NotificationHistory": notificationHistoryComponent,
|
||||
"PowerProfile"// HEAVY
|
||||
: powerProfileComponent,
|
||||
"ScreenRecorder": screenRecorderComponent,
|
||||
"SessionMenu": sessionMenuComponent,
|
||||
"Spacer": spacerComponent,
|
||||
"SystemMonitor": systemMonitorComponent,
|
||||
"Taskbar"// HEAVY
|
||||
: taskbarComponent,
|
||||
"TaskbarGrouped": taskbarGroupedComponent,
|
||||
"Tray": trayComponent,
|
||||
"Volume"// A BIT HEAVY ?
|
||||
: volumeComponent,
|
||||
"WiFi": wiFiComponent,
|
||||
"WallpaperSelector": wallpaperSelectorComponent,
|
||||
"Workspace": workspaceComponent // HEAVY
|
||||
})
|
||||
|
||||
property var widgetMetadata: ({
|
||||
"ActiveWindow": {
|
||||
"allowUserSettings": true,
|
||||
"showIcon": true,
|
||||
"hideMode": "hidden",
|
||||
"scrollingMode": "hover",
|
||||
"maxWidth": 145,
|
||||
"useFixedWidth": false,
|
||||
"colorizeIcons": false
|
||||
},
|
||||
"AudioVisualizer": {
|
||||
"allowUserSettings": true,
|
||||
"width": 200,
|
||||
"colorName": "primary",
|
||||
"hideWhenIdle": false
|
||||
},
|
||||
"Battery": {
|
||||
"allowUserSettings": true,
|
||||
"displayMode": "onhover",
|
||||
"warningThreshold": 30
|
||||
},
|
||||
"Bluetooth": {
|
||||
"allowUserSettings": true,
|
||||
"displayMode": "onhover"
|
||||
},
|
||||
"Brightness": {
|
||||
"allowUserSettings": true,
|
||||
"displayMode": "onhover"
|
||||
},
|
||||
"Clock": {
|
||||
"allowUserSettings": true,
|
||||
"usePrimaryColor": true,
|
||||
"useCustomFont": false,
|
||||
"customFont": "",
|
||||
"formatHorizontal": "HH:mm ddd, MMM dd",
|
||||
"formatVertical": "HH mm - dd MM"
|
||||
},
|
||||
"ControlCenter": {
|
||||
"allowUserSettings": true,
|
||||
"useDistroLogo": false,
|
||||
"icon": "noctalia",
|
||||
"customIconPath": "",
|
||||
"colorizeDistroLogo": false
|
||||
},
|
||||
"CustomButton": {
|
||||
"allowUserSettings": true,
|
||||
"icon": "heart",
|
||||
"leftClickExec": "",
|
||||
"rightClickExec": "",
|
||||
"middleClickExec": "",
|
||||
"textCommand": "",
|
||||
"textStream": false,
|
||||
"textIntervalMs": 3000,
|
||||
"textCollapse": "",
|
||||
"parseJson": false,
|
||||
"hideTextInVerticalBar": false
|
||||
},
|
||||
"KeyboardLayout": {
|
||||
"allowUserSettings": true,
|
||||
"displayMode": "onhover"
|
||||
},
|
||||
"LockKeys": {
|
||||
"allowUserSettings": true,
|
||||
"showCapsLock": true,
|
||||
"showNumLock": true,
|
||||
"showScrollLock": true,
|
||||
"capsLockIcon": "letter-c",
|
||||
"numLockIcon": "letter-n",
|
||||
"scrollLockIcon": "letter-s"
|
||||
},
|
||||
"MediaMini": {
|
||||
"allowUserSettings": true,
|
||||
"hideMode": "hidden",
|
||||
"scrollingMode": "hover",
|
||||
"maxWidth": 145,
|
||||
"useFixedWidth": false,
|
||||
"hideWhenIdle": false,
|
||||
"showAlbumArt": false,
|
||||
"showVisualizer": false,
|
||||
"visualizerType": "linear"
|
||||
},
|
||||
"Microphone": {
|
||||
"allowUserSettings": true,
|
||||
"displayMode": "onhover"
|
||||
},
|
||||
"NotificationHistory": {
|
||||
"allowUserSettings": true,
|
||||
"showUnreadBadge": true,
|
||||
"hideWhenZero": true
|
||||
},
|
||||
"Spacer": {
|
||||
"allowUserSettings": true,
|
||||
"width": 20
|
||||
},
|
||||
"SystemMonitor": {
|
||||
"allowUserSettings": true,
|
||||
"usePrimaryColor": false,
|
||||
"showCpuUsage": true,
|
||||
"showCpuTemp": true,
|
||||
"showMemoryUsage": true,
|
||||
"showMemoryAsPercent": false,
|
||||
"showNetworkStats": false,
|
||||
"showDiskUsage": false
|
||||
},
|
||||
"Taskbar": {
|
||||
"allowUserSettings": true,
|
||||
"onlySameOutput": true,
|
||||
"onlyActiveWorkspaces": true,
|
||||
"hideMode": "hidden",
|
||||
"colorizeIcons": false
|
||||
},
|
||||
"TaskbarGrouped": {
|
||||
"allowUserSettings": true,
|
||||
"showWorkspaceNumbers": true,
|
||||
"showNumbersOnlyWhenOccupied": true
|
||||
},
|
||||
"Tray": {
|
||||
"allowUserSettings": true,
|
||||
"blacklist": [],
|
||||
"colorizeIcons": false,
|
||||
"favorites": [],
|
||||
"drawerEnabled": true
|
||||
},
|
||||
"WiFi": {
|
||||
"allowUserSettings": true,
|
||||
"displayMode": "onhover"
|
||||
},
|
||||
"Workspace": {
|
||||
"allowUserSettings": true,
|
||||
"labelMode": "index",
|
||||
"hideUnoccupied": false,
|
||||
"characterCount": 2
|
||||
},
|
||||
"Volume": {
|
||||
"allowUserSettings": true,
|
||||
"displayMode": "onhover"
|
||||
}
|
||||
})
|
||||
|
||||
// Component definitions - these are loaded once at startup
|
||||
property Component activeWindowComponent: Component {
|
||||
ActiveWindow {}
|
||||
}
|
||||
property Component audioVisualizerComponent: Component {
|
||||
AudioVisualizer {}
|
||||
}
|
||||
property Component batteryComponent: Component {
|
||||
Battery {}
|
||||
}
|
||||
property Component bluetoothComponent: Component {
|
||||
Bluetooth {}
|
||||
}
|
||||
property Component brightnessComponent: Component {
|
||||
Brightness {}
|
||||
}
|
||||
property Component clockComponent: Component {
|
||||
Clock {}
|
||||
}
|
||||
property Component customButtonComponent: Component {
|
||||
CustomButton {}
|
||||
}
|
||||
property Component darkModeComponent: Component {
|
||||
DarkMode {}
|
||||
}
|
||||
property Component keyboardLayoutComponent: Component {
|
||||
KeyboardLayout {}
|
||||
}
|
||||
property Component keepAwakeComponent: Component {
|
||||
KeepAwake {}
|
||||
}
|
||||
property Component lockKeysComponent: Component {
|
||||
LockKeys {}
|
||||
}
|
||||
property Component mediaMiniComponent: Component {
|
||||
MediaMini {}
|
||||
}
|
||||
property Component microphoneComponent: Component {
|
||||
Microphone {}
|
||||
}
|
||||
property Component nightLightComponent: Component {
|
||||
NightLight {}
|
||||
}
|
||||
property Component noctaliaPerformanceComponent: Component {
|
||||
NoctaliaPerformance {}
|
||||
}
|
||||
property Component notificationHistoryComponent: Component {
|
||||
NotificationHistory {}
|
||||
}
|
||||
property Component powerProfileComponent: Component {
|
||||
PowerProfile {}
|
||||
}
|
||||
property Component sessionMenuComponent: Component {
|
||||
SessionMenu {}
|
||||
}
|
||||
property Component screenRecorderComponent: Component {
|
||||
ScreenRecorder {}
|
||||
}
|
||||
property Component controlCenterComponent: Component {
|
||||
ControlCenter {}
|
||||
}
|
||||
property Component spacerComponent: Component {
|
||||
Spacer {}
|
||||
}
|
||||
property Component systemMonitorComponent: Component {
|
||||
SystemMonitor {}
|
||||
}
|
||||
property Component trayComponent: Component {
|
||||
Tray {}
|
||||
}
|
||||
property Component volumeComponent: Component {
|
||||
Volume {}
|
||||
}
|
||||
property Component wiFiComponent: Component {
|
||||
WiFi {}
|
||||
}
|
||||
property Component wallpaperSelectorComponent: Component {
|
||||
WallpaperSelector {}
|
||||
}
|
||||
property Component workspaceComponent: Component {
|
||||
Workspace {}
|
||||
}
|
||||
property Component taskbarComponent: Component {
|
||||
Taskbar {}
|
||||
}
|
||||
property Component taskbarGroupedComponent: Component {
|
||||
TaskbarGrouped {}
|
||||
}
|
||||
|
||||
function init() {
|
||||
Logger.i("BarWidgetRegistry", "Service started")
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Helper function to get widget component by name
|
||||
function getWidget(id) {
|
||||
return widgets[id] || null
|
||||
}
|
||||
|
||||
// Helper function to check if widget exists
|
||||
function hasWidget(id) {
|
||||
return id in widgets
|
||||
}
|
||||
|
||||
// Get list of available widget id
|
||||
function getAvailableWidgets() {
|
||||
return Object.keys(widgets)
|
||||
}
|
||||
|
||||
// Helper function to check if widget has user settings
|
||||
function widgetHasUserSettings(id) {
|
||||
return (widgetMetadata[id] !== undefined) && (widgetMetadata[id].allowUserSettings === true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Panels.ControlCenter.Widgets
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Widget registry object mapping widget names to components
|
||||
property var widgets: ({
|
||||
"Bluetooth": bluetoothComponent,
|
||||
"Notifications": notificationsComponent,
|
||||
"KeepAwake": keepAwakeComponent,
|
||||
"NightLight": nightLightComponent,
|
||||
"PowerProfile": powerProfileComponent,
|
||||
"ScreenRecorder": screenRecorderComponent,
|
||||
"WiFi": wiFiComponent,
|
||||
"WallpaperSelector": wallpaperSelectorComponent,
|
||||
"CustomButton": customButtonComponent
|
||||
})
|
||||
|
||||
property var widgetMetadata: ({
|
||||
"CustomButton": {
|
||||
"allowUserSettings": true,
|
||||
"icon": "heart",
|
||||
"onClicked": "",
|
||||
"onRightClicked": "",
|
||||
"onMiddleClicked": "",
|
||||
"stateChecks": [],
|
||||
"generalTooltipText": "Custom Button",
|
||||
"enableOnStateLogic": false
|
||||
}
|
||||
})
|
||||
|
||||
// Component definitions - these are loaded once at startup
|
||||
property Component bluetoothComponent: Component {
|
||||
Bluetooth {}
|
||||
}
|
||||
property Component notificationsComponent: Component {
|
||||
Notifications {}
|
||||
}
|
||||
property Component keepAwakeComponent: Component {
|
||||
KeepAwake {}
|
||||
}
|
||||
property Component nightLightComponent: Component {
|
||||
NightLight {}
|
||||
}
|
||||
property Component powerProfileComponent: Component {
|
||||
PowerProfile {}
|
||||
}
|
||||
property Component screenRecorderComponent: Component {
|
||||
ScreenRecorder {}
|
||||
}
|
||||
property Component wiFiComponent: Component {
|
||||
WiFi {}
|
||||
}
|
||||
property Component wallpaperSelectorComponent: Component {
|
||||
WallpaperSelector {}
|
||||
}
|
||||
property Component customButtonComponent: Component {
|
||||
CustomButton {}
|
||||
}
|
||||
|
||||
function init() {
|
||||
Logger.i("ControlCenterWidgetRegistry", "Service started")
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Helper function to get widget component by name
|
||||
function getWidget(id) {
|
||||
return widgets[id] || null
|
||||
}
|
||||
|
||||
// Helper function to check if widget exists
|
||||
function hasWidget(id) {
|
||||
return id in widgets
|
||||
}
|
||||
|
||||
// Get list of available widget id
|
||||
function getAvailableWidgets() {
|
||||
return Object.keys(widgets)
|
||||
}
|
||||
|
||||
// Helper function to check if widget has user settings
|
||||
function widgetHasUserSettings(id) {
|
||||
return (widgetMetadata[id] !== undefined) && (widgetMetadata[id].allowUserSettings === true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// A ref. to the lockScreen, so it's accessible from anywhere
|
||||
// This is not a panel...
|
||||
property var lockScreen: null
|
||||
|
||||
// Panels
|
||||
property var registeredPanels: ({})
|
||||
property var openedPanel: null
|
||||
signal willOpen
|
||||
signal didClose
|
||||
|
||||
// Register this panel (called after panel is loaded)
|
||||
function registerPanel(panel) {
|
||||
registeredPanels[panel.objectName] = panel
|
||||
Logger.d("PanelService", "Registered panel:", panel.objectName)
|
||||
}
|
||||
|
||||
// Returns a panel (loads it on-demand if not yet loaded)
|
||||
function getPanel(name, screen) {
|
||||
if (!screen) {
|
||||
Logger.d("PanelService", "missing screen for getPanel:", name)
|
||||
// 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]
|
||||
}
|
||||
|
||||
Logger.w("PanelService", "Panel not found:", panelKey)
|
||||
return null
|
||||
}
|
||||
|
||||
// Check if a panel exists
|
||||
function hasPanel(name) {
|
||||
return name in registeredPanels
|
||||
}
|
||||
|
||||
// Helper to keep only one panel open at any time
|
||||
function willOpenPanel(panel) {
|
||||
if (openedPanel && openedPanel !== panel) {
|
||||
openedPanel.close()
|
||||
}
|
||||
openedPanel = panel
|
||||
|
||||
// emit signal
|
||||
willOpen()
|
||||
}
|
||||
|
||||
function closedPanel(panel) {
|
||||
if (openedPanel && openedPanel === panel) {
|
||||
openedPanel = null
|
||||
}
|
||||
|
||||
// emit signal
|
||||
didClose()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
import qs.Commons
|
||||
|
||||
|
||||
/**
|
||||
* SystemTrayService
|
||||
* This service ensures that Quickshell's SystemTray service is initialized
|
||||
* early in the shell startup to avoid programs that should stay in tray, not having access to one (let's hope this works).
|
||||
*/
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property bool initialized: false
|
||||
|
||||
Component.onCompleted: {
|
||||
if (SystemTray && SystemTray.items) {
|
||||
Logger.i("SystemTrayService", "SystemTray service initialized")
|
||||
initialized = true
|
||||
|
||||
// Monitor for tray items to confirm it's working
|
||||
if (SystemTray.items.valuesChanged) {
|
||||
Logger.d("SystemTrayService", "SystemTray is ready and monitoring for items")
|
||||
}
|
||||
} else {
|
||||
Logger.w("SystemTrayService", "SystemTray service not available")
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
// Explicit initialization function
|
||||
if (!initialized && SystemTray && SystemTray.items) {
|
||||
Logger.i("SystemTrayService", "SystemTray service initialized via init()")
|
||||
initialized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Simple signal-based notification system
|
||||
signal notify(string message, string description, string icon, string type, int duration)
|
||||
|
||||
// Convenience methods
|
||||
function showNotice(message, description = "", icon = "", duration = 3000) {
|
||||
notify(message, description, icon, "notice", duration)
|
||||
}
|
||||
|
||||
function showWarning(message, description = "", duration = 4000) {
|
||||
notify(message, description, "", "warning", duration)
|
||||
}
|
||||
|
||||
function showError(message, description = "", duration = 6000) {
|
||||
notify(message, description, "", "error", duration)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Modules.Tooltip
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property var activeTooltip: null
|
||||
property var pendingTooltip: null // Track tooltip being created
|
||||
|
||||
property Component tooltipComponent: Component {
|
||||
Tooltip {}
|
||||
}
|
||||
|
||||
function show(screen, target, text, direction, delay) {
|
||||
if (!Settings.data.ui.tooltipsEnabled) {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't create if no text
|
||||
if (!screen || !target || !text) {
|
||||
Logger.i("Tooltip", "No target or text")
|
||||
return
|
||||
}
|
||||
|
||||
// If we have a pending tooltip for a different target, cancel it
|
||||
if (pendingTooltip && pendingTooltip.targetItem !== target) {
|
||||
pendingTooltip.hideImmediately()
|
||||
pendingTooltip.destroy()
|
||||
pendingTooltip = null
|
||||
}
|
||||
|
||||
// If we have an active tooltip for a different target, hide it
|
||||
if (activeTooltip && activeTooltip.targetItem !== target) {
|
||||
activeTooltip.hideImmediately()
|
||||
// Don't destroy immediately - let it clean itself up
|
||||
activeTooltip = null
|
||||
}
|
||||
|
||||
// If we already have a tooltip for this target, just update it
|
||||
if (activeTooltip && activeTooltip.targetItem === target) {
|
||||
activeTooltip.updateText(text)
|
||||
return activeTooltip
|
||||
}
|
||||
|
||||
// Create new tooltip instance
|
||||
const newTooltip = tooltipComponent.createObject(null)
|
||||
|
||||
if (newTooltip) {
|
||||
// Track as pending until it's visible
|
||||
pendingTooltip = newTooltip
|
||||
|
||||
// Connect cleanup when tooltip hides
|
||||
newTooltip.visibleChanged.connect(() => {
|
||||
if (!newTooltip.visible) {
|
||||
// Clean up after a delay to avoid interfering with new tooltips
|
||||
Qt.callLater(() => {
|
||||
if (newTooltip && !newTooltip.visible) {
|
||||
if (activeTooltip === newTooltip) {
|
||||
activeTooltip = null
|
||||
}
|
||||
if (pendingTooltip === newTooltip) {
|
||||
pendingTooltip = null
|
||||
}
|
||||
newTooltip.destroy()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Tooltip is now visible, move from pending to active
|
||||
if (pendingTooltip === newTooltip) {
|
||||
activeTooltip = newTooltip
|
||||
pendingTooltip = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Show the tooltip
|
||||
newTooltip.show(screen, target, text, direction || "auto", delay || Style.tooltipDelay)
|
||||
|
||||
return newTooltip
|
||||
} else {
|
||||
Logger.e("Tooltip", "Failed to create tooltip instance")
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (pendingTooltip) {
|
||||
pendingTooltip.hide()
|
||||
}
|
||||
if (activeTooltip) {
|
||||
activeTooltip.hide()
|
||||
}
|
||||
}
|
||||
|
||||
function hideImmediately() {
|
||||
if (pendingTooltip) {
|
||||
pendingTooltip.hideImmediately()
|
||||
pendingTooltip.destroy()
|
||||
pendingTooltip = null
|
||||
}
|
||||
if (activeTooltip) {
|
||||
activeTooltip.hideImmediately()
|
||||
activeTooltip.destroy()
|
||||
activeTooltip = null
|
||||
}
|
||||
}
|
||||
|
||||
function updateText(newText) {
|
||||
if (activeTooltip) {
|
||||
activeTooltip.updateText(newText)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,534 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Qt.labs.folderlistmodel
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Commons
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property ListModel fillModeModel: ListModel {}
|
||||
readonly property string defaultDirectory: Settings.preprocessPath(Settings.data.wallpaper.directory)
|
||||
|
||||
// All available wallpaper transitions
|
||||
readonly property ListModel transitionsModel: ListModel {}
|
||||
|
||||
// All transition keys but filter out "none" and "random" so we are left with the real transitions
|
||||
readonly property var allTransitions: Array.from({
|
||||
"length": transitionsModel.count
|
||||
}, (_, i) => transitionsModel.get(i).key).filter(key => key !== "random" && key != "none")
|
||||
|
||||
property var wallpaperLists: ({})
|
||||
property int scanningCount: 0
|
||||
readonly property bool scanning: (scanningCount > 0)
|
||||
|
||||
// Cache for current wallpapers - can be updated directly since we use signals for notifications
|
||||
property var currentWallpapers: ({})
|
||||
|
||||
property bool isInitialized: false
|
||||
|
||||
// Signals for reactive UI updates
|
||||
signal wallpaperChanged(string screenName, string path)
|
||||
// Emitted when a wallpaper changes
|
||||
signal wallpaperDirectoryChanged(string screenName, string directory)
|
||||
// Emitted when a monitor's directory changes
|
||||
signal wallpaperListChanged(string screenName, int count)
|
||||
|
||||
// Emitted when available wallpapers list changes
|
||||
Connections {
|
||||
target: Settings.data.wallpaper
|
||||
function onDirectoryChanged() {
|
||||
root.refreshWallpapersList()
|
||||
// Emit directory change signals for monitors using the default directory
|
||||
if (!Settings.data.wallpaper.enableMultiMonitorDirectories) {
|
||||
// All monitors use the main directory
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
root.wallpaperDirectoryChanged(Quickshell.screens[i].name, root.defaultDirectory)
|
||||
}
|
||||
} else {
|
||||
// Only monitors without custom directories are affected
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
var screenName = Quickshell.screens[i].name
|
||||
var monitor = root.getMonitorConfig(screenName)
|
||||
if (!monitor || !monitor.directory) {
|
||||
root.wallpaperDirectoryChanged(screenName, root.defaultDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function onEnableMultiMonitorDirectoriesChanged() {
|
||||
root.refreshWallpapersList()
|
||||
// Notify all monitors about potential directory changes
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
var screenName = Quickshell.screens[i].name
|
||||
root.wallpaperDirectoryChanged(screenName, root.getMonitorDirectory(screenName))
|
||||
}
|
||||
}
|
||||
function onRandomEnabledChanged() {
|
||||
root.toggleRandomWallpaper()
|
||||
}
|
||||
function onRandomIntervalSecChanged() {
|
||||
root.restartRandomWallpaperTimer()
|
||||
}
|
||||
function onRecursiveSearchChanged() {
|
||||
root.refreshWallpapersList()
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
function init() {
|
||||
Logger.i("Wallpaper", "Service started")
|
||||
|
||||
translateModels()
|
||||
|
||||
// Rebuild cache from settings
|
||||
currentWallpapers = ({})
|
||||
var monitors = Settings.data.wallpaper.monitors || []
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
if (monitors[i].name && monitors[i].wallpaper) {
|
||||
currentWallpapers[monitors[i].name] = monitors[i].wallpaper
|
||||
}
|
||||
}
|
||||
|
||||
isInitialized = true
|
||||
Logger.d("Wallpaper", "Triggering initial wallpaper scan")
|
||||
Qt.callLater(refreshWallpapersList)
|
||||
}
|
||||
|
||||
// -------------------------------------------------
|
||||
function translateModels() {
|
||||
// Wait for i18n to be ready by retrying every time
|
||||
if (!I18n.isLoaded) {
|
||||
Qt.callLater(translateModels)
|
||||
return
|
||||
}
|
||||
|
||||
// Populate fillModeModel with translated names
|
||||
fillModeModel.append({
|
||||
"key": "center",
|
||||
"name": I18n.tr("wallpaper.fill-modes.center"),
|
||||
"uniform": 0.0
|
||||
})
|
||||
fillModeModel.append({
|
||||
"key": "crop",
|
||||
"name": I18n.tr("wallpaper.fill-modes.crop"),
|
||||
"uniform": 1.0
|
||||
})
|
||||
fillModeModel.append({
|
||||
"key": "fit",
|
||||
"name": I18n.tr("wallpaper.fill-modes.fit"),
|
||||
"uniform": 2.0
|
||||
})
|
||||
fillModeModel.append({
|
||||
"key": "stretch",
|
||||
"name": I18n.tr("wallpaper.fill-modes.stretch"),
|
||||
"uniform": 3.0
|
||||
})
|
||||
|
||||
// Populate transitionsModel with translated names
|
||||
transitionsModel.append({
|
||||
"key": "none",
|
||||
"name": I18n.tr("wallpaper.transitions.none")
|
||||
})
|
||||
transitionsModel.append({
|
||||
"key": "random",
|
||||
"name": I18n.tr("wallpaper.transitions.random")
|
||||
})
|
||||
transitionsModel.append({
|
||||
"key": "fade",
|
||||
"name": I18n.tr("wallpaper.transitions.fade")
|
||||
})
|
||||
transitionsModel.append({
|
||||
"key": "disc",
|
||||
"name": I18n.tr("wallpaper.transitions.disc")
|
||||
})
|
||||
transitionsModel.append({
|
||||
"key": "stripes",
|
||||
"name": I18n.tr("wallpaper.transitions.stripes")
|
||||
})
|
||||
transitionsModel.append({
|
||||
"key": "wipe",
|
||||
"name": I18n.tr("wallpaper.transitions.wipe")
|
||||
})
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
function getFillModeUniform() {
|
||||
for (var i = 0; i < fillModeModel.count; i++) {
|
||||
const mode = fillModeModel.get(i)
|
||||
if (mode.key === Settings.data.wallpaper.fillMode) {
|
||||
return mode.uniform
|
||||
}
|
||||
}
|
||||
// Fallback to crop
|
||||
return 1.0
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Get specific monitor wallpaper data
|
||||
function getMonitorConfig(screenName) {
|
||||
var monitors = Settings.data.wallpaper.monitors
|
||||
if (monitors !== undefined) {
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
if (monitors[i].name !== undefined && monitors[i].name === screenName) {
|
||||
return monitors[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Get specific monitor directory
|
||||
function getMonitorDirectory(screenName) {
|
||||
if (!Settings.data.wallpaper.enableMultiMonitorDirectories) {
|
||||
return root.defaultDirectory
|
||||
}
|
||||
|
||||
var monitor = getMonitorConfig(screenName)
|
||||
if (monitor !== undefined && monitor.directory !== undefined) {
|
||||
return Settings.preprocessPath(monitor.directory)
|
||||
}
|
||||
|
||||
// Fall back to the main/single directory
|
||||
return root.defaultDirectory
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Set specific monitor directory
|
||||
function setMonitorDirectory(screenName, directory) {
|
||||
var monitors = Settings.data.wallpaper.monitors || []
|
||||
var found = false
|
||||
|
||||
// Create a new array with updated values
|
||||
var newMonitors = monitors.map(function (monitor) {
|
||||
if (monitor.name === screenName) {
|
||||
found = true
|
||||
return {
|
||||
"name": screenName,
|
||||
"directory": directory,
|
||||
"wallpaper": monitor.wallpaper || ""
|
||||
}
|
||||
}
|
||||
return monitor
|
||||
})
|
||||
|
||||
if (!found) {
|
||||
newMonitors.push({
|
||||
"name": screenName,
|
||||
"directory": directory,
|
||||
"wallpaper": ""
|
||||
})
|
||||
}
|
||||
|
||||
// Update Settings with new array to ensure proper persistence
|
||||
Settings.data.wallpaper.monitors = newMonitors.slice()
|
||||
root.wallpaperDirectoryChanged(screenName, Settings.preprocessPath(directory))
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Get specific monitor wallpaper - now from cache
|
||||
function getWallpaper(screenName) {
|
||||
return currentWallpapers[screenName] || Settings.data.wallpaper.defaultWallpaper
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
function changeWallpaper(path, screenName) {
|
||||
if (screenName !== undefined) {
|
||||
_setWallpaper(screenName, path)
|
||||
} else {
|
||||
// If no screenName specified change for all screens
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
_setWallpaper(Quickshell.screens[i].name, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
function _setWallpaper(screenName, path) {
|
||||
if (path === "" || path === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (screenName === undefined) {
|
||||
Logger.w("Wallpaper", "setWallpaper", "no screen specified")
|
||||
return
|
||||
}
|
||||
|
||||
//Logger.i("Wallpaper", "setWallpaper on", screenName, ": ", path)
|
||||
|
||||
// Check if wallpaper actually changed
|
||||
var oldPath = currentWallpapers[screenName] || ""
|
||||
var wallpaperChanged = (oldPath !== path)
|
||||
|
||||
if (!wallpaperChanged) {
|
||||
// No change needed
|
||||
return
|
||||
}
|
||||
|
||||
// Update cache directly
|
||||
currentWallpapers[screenName] = path
|
||||
|
||||
// Update Settings - still need immutable update for Settings persistence
|
||||
// The slice() ensures Settings detects the change and saves properly
|
||||
var monitors = Settings.data.wallpaper.monitors || []
|
||||
var found = false
|
||||
|
||||
var newMonitors = monitors.map(function (monitor) {
|
||||
if (monitor.name === screenName) {
|
||||
found = true
|
||||
return {
|
||||
"name": screenName,
|
||||
"directory": Settings.preprocessPath(monitor.directory) || getMonitorDirectory(screenName),
|
||||
"wallpaper": path
|
||||
}
|
||||
}
|
||||
return monitor
|
||||
})
|
||||
|
||||
if (!found) {
|
||||
newMonitors.push({
|
||||
"name": screenName,
|
||||
"directory": getMonitorDirectory(screenName),
|
||||
"wallpaper": path
|
||||
})
|
||||
}
|
||||
|
||||
Settings.data.wallpaper.monitors = newMonitors.slice()
|
||||
|
||||
// Emit signal for this specific wallpaper change
|
||||
root.wallpaperChanged(screenName, path)
|
||||
|
||||
// Restart the random wallpaper timer
|
||||
if (randomWallpaperTimer.running) {
|
||||
randomWallpaperTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
function setRandomWallpaper() {
|
||||
Logger.d("Wallpaper", "setRandomWallpaper")
|
||||
|
||||
if (Settings.data.wallpaper.enableMultiMonitorDirectories) {
|
||||
// Pick a random wallpaper per screen
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
var screenName = Quickshell.screens[i].name
|
||||
var wallpaperList = getWallpapersList(screenName)
|
||||
|
||||
if (wallpaperList.length > 0) {
|
||||
var randomIndex = Math.floor(Math.random() * wallpaperList.length)
|
||||
var randomPath = wallpaperList[randomIndex]
|
||||
changeWallpaper(randomPath, screenName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Pick a random wallpaper common to all screens
|
||||
// We can use any screenName here, so we just pick the primary one.
|
||||
var wallpaperList = getWallpapersList(Screen.name)
|
||||
if (wallpaperList.length > 0) {
|
||||
var randomIndex = Math.floor(Math.random() * wallpaperList.length)
|
||||
var randomPath = wallpaperList[randomIndex]
|
||||
changeWallpaper(randomPath, undefined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
function toggleRandomWallpaper() {
|
||||
Logger.d("Wallpaper", "toggleRandomWallpaper")
|
||||
if (Settings.data.wallpaper.randomEnabled) {
|
||||
restartRandomWallpaperTimer()
|
||||
setRandomWallpaper()
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
function restartRandomWallpaperTimer() {
|
||||
if (Settings.data.wallpaper.isRandom) {
|
||||
randomWallpaperTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
function getWallpapersList(screenName) {
|
||||
if (screenName != undefined && wallpaperLists[screenName] != undefined) {
|
||||
return wallpaperLists[screenName]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
function refreshWallpapersList() {
|
||||
Logger.d("Wallpaper", "refreshWallpapersList", "recursive:", Settings.data.wallpaper.recursiveSearch)
|
||||
scanningCount = 0
|
||||
|
||||
if (Settings.data.wallpaper.recursiveSearch) {
|
||||
// Use Process-based recursive search for all screens
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
var screenName = Quickshell.screens[i].name
|
||||
var directory = getMonitorDirectory(screenName)
|
||||
scanDirectoryRecursive(screenName, directory)
|
||||
}
|
||||
} else {
|
||||
// Use FolderListModel (non-recursive)
|
||||
// Force refresh by toggling each scanner's currentDirectory
|
||||
for (var i = 0; i < wallpaperScanners.count; i++) {
|
||||
var scanner = wallpaperScanners.objectAt(i)
|
||||
if (scanner) {
|
||||
// Capture scanner in closure
|
||||
(function (s) {
|
||||
var directory = root.getMonitorDirectory(s.screenName)
|
||||
// Trigger a change by setting to /tmp (always exists) then back to the actual directory
|
||||
// Note: This causes harmless Qt warnings (QTBUG-52262) but is necessary to force FolderListModel to re-scan
|
||||
s.currentDirectory = "/tmp"
|
||||
Qt.callLater(function () {
|
||||
s.currentDirectory = directory
|
||||
})
|
||||
})(scanner)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process instances for recursive scanning (one per screen)
|
||||
property var recursiveProcesses: ({})
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
function scanDirectoryRecursive(screenName, directory) {
|
||||
if (!directory || directory === "") {
|
||||
Logger.w("Wallpaper", "Empty directory for", screenName)
|
||||
wallpaperLists[screenName] = []
|
||||
wallpaperListChanged(screenName, 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Cancel any existing scan for this screen
|
||||
if (recursiveProcesses[screenName]) {
|
||||
Logger.d("Wallpaper", "Cancelling existing scan for", screenName)
|
||||
recursiveProcesses[screenName].running = false
|
||||
recursiveProcesses[screenName].destroy()
|
||||
delete recursiveProcesses[screenName]
|
||||
scanningCount--
|
||||
}
|
||||
|
||||
scanningCount++
|
||||
Logger.i("Wallpaper", "Starting recursive scan for", screenName, "in", directory)
|
||||
|
||||
// Create Process component inline
|
||||
var processComponent = Qt.createComponent("", root)
|
||||
var processString = `
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
id: process
|
||||
command: ["find", "` + directory + `", "-type", "f", "(", "-iname", "*.jpg", "-o", "-iname", "*.jpeg", "-o", "-iname", "*.png", "-o", "-iname", "*.gif", "-o", "-iname", "*.pnm", "-o", "-iname", "*.bmp", ")"]
|
||||
stdout: StdioCollector {}
|
||||
stderr: StdioCollector {}
|
||||
}
|
||||
`
|
||||
|
||||
var processObject = Qt.createQmlObject(processString, root, "RecursiveScan_" + screenName)
|
||||
|
||||
// Store reference to avoid garbage collection
|
||||
recursiveProcesses[screenName] = processObject
|
||||
|
||||
var handler = function (exitCode) {
|
||||
scanningCount--
|
||||
Logger.d("Wallpaper", "Process exited with code", exitCode, "for", screenName)
|
||||
if (exitCode === 0) {
|
||||
var lines = processObject.stdout.text.split('\n')
|
||||
var files = []
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var line = lines[i].trim()
|
||||
if (line !== '') {
|
||||
files.push(line)
|
||||
}
|
||||
}
|
||||
// Sort files for consistent ordering
|
||||
files.sort()
|
||||
wallpaperLists[screenName] = files
|
||||
Logger.i("Wallpaper", "Recursive scan completed for", screenName, "found", files.length, "files")
|
||||
wallpaperListChanged(screenName, files.length)
|
||||
} else {
|
||||
Logger.w("Wallpaper", "Recursive scan failed for", screenName, "exit code:", exitCode, "(directory might not exist)")
|
||||
wallpaperLists[screenName] = []
|
||||
wallpaperListChanged(screenName, 0)
|
||||
}
|
||||
// Clean up
|
||||
delete recursiveProcesses[screenName]
|
||||
processObject.destroy()
|
||||
}
|
||||
|
||||
processObject.exited.connect(handler)
|
||||
Logger.d("Wallpaper", "Starting process for", screenName)
|
||||
processObject.running = true
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
Timer {
|
||||
id: randomWallpaperTimer
|
||||
interval: Settings.data.wallpaper.randomIntervalSec * 1000
|
||||
running: Settings.data.wallpaper.randomEnabled
|
||||
repeat: true
|
||||
onTriggered: setRandomWallpaper()
|
||||
triggeredOnStart: false
|
||||
}
|
||||
|
||||
// Instantiator (not Repeater) to create FolderListModel for each monitor
|
||||
Instantiator {
|
||||
id: wallpaperScanners
|
||||
model: Quickshell.screens
|
||||
delegate: FolderListModel {
|
||||
property string screenName: modelData.name
|
||||
property string currentDirectory: root.getMonitorDirectory(screenName)
|
||||
|
||||
folder: "file://" + currentDirectory
|
||||
nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"]
|
||||
showDirs: false
|
||||
sortField: FolderListModel.Name
|
||||
|
||||
// Watch for directory changes via property binding
|
||||
onCurrentDirectoryChanged: {
|
||||
folder = "file://" + currentDirectory
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Connect to directory change signal
|
||||
root.wallpaperDirectoryChanged.connect(function (screen, directory) {
|
||||
if (screen === screenName) {
|
||||
currentDirectory = directory
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onStatusChanged: {
|
||||
if (status === FolderListModel.Null) {
|
||||
// Flush the list
|
||||
root.wallpaperLists[screenName] = []
|
||||
root.wallpaperListChanged(screenName, 0)
|
||||
} else if (status === FolderListModel.Loading) {
|
||||
// Flush the list
|
||||
root.wallpaperLists[screenName] = []
|
||||
scanningCount++
|
||||
} else if (status === FolderListModel.Ready) {
|
||||
var files = []
|
||||
for (var i = 0; i < count; i++) {
|
||||
var directory = root.getMonitorDirectory(screenName)
|
||||
var filepath = directory + "/" + get(i, "fileName")
|
||||
files.push(filepath)
|
||||
}
|
||||
|
||||
// Update the list
|
||||
root.wallpaperLists[screenName] = files
|
||||
|
||||
scanningCount--
|
||||
Logger.d("Wallpaper", "List refreshed for", screenName, "count:", files.length)
|
||||
root.wallpaperListChanged(screenName, files.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user