import QtQuick import Quickshell import Quickshell.Services.Pipewire import qs.Common import qs.Services import qs.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 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); return { "x": 0.1, "y": 0.15 }; } function saveWidgetPosition(widgetId, fractionX, fractionY) { if (daemon) daemon.saveWidgetPosition(root.targetScreen, widgetId, fractionX, fractionY); } 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() } GameBarTopBar { id: topBar panel: root anchors.top: parent.top anchors.topMargin: Theme.spacingL anchors.horizontalCenter: parent.horizontalCenter } Repeater { model: root.daemon ? root.daemon.widgetDefs : [] delegate: GameBarWindow { 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 "media": return mediaWidgetComponent; case "home": return homeWidgetComponent; case "system": return systemWidgetComponent; } return null; } } } Component { id: audioWidgetComponent GameBarAudioWidget {} } Component { id: appsWidgetComponent GameBarAppMixer {} } Component { id: mediaWidgetComponent GameBarMediaWidget {} } Component { id: homeWidgetComponent GameBarHomeWidget { service: root.daemon ? root.daemon.homeService : null } } Component { id: systemWidgetComponent GameBarSystemWidget {} } }