Refine quickshell bar and Noctalia compatibility
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
|
||||
// Wraps Noctalia panel entry points, which are plain Item content intended to
|
||||
// be hosted inside Noctalia's SmartPanel. We host them as anchored PopupWindows
|
||||
// so clicking a Noctalia bar widget behaves like native Omarchy popups instead
|
||||
// of opening a tiled app window.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string panelSource: ""
|
||||
property var pluginApi: null
|
||||
property var manifest: ({})
|
||||
property string omarchyPath: ""
|
||||
property var shell: null
|
||||
property var pluginRegistry: null
|
||||
property var barWidgetRegistry: null
|
||||
property bool popupOpen: false
|
||||
property bool closingFromShell: false
|
||||
|
||||
readonly property var anchorItem: pluginApi ? pluginApi.panelAnchorItem : null
|
||||
readonly property var anchorWindow: anchorItem ? anchorItem.QsWindow.window : null
|
||||
readonly property string barPosition: {
|
||||
if (shell && shell.barConfig && shell.barConfig.position) return String(shell.barConfig.position)
|
||||
return "top"
|
||||
}
|
||||
|
||||
function open(payloadJson) {
|
||||
popupOpen = true
|
||||
}
|
||||
|
||||
function close() {
|
||||
closingFromShell = true
|
||||
popupOpen = false
|
||||
if (pluginApi) {
|
||||
pluginApi.panelOpenScreen = null
|
||||
pluginApi.panelAnchorItem = null
|
||||
}
|
||||
closingFromShell = false
|
||||
}
|
||||
|
||||
function dismiss() {
|
||||
if (!popupOpen) return
|
||||
if (pluginApi && typeof pluginApi.closePanel === "function") pluginApi.closePanel(null)
|
||||
else if (shell && manifest && manifest.id && typeof shell.hide === "function") shell.hide(manifest.id)
|
||||
else close()
|
||||
}
|
||||
|
||||
PopupWindow {
|
||||
id: popup
|
||||
visible: root.popupOpen
|
||||
color: "transparent"
|
||||
implicitWidth: Math.max(320, panelLoader.item && panelLoader.item.contentPreferredWidth
|
||||
? panelLoader.item.contentPreferredWidth : 420)
|
||||
implicitHeight: Math.max(260, panelLoader.item && panelLoader.item.contentPreferredHeight
|
||||
? panelLoader.item.contentPreferredHeight : 520)
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible && root.popupOpen && !root.closingFromShell) root.dismiss()
|
||||
}
|
||||
|
||||
// Do not use HyprlandFocusGrab here. Noctalia panels are larger,
|
||||
// interactive surfaces with tabs and nested Flickables; focus grabs made
|
||||
// internal clicks (notably tab switches) look like outside clicks and also
|
||||
// interfered with wheel scrolling. Native Omarchy micro-popups can keep
|
||||
// focus-grab dismissal, but compat panels behave more like pinned panels.
|
||||
anchor {
|
||||
id: popupAnchor
|
||||
window: root.anchorWindow
|
||||
adjustment: PopupAdjustment.Slide
|
||||
edges: Edges.Top | Edges.Left
|
||||
gravity: Edges.Bottom | Edges.Right
|
||||
rect.width: 1
|
||||
rect.height: 1
|
||||
|
||||
onAnchoring: {
|
||||
var target = root.anchorItem
|
||||
var window = root.anchorWindow
|
||||
if (!target || !window) {
|
||||
popupAnchor.rect.x = 0
|
||||
popupAnchor.rect.y = 0
|
||||
return
|
||||
}
|
||||
|
||||
var popupWidth = popup.implicitWidth
|
||||
var popupHeight = popup.implicitHeight
|
||||
var margin = 8
|
||||
var localX = target.width / 2 - popupWidth / 2
|
||||
var localY = target.height + margin
|
||||
|
||||
if (root.barPosition === "bottom") {
|
||||
localY = -popupHeight - margin
|
||||
} else if (root.barPosition === "left") {
|
||||
localX = target.width + margin
|
||||
localY = target.height / 2 - popupHeight / 2
|
||||
} else if (root.barPosition === "right") {
|
||||
localX = -popupWidth - margin
|
||||
localY = target.height / 2 - popupHeight / 2
|
||||
}
|
||||
|
||||
var point = window.contentItem.mapFromItem(target, localX, localY)
|
||||
popupAnchor.rect.x = Math.round(point.x)
|
||||
popupAnchor.rect.y = Math.round(point.y)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: 1
|
||||
radius: 0
|
||||
|
||||
Loader {
|
||||
id: panelLoader
|
||||
anchors.fill: parent
|
||||
source: root.panelSource
|
||||
onLoaded: root.injectPanelProps()
|
||||
onStatusChanged: {
|
||||
if (status === Loader.Error) {
|
||||
console.warn("noctalia panel failed for " + (root.manifest ? root.manifest.id : "") + ":", errorString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPluginApiChanged: injectPanelProps()
|
||||
onManifestChanged: injectPanelProps()
|
||||
|
||||
function injectPanelProps() {
|
||||
var item = panelLoader.item
|
||||
if (!item) return
|
||||
if ("pluginApi" in item) item.pluginApi = root.pluginApi
|
||||
if ("manifest" in item) item.manifest = root.manifest
|
||||
if ("omarchyPath" in item) item.omarchyPath = root.omarchyPath
|
||||
if ("shell" in item) item.shell = root.shell
|
||||
if ("pluginRegistry" in item) item.pluginRegistry = root.pluginRegistry
|
||||
if ("barWidgetRegistry" in item) item.barWidgetRegistry = root.barWidgetRegistry
|
||||
if ("screen" in item) {
|
||||
var screens = Quickshell.screens
|
||||
item.screen = screens && screens.length > 0 ? screens[0] : null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,10 +46,14 @@ Item {
|
||||
property var _hostBridge: ({})
|
||||
property var mainInstance: null
|
||||
|
||||
// Resolved every read so the plugin sees current shell.json state.
|
||||
readonly property var pluginSettings: _settingsProvider ? _settingsProvider() : ({})
|
||||
// Writable because Noctalia Settings.qml components commonly stage edits
|
||||
// with `pluginApi.pluginSettings = ...; pluginApi.saveSettings()`.
|
||||
// The initial binding resolves shell.json + defaults; assignment during
|
||||
// save intentionally replaces it with the staged value to persist.
|
||||
property var pluginSettings: _settingsProvider ? _settingsProvider() : ({})
|
||||
|
||||
property var panelOpenScreen: null
|
||||
property var panelAnchorItem: null
|
||||
property var ipcHandlers: ({})
|
||||
|
||||
// Noctalia i18n surface — v1 returns the key as-is.
|
||||
@@ -62,19 +66,28 @@ Item {
|
||||
function trp(key, count, interp) { return String(key === undefined ? "" : key) }
|
||||
function hasTranslation(key) { return false }
|
||||
|
||||
function saveSettings() {
|
||||
function saveSettings(settings) {
|
||||
if (settings !== undefined) pluginSettings = settings
|
||||
if (_hostBridge && typeof _hostBridge.persistSettings === "function")
|
||||
_hostBridge.persistSettings(pluginId, pluginSettings)
|
||||
// Re-read after persisting so mainInstance/bar widgets see the
|
||||
// canonical merged settings (defaults + saved overrides) immediately.
|
||||
if (_settingsProvider) pluginSettings = _settingsProvider()
|
||||
if (mainInstance && typeof mainInstance.refresh === "function") mainInstance.refresh()
|
||||
}
|
||||
|
||||
function openPanel(screen, buttonItem) {
|
||||
panelOpenScreen = screen
|
||||
panelOpenScreen = screen || true
|
||||
panelAnchorItem = buttonItem || null
|
||||
if (_hostBridge && _hostBridge.tooltipService && typeof _hostBridge.tooltipService.hide === "function")
|
||||
_hostBridge.tooltipService.hide(buttonItem)
|
||||
if (_hostBridge && typeof _hostBridge.openPanel === "function")
|
||||
_hostBridge.openPanel(pluginId, screen, buttonItem)
|
||||
}
|
||||
|
||||
function closePanel(screen) {
|
||||
panelOpenScreen = null
|
||||
panelAnchorItem = null
|
||||
if (_hostBridge && typeof _hostBridge.closePanel === "function")
|
||||
_hostBridge.closePanel(pluginId, screen)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ We ship just enough of Noctalia's QML namespace to render typical bar widgets.
|
||||
| Module | Symbols |
|
||||
|---|---|
|
||||
| `qs.Commons` | `Color`, `Style`, `Logger`, `Settings` (read-only), `I18n` (stub), `Time`, `Icons` (~50 entries), `ThemeIcons` (stub), `ShellState` (stub) |
|
||||
| `qs.Widgets` | `NText`, `NIcon`, `NIconButton`, `NBox`, `NButton`, `NPopupContextMenu`, `NScrollText`, `NToggle`, `NSpinBox`, `NSlider`, `NTextInput`, `NComboBox`, `NCheckbox`, `NDivider` |
|
||||
| `qs.Widgets` | `NText`, `NIcon`, `NIconButton`, `NBox`, `NButton`, `NPopupContextMenu`, `NScrollText`, `NToggle`, `NSpinBox`, `NSlider`, `NTextInput`, `NComboBox`, `NCheckbox`, `NDivider`, `NTabBar`, `NTabButton` |
|
||||
| `qs.Services.UI` | `BarService`, `TooltipService`, `PanelService` |
|
||||
| `qs.Services.System` | `HostService` (reads `/etc/os-release`) |
|
||||
| `qs.Services.Power` | `PowerProfileService` (returns `noctaliaPerformanceMode: false`) |
|
||||
|
||||
Reference in New Issue
Block a user