import QtQuick import Quickshell import Quickshell.Services.Pipewire import qs.Common import qs.Services import qs.Widgets import "../widgets" FocusScope { id: root property var overlayRef: null readonly property var daemon: overlayRef ? overlayRef.daemon : null readonly property var pluginData: (daemon && daemon.pluginData) ? daemon.pluginData : ({}) readonly property var targetScreen: overlayRef ? overlayRef.effectiveScreen : null readonly property real edgeMargin: Theme.spacingM // Changes when the overlay lands on a different monitor (or that monitor // changes resolution); widgets re-read their saved spot when it does. readonly property string screenKey: daemon ? daemon.screenKey(root.targetScreen) : "" readonly property real topLimit: topBar.y + topBar.height + Theme.spacingM property int windowZCounter: 1 function nextWindowZ() { root.windowZCounter = root.windowZCounter + 1; return root.windowZCounter; } function requestClose() { if (overlayRef) overlayRef.requestClose(); } function isWidgetVisible(widgetId) { return daemon ? daemon.isWidgetVisible(widgetId) : false; } function toggleWidget(widgetId) { if (daemon) daemon.toggleWidget(widgetId); } function widgetPosition(widgetId) { if (daemon) return daemon.widgetPosition(root.targetScreen, widgetId, root.width, root.height); return { "x": root.width * 0.1, "y": root.height * 0.15 }; } function saveWidgetPosition(widgetId, x, y) { if (daemon) daemon.saveWidgetPosition(root.targetScreen, widgetId, x, y); } anchors.fill: parent focus: true // The modal's own Escape handler sits on a sibling focus scope, so the key // never reaches it once this content holds focus - handle it here instead. Keys.onEscapePressed: event => { root.requestClose(); event.accepted = true; } Component.onCompleted: root.forceActiveFocus() // Keeps the default sink/source properties live while the overlay is open. PwObjectTracker { objects: [AudioService.sink, AudioService.source].filter(node => !!node) } // Clicking empty space closes, like clicking outside a dialog. MouseArea { anchors.fill: parent onClicked: root.requestClose() } TopBar { id: topBar panel: root anchors.top: parent.top anchors.topMargin: Theme.spacingL anchors.horizontalCenter: parent.horizontalCenter } Repeater { model: root.daemon ? root.daemon.widgetDefs : [] delegate: WidgetWindow { required property var modelData panel: root widgetId: modelData.id title: modelData.title icon: modelData.icon widgetWidth: modelData.width widgetHeight: modelData.height minimumY: root.topLimit shown: root.isWidgetVisible(modelData.id) contentComponent: { switch (modelData.id) { case "audio": return audioWidgetComponent; case "apps": return appsWidgetComponent; case "timer": return timerWidgetComponent; case "capture": return captureWidgetComponent; case "media": return mediaWidgetComponent; case "visualizer": return visualizerWidgetComponent; case "bluetooth": return bluetoothWidgetComponent; case "notepad": return notepadWidgetComponent; case "home": return homeWidgetComponent; case "system": return systemWidgetComponent; } return null; } } } Component { id: audioWidgetComponent AudioWidget {} } Component { id: appsWidgetComponent AppMixer {} } Component { id: timerWidgetComponent TimerWidget { service: root.daemon ? root.daemon.timerService : null } } Component { id: captureWidgetComponent CaptureWidget { service: root.daemon ? root.daemon.captureService : null panel: root } } Component { id: visualizerWidgetComponent VisualizerWidget {} } Component { id: bluetoothWidgetComponent BluetoothWidget {} } Component { id: notepadWidgetComponent NotepadWidget {} } Component { id: mediaWidgetComponent MediaWidget {} } Component { id: homeWidgetComponent HomeWidget { service: root.daemon ? root.daemon.homeService : null } } Component { id: systemWidgetComponent SystemWidget {} } }