mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-01 20:28:16 +02:00
Remove unreleased Noctalia shell compatibility
This commit is contained in:
@@ -58,76 +58,11 @@ QtObject {
|
||||
property color unselectedBorder: root.pick("image-picker.unselected-border", root.foreground)
|
||||
}
|
||||
|
||||
// Noctalia palette tokens used by the compat widgets. Mapped onto the
|
||||
// foundational palette; not exposed in shell.toml.
|
||||
readonly property color mPrimary: accent
|
||||
readonly property color mSecondary: Qt.darker(accent, 1.2)
|
||||
readonly property color mTertiary: Qt.lighter(accent, 1.3)
|
||||
readonly property color mSurface: background
|
||||
readonly property color mSurfaceVariant: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.06)
|
||||
readonly property color mOnSurface: foreground
|
||||
readonly property color mOnSurfaceVariant: Qt.darker(foreground, 1.4)
|
||||
readonly property color mOutline: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.18)
|
||||
readonly property color mHover: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.14)
|
||||
readonly property color mOnHover: foreground
|
||||
readonly property color mError: urgent
|
||||
readonly property color mOnError: background
|
||||
|
||||
// Plugins read this to know whether to skip mid-flight transitions. We
|
||||
// don't ship theme transitions ourselves, so it's always false.
|
||||
readonly property bool isTransitioning: false
|
||||
|
||||
function alpha(c, opacity) {
|
||||
if (!c) return Qt.rgba(0, 0, 0, opacity)
|
||||
return Qt.rgba(c.r, c.g, c.b, opacity)
|
||||
}
|
||||
|
||||
// Noctalia's smartAlpha picks an alpha based on the host theme's perceived
|
||||
// contrast. A flat 0.6 reads acceptably across our themes; cheap, no math.
|
||||
function smartAlpha(c) {
|
||||
return alpha(c, 0.6)
|
||||
}
|
||||
|
||||
// adaptiveOpacity takes a 0..1 ratio and clamps it. Plugins use it for
|
||||
// fade animations relative to a "full" opacity value.
|
||||
function adaptiveOpacity(value) {
|
||||
if (value === undefined || value === null) return 1.0
|
||||
return Math.max(0, Math.min(1, Number(value)))
|
||||
}
|
||||
|
||||
// Plugins occasionally pass a color key like "accent" or "primary" via
|
||||
// their own settings. resolveColorKey returns a color; resolveColorKeyOptional
|
||||
// returns null/undefined-ish for "none".
|
||||
function resolveColorKey(key) {
|
||||
var resolved = resolveColorKeyOptional(key)
|
||||
return resolved === null ? foreground : resolved
|
||||
}
|
||||
|
||||
function resolveColorKeyOptional(key) {
|
||||
var k = String(key || "").toLowerCase()
|
||||
if (!k || k === "none") return null
|
||||
switch (k) {
|
||||
case "primary": return mPrimary
|
||||
case "secondary": return mSecondary
|
||||
case "tertiary": return mTertiary
|
||||
case "surface": return mSurface
|
||||
case "surfacevariant": return mSurfaceVariant
|
||||
case "onsurface": return mOnSurface
|
||||
case "onsurfacevariant": return mOnSurfaceVariant
|
||||
case "outline": return mOutline
|
||||
case "hover": return mHover
|
||||
case "onhover": return mOnHover
|
||||
case "error": return mError
|
||||
case "onerror": return mOnError
|
||||
case "accent": return accent
|
||||
case "foreground": return foreground
|
||||
case "background": return background
|
||||
case "urgent":
|
||||
case "red": return urgent
|
||||
}
|
||||
return Qt.color(k)
|
||||
}
|
||||
|
||||
function loadColors(raw) {
|
||||
var lines = String(raw || "").split("\n")
|
||||
var foundAccent = false
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
// Noctalia compat shim. Their plugins normally route translations through
|
||||
// pluginApi.tr(), not this singleton, so the stub here mostly exists to
|
||||
// satisfy `import qs.Commons` style usage.
|
||||
QtObject {
|
||||
readonly property string langCode: "en"
|
||||
|
||||
function tr(key, interp) { return String(key === undefined ? "" : key) }
|
||||
function trp(key, count, interp) { return String(key === undefined ? "" : key) }
|
||||
function hasTranslation(key) { return false }
|
||||
|
||||
signal translationsLoaded()
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
// Noctalia compat shim. Their plugins resolve Tabler icon names to glyphs via
|
||||
// this singleton. The original ships ~3000 mappings; for v1 we cover the
|
||||
// names most commonly used by the plugins in their repo and fall back to
|
||||
// "?" for unknowns so missing glyphs are visually obvious.
|
||||
QtObject {
|
||||
// Tabler icon names → Nerd Font glyphs. Most entries are mdi-* substitutes
|
||||
// since Omarchy installs JetBrains Mono Nerd Font which includes Material
|
||||
// Design Icons. Glyph codepoints embedded as actual chars (Python helper
|
||||
// when the write tool strips multi-byte codepoints in some positions).
|
||||
readonly property var glyphs: ({
|
||||
"bell": "\udb80\udc9a", // mdi-bell
|
||||
"bell-off": "\udb80\udc9b", // mdi-bell_off
|
||||
"bell-ring": "\udb80\udd6b", // mdi-bell_ring
|
||||
"clock": "\udb80\udc50", // mdi-clock
|
||||
"clock-outline": "\udb80\udc51", // mdi-clock_outline
|
||||
"wifi": "\udb81\udda9", // mdi-wifi
|
||||
"wifi-off": "\udb81\uddaa", // mdi-wifi_off
|
||||
"ethernet": "\udb80\udc02", // mdi-ethernet
|
||||
"bluetooth": "\udb80\udcaf", // mdi-bluetooth
|
||||
"bluetooth-off": "\udb80\udcb2", // mdi-bluetooth_off
|
||||
"volume": "\udb81\udd7e", // mdi-volume_high
|
||||
"volume-up": "\udb81\udd7e",
|
||||
"volume-down": "\udb81\udd7f",
|
||||
"volume-off": "\udb83\udc08", // mdi-volume_variant_off
|
||||
"volume-mute": "\udb83\udc08",
|
||||
"headphones": "\udb80\udecb", // mdi-headphones
|
||||
"microphone": "\udb80\udf6c", // mdi-microphone
|
||||
"microphone-off": "\udb80\udf6d", // mdi-microphone_off
|
||||
"play": "\udb81\udc0a", // mdi-play
|
||||
"pause": "\udb80\udfe4", // mdi-pause
|
||||
"skip-back": "\udb81\udcae", // mdi-skip_previous
|
||||
"skip-forward": "\udb81\udcad", // mdi-skip_next
|
||||
"music": "\udb81\udd5a", // mdi-music
|
||||
"weather": "\udb81\udd99", // mdi-weather_sunny
|
||||
"weather-sun": "\udb81\udd99",
|
||||
"weather-moon": "\udb81\udd94", // mdi-weather_night
|
||||
"weather-cloud": "\udb81\udd90", // mdi-weather_cloudy
|
||||
"weather-rain": "\udb81\udd96", // mdi-weather_pouring
|
||||
"battery": "\udb80\udc83", // mdi-battery
|
||||
"battery-charging": "\udb80\udc84",
|
||||
"cpu": "\udb83\udee0", // mdi-cpu_64_bit
|
||||
"memory": "\udb80\udd5b", // mdi-memory
|
||||
"tools": "\udb80\udd64", // mdi-tools
|
||||
"settings": "\udb80\udc93", // mdi-cog
|
||||
"settings-outline":"\udb80\udcbb",
|
||||
"power": "\udb80\udc25", // mdi-power
|
||||
"lock": "\udb80\udd3e", // mdi-lock
|
||||
"unlock": "\udb80\udd3f",
|
||||
"refresh": "\udb81\udc50", // mdi-refresh
|
||||
"ai": "", // mdi-robot
|
||||
"robot": "",
|
||||
"x": "\udb80\udd56", // mdi-close
|
||||
"check": "\udb80\udc12", // mdi-check
|
||||
"chevron-down": "\udb80\udd40",
|
||||
"chevron-up": "\udb80\udd43",
|
||||
"chevron-left": "\udb80\udd41",
|
||||
"chevron-right": "\udb80\udd42",
|
||||
"plus": "\udb80\udc15",
|
||||
"minus": "\udb80\udc16",
|
||||
"leaf": "\udb80\udf2a",
|
||||
"rocket": "\udb81\udc63",
|
||||
"balance": "\udb81\uddd1",
|
||||
"moon": "\udb81\udd94",
|
||||
"sun": "\udb81\udd99",
|
||||
"trash": "\udb81\udcd7", // mdi-delete
|
||||
"edit": "\udb80\udd66",
|
||||
"search": "\udb80\udd6f",
|
||||
"menu": "\udb80\udd6c",
|
||||
"home": "\udb80\udf0c",
|
||||
"folder": "\udb80\udd99",
|
||||
"file": "\udb80\udd97",
|
||||
"calendar": "\udb80\udcf7", // mdi-calendar
|
||||
"circle": "\udb80\udd2f"
|
||||
})
|
||||
|
||||
function get(name) {
|
||||
var key = String(name || "").toLowerCase()
|
||||
if (glyphs[key]) return glyphs[key]
|
||||
return "?"
|
||||
}
|
||||
|
||||
function has(name) {
|
||||
return !!glyphs[String(name || "").toLowerCase()]
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
// Noctalia compat shim. Plugins call Logger.d/i/w/e routinely.
|
||||
QtObject {
|
||||
function format(args) {
|
||||
var out = []
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
var a = args[i]
|
||||
out.push(a === undefined ? "undefined" : (a === null ? "null" : String(a)))
|
||||
}
|
||||
return out.join(" ")
|
||||
}
|
||||
|
||||
function d() { console.debug(format(arguments)) }
|
||||
function i() { console.info(format(arguments)) }
|
||||
function w() { console.warn(format(arguments)) }
|
||||
function e() { console.error(format(arguments)) }
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
// Noctalia compat shim. Their plugins read Settings.data.* and call
|
||||
// Settings.getBarPositionForScreen / getBarWidgetsForScreen. We expose a
|
||||
// read-only view of our shell.json so reads succeed; writes are best routed
|
||||
// through pluginApi.saveSettings() instead.
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property var data: ({
|
||||
bar: {
|
||||
position: "top",
|
||||
widgets: { left: [], center: [], right: [] }
|
||||
},
|
||||
general: {},
|
||||
colorSchemes: { darkMode: true }
|
||||
})
|
||||
|
||||
// Plugins use this gate around verbose debug logging. Defaulting to false
|
||||
// keeps their Logger.d calls silent unless we explicitly flip it.
|
||||
property bool isDebug: false
|
||||
|
||||
// shellConfig is wired by shell.qml at startup; reading it keeps Settings.data
|
||||
// in lockstep with the live shell config.
|
||||
property var shellConfig: null
|
||||
onShellConfigChanged: rebuildData()
|
||||
|
||||
function rebuildData() {
|
||||
var sc = shellConfig || {}
|
||||
var bar = (sc && sc.bar) ? sc.bar : {}
|
||||
var layout = (bar && bar.layout) ? bar.layout : {}
|
||||
data = {
|
||||
bar: {
|
||||
position: bar.position || "top",
|
||||
centerAnchor: bar.centerAnchor || "",
|
||||
fontFamily: bar.fontFamily || "JetBrainsMono Nerd Font",
|
||||
widgets: {
|
||||
left: Array.isArray(layout.left) ? layout.left : [],
|
||||
center: Array.isArray(layout.center) ? layout.center : [],
|
||||
right: Array.isArray(layout.right) ? layout.right : []
|
||||
}
|
||||
},
|
||||
general: {},
|
||||
colorSchemes: { darkMode: true }
|
||||
}
|
||||
}
|
||||
|
||||
function getBarPositionForScreen(name) {
|
||||
return data && data.bar ? data.bar.position : "top"
|
||||
}
|
||||
|
||||
function getBarWidgetsForScreen(name) {
|
||||
return data && data.bar ? data.bar.widgets : { left: [], center: [], right: [] }
|
||||
}
|
||||
|
||||
function getScreenOverrideEntry(name) { return null }
|
||||
function hasScreenOverride(name, key) { return false }
|
||||
function setScreenOverride(name, key, value) { /* not supported */ }
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
// Noctalia compat shim. Their plugins occasionally introspect global UI
|
||||
// state through this singleton. Returning safe defaults lets reads succeed
|
||||
// without us tracking the state ourselves.
|
||||
QtObject {
|
||||
readonly property bool isLoaded: true
|
||||
property var activeScreen: null
|
||||
property bool launcherOpen: false
|
||||
property bool settingsOpen: false
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import "." as Commons
|
||||
|
||||
// Noctalia compat shim. Sizing/spacing/typography tokens that plugins
|
||||
// reference unconditionally. Real values picked to roughly match what
|
||||
// Omarchy renders today — close enough that plugin layouts don't look
|
||||
// jarring next to native widgets.
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
// Radii. Omarchy uses sharp corners; all radius tokens collapse to 0 so
|
||||
// Noctalia plugins inherit the same look as native widgets.
|
||||
readonly property real radiusXXS: 0
|
||||
readonly property real radiusXS: 0
|
||||
readonly property real radiusS: 0
|
||||
readonly property real radiusM: 0
|
||||
readonly property real radiusL: 0
|
||||
readonly property real radiusXL: 0
|
||||
readonly property real iRadiusXS: 0
|
||||
readonly property real iRadiusS: 0
|
||||
readonly property real iRadiusM: 0
|
||||
readonly property real iRadiusL: 0
|
||||
|
||||
// Margins / paddings. Noctalia uses two parallel scales for margins (one
|
||||
// tighter, one looser); we map them to the same values.
|
||||
readonly property real marginXXS: 1
|
||||
readonly property real marginXS: 2
|
||||
readonly property real marginS: 4
|
||||
readonly property real marginM: 6
|
||||
readonly property real marginL: 10
|
||||
readonly property real marginXL: 14
|
||||
readonly property real margin2XXS: 1
|
||||
readonly property real margin2XS: 2
|
||||
readonly property real margin2S: 3
|
||||
readonly property real margin2M: 5
|
||||
readonly property real margin2L: 8
|
||||
readonly property real margin2XL: 12
|
||||
|
||||
// Border widths.
|
||||
readonly property real borderS: 1
|
||||
readonly property real borderM: 1
|
||||
readonly property real borderL: 2
|
||||
|
||||
// Font sizes.
|
||||
readonly property real fontSizeXXS: 9
|
||||
readonly property real fontSizeXS: 10
|
||||
readonly property real fontSizeS: 11
|
||||
readonly property real fontSizeM: 12
|
||||
readonly property real fontSizeL: 14
|
||||
readonly property real fontSizeXL: 16
|
||||
readonly property real fontSizeXXL: 20
|
||||
readonly property real fontSizeXXXL: 24
|
||||
|
||||
// Font weights.
|
||||
readonly property int fontWeightLight: 300
|
||||
readonly property int fontWeightRegular: 400
|
||||
readonly property int fontWeightMedium: 500
|
||||
readonly property int fontWeightSemiBold: 600
|
||||
readonly property int fontWeightBold: 700
|
||||
|
||||
// Opacities.
|
||||
readonly property real opacityLight: 0.4
|
||||
readonly property real opacityMedium: 0.6
|
||||
readonly property real opacityHeavy: 0.8
|
||||
readonly property real opacityFull: 1.0
|
||||
|
||||
// Animation durations (ms).
|
||||
readonly property int animationFast: 100
|
||||
readonly property int animationNormal: 160
|
||||
readonly property int animationSlow: 250
|
||||
readonly property int animationSlower: 400
|
||||
readonly property int animationSlowest: 600
|
||||
|
||||
// Tooltip delay (ms).
|
||||
readonly property int tooltipDelay: 400
|
||||
|
||||
// Capsule (bar pill) tokens. The Omarchy bar is flat, no capsule background,
|
||||
// so we route these to transparent + the foreground outline for visual hint.
|
||||
readonly property color capsuleColor: Qt.rgba(0, 0, 0, 0)
|
||||
readonly property color capsuleBorderColor: Qt.rgba(0, 0, 0, 0)
|
||||
readonly property real capsuleBorderWidth: 0
|
||||
readonly property real baseWidgetSize: 22
|
||||
|
||||
// UI scale ratio. Noctalia plugins multiply lengths by this when
|
||||
// applyUiScale is true; we leave it at 1.
|
||||
readonly property real uiScaleRatio: 1.0
|
||||
|
||||
// Helpers. Plugins pass screen names but in practice we ignore them and
|
||||
// return our shell-wide values.
|
||||
function getBarHeightForScreen(name) { return 28 }
|
||||
function getCapsuleHeightForScreen(name) { return 22 }
|
||||
function getBarFontSizeForScreen(name) { return fontSizeM }
|
||||
function getBarRadiusForScreen(name) { return radiusM }
|
||||
|
||||
function pixelAlignCenter(parentSize, childSize) {
|
||||
return Math.round((Number(parentSize) - Number(childSize)) / 2)
|
||||
}
|
||||
|
||||
function toOdd(n) {
|
||||
var i = Math.floor(Number(n))
|
||||
return i | 1
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
// Noctalia compat shim. Their plugins resolve app icons through XDG icon
|
||||
// themes; we don't ship that machinery, so iconForAppId returns empty and
|
||||
// callers fall back to their default glyph.
|
||||
QtObject {
|
||||
function iconFromName(name) { return "" }
|
||||
function iconForAppId(appId) { return "" }
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
// Noctalia compat shim.
|
||||
QtObject {
|
||||
function getFormattedTimestamp() {
|
||||
return new Date().toISOString()
|
||||
}
|
||||
|
||||
function nowMs() {
|
||||
return Date.now()
|
||||
}
|
||||
|
||||
function formatDate(date, fmt) {
|
||||
if (!date) date = new Date()
|
||||
return Qt.formatDateTime(date, fmt || "yyyy-MM-dd HH:mm:ss")
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,2 @@
|
||||
module qs.Commons
|
||||
singleton Color 1.0 Color.qml
|
||||
singleton Style 1.0 Style.qml
|
||||
singleton Logger 1.0 Logger.qml
|
||||
singleton Settings 1.0 Settings.qml
|
||||
singleton I18n 1.0 I18n.qml
|
||||
singleton Time 1.0 Time.qml
|
||||
singleton Icons 1.0 Icons.qml
|
||||
singleton ThemeIcons 1.0 ThemeIcons.qml
|
||||
singleton ShellState 1.0 ShellState.qml
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
// Noctalia compat shim. The "noctaliaPerformanceMode" flag was a v3 internal
|
||||
// state Noctalia plugins occasionally check. We hardcode false; plugins that
|
||||
// branch on it will fall into the non-performance code path.
|
||||
QtObject {
|
||||
readonly property bool noctaliaPerformanceMode: false
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
module qs.Services.Power
|
||||
singleton PowerProfileService 1.0 PowerProfileService.qml
|
||||
@@ -1,36 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
// Noctalia compat shim. activate-linux and a few other plugins display
|
||||
// distro/host info. We parse /etc/os-release lazily.
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property string osPretty: ""
|
||||
property string osName: ""
|
||||
property string osVersion: ""
|
||||
property string hostname: ""
|
||||
|
||||
property Process osReleaseProc: Process {
|
||||
command: ["bash", "-c",
|
||||
"( . /etc/os-release && printf '%s\\t%s\\t%s\\n' \"$PRETTY_NAME\" \"$NAME\" \"$VERSION_ID\" ); hostname"]
|
||||
onExited: {
|
||||
var text = String(osReleaseStdout.text || "").trim().split("\n")
|
||||
if (text.length >= 1) {
|
||||
var fields = text[0].split("\t")
|
||||
root.osPretty = fields[0] || ""
|
||||
root.osName = fields[1] || ""
|
||||
root.osVersion = fields[2] || ""
|
||||
}
|
||||
if (text.length >= 2) root.hostname = text[1].trim()
|
||||
}
|
||||
stdout: StdioCollector {
|
||||
id: osReleaseStdout
|
||||
waitForEnd: true
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: osReleaseProc.running = true
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
module qs.Services.System
|
||||
singleton HostService 1.0 HostService.qml
|
||||
@@ -1,80 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
// Noctalia compat shim. Plugin bar widgets call into BarService for
|
||||
// registration bookkeeping and to ask the host where their tooltip should
|
||||
// pop. We track the bar reference (set by the host at startup) and expose
|
||||
// the helpers plugins actually use.
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
// Wired by omarchy-shell's Bar.qml on construction so we can reach back
|
||||
// for things like position and the tooltip popup.
|
||||
property var bar: null
|
||||
|
||||
// Plugins read this when computing their own layout-revision markers.
|
||||
property int widgetsRevision: 0
|
||||
|
||||
// Bookkeeping for plugin widgets. Not consulted by Omarchy itself; some
|
||||
// plugins call lookupWidget() to coordinate across instances.
|
||||
property var registered: ({})
|
||||
|
||||
function registerWidget(screen, section, widgetId, index, item) {
|
||||
var key = (screen && screen.name ? screen.name : "_") + ":" + section + ":" + widgetId + ":" + index
|
||||
var next = {}
|
||||
for (var k in registered) next[k] = registered[k]
|
||||
next[key] = { screen: screen, section: section, widgetId: widgetId, index: index, item: item }
|
||||
registered = next
|
||||
widgetsRevision++
|
||||
}
|
||||
|
||||
function unregisterWidget(screen, section, widgetId, index) {
|
||||
var key = (screen && screen.name ? screen.name : "_") + ":" + section + ":" + widgetId + ":" + index
|
||||
if (!registered[key]) return
|
||||
var next = {}
|
||||
for (var k in registered) if (k !== key) next[k] = registered[k]
|
||||
registered = next
|
||||
widgetsRevision++
|
||||
}
|
||||
|
||||
// Plugins occasionally search for a sibling widget by id. We do a linear
|
||||
// scan since the registry is small in practice.
|
||||
function lookupWidget(widgetId) {
|
||||
for (var k in registered) {
|
||||
if (registered[k].widgetId === widgetId) return registered[k]
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Translate the bar's edge to a tooltip direction string. Noctalia expects
|
||||
// strings; the Omarchy bar drives its own tooltip popup off bar.position.
|
||||
function getTooltipDirection(screenName) {
|
||||
var pos = bar ? bar.position : "top"
|
||||
if (pos === "top") return "down"
|
||||
if (pos === "bottom") return "up"
|
||||
if (pos === "left") return "right"
|
||||
if (pos === "right") return "left"
|
||||
return "down"
|
||||
}
|
||||
|
||||
function getPillDirection(item) {
|
||||
var pos = bar ? bar.position : "top"
|
||||
return pos === "left" || pos === "right" ? "horizontal" : "vertical"
|
||||
}
|
||||
|
||||
// Forward into the omarchy-shell host. Most Noctalia widgets call this
|
||||
// from a right-click handler.
|
||||
function openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings) {
|
||||
if (bar && bar.shell && typeof bar.shell.summon === "function") {
|
||||
bar.shell.summon("omarchy.settings",
|
||||
JSON.stringify({ focusWidgetId: widgetId, section: section, index: sectionWidgetIndex }))
|
||||
}
|
||||
}
|
||||
|
||||
function openPluginSettings(screen, manifest) {
|
||||
if (bar && bar.shell && typeof bar.shell.summon === "function") {
|
||||
bar.shell.summon("omarchy.settings",
|
||||
JSON.stringify({ focusPluginId: manifest ? manifest.id : "" }))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
// Noctalia compat shim. Plugins use this for context menus and to reach
|
||||
// the host's panels. We forward into the omarchy-shell host where there's
|
||||
// a matching surface, and log a warning for anything we don't ship in v1.
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property var bar: null
|
||||
property var shell: null
|
||||
|
||||
// Plugins call this on right-click with a Menu component. The Noctalia
|
||||
// implementation parents the menu to the screen's overlay; we simply
|
||||
// call `open()` on whatever the plugin passes — most plugin context menus
|
||||
// are plain QtQuick `Menu` items that know how to show themselves.
|
||||
function showContextMenu(menu, item, screen) {
|
||||
if (!menu) return
|
||||
if (typeof menu.popup === "function") menu.popup()
|
||||
else if (typeof menu.open === "function") menu.open()
|
||||
}
|
||||
|
||||
function closeContextMenu(screen) {
|
||||
// No-op; the Menu / PopupWindow closes itself on outside click.
|
||||
}
|
||||
|
||||
function openLauncherWithSearch(screen, prefix) {
|
||||
console.warn("PanelService.openLauncherWithSearch is not supported in the Omarchy compat layer")
|
||||
}
|
||||
|
||||
function getPanel(name, screen) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
// Noctalia compat shim. Their plugins call TooltipService.show(item, text)
|
||||
// on hover; we route that into the Omarchy bar's shared tooltip popup.
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
// Wired by Bar.qml on construction.
|
||||
property var bar: null
|
||||
|
||||
function show(item, text, direction) {
|
||||
if (!bar || !item) return
|
||||
if (typeof bar.showTooltip === "function") {
|
||||
bar.showTooltip(item, text || "")
|
||||
}
|
||||
}
|
||||
|
||||
function hide(item) {
|
||||
if (!bar) return
|
||||
if (item && typeof bar.hideTooltip === "function") {
|
||||
bar.hideTooltip(item)
|
||||
} else if (bar.tooltipTarget) {
|
||||
// Noctalia plugins often call TooltipService.hide() without the source
|
||||
// item on click. Native bar.hideTooltip(target) ignores null targets, so
|
||||
// clear the currently visible tooltip explicitly.
|
||||
bar.hideTooltip(bar.tooltipTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
module qs.Services.UI
|
||||
singleton BarService 1.0 BarService.qml
|
||||
singleton TooltipService 1.0 TooltipService.qml
|
||||
singleton PanelService 1.0 PanelService.qml
|
||||
@@ -1,9 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
|
||||
Rectangle {
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
radius: Style.radiusM
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Commons
|
||||
|
||||
Button {
|
||||
id: root
|
||||
property string label: ""
|
||||
property color colorBg: Color.mSurfaceVariant
|
||||
property color colorFg: Color.mOnSurface
|
||||
|
||||
text: label || ""
|
||||
background: Rectangle {
|
||||
color: root.hovered ? Color.mHover : root.colorBg
|
||||
radius: Style.radiusM
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
}
|
||||
contentItem: Text {
|
||||
text: root.text
|
||||
color: root.colorFg
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: Style.fontSizeM
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Commons
|
||||
|
||||
CheckBox {
|
||||
id: root
|
||||
property string label: ""
|
||||
text: label || ""
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: Style.fontSizeS
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Commons
|
||||
|
||||
ComboBox {
|
||||
id: root
|
||||
|
||||
property string label: ""
|
||||
// Noctalia's NComboBox API commonly uses a model of { key, name } objects,
|
||||
// a currentKey property, and an onSelected(key, item) handler. Qt's ComboBox
|
||||
// only knows currentIndex/currentText, so bridge the small API surface here.
|
||||
property string currentKey: ""
|
||||
|
||||
signal selected(var key, var item)
|
||||
|
||||
textRole: "name"
|
||||
valueRole: "key"
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: Style.fontSizeS
|
||||
implicitHeight: 32
|
||||
leftPadding: 10
|
||||
rightPadding: 28
|
||||
topPadding: 4
|
||||
bottomPadding: 4
|
||||
|
||||
function itemAt(index) {
|
||||
if (index < 0) return null
|
||||
if (Array.isArray(root.model)) return root.model[index] || null
|
||||
return null
|
||||
}
|
||||
|
||||
function keyAt(index) {
|
||||
var item = itemAt(index)
|
||||
if (!item) return ""
|
||||
if (item.key !== undefined && item.key !== null) return String(item.key)
|
||||
if (item.value !== undefined && item.value !== null) return String(item.value)
|
||||
if (item.name !== undefined && item.name !== null) return String(item.name)
|
||||
return String(item)
|
||||
}
|
||||
|
||||
function syncCurrentIndex() {
|
||||
if (!Array.isArray(root.model)) return
|
||||
for (var i = 0; i < root.model.length; i++) {
|
||||
if (keyAt(i) === String(root.currentKey || "")) {
|
||||
if (root.currentIndex !== i) root.currentIndex = i
|
||||
return
|
||||
}
|
||||
}
|
||||
if (root.model.length > 0 && root.currentIndex < 0) root.currentIndex = 0
|
||||
}
|
||||
|
||||
Component.onCompleted: syncCurrentIndex()
|
||||
onCurrentKeyChanged: syncCurrentIndex()
|
||||
onModelChanged: syncCurrentIndex()
|
||||
|
||||
onActivated: function(index) {
|
||||
var item = itemAt(index)
|
||||
var key = keyAt(index)
|
||||
root.currentKey = key
|
||||
root.selected(key, item)
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: root.displayText
|
||||
color: Color.mOnSurface
|
||||
font: root.font
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: root.activeFocus ? Color.mPrimary : Color.mOutline
|
||||
border.width: Style.borderS
|
||||
radius: 0
|
||||
}
|
||||
|
||||
indicator: Text {
|
||||
x: root.width - width - 10
|
||||
y: (root.height - height) / 2
|
||||
text: "▾"
|
||||
color: Color.mOnSurfaceVariant
|
||||
font.family: root.font.family
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
id: del
|
||||
required property int index
|
||||
required property var modelData
|
||||
width: ListView.view ? ListView.view.width : root.width
|
||||
height: 28
|
||||
highlighted: root.highlightedIndex === del.index
|
||||
|
||||
contentItem: Text {
|
||||
text: {
|
||||
var m = del.modelData
|
||||
if (m === null || m === undefined) return ""
|
||||
if (root.textRole && m[root.textRole] !== undefined) return String(m[root.textRole])
|
||||
return String(m)
|
||||
}
|
||||
color: Color.mOnSurface
|
||||
font: root.font
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: del.highlighted || del.hovered
|
||||
? Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.14)
|
||||
: "transparent"
|
||||
radius: 0
|
||||
}
|
||||
}
|
||||
|
||||
popup: Popup {
|
||||
y: root.height
|
||||
width: root.width
|
||||
implicitHeight: Math.min(contentItem.implicitHeight + 2, 280)
|
||||
padding: 1
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
radius: 0
|
||||
}
|
||||
|
||||
contentItem: ListView {
|
||||
clip: true
|
||||
implicitHeight: contentHeight
|
||||
model: root.popup.visible ? root.delegateModel : null
|
||||
currentIndex: root.highlightedIndex
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
ScrollIndicator.vertical: ScrollIndicator { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
|
||||
Rectangle {
|
||||
implicitHeight: 1
|
||||
color: Color.mOutline
|
||||
opacity: 0.4
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
|
||||
// Noctalia compat shim. Renders an icon by name through the Icons map.
|
||||
Text {
|
||||
id: root
|
||||
property string icon: ""
|
||||
property real pointSize: 14
|
||||
property bool applyUiScale: true
|
||||
|
||||
text: Icons.get(icon)
|
||||
color: Color.mOnSurface
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: Math.round(pointSize * (applyUiScale ? Style.uiScaleRatio : 1))
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
import qs.Services.UI
|
||||
|
||||
// Noctalia compat shim. The widget plugins lean on this heavily; it's the
|
||||
// primary clickable bar element. We mimic Noctalia's surface area
|
||||
// (icon/text/tooltip/colors/border) but skip the long tail of properties
|
||||
// no observed plugin actually sets.
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string icon: ""
|
||||
property string text: ""
|
||||
property string tooltipText: ""
|
||||
property string tooltipDirection: ""
|
||||
property real baseSize: Style.baseWidgetSize
|
||||
property bool applyUiScale: false
|
||||
property real customRadius: -1
|
||||
property color colorBg: Style.capsuleColor
|
||||
property color colorFg: Color.mOnSurface
|
||||
property color colorBgHover: Color.mHover
|
||||
property color colorFgHover: Color.mOnHover
|
||||
property color colorBorder: Style.capsuleBorderColor
|
||||
property color colorBorderHover: Style.capsuleBorderColor
|
||||
property bool flat: false
|
||||
|
||||
signal clicked()
|
||||
signal rightClicked()
|
||||
signal middleClicked()
|
||||
signal wheel(int delta)
|
||||
|
||||
readonly property real effectiveSize: Math.round(baseSize * (applyUiScale ? Style.uiScaleRatio : 1))
|
||||
|
||||
implicitWidth: effectiveSize
|
||||
implicitHeight: effectiveSize
|
||||
radius: customRadius >= 0 ? customRadius : Style.radiusM
|
||||
color: mouse.containsMouse ? colorBgHover : colorBg
|
||||
border.color: mouse.containsMouse ? colorBorderHover : colorBorder
|
||||
border.width: Style.capsuleBorderWidth
|
||||
|
||||
Behavior on color { ColorAnimation { duration: Style.animationFast } }
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: root.icon ? Icons.get(root.icon) : root.text
|
||||
color: mouse.containsMouse ? root.colorFgHover : root.colorFg
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: Math.round(Style.fontSizeM * (root.applyUiScale ? Style.uiScaleRatio : 1))
|
||||
Behavior on color { ColorAnimation { duration: Style.animationFast } }
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
onEntered: if (root.tooltipText) TooltipService.show(root, root.tooltipText, root.tooltipDirection)
|
||||
onExited: TooltipService.hide(root)
|
||||
onClicked: function(m) {
|
||||
if (m.button === Qt.RightButton) root.rightClicked()
|
||||
else if (m.button === Qt.MiddleButton) root.middleClicked()
|
||||
else root.clicked()
|
||||
}
|
||||
onWheel: function(w) { root.wheel(w.angleDelta.y) }
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Commons
|
||||
|
||||
// Noctalia compat shim. Plugins instantiate this with a model of action
|
||||
// objects and expect `triggered(action)` when a row is picked.
|
||||
Menu {
|
||||
id: root
|
||||
|
||||
property var model: []
|
||||
// Noctalia passes the owning ShellScreen through here. Qt Quick Controls
|
||||
// Menu doesn't need it, but accepting the property keeps plugin QML from
|
||||
// failing at load time.
|
||||
property var screen: null
|
||||
signal triggered(var action, var item)
|
||||
|
||||
Instantiator {
|
||||
model: root.model
|
||||
delegate: MenuItem {
|
||||
required property var modelData
|
||||
text: modelData ? String(modelData.label || modelData.action || "") : ""
|
||||
onTriggered: root.triggered(modelData ? String(modelData.action || "") : "", modelData)
|
||||
}
|
||||
onObjectAdded: function(index, object) { root.insertItem(index, object) }
|
||||
onObjectRemoved: function(index, object) { root.removeItem(object) }
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
|
||||
// Noctalia compat shim. Auto-scrolling label.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string text: ""
|
||||
property real maxWidth: 200
|
||||
property real pointSize: Style.fontSizeM
|
||||
property int scrollMode: NScrollText.ScrollMode.Hover
|
||||
property bool forcedHover: false
|
||||
property real fadeExtent: 0.1
|
||||
property real fadeCornerRadius: 0
|
||||
property bool fadeRoundLeftCorners: false
|
||||
property bool fadeRoundRightCorners: false
|
||||
property color textColor: Color.mOnSurface
|
||||
|
||||
enum ScrollMode { Always, Hover, Never }
|
||||
|
||||
implicitHeight: label.implicitHeight
|
||||
implicitWidth: Math.min(maxWidth, label.implicitWidth)
|
||||
clip: true
|
||||
|
||||
readonly property bool overflowing: label.implicitWidth > width
|
||||
readonly property bool shouldScroll: {
|
||||
if (!overflowing) return false
|
||||
if (scrollMode === NScrollText.ScrollMode.Always) return true
|
||||
if (scrollMode === NScrollText.ScrollMode.Hover) return forcedHover
|
||||
return false
|
||||
}
|
||||
|
||||
Text {
|
||||
id: label
|
||||
text: root.text
|
||||
color: root.textColor
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: root.pointSize
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
height: parent.height
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
NumberAnimation on x {
|
||||
id: scrollAnim
|
||||
running: root.shouldScroll
|
||||
loops: Animation.Infinite
|
||||
duration: Math.max(5000, label.implicitWidth * 22)
|
||||
from: root.width
|
||||
to: -label.implicitWidth
|
||||
easing.type: Easing.Linear
|
||||
}
|
||||
|
||||
onShouldScrollChanged: if (!root.shouldScroll) x = 0
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Commons
|
||||
|
||||
Slider {
|
||||
id: root
|
||||
property string label: ""
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Commons
|
||||
|
||||
SpinBox {
|
||||
id: root
|
||||
property string label: ""
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: Style.fontSizeS
|
||||
editable: true
|
||||
implicitHeight: 32
|
||||
leftPadding: 10
|
||||
rightPadding: 24
|
||||
topPadding: 4
|
||||
bottomPadding: 4
|
||||
|
||||
background: Rectangle {
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: root.activeFocus ? Color.mPrimary : Color.mOutline
|
||||
border.width: Style.borderS
|
||||
radius: Style.radiusS
|
||||
}
|
||||
|
||||
contentItem: TextInput {
|
||||
text: root.displayText
|
||||
font: root.font
|
||||
color: Color.mOnSurface
|
||||
selectionColor: Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.35)
|
||||
selectedTextColor: Color.mOnSurface
|
||||
horizontalAlignment: Qt.AlignLeft
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
readOnly: !root.editable
|
||||
validator: root.validator
|
||||
inputMethodHints: Qt.ImhFormattedNumbersOnly
|
||||
}
|
||||
|
||||
up.indicator: Rectangle {
|
||||
x: root.mirrored ? 0 : root.width - width
|
||||
width: 20
|
||||
height: root.height / 2
|
||||
color: root.up.pressed
|
||||
? Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.16)
|
||||
: root.up.hovered
|
||||
? Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.08)
|
||||
: "transparent"
|
||||
radius: Style.radiusS
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "▲"
|
||||
font.family: root.font.family
|
||||
font.pixelSize: 7
|
||||
color: root.up.hovered ? Color.mOnSurface : Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
|
||||
down.indicator: Rectangle {
|
||||
x: root.mirrored ? 0 : root.width - width
|
||||
y: root.height / 2
|
||||
width: 20
|
||||
height: root.height / 2
|
||||
color: root.down.pressed
|
||||
? Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.16)
|
||||
: root.down.hovered
|
||||
? Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.08)
|
||||
: "transparent"
|
||||
radius: Style.radiusS
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "▼"
|
||||
font.family: root.font.family
|
||||
font.pixelSize: 7
|
||||
color: root.down.hovered ? Color.mOnSurface : Color.mOnSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property int currentIndex: 0
|
||||
property int margins: Style.marginXS
|
||||
property bool distributeEvenly: false
|
||||
|
||||
implicitHeight: 32
|
||||
radius: Style.radiusS
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: Color.mOutline
|
||||
border.width: Style.borderS
|
||||
|
||||
default property alias content: row.children
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.margins
|
||||
spacing: Style.marginXS
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Commons
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string text: ""
|
||||
property int tabIndex: -1
|
||||
property bool checked: false
|
||||
|
||||
signal clicked()
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: label.implicitWidth + Style.marginL * 2
|
||||
implicitHeight: 26
|
||||
radius: Style.radiusXS
|
||||
color: area.containsMouse
|
||||
? Color.mHover
|
||||
: (checked ? Qt.rgba(Color.mPrimary.r, Color.mPrimary.g, Color.mPrimary.b, 0.18) : "transparent")
|
||||
border.color: checked ? Color.mPrimary : "transparent"
|
||||
border.width: checked ? 1 : 0
|
||||
|
||||
Behavior on color { ColorAnimation { duration: Style.animationFast } }
|
||||
|
||||
Text {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
text: root.text
|
||||
color: checked ? Color.mPrimary : Color.mOnSurface
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: Style.fontSizeS
|
||||
font.bold: checked
|
||||
elide: Text.ElideRight
|
||||
width: Math.max(0, parent.width - Style.marginM * 2)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: area
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
|
||||
// Noctalia compat shim. The original supports pointSize (interpreted as
|
||||
// pixel size in Noctalia) plus an applyUiScale flag. We translate to
|
||||
// font.pixelSize directly; uiScaleRatio comes from Style.
|
||||
Text {
|
||||
id: root
|
||||
property real pointSize: 12
|
||||
property bool applyUiScale: true
|
||||
property var customFont: null
|
||||
|
||||
color: Color.mOnSurface
|
||||
font.family: customFont || "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: Math.round(pointSize * (applyUiScale ? Style.uiScaleRatio : 1))
|
||||
font.weight: Style.fontWeightRegular
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Commons
|
||||
|
||||
TextField {
|
||||
id: root
|
||||
property string label: ""
|
||||
property string description: ""
|
||||
property string defaultValue: ""
|
||||
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: Style.fontSizeS
|
||||
color: Color.mOnSurface
|
||||
background: Rectangle {
|
||||
color: Color.mSurfaceVariant
|
||||
border.color: root.focus ? Color.mPrimary : Color.mOutline
|
||||
border.width: Style.borderS
|
||||
radius: Style.radiusS
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string label: ""
|
||||
property string text: ""
|
||||
property bool checked: false
|
||||
|
||||
signal toggled(var value)
|
||||
signal clicked()
|
||||
|
||||
implicitWidth: labelItem.visible ? indicator.width + 8 + labelItem.implicitWidth : indicator.width
|
||||
implicitHeight: Math.max(indicator.height, labelItem.implicitHeight)
|
||||
color: "transparent"
|
||||
opacity: enabled ? 1 : 0.4
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
width: 48
|
||||
height: 22
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
radius: 0
|
||||
color: root.checked
|
||||
? Color.mPrimary
|
||||
: Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.10)
|
||||
border.color: root.checked
|
||||
? Color.mPrimary
|
||||
: Qt.rgba(Color.mOnSurface.r, Color.mOnSurface.g, Color.mOnSurface.b, 0.4)
|
||||
border.width: Style.borderS
|
||||
|
||||
Rectangle {
|
||||
width: 16
|
||||
height: 16
|
||||
radius: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: root.checked ? parent.width - width - 3 : 3
|
||||
color: root.checked ? Color.mSurface : Color.mOnSurface
|
||||
Behavior on x { NumberAnimation { duration: Style.animationFast } }
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: labelItem
|
||||
visible: (root.label || root.text) !== ""
|
||||
anchors.left: indicator.right
|
||||
anchors.leftMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.label || root.text
|
||||
color: Color.mOnSurface
|
||||
font.family: "JetBrainsMono Nerd Font"
|
||||
font.pixelSize: Style.fontSizeS
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: root.enabled
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.checked = !root.checked
|
||||
root.clicked()
|
||||
root.toggled(root.checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
module qs.Widgets
|
||||
NText 1.0 NText.qml
|
||||
NIcon 1.0 NIcon.qml
|
||||
NIconButton 1.0 NIconButton.qml
|
||||
NBox 1.0 NBox.qml
|
||||
NButton 1.0 NButton.qml
|
||||
NPopupContextMenu 1.0 NPopupContextMenu.qml
|
||||
NScrollText 1.0 NScrollText.qml
|
||||
NToggle 1.0 NToggle.qml
|
||||
NSpinBox 1.0 NSpinBox.qml
|
||||
NSlider 1.0 NSlider.qml
|
||||
NTextInput 1.0 NTextInput.qml
|
||||
NComboBox 1.0 NComboBox.qml
|
||||
NCheckbox 1.0 NCheckbox.qml
|
||||
NDivider 1.0 NDivider.qml
|
||||
NTabBar 1.0 NTabBar.qml
|
||||
NTabButton 1.0 NTabButton.qml
|
||||
@@ -1,146 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
// Builds the `pluginApi` QtObject that Noctalia plugins expect. The host
|
||||
// (omarchy-shell or the bar) calls create() once per plugin and caches the
|
||||
// returned object so the same handle is reused across the plugin's bar
|
||||
// widget, panel, settings form, etc.
|
||||
Item {
|
||||
// create(pluginId, manifest, settingsProvider, hostBridge) -> QtObject
|
||||
//
|
||||
// settingsProvider: function() that returns the plugin's current settings
|
||||
// merged with defaults. Called fresh on every read so the plugin always
|
||||
// sees the live shell.json entry, not a stale snapshot from when create()
|
||||
// was first called.
|
||||
// hostBridge: object with these methods (all optional):
|
||||
// - persistSettings(pluginId, settings): write back to shell.json via
|
||||
// the shell's updateEntryInline helper.
|
||||
// - openPanel(pluginId, screen, buttonItem): summon the plugin's panel
|
||||
// entry point through the shell's plugin host.
|
||||
// - closePanel(pluginId, screen): hide a previously-summoned panel.
|
||||
// - currentScreen(): return the screen the plugin should anchor to.
|
||||
// togglePanel is provided by the factory itself and routes through the
|
||||
// open/closePanel bridge methods. Main.qml service instances are wired
|
||||
// separately by the shell and set on api.mainInstance once instantiated.
|
||||
function create(pluginId, manifest, settingsProvider, hostBridge) {
|
||||
var api = pluginApiFactory.createObject(null, {
|
||||
pluginId: pluginId,
|
||||
pluginDir: manifest && manifest.__sourceDir ? manifest.__sourceDir : "",
|
||||
manifest: manifest || ({}),
|
||||
_settingsProvider: settingsProvider || (function() { return {} }),
|
||||
_hostBridge: hostBridge || ({})
|
||||
})
|
||||
return api
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pluginApiFactory
|
||||
|
||||
QtObject {
|
||||
id: api
|
||||
|
||||
property string pluginId: ""
|
||||
property string pluginDir: ""
|
||||
property var manifest: ({})
|
||||
property var _settingsProvider
|
||||
property var _hostBridge: ({})
|
||||
property var mainInstance: null
|
||||
|
||||
// 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.
|
||||
readonly property string currentLanguage: "en"
|
||||
readonly property var pluginTranslations: ({})
|
||||
readonly property var pluginFallbackTranslations: ({})
|
||||
readonly property int translationVersion: 0
|
||||
|
||||
function tr(key, interp) { return String(key === undefined ? "" : key) }
|
||||
function trp(key, count, interp) { return String(key === undefined ? "" : key) }
|
||||
function hasTranslation(key) { return false }
|
||||
|
||||
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 || 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)
|
||||
}
|
||||
|
||||
function togglePanel(screen, buttonItem) {
|
||||
if (panelOpenScreen) closePanel(screen)
|
||||
else openPanel(screen, buttonItem)
|
||||
}
|
||||
|
||||
function withCurrentScreen(cb) {
|
||||
if (typeof cb !== "function") return
|
||||
var s = (_hostBridge && typeof _hostBridge.currentScreen === "function")
|
||||
? _hostBridge.currentScreen() : null
|
||||
cb(s)
|
||||
}
|
||||
|
||||
function openLauncher(screen) {
|
||||
console.warn("pluginApi.openLauncher is not supported in the Omarchy compat layer (plugin=" + pluginId + ")")
|
||||
}
|
||||
function closeLauncher(screen) { /* no-op */ }
|
||||
function toggleLauncher(screen) { openLauncher(screen) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
# Noctalia plugin compatibility (omarchy-shell)
|
||||
|
||||
The omarchy-shell can load most bar widget plugins from the
|
||||
[noctalia-dev/noctalia-plugins](https://github.com/noctalia-dev/noctalia-plugins)
|
||||
ecosystem without any modification to the plugin code.
|
||||
|
||||
## Install a Noctalia plugin
|
||||
|
||||
```sh
|
||||
git clone https://github.com/noctalia-dev/noctalia-plugins /tmp/noctalia-plugins
|
||||
ln -s /tmp/noctalia-plugins/asus-um5606-fan-state \
|
||||
~/.config/omarchy/plugins/asus-um5606-fan-state
|
||||
omarchy-shell-ipc shell rescanPlugins
|
||||
```
|
||||
|
||||
The plugin id you see inside Omarchy is prefixed: `noctalia.asus-um5606-fan-state`.
|
||||
Add it via the bar customizer or by editing `~/.config/omarchy/shell.json` directly.
|
||||
|
||||
## What's supported in v1
|
||||
|
||||
| Noctalia concept | Omarchy support |
|
||||
|---|---|
|
||||
| `entryPoints.barWidget` | yes — registered through `BarWidgetRegistry` |
|
||||
| `entryPoints.panel` | yes — opened via `pluginApi.openPanel()` |
|
||||
| `entryPoints.settings` | yes — embedded in the settings dialog |
|
||||
| `entryPoints.main` | yes — instantiated as a hidden service, exposed via `pluginApi.mainInstance` |
|
||||
| `entryPoints.desktopWidget` | **no** — skipped with a console warning |
|
||||
| `entryPoints.launcherProvider` | **no** — skipped with a console warning |
|
||||
| Plugin i18n (`i18n/<lang>.json`) | **no** — `tr()` returns the raw key |
|
||||
| Plugin install from git URL | **no** — manual drop into `~/.config/omarchy/plugins/` |
|
||||
| Hot reload on plugin change | **no** — call `omarchy-shell-ipc shell rescanPlugins` |
|
||||
|
||||
## Shim surface
|
||||
|
||||
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`, `NTabBar`, `NTabButton` |
|
||||
| `qs.Services.UI` | `BarService`, `TooltipService`, `PanelService` |
|
||||
| `qs.Services.System` | `HostService` (reads `/etc/os-release`) |
|
||||
| `qs.Services.Power` | `PowerProfileService` (returns `noctaliaPerformanceMode: false`) |
|
||||
|
||||
Anything outside this surface logs a `console.warn` instead of crashing the
|
||||
shell, but the plugin may not render correctly.
|
||||
|
||||
## `pluginApi` surface
|
||||
|
||||
A Noctalia plugin's `pluginApi` is built per-plugin and injected onto the bar
|
||||
widget / panel / settings entry points. The implementation lives at
|
||||
`compat/noctalia/PluginApiFactory.qml`.
|
||||
|
||||
| Property / method | Behaviour |
|
||||
|---|---|
|
||||
| `pluginId`, `pluginDir`, `manifest` | Provided as expected. |
|
||||
| `pluginSettings` | Live read of the plugin's entry in `~/.config/omarchy/shell.json`, merged with the manifest's `metadata.defaultSettings`. |
|
||||
| `mainInstance` | Live instance of `Main.qml` when the plugin declares one. |
|
||||
| `saveSettings()` | Persists the merged settings into the plugin's entry in `shell.json`. |
|
||||
| `openPanel(screen, btn)` / `closePanel(screen)` / `togglePanel(screen, btn)` | Routes through `omarchy-shell-ipc shell summon/hide` against the plugin's panel entry point. |
|
||||
| `withCurrentScreen(cb)` | Calls `cb` with the bar's currently-rendering screen. |
|
||||
| `tr/trp/hasTranslation` | Returns the key as-is; no i18n in v1. |
|
||||
| `openLauncher(...)` family | Stub — logs a warning. |
|
||||
|
||||
## Known limitations
|
||||
|
||||
- Plugin labels show raw translation keys for non-English locales.
|
||||
- Icon names not present in our compact `Icons` map render as `?`.
|
||||
- Plugins that declare only `desktopWidget` or `launcherProvider` and no
|
||||
bar/panel/main do not appear in the Omarchy catalog.
|
||||
- Some plugins call `Color.mPrimary` for accent colors that don't perfectly
|
||||
match Omarchy's theme palette; results are close but not pixel-identical.
|
||||
@@ -26,11 +26,8 @@ Item {
|
||||
// the bar just renders whatever it's handed. The bar font follows the
|
||||
// OS-level fontconfig monospace binding — it is not stored in shell.json.
|
||||
required property var barConfig
|
||||
// Injected by the host shell so the bar can detect Noctalia-compat plugins
|
||||
// and look up manifests when wiring per-widget Noctalia pluginApi.
|
||||
property var pluginRegistry: null
|
||||
// Injected by the host shell. Used so Noctalia plugins that reach for
|
||||
// shell-wide APIs (openPanel, currentScreen) can do so via pluginApi.
|
||||
// Injected by the host shell. Used for shell-wide actions such as opening
|
||||
// settings and persisting inline widget state.
|
||||
property var shell: null
|
||||
// Mirrors the on-disk `bar-off` flag so the user can hide the bar without
|
||||
// killing the entire shell. Wired to BarPanel.visible below; updated by the
|
||||
@@ -93,40 +90,6 @@ Item {
|
||||
if (activePopout === owner) activePopout = null
|
||||
}
|
||||
|
||||
// -------------------------------------------------- Noctalia compat helpers
|
||||
//
|
||||
// The shell owns pluginApi creation and Main.qml service instantiation now;
|
||||
// bar widgets just look up their api by moduleName. Keeping section/index
|
||||
// helpers here because they're needed for Noctalia bar-widget injection
|
||||
// (widgetId, section, sectionWidgetIndex, sectionWidgetsCount).
|
||||
|
||||
function sectionOfEntry(entry) {
|
||||
var sections = ["left", "center", "right"]
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
var list = layoutConfig[sections[s]] || []
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
if (list[i] === entry) return sections[s]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
function indexOfEntry(entry) {
|
||||
var section = sectionOfEntry(entry)
|
||||
var list = section ? (layoutConfig[section] || []) : []
|
||||
for (var i = 0; i < list.length; i++) if (list[i] === entry) return i
|
||||
return -1
|
||||
}
|
||||
|
||||
function entriesOfSection(section) {
|
||||
return Array.isArray(layoutConfig[section]) ? layoutConfig[section] : []
|
||||
}
|
||||
|
||||
function noctaliaPluginApiFor(moduleName) {
|
||||
if (!shell || typeof shell.noctaliaPluginApiFor !== "function") return null
|
||||
return shell.noctaliaPluginApiFor(moduleName)
|
||||
}
|
||||
|
||||
readonly property bool vertical: position === "left" || position === "right"
|
||||
readonly property int barSize: vertical ? 28 : 26
|
||||
|
||||
@@ -1269,20 +1232,6 @@ Item {
|
||||
if ("bar" in target) target.bar = root
|
||||
if ("moduleName" in target) target.moduleName = moduleName
|
||||
if ("settings" in target) target.settings = moduleSettings
|
||||
|
||||
// Noctalia compat injection. Only kicks in for plugins whose manifest
|
||||
// was translated from Noctalia shape; for our first-party widgets these
|
||||
// properties don't exist on the target item so nothing happens.
|
||||
var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[moduleName] : null
|
||||
if (manifest && manifest.__noctaliaCompat) {
|
||||
if ("pluginApi" in target) target.pluginApi = root.noctaliaPluginApiFor(moduleName)
|
||||
if ("widgetId" in target) target.widgetId = moduleName
|
||||
if ("section" in target) target.section = root.sectionOfEntry(entry)
|
||||
if ("sectionWidgetIndex" in target) target.sectionWidgetIndex = root.indexOfEntry(entry)
|
||||
if ("sectionWidgetsCount" in target) target.sectionWidgetsCount = root.entriesOfSection(root.sectionOfEntry(entry)).length
|
||||
if ("screen" in target && root.QsWindow && root.QsWindow.window)
|
||||
target.screen = root.QsWindow.window.screen
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
|
||||
@@ -370,7 +370,7 @@ Item {
|
||||
displayName: meta.displayName || manifest.name || key,
|
||||
name: meta.displayName || manifest.name || key,
|
||||
description: meta.description || manifest.description || "",
|
||||
category: meta.category || (manifest.__noctaliaCompat ? "Noctalia" : "Plugin"),
|
||||
category: meta.category || "Plugin",
|
||||
allowMultiple: meta.allowMultiple === true,
|
||||
settingsForm: meta.settingsForm || "",
|
||||
schema: Array.isArray(meta.schema) ? meta.schema : [],
|
||||
@@ -402,17 +402,9 @@ Item {
|
||||
var meta = widgetMetadata(id)
|
||||
if (meta.settingsForm) return true
|
||||
if (widgetSchema(id).length > 0) return true
|
||||
var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[id] : null
|
||||
if (manifest && manifest.__noctaliaCompat && manifest.entryPoints && manifest.entryPoints.settings)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
function widgetIsNoctaliaPlugin(id) {
|
||||
var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[id] : null
|
||||
return !!(manifest && manifest.__noctaliaCompat)
|
||||
}
|
||||
|
||||
function widgetAllowsMultiple(id) {
|
||||
var meta = widgetMetadata(id)
|
||||
if (meta.allowMultiple === true) return true
|
||||
@@ -465,8 +457,7 @@ Item {
|
||||
var allowsMultiple = widgetAllowsMultiple(id)
|
||||
if (!allowsMultiple && existingInBar[id]) continue
|
||||
result.push({ id: id, name: widgetName(id), description: widgetDescription(id),
|
||||
elsewhere: allowsMultiple && !!existingInBar[id] && !existsHere,
|
||||
isNoctalia: widgetIsNoctaliaPlugin(id) })
|
||||
elsewhere: allowsMultiple && !!existingInBar[id] && !existsHere })
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -986,7 +977,6 @@ Item {
|
||||
|
||||
Text {
|
||||
text: modelData.name
|
||||
+ (modelData.isNoctalia ? " (Noctalia)" : "")
|
||||
+ (modelData.elsewhere ? " (elsewhere)" : "")
|
||||
color: root.foreground
|
||||
font.family: root.fontFamily
|
||||
@@ -1246,9 +1236,6 @@ Item {
|
||||
case "weatherSettings": return weatherSettingsComponent
|
||||
}
|
||||
}
|
||||
var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[id] : null
|
||||
if (manifest && manifest.__noctaliaCompat && manifest.entryPoints && manifest.entryPoints.settings)
|
||||
return noctaliaSettingsComponent
|
||||
if (widgetSchema(id).length > 0) return dynamicSettingsComponent
|
||||
return null
|
||||
}
|
||||
@@ -1262,53 +1249,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: noctaliaSettingsComponent
|
||||
|
||||
Item {
|
||||
id: noctaliaForm
|
||||
property var entry: ({})
|
||||
property string pluginId: entry && entry.id ? String(entry.id) : ""
|
||||
property var manifest: pluginId && root.pluginRegistry
|
||||
? root.pluginRegistry.installedPlugins[pluginId] : null
|
||||
|
||||
function saveSettings() {
|
||||
if (settingsLoader.item && typeof settingsLoader.item.saveSettings === "function") {
|
||||
settingsLoader.item.saveSettings()
|
||||
} else {
|
||||
console.warn("Noctalia settings form has no saveSettings():", pluginId)
|
||||
}
|
||||
}
|
||||
|
||||
implicitHeight: settingsLoader.item ? settingsLoader.item.implicitHeight : 0
|
||||
implicitWidth: settingsLoader.item ? settingsLoader.item.implicitWidth : 0
|
||||
|
||||
Loader {
|
||||
id: settingsLoader
|
||||
anchors.fill: parent
|
||||
source: {
|
||||
if (!noctaliaForm.manifest) return ""
|
||||
return root.pluginRegistry.entryPointUrl(noctaliaForm.manifest, "settings")
|
||||
}
|
||||
asynchronous: false
|
||||
onLoaded: {
|
||||
if (!item) return
|
||||
var api = (root.shell && typeof root.shell.noctaliaPluginApiFor === "function")
|
||||
? root.shell.noctaliaPluginApiFor(noctaliaForm.pluginId) : null
|
||||
if (api && "pluginApi" in item) item.pluginApi = api
|
||||
if ("screen" in item && root.QsWindow && root.QsWindow.window)
|
||||
item.screen = root.QsWindow.window.screen
|
||||
}
|
||||
onStatusChanged: {
|
||||
if (status === Loader.Error) {
|
||||
console.warn("noctalia Settings.qml failed for " + noctaliaForm.pluginId + ":",
|
||||
sourceComponent ? sourceComponent.errorString() : "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: spacerSettingsComponent
|
||||
|
||||
|
||||
@@ -44,98 +44,11 @@ QtObject {
|
||||
return true
|
||||
}
|
||||
|
||||
// Detect a Noctalia-shape manifest (https://github.com/noctalia-dev/noctalia-plugins).
|
||||
// Noctalia manifests don't carry our schemaVersion; we recognise them by the
|
||||
// presence of `minNoctaliaVersion` or `metadata.defaultSettings`, or an
|
||||
// `entryPoints` map that names roles Noctalia owns (main/barWidget/panel).
|
||||
function isNoctaliaShape(manifest) {
|
||||
if (!isPlainObject(manifest)) return false
|
||||
if (manifest.schemaVersion !== undefined) return false
|
||||
if (manifest.minNoctaliaVersion !== undefined) return true
|
||||
if (isPlainObject(manifest.metadata) && isPlainObject(manifest.metadata.defaultSettings)) return true
|
||||
if (isPlainObject(manifest.entryPoints)) {
|
||||
var ep = manifest.entryPoints
|
||||
if (ep.barWidget || ep.main || ep.panel || ep.desktopWidget
|
||||
|| ep.launcherProvider) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Translate a Noctalia manifest into the shape we validate normally. We do
|
||||
// not modify the on-disk file; the in-memory copy gets a __noctaliaCompat
|
||||
// marker so downstream code can branch where the semantics diverge.
|
||||
function translateNoctaliaManifest(src) {
|
||||
var ep = isPlainObject(src.entryPoints) ? src.entryPoints : {}
|
||||
var kinds = []
|
||||
var unsupported = []
|
||||
if (ep.main) kinds.push("service")
|
||||
if (ep.barWidget) kinds.push("bar-widget")
|
||||
if (ep.panel) kinds.push("panel")
|
||||
// Out of scope for v1 — warn loudly so we don't silently strand the plugin.
|
||||
if (ep.desktopWidget) unsupported.push("desktopWidget")
|
||||
if (ep.launcherProvider) unsupported.push("launcherProvider")
|
||||
|
||||
if (kinds.length === 0) {
|
||||
console.warn("PluginRegistry: noctalia plugin '" + src.id + "' has no supported entryPoints"
|
||||
+ (unsupported.length ? " (unsupported in v1: " + unsupported.join(", ") + ")" : ""))
|
||||
return null
|
||||
}
|
||||
if (unsupported.length) {
|
||||
console.warn("PluginRegistry: noctalia plugin '" + src.id
|
||||
+ "' uses entryPoints not supported in v1, ignoring: " + unsupported.join(", "))
|
||||
}
|
||||
|
||||
var translated = {
|
||||
schemaVersion: 1,
|
||||
id: "noctalia." + String(src.id),
|
||||
name: src.name || src.id,
|
||||
version: src.version || "0.0.0",
|
||||
author: src.author || "",
|
||||
description: src.description || "",
|
||||
kinds: kinds,
|
||||
activation: kinds.indexOf("service") !== -1 ? "persistent" : "on-demand",
|
||||
entryPoints: {},
|
||||
__noctaliaCompat: true,
|
||||
__noctaliaOriginal: src
|
||||
}
|
||||
|
||||
if (ep.barWidget) translated.entryPoints.barWidget = ep.barWidget
|
||||
if (ep.main) translated.entryPoints.service = ep.main
|
||||
if (ep.panel) translated.entryPoints.panel = ep.panel
|
||||
if (ep.settings) translated.entryPoints.settings = ep.settings
|
||||
|
||||
var defaults = (isPlainObject(src.metadata) && isPlainObject(src.metadata.defaultSettings))
|
||||
? src.metadata.defaultSettings : {}
|
||||
if (translated.entryPoints.barWidget) {
|
||||
translated.barWidget = {
|
||||
displayName: translated.name,
|
||||
description: translated.description,
|
||||
category: "Noctalia",
|
||||
allowMultiple: false,
|
||||
defaults: defaults,
|
||||
schema: []
|
||||
}
|
||||
}
|
||||
if (defaults && Object.keys(defaults).length > 0) translated.defaults = defaults
|
||||
|
||||
return translated
|
||||
}
|
||||
|
||||
function validateManifest(manifest, sourcePath) {
|
||||
if (!isPlainObject(manifest)) {
|
||||
console.warn("PluginRegistry: manifest is not an object at " + sourcePath)
|
||||
return null
|
||||
}
|
||||
// Noctalia shape — translate before validation. The translated manifest
|
||||
// goes through the standard schemaVersion=1 path below.
|
||||
if (isNoctaliaShape(manifest)) {
|
||||
var preservedSourceDir = manifest.__sourceDir
|
||||
var preservedFirstParty = manifest.__isFirstParty
|
||||
manifest = translateNoctaliaManifest(manifest)
|
||||
if (!manifest) return null
|
||||
manifest.__sourceDir = preservedSourceDir
|
||||
manifest.__isFirstParty = preservedFirstParty
|
||||
}
|
||||
if (manifest.schemaVersion !== 1) {
|
||||
console.warn("PluginRegistry: unsupported schemaVersion at " + sourcePath)
|
||||
return null
|
||||
|
||||
@@ -5,9 +5,6 @@ import Quickshell.Io
|
||||
|
||||
import "plugins/bar"
|
||||
import "services"
|
||||
import "compat/noctalia" as Compat
|
||||
import qs.Services.UI as NoctaliaUI
|
||||
import qs.Commons as NoctaliaCommons
|
||||
|
||||
ShellRoot {
|
||||
id: shell
|
||||
@@ -161,20 +158,8 @@ ShellRoot {
|
||||
// case the user dir already existed at startup.
|
||||
pluginRegistry.rescan()
|
||||
shell._syncFirstPartyServices()
|
||||
|
||||
// Wire Noctalia compat singletons. Plugins read state from these globals;
|
||||
// we hand them references to the host so they can route into the right
|
||||
// popup, tooltip, etc.
|
||||
NoctaliaUI.BarService.bar = bar
|
||||
NoctaliaUI.TooltipService.bar = bar
|
||||
NoctaliaUI.PanelService.bar = bar
|
||||
NoctaliaUI.PanelService.shell = shell
|
||||
NoctaliaCommons.Settings.shellConfig = shell.shellConfig
|
||||
}
|
||||
|
||||
// Keep Noctalia.Settings.data in sync with whatever shell.json currently is.
|
||||
onShellConfigChanged: NoctaliaCommons.Settings.shellConfig = shell.shellConfig
|
||||
|
||||
function mutateShellConfig(mutator) {
|
||||
var copy = JSON.parse(JSON.stringify(shellConfig || builtinShellConfig))
|
||||
mutator(copy)
|
||||
@@ -190,181 +175,14 @@ ShellRoot {
|
||||
omarchyPath: shell.omarchyPath
|
||||
barWidgetRegistry: shell.barWidgetRegistry
|
||||
barConfig: shell.barConfig
|
||||
pluginRegistry: shell.pluginRegistry
|
||||
shell: shell
|
||||
}
|
||||
|
||||
// ------------------------------------------------- Noctalia plugin API
|
||||
//
|
||||
// One pluginApi instance per plugin, cached for the life of the shell. The
|
||||
// factory is host-owned (shell, not bar) so non-bar Noctalia plugins
|
||||
// (panels, services) can call into it too. Settings lookups walk the live
|
||||
// shell.json on every read so reorders/edits never leave a stale entry
|
||||
// captured in the closure.
|
||||
Compat.PluginApiFactory { id: noctaliaApiFactory }
|
||||
|
||||
Item {
|
||||
id: noctaliaServiceHost
|
||||
visible: false
|
||||
}
|
||||
|
||||
property var _noctaliaApis: ({})
|
||||
property var _noctaliaServices: ({})
|
||||
|
||||
function findShellEntry(pluginId) {
|
||||
var config = shellConfig
|
||||
if (!isPlainObject(config)) return null
|
||||
if (isPlainObject(config.bar) && isPlainObject(config.bar.layout)) {
|
||||
var sections = ["left", "center", "right"]
|
||||
for (var s = 0; s < sections.length; s++) {
|
||||
var arr = config.bar.layout[sections[s]]
|
||||
if (!Array.isArray(arr)) continue
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (arr[i] && arr[i].id === pluginId) return arr[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(config.plugins)) {
|
||||
for (var p = 0; p < config.plugins.length; p++) {
|
||||
if (config.plugins[p] && config.plugins[p].id === pluginId) return config.plugins[p]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function noctaliaPluginApiFor(pluginId) {
|
||||
var key = String(pluginId)
|
||||
if (!key) return null
|
||||
var existing = _noctaliaApis[key]
|
||||
if (existing) return existing
|
||||
var manifest = pluginRegistry && pluginRegistry.installedPlugins
|
||||
? pluginRegistry.installedPlugins[key] : null
|
||||
if (!manifest || !manifest.__noctaliaCompat) return null
|
||||
|
||||
var api = noctaliaApiFactory.create(
|
||||
key,
|
||||
manifest,
|
||||
function() {
|
||||
// Compute effective settings on every read. We can't capture an entry
|
||||
// reference because mutateSection rebuilds the layout objects and the
|
||||
// captured reference would point at a snapshot.
|
||||
var defaults = (manifest.barWidget && manifest.barWidget.defaults) || manifest.defaults || {}
|
||||
var merged = {}
|
||||
for (var k in defaults) merged[k] = defaults[k]
|
||||
var entry = findShellEntry(key)
|
||||
if (entry) {
|
||||
for (var ek in entry) if (ek !== "id") merged[ek] = entry[ek]
|
||||
}
|
||||
return merged
|
||||
},
|
||||
{
|
||||
persistSettings: function(_pluginId, settings) {
|
||||
if (typeof shell.updateEntryInline === "function")
|
||||
shell.updateEntryInline(key, settings)
|
||||
},
|
||||
openPanel: function(_pluginId, _screen, _btn) {
|
||||
shell.summon(key, JSON.stringify({ source: "noctalia" }))
|
||||
},
|
||||
closePanel: function(_pluginId, _screen) { shell.hide(key) },
|
||||
tooltipService: NoctaliaUI.TooltipService,
|
||||
currentScreen: function() {
|
||||
var screens = Quickshell.screens
|
||||
return screens && screens.length > 0 ? screens[0] : null
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
var next = ({})
|
||||
for (var existingKey in _noctaliaApis) next[existingKey] = _noctaliaApis[existingKey]
|
||||
next[key] = api
|
||||
_noctaliaApis = next
|
||||
return api
|
||||
}
|
||||
|
||||
// -------------------------------------------------- noctalia service main
|
||||
//
|
||||
// Noctalia plugins with `entryPoints.main` (translated to kind="service")
|
||||
// are instantiated once per shell session when enabled. The Main.qml is
|
||||
// typically headless — a data source the plugin's bar widget reads from.
|
||||
// We rely on the shared pluginApi so the service and any bar widgets see
|
||||
// the same handle.
|
||||
function ensureNoctaliaService(pluginId) {
|
||||
var key = String(pluginId)
|
||||
if (_noctaliaServices[key]) return _noctaliaServices[key]
|
||||
var manifest = pluginRegistry && pluginRegistry.installedPlugins
|
||||
? pluginRegistry.installedPlugins[key] : null
|
||||
if (!manifest || !manifest.__noctaliaCompat) return null
|
||||
if (!manifest.entryPoints || !manifest.entryPoints.service) return null
|
||||
var url = pluginRegistry.entryPointUrl(manifest, "service")
|
||||
if (!url) return null
|
||||
var api = noctaliaPluginApiFor(key)
|
||||
if (!api) return null
|
||||
|
||||
var comp = Qt.createComponent(url, Component.PreferSynchronous)
|
||||
function finalize() {
|
||||
if (comp.status !== Component.Ready) {
|
||||
console.warn("noctalia service load failed for " + key + ": " + comp.errorString())
|
||||
return
|
||||
}
|
||||
var inst = comp.createObject(noctaliaServiceHost, { pluginApi: api })
|
||||
if (!inst) {
|
||||
console.warn("noctalia service createObject returned null for", key)
|
||||
return
|
||||
}
|
||||
var snext = ({})
|
||||
for (var sk in _noctaliaServices) snext[sk] = _noctaliaServices[sk]
|
||||
snext[key] = inst
|
||||
_noctaliaServices = snext
|
||||
api.mainInstance = inst
|
||||
}
|
||||
if (comp.status === Component.Loading) {
|
||||
comp.statusChanged.connect(finalize)
|
||||
return null
|
||||
}
|
||||
finalize()
|
||||
return _noctaliaServices[key] || null
|
||||
}
|
||||
|
||||
function _syncNoctaliaServices() {
|
||||
if (!pluginRegistry || !pluginRegistry.installedPlugins) return
|
||||
var plugins = pluginRegistry.installedPlugins
|
||||
for (var id in plugins) {
|
||||
var m = plugins[id]
|
||||
if (!m || !m.__noctaliaCompat) continue
|
||||
if (!m.entryPoints || !m.entryPoints.service) continue
|
||||
if (!pluginRegistry.isEnabled(id)) continue
|
||||
if (_noctaliaServices[id]) continue
|
||||
ensureNoctaliaService(id)
|
||||
}
|
||||
// Drop services for plugins that have been disabled or removed.
|
||||
for (var existingId in _noctaliaServices) {
|
||||
var stillThere = plugins[existingId]
|
||||
var stillEnabled = stillThere && pluginRegistry.isEnabled(existingId)
|
||||
if (stillThere && stillEnabled) continue
|
||||
var inst = _noctaliaServices[existingId]
|
||||
if (inst && typeof inst.destroy === "function") inst.destroy()
|
||||
var next = ({})
|
||||
for (var k in _noctaliaServices) if (k !== existingId) next[k] = _noctaliaServices[k]
|
||||
_noctaliaServices = next
|
||||
var apis = ({})
|
||||
for (var ak in _noctaliaApis) apis[ak] = _noctaliaApis[ak]
|
||||
if (apis[existingId]) apis[existingId].mainInstance = null
|
||||
_noctaliaApis = apis
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: shell.pluginRegistry
|
||||
function onPluginsChanged() { shell._syncNoctaliaServices() }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------- first-party services
|
||||
//
|
||||
// Generic loader for any first-party plugin that declares kind "service".
|
||||
// The notification daemon is the first user; future first-party services
|
||||
// (background updaters, idle inhibitor, etc.) plug in here. Mirrors
|
||||
// ensureNoctaliaService but skips the noctalia-specific pluginApi handoff
|
||||
// and gates on `__isFirstParty` instead of `__noctaliaCompat`.
|
||||
// (background updaters, idle inhibitor, etc.) plug in here.
|
||||
Item {
|
||||
id: firstPartyServiceHost
|
||||
visible: false
|
||||
@@ -382,7 +200,6 @@ ShellRoot {
|
||||
var manifest = pluginRegistry && pluginRegistry.installedPlugins
|
||||
? pluginRegistry.installedPlugins[key] : null
|
||||
if (!manifest || !manifest.__isFirstParty) return null
|
||||
if (manifest.__noctaliaCompat) return null
|
||||
if (!Array.isArray(manifest.kinds) || manifest.kinds.indexOf("service") === -1) return null
|
||||
if (!manifest.entryPoints || !manifest.entryPoints.service) return null
|
||||
var url = pluginRegistry.entryPointUrl(manifest, "service")
|
||||
@@ -421,7 +238,7 @@ ShellRoot {
|
||||
var plugins = pluginRegistry.installedPlugins
|
||||
for (var id in plugins) {
|
||||
var m = plugins[id]
|
||||
if (!m || !m.__isFirstParty || m.__noctaliaCompat) continue
|
||||
if (!m || !m.__isFirstParty) continue
|
||||
if (!Array.isArray(m.kinds) || m.kinds.indexOf("service") === -1) continue
|
||||
if (!m.entryPoints || !m.entryPoints.service) continue
|
||||
if (!pluginRegistry.isEnabled(id)) continue
|
||||
@@ -446,14 +263,11 @@ ShellRoot {
|
||||
function onPluginsChanged() { shell._syncFirstPartyServices() }
|
||||
}
|
||||
|
||||
// Used by Noctalia compat: pluginApi.saveSettings() ends up writing inline
|
||||
// settings to the plugin's entry in shell.json. moduleName is the entry id
|
||||
// (the bare manifest id, e.g. "noctalia.air-quality"); settings is the
|
||||
// merged plugin state. Returns true if anything actually changed.
|
||||
// Compute the proposed new shellConfig in a local clone, and only persist
|
||||
// if anything actually changed. Lets Noctalia plugins call saveSettings()
|
||||
// repeatedly with identical values (common for reactive QML bindings)
|
||||
// without dirtying shell.json or thrashing the file watcher.
|
||||
// Writes inline settings to a bar layout entry or top-level plugin entry in
|
||||
// shell.json. moduleName is the entry id; settings is the merged plugin
|
||||
// state. Returns true if anything actually changed. Compute the proposed
|
||||
// new shellConfig in a local clone, and only persist if anything actually
|
||||
// changed so reactive bindings do not dirty shell.json unnecessarily.
|
||||
function updateEntryInline(moduleName, settings) {
|
||||
var stripped = String(moduleName)
|
||||
var copy = JSON.parse(JSON.stringify(shellConfig || builtinShellConfig))
|
||||
@@ -647,9 +461,7 @@ ShellRoot {
|
||||
readonly property string sourceUrl: shell.pluginRegistry.entryPointUrl(manifest, entryKind)
|
||||
|
||||
property Loader panelLoader: Loader {
|
||||
readonly property bool wrapNoctaliaPanel: !!(panelEntry.manifest && panelEntry.manifest.__noctaliaCompat && panelEntry.entryKind === "panel")
|
||||
source: wrapNoctaliaPanel ? "" : panelEntry.sourceUrl
|
||||
sourceComponent: wrapNoctaliaPanel ? noctaliaPanelWrapperComponent : null
|
||||
source: panelEntry.sourceUrl
|
||||
active: panelEntry.sourceUrl !== "" && (panelEntry.keepLoaded || shell.openPanelIds[panelEntry.pluginId] === true)
|
||||
asynchronous: true
|
||||
onLoaded: {
|
||||
@@ -663,11 +475,6 @@ ShellRoot {
|
||||
// (e.g. omarchy.notifications) read shared state off `service`. We
|
||||
// hand them the matching service instance if one was loaded.
|
||||
if ("service" in item) item.service = shell.firstPartyServiceFor(panelEntry.pluginId)
|
||||
// Noctalia panel/overlay/menu plugins expect a pluginApi just like
|
||||
// their bar widgets do. First-party Omarchy plugins don't declare
|
||||
// the property so the `in target` check skips them.
|
||||
if (panelEntry.manifest && panelEntry.manifest.__noctaliaCompat && "pluginApi" in item)
|
||||
item.pluginApi = shell.noctaliaPluginApiFor(panelEntry.pluginId)
|
||||
shell.registerPanelLoader(panelEntry.pluginId, this)
|
||||
}
|
||||
onStatusChanged: {
|
||||
@@ -683,12 +490,6 @@ ShellRoot {
|
||||
}
|
||||
Component.onDestruction: shell.unregisterPanelLoader(panelEntry.pluginId)
|
||||
}
|
||||
|
||||
property Component noctaliaPanelWrapperComponent: Component {
|
||||
Compat.PanelWrapper {
|
||||
panelSource: panelEntry.sourceUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,8 +499,8 @@ ShellRoot {
|
||||
// Each enabled plugin with kind "bar-widget" gets a Component created from
|
||||
// its manifest entry point and registered under its plain manifest id.
|
||||
// First-party widget ids (calendar, weather, etc.) are short and don't
|
||||
// collide with namespaced plugin ids like noctalia.air-quality, so we don't
|
||||
// need a separate "plugin:" namespace anymore.
|
||||
// collide with namespaced plugin ids, so we don't need a separate
|
||||
// "plugin:" namespace anymore.
|
||||
Connections {
|
||||
target: shell.pluginRegistry
|
||||
function onPluginsChanged() { shell.syncPluginWidgets() }
|
||||
|
||||
Reference in New Issue
Block a user