mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2025-12-06 06:36:15 +00:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
860e721709 | ||
|
|
88ece93db2 | ||
|
|
2d290bf5f7 | ||
|
|
891c8660e3 | ||
|
|
a734235cd0 | ||
|
|
8fdc6a0f72 | ||
|
|
603f499355 | ||
|
|
2b8b97ab3b | ||
|
|
458ef3c0d5 | ||
|
|
c4008e3899 | ||
|
|
6c3299ad10 | ||
|
|
6fe498ce19 | ||
|
|
4e67f26576 | ||
|
|
b2d46ab759 | ||
|
|
0d3cc917fa | ||
|
|
ac591da6c5 | ||
|
|
c7709b5f21 | ||
|
|
e6370904cd | ||
|
|
e412cee52f | ||
|
|
c3019230ae | ||
|
|
c7ab350cbd | ||
|
|
b65d82d895 | ||
|
|
89eb5ecde6 | ||
|
|
b374f167ef | ||
|
|
28026a4c37 | ||
|
|
b8bce3d421 | ||
|
|
6fba3457f7 | ||
|
|
07a6a16011 | ||
|
|
6b61599633 | ||
|
|
1bd093db7f | ||
|
|
3d9295856c | ||
|
|
a1aabd02f5 | ||
|
|
ae2d3eddd6 | ||
|
|
b75c358f54 | ||
|
|
0972a55aad | ||
|
|
112f71b633 | ||
|
|
e67d7166de | ||
|
|
6e88118ca9 | ||
|
|
75b7f0fcb0 | ||
|
|
47f72d9498 | ||
|
|
85d7dc2506 | ||
|
|
1305efec24 | ||
|
|
8af8bf2e2e | ||
|
|
abd6a66297 | ||
|
|
2e9a812513 | ||
|
|
8d845e7cd0 | ||
|
|
a1dcef8dec | ||
|
|
38e0bb8e64 | ||
|
|
8811cb3d13 | ||
|
|
a872682eb8 | ||
|
|
46b8317330 | ||
|
|
8204460112 | ||
|
|
292337dc00 | ||
|
|
0b790c219d | ||
|
|
7acca17b83 | ||
|
|
cdfb110007 | ||
|
|
b7d8f92414 |
34
Assets/ColorScheme/Kanagawa.json
Normal file
34
Assets/ColorScheme/Kanagawa.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"dark": {
|
||||
"mPrimary": "#76946a",
|
||||
"mOnPrimary": "#1f1f28",
|
||||
"mSecondary": "#c0a36e",
|
||||
"mOnSecondary": "#1f1f28",
|
||||
"mTertiary": "#7e9cd8",
|
||||
"mOnTertiary": "#1f1f28",
|
||||
"mError": "#c34043",
|
||||
"mOnError": "#1f1f28",
|
||||
"mSurface": "#1f1f28",
|
||||
"mOnSurface": "#717c7c",
|
||||
"mSurfaceVariant": "#2a2a37",
|
||||
"mOnSurfaceVariant": "#c8c093",
|
||||
"mOutline": "#363646",
|
||||
"mShadow": "#1f1f28"
|
||||
},
|
||||
"light": {
|
||||
"mPrimary": "#6f894e",
|
||||
"mOnPrimary": "#f2ecbc",
|
||||
"mSecondary": "#77713f",
|
||||
"mOnSecondary": "#f2ecbc",
|
||||
"mTertiary": "#4d699b",
|
||||
"mOnTertiary": "#f2ecbc",
|
||||
"mError": "#c84053",
|
||||
"mOnError": "#f2ecbc",
|
||||
"mSurface": "#f2ecbc",
|
||||
"mOnSurface": "#8a8980",
|
||||
"mSurfaceVariant": "#e5ddb0",
|
||||
"mOnSurfaceVariant": "#545464",
|
||||
"mOutline": "#cfc49c",
|
||||
"mShadow": "#f2ecbc"
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ Singleton {
|
||||
"search": "search",
|
||||
"warning": "exclamation-circle",
|
||||
"stop": "player-stop-filled",
|
||||
"busy": "hourglass-empty",
|
||||
"media-pause": "player-pause-filled",
|
||||
"media-play": "player-play-filled",
|
||||
"media-prev": "player-skip-back-filled",
|
||||
@@ -101,13 +102,13 @@ Singleton {
|
||||
"settings-display": "device-desktop",
|
||||
"settings-network": "sitemap",
|
||||
"settings-brightness": "brightness-up",
|
||||
"settings-weather": "cloud-sun",
|
||||
"settings-location": "world-pin",
|
||||
"settings-color-scheme": "palette",
|
||||
"settings-wallpaper": "paint",
|
||||
"settings-wallpaper-selector": "library-photo",
|
||||
"settings-screen-recorder": "video",
|
||||
"settings-hooks": "link",
|
||||
"settings-notification": "bell",
|
||||
"settings-notifications": "bell",
|
||||
"settings-about": "info-square-rounded",
|
||||
"bluetooth": "bluetooth",
|
||||
"bt-device-generic": "bluetooth",
|
||||
|
||||
@@ -134,37 +134,17 @@ Singleton {
|
||||
// Backup the widget definition before altering
|
||||
const widgetBefore = JSON.stringify(widget)
|
||||
|
||||
// Migrate old bar settings to proper per widget settings
|
||||
switch (widget.id) {
|
||||
case "ActiveWindow":
|
||||
widget.showIcon = widget.showIcon !== undefined ? widget.showIcon : adapter.bar.showActiveWindowIcon
|
||||
break
|
||||
case "Battery":
|
||||
widget.alwaysShowPercentage = widget.alwaysShowPercentage !== undefined ? widget.alwaysShowPercentage : adapter.bar.alwaysShowBatteryPercentage
|
||||
break
|
||||
// Get back to global settings for these two clock settings
|
||||
case "Clock":
|
||||
widget.use12HourClock = widget.use12HourClock !== undefined ? widget.use12HourClock : adapter.location.use12HourClock
|
||||
widget.reverseDayMonth = widget.reverseDayMonth !== undefined ? widget.reverseDayMonth : adapter.location.reverseDayMonth
|
||||
if (widget.showDate !== undefined) {
|
||||
widget.displayFormat = "time-date"
|
||||
} else if (widget.showSeconds) {
|
||||
widget.displayFormat = "time-seconds"
|
||||
if (widget.use12HourClock !== undefined) {
|
||||
adapter.location.use12hourFormat = widget.use12HourClock
|
||||
delete widget.use12HourClock
|
||||
}
|
||||
if (widget.reverseDayMonth !== undefined) {
|
||||
adapter.location.monthBeforeDay = widget.reverseDayMonth
|
||||
delete widget.reverseDayMonth
|
||||
}
|
||||
delete widget.showDate
|
||||
delete widget.showSeconds
|
||||
break
|
||||
case "MediaMini":
|
||||
widget.showAlbumArt = widget.showAlbumArt !== undefined ? widget.showAlbumArt : adapter.audio.showMiniplayerAlbumArt
|
||||
widget.showVisualizer = widget.showVisualizer !== undefined ? widget.showVisualizer : adapter.audio.showMiniplayerCava
|
||||
break
|
||||
case "SidePanelToggle":
|
||||
widget.useDistroLogo = widget.useDistroLogo !== undefined ? widget.useDistroLogo : adapter.bar.useDistroLogo
|
||||
break
|
||||
case "SystemMonitor":
|
||||
widget.showNetworkStats = widget.showNetworkStats !== undefined ? widget.showNetworkStats : adapter.bar.showNetworkStats
|
||||
break
|
||||
case "Workspace":
|
||||
widget.labelMode = widget.labelMode !== undefined ? widget.labelMode : adapter.bar.showWorkspaceLabel
|
||||
break
|
||||
}
|
||||
|
||||
@@ -197,6 +177,9 @@ Singleton {
|
||||
|
||||
MatugenService.init()
|
||||
|
||||
// Ensure wallpapers are restored after settings have been loaded
|
||||
WallpaperService.init()
|
||||
|
||||
FontService.init()
|
||||
|
||||
HooksService.init()
|
||||
@@ -280,12 +263,6 @@ Singleton {
|
||||
property real marginVertical: 0.25
|
||||
property real marginHorizontal: 0.25
|
||||
|
||||
property bool showActiveWindowIcon: true // TODO: delete
|
||||
property bool alwaysShowBatteryPercentage: false // TODO: delete
|
||||
property bool showNetworkStats: false // TODO: delete
|
||||
property bool useDistroLogo: false // TODO: delete
|
||||
property string showWorkspaceLabel: "none" // TODO: delete
|
||||
|
||||
// Widget configuration for modular bar system
|
||||
property JsonObject widgets
|
||||
widgets: JsonObject {
|
||||
@@ -333,7 +310,6 @@ Singleton {
|
||||
property bool forceBlackScreenCorners: false
|
||||
property real radiusRatio: 1.0
|
||||
property real screenRadiusRatio: 1.0
|
||||
// Animation speed multiplier (0.1x - 2.0x)
|
||||
property real animationSpeed: 1.0
|
||||
}
|
||||
|
||||
@@ -341,10 +317,9 @@ Singleton {
|
||||
property JsonObject location: JsonObject {
|
||||
property string name: defaultLocation
|
||||
property bool useFahrenheit: false
|
||||
|
||||
property bool reverseDayMonth: false // TODO: delete
|
||||
property bool use12HourClock: false // TODO: delete
|
||||
property bool showDateWithClock: false // TODO: delete
|
||||
property bool use12hourFormat: false
|
||||
property bool monthBeforeDay: false
|
||||
property bool showWeekNumberInCalendar: false
|
||||
}
|
||||
|
||||
// screen recorder
|
||||
@@ -378,13 +353,13 @@ Singleton {
|
||||
|
||||
// applauncher
|
||||
property JsonObject appLauncher: JsonObject {
|
||||
// When disabled, Launcher hides clipboard command and ignores cliphist
|
||||
property bool enableClipboardHistory: false
|
||||
// Position: center, top_left, top_right, bottom_left, bottom_right, bottom_center, top_center
|
||||
property string position: "center"
|
||||
property real backgroundOpacity: 1.0
|
||||
property list<string> pinnedExecs: []
|
||||
property bool useApp2Unit: false
|
||||
property bool sortByMostUsed: true
|
||||
}
|
||||
|
||||
// dock
|
||||
@@ -406,7 +381,7 @@ Singleton {
|
||||
property JsonObject notifications: JsonObject {
|
||||
property bool doNotDisturb: false
|
||||
property list<string> monitors: []
|
||||
// Last time the user opened the notification history (ms since epoch)
|
||||
// Last time the user opened the notification history (ms since e899999999999998poch)
|
||||
property real lastSeenTs: 0
|
||||
// Duration settings for different urgency levels (in seconds)
|
||||
property int lowUrgencyDuration: 3
|
||||
@@ -421,9 +396,6 @@ Singleton {
|
||||
property string visualizerType: "linear"
|
||||
property list<string> mprisBlacklist: []
|
||||
property string preferredPlayer: ""
|
||||
|
||||
property bool showMiniplayerAlbumArt: false // TODO: delete
|
||||
property bool showMiniplayerCava: false // TODO: delete
|
||||
}
|
||||
|
||||
// ui
|
||||
|
||||
@@ -15,7 +15,7 @@ Singleton {
|
||||
return Math.floor(date / 1000)
|
||||
}
|
||||
|
||||
function formatDate(reverseDayMonth = true) {
|
||||
function formatDate(monthBeforeDay = true) {
|
||||
let now = date
|
||||
let dayName = now.toLocaleDateString(Qt.locale(), "ddd")
|
||||
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
|
||||
@@ -40,7 +40,7 @@ Singleton {
|
||||
let month = now.toLocaleDateString(Qt.locale(), "MMMM")
|
||||
let year = now.toLocaleDateString(Qt.locale(), "yyyy")
|
||||
|
||||
return `${dayName}, ` + (reverseDayMonth ? `${month} ${day}${suffix} ${year}` : `${day}${suffix} ${month} ${year}`)
|
||||
return `${dayName}, ` + (monthBeforeDay ? `${month} ${day}${suffix} ${year}` : `${day}${suffix} ${month} ${year}`)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
delegate: Loader {
|
||||
required property ShellScreen modelData
|
||||
|
||||
// Dimmer is only active on the screen where the panel is currently open.
|
||||
active: {
|
||||
if (Settings.isLoaded && Settings.data.general.dimDesktop && modelData !== undefined && PanelService.openedPanel !== null && PanelService.openedPanel.item !== undefined && PanelService.openedPanel.item !== null) {
|
||||
return (PanelService.openedPanel.item.screen === modelData)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
id: panel
|
||||
|
||||
property real customOpacity: 0
|
||||
|
||||
Component.onCompleted: {
|
||||
if (modelData) {
|
||||
Logger.log("Dimmer", "Loaded on", modelData.name)
|
||||
}
|
||||
|
||||
// When a NPanel opens it seems it is initialized with the primary screen for a very brief moment
|
||||
// before the screen actually updates to the proper value. We use a timer to delay the fade in to avoid
|
||||
// a single frame flicker on the main screen when opening a panel on another screen.
|
||||
fadeInTimer.start()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PanelService
|
||||
function onWillClose() {
|
||||
customOpacity = Style.opacityNone
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: fadeInTimer
|
||||
interval: 100
|
||||
onTriggered: customOpacity = Style.opacityHeavy
|
||||
}
|
||||
|
||||
screen: modelData
|
||||
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
WlrLayershell.namespace: "quickshell-dimmer"
|
||||
|
||||
// mask: Region {}
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
right: true
|
||||
left: true
|
||||
}
|
||||
|
||||
color: Qt.alpha(Color.mShadow, customOpacity)
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Notification
|
||||
import qs.Modules.Bar.Extras
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
@@ -88,7 +89,7 @@ Variants {
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.left
|
||||
delegate: NWidgetLoader {
|
||||
delegate: BarWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
@@ -111,7 +112,7 @@ Variants {
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.center
|
||||
delegate: NWidgetLoader {
|
||||
delegate: BarWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
@@ -135,7 +136,7 @@ Variants {
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.right
|
||||
delegate: NWidgetLoader {
|
||||
delegate: BarWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
@@ -169,7 +170,7 @@ Variants {
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.left
|
||||
delegate: NWidgetLoader {
|
||||
delegate: BarWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
@@ -194,7 +195,7 @@ Variants {
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.center
|
||||
delegate: NWidgetLoader {
|
||||
delegate: BarWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
@@ -220,7 +221,7 @@ Variants {
|
||||
|
||||
Repeater {
|
||||
model: Settings.data.bar.widgets.right
|
||||
delegate: NWidgetLoader {
|
||||
delegate: BarWidgetLoader {
|
||||
widgetId: (modelData.id !== undefined ? modelData.id : "")
|
||||
widgetProps: {
|
||||
"screen": root.modelData || null,
|
||||
|
||||
@@ -8,12 +8,14 @@ Item {
|
||||
|
||||
property string widgetId: ""
|
||||
property var widgetProps: ({})
|
||||
property bool enabled: true
|
||||
property string screenName: widgetProps.screen ? widgetProps.screen.name : ""
|
||||
property string section: widgetProps.section || ""
|
||||
property int sectionIndex: widgetProps.sectionWidgetIndex || 0
|
||||
|
||||
Connections {
|
||||
target: ScalingService
|
||||
function onScaleChanged(screenName, scale) {
|
||||
if (loader.item && loader.item.screen && screenName === loader.item.screen.name) {
|
||||
function onScaleChanged(aScreenName, scale) {
|
||||
if (loader.item && loader.item.screen && aScreenName === screenName) {
|
||||
loader.item['scaling'] = scale
|
||||
}
|
||||
}
|
||||
@@ -27,7 +29,7 @@ Item {
|
||||
id: loader
|
||||
|
||||
anchors.fill: parent
|
||||
active: Settings.isLoaded && enabled && widgetId !== ""
|
||||
active: Settings.isLoaded && widgetId !== ""
|
||||
sourceComponent: {
|
||||
if (!active) {
|
||||
return null
|
||||
@@ -45,18 +47,30 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Register this widget instance with BarService
|
||||
if (screenName && section) {
|
||||
BarService.registerWidget(screenName, section, widgetId, sectionIndex, item)
|
||||
}
|
||||
|
||||
if (item.hasOwnProperty("onLoaded")) {
|
||||
item.onLoaded()
|
||||
}
|
||||
|
||||
Logger.log("NWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name)
|
||||
//Logger.log("BarWidgetLoader", "Loaded", widgetId, "on screen", item.screen.name)
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
// Unregister when destroyed
|
||||
if (screenName && section) {
|
||||
BarService.unregisterWidget(screenName, section, widgetId, sectionIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error handling
|
||||
onWidgetIdChanged: {
|
||||
if (widgetId && !BarWidgetRegistry.hasWidget(widgetId)) {
|
||||
Logger.warn("WidgetLoader", "Widget not found in registry:", widgetId)
|
||||
Logger.warn("BarWidgetLoader", "Widget not found in bar registry:", widgetId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,13 +235,13 @@ PopupWindow {
|
||||
openLeft = false
|
||||
} else {
|
||||
// Bar is horizontal (top/bottom) or undefined, use space-based logic
|
||||
openLeft = (globalPos.x + entry.width + submenuWidth > (screen ? screen.width : Screen.width))
|
||||
openLeft = (globalPos.x + entry.width + submenuWidth > screen.width)
|
||||
|
||||
// Secondary check: ensure we don't open off-screen
|
||||
if (openLeft && globalPos.x - submenuWidth < 0) {
|
||||
// Would open off the left edge, force right opening
|
||||
openLeft = false
|
||||
} else if (!openLeft && globalPos.x + entry.width + submenuWidth > (screen ? screen.width : Screen.width)) {
|
||||
} else if (!openLeft && globalPos.x + entry.width + submenuWidth > screen.width) {
|
||||
// Would open off the right edge, force left opening
|
||||
openLeft = true
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ Item {
|
||||
id: pill
|
||||
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
rightOpen: BarWidgetRegistry.getPillDirection(root)
|
||||
rightOpen: BarService.getPillDirection(root)
|
||||
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent, charging, isReady)
|
||||
text: (isReady || testMode) ? Math.round(percent) : "-"
|
||||
suffix: "%"
|
||||
|
||||
@@ -78,7 +78,7 @@ Item {
|
||||
id: pill
|
||||
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
rightOpen: BarWidgetRegistry.getPillDirection(root)
|
||||
rightOpen: BarService.getPillDirection(root)
|
||||
icon: getIcon()
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: {
|
||||
|
||||
@@ -30,10 +30,10 @@ Rectangle {
|
||||
|
||||
readonly property string barPosition: Settings.data.bar.position
|
||||
readonly property bool compact: (Settings.data.bar.density === "compact")
|
||||
readonly property bool use12h: Settings.data.location.use12hourFormat
|
||||
readonly property bool monthBeforeDay: Settings.data.location.monthBeforeDay
|
||||
|
||||
// Resolve settings: try user settings or defaults from BarWidgetRegistry
|
||||
readonly property bool use12h: widgetSettings.use12HourClock !== undefined ? widgetSettings.use12HourClock : widgetMetadata.use12HourClock
|
||||
readonly property bool reverseDayMonth: widgetSettings.reverseDayMonth !== undefined ? widgetSettings.reverseDayMonth : widgetMetadata.reverseDayMonth
|
||||
readonly property string displayFormat: widgetSettings.displayFormat !== undefined ? widgetSettings.displayFormat : widgetMetadata.displayFormat
|
||||
|
||||
// Use compact mode for vertical bars
|
||||
@@ -119,7 +119,7 @@ Rectangle {
|
||||
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
|
||||
const day = now.getDate().toString().padStart(2, '0')
|
||||
let month = now.toLocaleDateString(Qt.locale(), "MMM")
|
||||
timeStr += " - " + (reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`)
|
||||
timeStr += " - " + (monthBeforeDay ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`)
|
||||
}
|
||||
|
||||
return timeStr
|
||||
@@ -158,11 +158,9 @@ Rectangle {
|
||||
// Compact mode: date section (last 2 lines)
|
||||
switch (index) {
|
||||
case 0:
|
||||
// Day
|
||||
return now.getDate().toString().padStart(2, '0')
|
||||
return monthBeforeDay ? (now.getMonth() + 1).toString().padStart(2, '0') : now.getDate().toString().padStart(2, '0')
|
||||
case 1:
|
||||
// Month
|
||||
return (now.getMonth() + 1).toString().padStart(2, '0')
|
||||
return monthBeforeDay ? now.getDate().toString().padStart(2, '0') : (now.getMonth() + 1).toString().padStart(2, '0')
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -184,7 +182,7 @@ Rectangle {
|
||||
const now = Time.date
|
||||
const day = now.getDate().toString().padStart(2, '0')
|
||||
const month = (now.getMonth() + 1).toString().padStart(2, '0')
|
||||
return reverseDayMonth ? `${month}/${day}` : `${day}/${month}`
|
||||
return monthBeforeDay ? `${month}/${day}` : `${day}/${month}`
|
||||
}
|
||||
|
||||
// Enable fixed-width font for consistent spacing
|
||||
@@ -199,7 +197,7 @@ Rectangle {
|
||||
|
||||
NTooltip {
|
||||
id: tooltip
|
||||
text: `${Time.formatDate(reverseDayMonth)}.`
|
||||
text: `${Time.formatDate(monthBeforeDay)}.`
|
||||
target: clockContainer
|
||||
positionAbove: Settings.data.bar.position === "bottom"
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ Item {
|
||||
BarPill {
|
||||
id: pill
|
||||
|
||||
rightOpen: BarWidgetRegistry.getPillDirection(root)
|
||||
rightOpen: BarService.getPillDirection(root)
|
||||
icon: customIcon
|
||||
text: _dynamicText
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
|
||||
@@ -44,7 +44,7 @@ Item {
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
rightOpen: BarWidgetRegistry.getPillDirection(root)
|
||||
rightOpen: BarService.getPillDirection(root)
|
||||
icon: "keyboard"
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: currentLayout.toUpperCase()
|
||||
|
||||
@@ -89,7 +89,7 @@ Item {
|
||||
|
||||
BarPill {
|
||||
id: pill
|
||||
rightOpen: BarWidgetRegistry.getPillDirection(root)
|
||||
rightOpen: BarService.getPillDirection(root)
|
||||
icon: getIcon()
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
|
||||
@@ -11,45 +11,16 @@ NIconButton {
|
||||
|
||||
property ShellScreen screen
|
||||
property real scaling: 1.0
|
||||
readonly property bool hasPP: PowerProfileService.available
|
||||
|
||||
baseSize: Style.capsuleHeight
|
||||
visible: hasPP
|
||||
visible: PowerProfileService.available
|
||||
|
||||
function profileIcon() {
|
||||
if (!hasPP)
|
||||
return "balanced"
|
||||
if (PowerProfileService.profile === PowerProfile.Performance)
|
||||
return "performance"
|
||||
if (PowerProfileService.profile === PowerProfile.Balanced)
|
||||
return "balanced"
|
||||
if (PowerProfileService.profile === PowerProfile.PowerSaver)
|
||||
return "powersaver"
|
||||
}
|
||||
|
||||
function profileName() {
|
||||
if (!hasPP)
|
||||
return "Unknown"
|
||||
if (PowerProfileService.profile === PowerProfile.Performance)
|
||||
return "Performance"
|
||||
if (PowerProfileService.profile === PowerProfile.Balanced)
|
||||
return "Balanced"
|
||||
if (PowerProfileService.profile === PowerProfile.PowerSaver)
|
||||
return "Power Saver"
|
||||
}
|
||||
|
||||
function changeProfile() {
|
||||
if (!hasPP)
|
||||
return
|
||||
PowerProfileService.cycleProfile()
|
||||
}
|
||||
|
||||
icon: root.profileIcon()
|
||||
tooltipText: root.profileName()
|
||||
icon: PowerProfileService.getIcon()
|
||||
tooltipText: `Current power profile is "${PowerProfileService.getName()}".`
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
colorBg: (PowerProfileService.profile === PowerProfile.Balanced) ? (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) : Color.mPrimary
|
||||
colorFg: (PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnSurface : Color.mOnPrimary
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
onClicked: root.changeProfile()
|
||||
onClicked: PowerProfileService.cycleProfile()
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ NIconButton {
|
||||
|
||||
visible: ScreenRecorderService.isRecording
|
||||
icon: "camera-video"
|
||||
tooltipText: "Screen recording is active\nClick to stop recording"
|
||||
tooltipText: "Screen recording is active.\nClick to stop recording."
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
baseSize: Style.capsuleHeight
|
||||
colorBg: Color.mPrimary
|
||||
|
||||
@@ -76,7 +76,7 @@ Item {
|
||||
id: pill
|
||||
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
rightOpen: BarWidgetRegistry.getPillDirection(root)
|
||||
rightOpen: BarService.getPillDirection(root)
|
||||
icon: getIcon()
|
||||
autoHide: false // Important to be false so we can hover as long as we want
|
||||
text: Math.floor(AudioService.volume * 100)
|
||||
|
||||
@@ -163,7 +163,7 @@ ColumnLayout {
|
||||
}
|
||||
return "Connect"
|
||||
}
|
||||
icon: (isBusy ? "hourglass-split" : null)
|
||||
icon: (isBusy ? "busy" : null)
|
||||
onClicked: {
|
||||
if (modelData.connected) {
|
||||
BluetoothService.disconnectDevice(modelData)
|
||||
|
||||
@@ -10,7 +10,7 @@ import qs.Widgets
|
||||
NPanel {
|
||||
id: root
|
||||
|
||||
preferredWidth: 340
|
||||
preferredWidth: Settings.data.location.showWeekNumberInCalendar ? 350 : 330
|
||||
preferredHeight: 320
|
||||
|
||||
// Main Column
|
||||
@@ -60,7 +60,7 @@ NPanel {
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginS * scaling
|
||||
Layout.bottomMargin: Style.marginM * scaling
|
||||
Layout.bottomMargin: Style.marginL * scaling
|
||||
}
|
||||
|
||||
// Columns label (respects locale's first day of week)
|
||||
@@ -68,62 +68,172 @@ NPanel {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginS * scaling // Align with grid
|
||||
Layout.rightMargin: Style.marginS * scaling
|
||||
Layout.bottomMargin: Style.marginM * scaling
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: 7
|
||||
// Week header spacer or label (same width as week number column)
|
||||
Item {
|
||||
visible: Settings.data.location.showWeekNumberInCalendar
|
||||
Layout.preferredWidth: visible ? Style.baseWidgetSize * scaling : 0
|
||||
|
||||
NText {
|
||||
text: {
|
||||
// Use the locale's first day of week setting
|
||||
let firstDay = Qt.locale().firstDayOfWeek
|
||||
let dayIndex = (firstDay + index) % 7
|
||||
return Qt.locale().dayName(dayIndex, Locale.ShortFormat)
|
||||
}
|
||||
color: Color.mSecondary
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
anchors.centerIn: parent
|
||||
text: "Week"
|
||||
color: Color.mOutline
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightRegular
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: Style.baseWidgetSize * scaling
|
||||
}
|
||||
}
|
||||
|
||||
// Day name headers - now properly aligned with calendar grid
|
||||
GridLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
columns: 7
|
||||
rows: 1
|
||||
columnSpacing: 0
|
||||
rowSpacing: 0
|
||||
|
||||
Repeater {
|
||||
model: 7
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: Style.baseWidgetSize * scaling
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
// Use the locale's first day of week setting
|
||||
let firstDay = Qt.locale().firstDayOfWeek
|
||||
let dayIndex = (firstDay + index) % 7
|
||||
return Qt.locale().dayName(dayIndex, Locale.ShortFormat)
|
||||
}
|
||||
color: Color.mSecondary
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Grids: days
|
||||
MonthGrid {
|
||||
id: grid
|
||||
|
||||
// Grids: days with optional week numbers
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true // Take remaining space
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: Style.marginS * scaling
|
||||
Layout.rightMargin: Style.marginS * scaling
|
||||
spacing: 0
|
||||
month: Time.date.getMonth()
|
||||
year: Time.date.getFullYear()
|
||||
locale: Qt.locale() // Use system locale
|
||||
|
||||
delegate: Rectangle {
|
||||
width: (Style.baseWidgetSize * scaling)
|
||||
height: (Style.baseWidgetSize * scaling)
|
||||
radius: Style.radiusS * scaling
|
||||
color: model.today ? Color.mPrimary : Color.transparent
|
||||
// Week numbers column (only visible when enabled)
|
||||
GridLayout {
|
||||
visible: Settings.data.location.showWeekNumberInCalendar
|
||||
Layout.preferredWidth: visible ? Style.baseWidgetSize * scaling : 0
|
||||
Layout.fillHeight: true
|
||||
columns: 1
|
||||
rows: 6
|
||||
columnSpacing: 0
|
||||
rowSpacing: 0
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: model.day
|
||||
color: model.today ? Color.mOnPrimary : Color.mOnSurface
|
||||
opacity: model.month === grid.month ? Style.opacityHeavy : Style.opacityLight
|
||||
font.pointSize: (Style.fontSizeM * scaling)
|
||||
font.weight: model.today ? Style.fontWeightBold : Style.fontWeightRegular
|
||||
Repeater {
|
||||
model: 6 // Maximum 6 weeks in a month view
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: Color.transparent
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
color: Color.mOutline
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
text: {
|
||||
// Calculate the first day shown in the calendar grid
|
||||
let firstDay = new Date(grid.year, grid.month, 1)
|
||||
let firstDayOfWeek = Qt.locale().firstDayOfWeek
|
||||
let startOffset = (firstDay.getDay() - firstDayOfWeek + 7) % 7
|
||||
let gridStartDate = new Date(grid.year, grid.month, 1 - startOffset)
|
||||
|
||||
// Get the date for the start of this specific row
|
||||
let rowDate = new Date(gridStartDate)
|
||||
rowDate.setDate(gridStartDate.getDate() + (index * 7))
|
||||
|
||||
// Calculate week number based on the Thursday of the visual row
|
||||
// This correctly handles rows that span two different ISO weeks.
|
||||
let thursdayOfRow = new Date(rowDate)
|
||||
let offsetToThursday = (4 - thursdayOfRow.getDay() + 7) % 7
|
||||
thursdayOfRow.setDate(thursdayOfRow.getDate() + offsetToThursday)
|
||||
|
||||
// Check if this row is visible (contains days from current month)
|
||||
let rowEndDate = new Date(rowDate)
|
||||
rowEndDate.setDate(rowDate.getDate() + 6)
|
||||
|
||||
if (rowDate.getMonth() === grid.month || rowEndDate.getMonth() === grid.month || (rowDate.getMonth() < grid.month && rowEndDate.getMonth() > grid.month)) {
|
||||
return `${getISOWeekNumber(thursdayOfRow)}`
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
// The actual calendar grid
|
||||
MonthGrid {
|
||||
id: grid
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 0
|
||||
month: Time.date.getMonth()
|
||||
year: Time.date.getFullYear()
|
||||
locale: Qt.locale()
|
||||
|
||||
delegate: Rectangle {
|
||||
width: Style.baseWidgetSize * scaling
|
||||
height: Style.baseWidgetSize * scaling
|
||||
radius: Style.radiusS * scaling
|
||||
color: model.today ? Color.mPrimary : Color.transparent
|
||||
|
||||
NText {
|
||||
anchors.centerIn: parent
|
||||
text: model.day
|
||||
color: model.today ? Color.mOnPrimary : Color.mOnSurface
|
||||
opacity: model.month === grid.month ? Style.opacityHeavy : Style.opacityLight
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: model.today ? Style.fontWeightBold : Style.fontWeightRegular
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getISOWeekNumber(date) {
|
||||
// Create a copy of the date and normalize to noon to prevent DST issues
|
||||
const targetDate = new Date(date.getTime())
|
||||
targetDate.setHours(12, 0, 0, 0)
|
||||
|
||||
// Roll the date to the Thursday of the week.
|
||||
// getDay() is 0 for Sunday, we want Monday to be 1 and Sunday to be 7.
|
||||
const dayOfWeek = targetDate.getDay() || 7
|
||||
targetDate.setDate(targetDate.getDate() - dayOfWeek + 4)
|
||||
|
||||
// Get the first day of that Thursday's year
|
||||
const yearStart = new Date(targetDate.getFullYear(), 0, 1)
|
||||
|
||||
// Calculate the difference in days and find the week number
|
||||
const dayOfYear = ((targetDate - yearStart) / 86400000) + 1
|
||||
return Math.ceil(dayOfYear / 7)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ Item {
|
||||
IpcHandler {
|
||||
target: "notifications"
|
||||
function toggleHistory() {
|
||||
notificationHistoryPanel.toggle()
|
||||
// Will attempt to open the panel next to the bar button if any.
|
||||
notificationHistoryPanel.toggle(BarService.lookupWidget("NotificationHistory"))
|
||||
}
|
||||
function toggleDND() {
|
||||
Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
|
||||
@@ -118,13 +119,20 @@ Item {
|
||||
IpcHandler {
|
||||
target: "sidePanel"
|
||||
function toggle() {
|
||||
sidePanel.toggle()
|
||||
// Will attempt to open the panel next to the bar button if any.
|
||||
sidePanel.toggle(BarService.lookupWidget("SidePanelToggle"))
|
||||
}
|
||||
}
|
||||
|
||||
// Wallpaper IPC: trigger a new random wallpaper
|
||||
IpcHandler {
|
||||
target: "wallpaper"
|
||||
function toggle() {
|
||||
if (Settings.data.wallpaper.enabled) {
|
||||
wallpaperSelector.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
function random() {
|
||||
if (Settings.data.wallpaper.enabled) {
|
||||
WallpaperService.setRandomWallpaper()
|
||||
|
||||
@@ -11,6 +11,38 @@ Item {
|
||||
property bool handleSearch: true
|
||||
property var entries: []
|
||||
|
||||
// Persistent usage tracking stored in cacheDir
|
||||
property string usageFilePath: Settings.cacheDir + "launcher_app_usage.json"
|
||||
|
||||
// Debounced saver to avoid excessive IO
|
||||
Timer {
|
||||
id: saveTimer
|
||||
interval: 750
|
||||
repeat: false
|
||||
onTriggered: usageFile.writeAdapter()
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: usageFile
|
||||
path: usageFilePath
|
||||
printErrors: false
|
||||
watchChanges: false
|
||||
|
||||
onLoadFailed: function (error) {
|
||||
if (error.toString().includes("No such file") || error === 2) {
|
||||
writeAdapter()
|
||||
}
|
||||
}
|
||||
|
||||
onAdapterUpdated: saveTimer.start()
|
||||
|
||||
JsonAdapter {
|
||||
id: usageAdapter
|
||||
// key: app id/command, value: integer count
|
||||
property var counts: ({})
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
loadApplications()
|
||||
}
|
||||
@@ -36,8 +68,20 @@ Item {
|
||||
return []
|
||||
|
||||
if (!query || query.trim() === "") {
|
||||
// Return all apps alphabetically
|
||||
return entries.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())).map(app => createResultEntry(app))
|
||||
// Return all apps, optionally sorted by usage
|
||||
let sorted
|
||||
if (Settings.data.appLauncher.sortByMostUsed) {
|
||||
sorted = entries.slice().sort((a, b) => {
|
||||
const ua = getUsageCount(a)
|
||||
const ub = getUsageCount(b)
|
||||
if (ub !== ua)
|
||||
return ub - ua
|
||||
return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase())
|
||||
})
|
||||
} else {
|
||||
sorted = entries.slice().sort((a, b) => (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase()))
|
||||
}
|
||||
return sorted.map(app => createResultEntry(app))
|
||||
}
|
||||
|
||||
// Use fuzzy search if available, fallback to simple search
|
||||
@@ -84,6 +128,9 @@ Item {
|
||||
launcher.closeCompleted()
|
||||
|
||||
Logger.log("ApplicationsPlugin", `Launching: ${app.name}`)
|
||||
// Record usage and persist asynchronously
|
||||
if (Settings.data.appLauncher.sortByMostUsed)
|
||||
recordUsage(app)
|
||||
if (Settings.data.appLauncher.useApp2Unit && app.id) {
|
||||
Logger.log("ApplicationsPlugin", `Using app2unit for: ${app.id}`)
|
||||
if (app.runInTerminal)
|
||||
@@ -98,4 +145,33 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// Usage tracking helpers
|
||||
function getAppKey(app) {
|
||||
if (app && app.id)
|
||||
return String(app.id)
|
||||
if (app && app.command && app.command.join)
|
||||
return app.command.join(" ")
|
||||
return String(app && app.name ? app.name : "unknown")
|
||||
}
|
||||
|
||||
function getUsageCount(app) {
|
||||
const key = getAppKey(app)
|
||||
const m = usageAdapter && usageAdapter.counts ? usageAdapter.counts : null
|
||||
if (!m)
|
||||
return 0
|
||||
const v = m[key]
|
||||
return typeof v === 'number' && isFinite(v) ? v : 0
|
||||
}
|
||||
|
||||
function recordUsage(app) {
|
||||
const key = getAppKey(app)
|
||||
if (!usageAdapter.counts)
|
||||
usageAdapter.counts = ({})
|
||||
const current = getUsageCount(app)
|
||||
usageAdapter.counts[key] = current + 1
|
||||
// Trigger save via debounced timer
|
||||
saveTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,15 @@ Loader {
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime() {
|
||||
return Settings.data.location.use12hourFormat ? Qt.formatDateTime(new Date(), "h:mm A") : Qt.formatDateTime(new Date(), "HH:mm")
|
||||
}
|
||||
|
||||
function formatDate() {
|
||||
// For full text date, day is always before month, so we use this format for everybody: Wednesday, September 17.
|
||||
return Qt.formatDateTime(new Date(), "dddd, MMMM d")
|
||||
}
|
||||
|
||||
function scheduleUnloadAfterUnlock() {
|
||||
unloadAfterUnlockTimer.start()
|
||||
}
|
||||
@@ -137,9 +146,10 @@ Loader {
|
||||
|
||||
NText {
|
||||
id: timeText
|
||||
text: Qt.formatDateTime(new Date(), "HH:mm")
|
||||
text: formatTime()
|
||||
font.family: Settings.data.ui.fontBillboard
|
||||
font.pointSize: Style.fontSizeXXXL * 6 * scaling
|
||||
// Smaller time display when using longer 12 hour format
|
||||
font.pointSize: Settings.data.location.use12hourFormat ? Style.fontSizeXXXL * 4 * scaling : Style.fontSizeXXXL * 5 * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
font.letterSpacing: -2 * scaling
|
||||
color: Color.mOnSurface
|
||||
@@ -163,7 +173,7 @@ Loader {
|
||||
|
||||
NText {
|
||||
id: dateText
|
||||
text: Qt.formatDateTime(new Date(), "dddd, MMMM d")
|
||||
text: formatDate()
|
||||
font.family: Settings.data.ui.fontBillboard
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
font.weight: Font.Light
|
||||
@@ -505,6 +515,19 @@ Loader {
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NText {
|
||||
text: "Password:"
|
||||
color: Color.mPrimary
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
}
|
||||
|
||||
TextInput {
|
||||
id: passwordInput
|
||||
@@ -877,8 +900,8 @@ Loader {
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
timeText.text = Qt.formatDateTime(new Date(), "HH:mm")
|
||||
dateText.text = Qt.formatDateTime(new Date(), "dddd, MMMM d")
|
||||
timeText.text = formatTime()
|
||||
dateText.text = formatDate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,6 +259,7 @@ Variants {
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightMedium
|
||||
color: Color.mOnSurface
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.fillWidth: true
|
||||
maximumLineCount: 3
|
||||
@@ -269,6 +270,7 @@ Variants {
|
||||
text: model.body || ""
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
color: Color.mOnSurface
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
Layout.fillWidth: true
|
||||
maximumLineCount: 5
|
||||
|
||||
@@ -169,6 +169,7 @@ NPanel {
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Font.Medium
|
||||
color: Color.mPrimary
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
maximumLineCount: 2
|
||||
@@ -179,6 +180,7 @@ NPanel {
|
||||
text: (body || "").substring(0, 150)
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
color: Color.mOnSurface
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
maximumLineCount: 3
|
||||
|
||||
@@ -15,14 +15,10 @@ ColumnLayout {
|
||||
|
||||
// Local state
|
||||
property string valueDisplayFormat: widgetData.displayFormat !== undefined ? widgetData.displayFormat : widgetMetadata.displayFormat
|
||||
property bool valueUse12h: widgetData.use12HourClock !== undefined ? widgetData.use12HourClock : widgetMetadata.use12HourClock
|
||||
property bool valueReverseDayMonth: widgetData.reverseDayMonth !== undefined ? widgetData.reverseDayMonth : widgetMetadata.reverseDayMonth
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.displayFormat = valueDisplayFormat
|
||||
settings.use12HourClock = valueUse12h
|
||||
settings.reverseDayMonth = valueReverseDayMonth
|
||||
return settings
|
||||
}
|
||||
|
||||
@@ -50,16 +46,4 @@ ColumnLayout {
|
||||
onSelected: key => valueDisplayFormat = key
|
||||
minimumWidth: 230 * scaling
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use 12-hour clock"
|
||||
checked: valueUse12h
|
||||
onToggled: checked => valueUse12h = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Reverse day and month"
|
||||
checked: valueReverseDayMonth
|
||||
onToggled: checked => valueReverseDayMonth = checked
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,13 +51,13 @@ ColumnLayout {
|
||||
id: iconPicker
|
||||
modal: true
|
||||
width: {
|
||||
var w = Math.round(Math.max(Screen.width * 0.35, 900) * scaling)
|
||||
w = Math.min(w, Screen.width - Style.marginL * 2)
|
||||
var w = Math.round(Math.max(screen.width * 0.35, 900) * scaling)
|
||||
w = Math.min(w, screen.width - Style.marginL * 2)
|
||||
return w
|
||||
}
|
||||
height: {
|
||||
var h = Math.round(Math.max(Screen.height * 0.65, 700) * scaling)
|
||||
h = Math.min(h, Screen.height - Style.barHeight * scaling - Style.marginL * 2)
|
||||
var h = Math.round(Math.max(screen.height * 0.65, 700) * scaling)
|
||||
h = Math.min(h, screen.height - Style.barHeight * scaling - Style.marginL * 2)
|
||||
return h
|
||||
}
|
||||
anchors.centerIn: Overlay.overlay
|
||||
|
||||
@@ -21,23 +21,24 @@ NPanel {
|
||||
|
||||
panelKeyboardFocus: true
|
||||
|
||||
draggable: true
|
||||
|
||||
// Tabs enumeration, order is NOT relevant
|
||||
enum Tab {
|
||||
About,
|
||||
Audio,
|
||||
Bar,
|
||||
Dock,
|
||||
Hooks,
|
||||
Launcher,
|
||||
ColorScheme,
|
||||
Display,
|
||||
Dock,
|
||||
General,
|
||||
Hooks,
|
||||
Launcher,
|
||||
Location,
|
||||
Network,
|
||||
Notification,
|
||||
Notifications,
|
||||
ScreenRecorder,
|
||||
Weather,
|
||||
Wallpaper,
|
||||
WallpaperSelector
|
||||
Wallpaper
|
||||
}
|
||||
|
||||
property int requestedTab: SettingsPanel.Tab.General
|
||||
@@ -45,13 +46,6 @@ NPanel {
|
||||
property var tabsModel: []
|
||||
property var activeScrollView: null
|
||||
|
||||
Connections {
|
||||
target: Settings.data.wallpaper
|
||||
function onEnabledChanged() {
|
||||
updateTabsModel()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updateTabsModel()
|
||||
}
|
||||
@@ -81,8 +75,8 @@ NPanel {
|
||||
Tabs.NetworkTab {}
|
||||
}
|
||||
Component {
|
||||
id: weatherTab
|
||||
Tabs.WeatherTab {}
|
||||
id: locationTab
|
||||
Tabs.LocationTab {}
|
||||
}
|
||||
Component {
|
||||
id: colorSchemeTab
|
||||
@@ -92,10 +86,6 @@ NPanel {
|
||||
id: wallpaperTab
|
||||
Tabs.WallpaperTab {}
|
||||
}
|
||||
Component {
|
||||
id: wallpaperSelectorTab
|
||||
Tabs.WallpaperSelectorTab {}
|
||||
}
|
||||
Component {
|
||||
id: screenRecorderTab
|
||||
Tabs.ScreenRecorderTab {}
|
||||
@@ -113,8 +103,8 @@ NPanel {
|
||||
Tabs.DockTab {}
|
||||
}
|
||||
Component {
|
||||
id: notificationTab
|
||||
Tabs.NotificationTab {}
|
||||
id: notificationsTab
|
||||
Tabs.NotificationsTab {}
|
||||
}
|
||||
|
||||
// Order *DOES* matter
|
||||
@@ -150,20 +140,20 @@ NPanel {
|
||||
"icon": "settings-display",
|
||||
"source": displayTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Notification,
|
||||
"label": "Notification",
|
||||
"icon": "settings-notification",
|
||||
"source": notificationTab
|
||||
"id": SettingsPanel.Tab.Notifications,
|
||||
"label": "Notifications",
|
||||
"icon": "settings-notifications",
|
||||
"source": notificationsTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Network,
|
||||
"label": "Network",
|
||||
"icon": "settings-network",
|
||||
"source": networkTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Weather,
|
||||
"label": "Weather",
|
||||
"icon": "settings-weather",
|
||||
"source": weatherTab
|
||||
"id": SettingsPanel.Tab.Location,
|
||||
"label": "Location",
|
||||
"icon": "settings-location",
|
||||
"source": locationTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.ColorScheme,
|
||||
"label": "Color Scheme",
|
||||
@@ -174,35 +164,23 @@ NPanel {
|
||||
"label": "Wallpaper",
|
||||
"icon": "settings-wallpaper",
|
||||
"source": wallpaperTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.ScreenRecorder,
|
||||
"label": "Screen Recorder",
|
||||
"icon": "settings-screen-recorder",
|
||||
"source": screenRecorderTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Hooks,
|
||||
"label": "Hooks",
|
||||
"icon": "settings-hooks",
|
||||
"source": hooksTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.About,
|
||||
"label": "About",
|
||||
"icon": "settings-about",
|
||||
"source": aboutTab
|
||||
}]
|
||||
|
||||
// Only add the Wallpaper Selector tab if the feature is enabled
|
||||
if (Settings.data.wallpaper.enabled) {
|
||||
newTabs.push({
|
||||
"id": SettingsPanel.Tab.WallpaperSelector,
|
||||
"label": "Wallpaper Selector",
|
||||
"icon": "settings-wallpaper-selector",
|
||||
"source": wallpaperSelectorTab
|
||||
})
|
||||
}
|
||||
|
||||
newTabs.push({
|
||||
"id": SettingsPanel.Tab.ScreenRecorder,
|
||||
"label": "Screen Recorder",
|
||||
"icon": "settings-screen-recorder",
|
||||
"source": screenRecorderTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.Hooks,
|
||||
"label": "Hooks",
|
||||
"icon": "settings-hooks",
|
||||
"source": hooksTab
|
||||
}, {
|
||||
"id": SettingsPanel.Tab.About,
|
||||
"label": "About",
|
||||
"icon": "settings-about",
|
||||
"source": aboutTab
|
||||
})
|
||||
|
||||
root.tabsModel = newTabs // Assign the generated list to the model
|
||||
}
|
||||
// When the panel opens, choose the appropriate tab
|
||||
|
||||
@@ -17,7 +17,7 @@ ColumnLayout {
|
||||
property var contributors: GitHubService.contributors
|
||||
|
||||
NHeader {
|
||||
label: "Noctalia Shell"
|
||||
label: "Noctalia shell"
|
||||
description: "A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell."
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ ColumnLayout {
|
||||
columnSpacing: Style.marginS * scaling
|
||||
|
||||
NText {
|
||||
text: "Latest Version:"
|
||||
text: "Latest version:"
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Installed Version:"
|
||||
text: "Installed version:"
|
||||
color: Color.mOnSurface
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ ColumnLayout {
|
||||
|
||||
NHeader {
|
||||
label: "Volumes"
|
||||
description: "Configure volume controls and audio levels."
|
||||
description: "Adjust volume controls and audio levels."
|
||||
}
|
||||
|
||||
property real localVolume: AudioService.volume
|
||||
@@ -30,7 +30,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Output Volume"
|
||||
label: "Output volume"
|
||||
description: "System-wide volume level."
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: "Mute Audio Output"
|
||||
description: "Mute or unmute the default audio output."
|
||||
label: "Mute audio output"
|
||||
description: "Mute the system's main audio output."
|
||||
checked: AudioService.muted
|
||||
onToggled: checked => {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
@@ -84,7 +84,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Input Volume"
|
||||
label: "Input volume"
|
||||
description: "Microphone input volume level."
|
||||
}
|
||||
|
||||
@@ -105,8 +105,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NToggle {
|
||||
label: "Mute Audio Input"
|
||||
description: "Mute or unmute the default audio input (microphone)."
|
||||
label: "Mute audio input"
|
||||
description: "Mute the default audio input (microphone)."
|
||||
checked: AudioService.inputMuted
|
||||
onToggled: checked => AudioService.setInputMuted(checked)
|
||||
}
|
||||
@@ -119,7 +119,7 @@ ColumnLayout {
|
||||
|
||||
NSpinBox {
|
||||
Layout.fillWidth: true
|
||||
label: "Volume Step Size"
|
||||
label: "Volume step size"
|
||||
description: "Adjust the step size for volume changes (scroll wheel, keyboard shortcuts)."
|
||||
minimum: 1
|
||||
maximum: 25
|
||||
@@ -141,8 +141,8 @@ ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Audio Devices"
|
||||
description: "Configure audio input and output devices."
|
||||
label: "Audio devices"
|
||||
description: "Choose your audio input and output devices."
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
@@ -157,7 +157,7 @@ ColumnLayout {
|
||||
Layout.bottomMargin: Style.marginL * scaling
|
||||
|
||||
NLabel {
|
||||
label: "Output Device"
|
||||
label: "Output device"
|
||||
description: "Select the desired audio output device."
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Input Device"
|
||||
label: "Input device"
|
||||
description: "Select the desired audio input device."
|
||||
}
|
||||
|
||||
@@ -213,14 +213,14 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Media Player"
|
||||
description: "Configure your favorite media players."
|
||||
label: "Media players"
|
||||
description: "Set your preferred and ignored media applications."
|
||||
}
|
||||
|
||||
// Preferred player
|
||||
NTextInput {
|
||||
label: "Preferred Player"
|
||||
description: "Substring to match MPRIS player (identity/bus/desktop)."
|
||||
label: "Primary player"
|
||||
description: "Enter a keyword to identify your main player."
|
||||
placeholderText: "e.g. spotify, vlc, mpv"
|
||||
text: Settings.data.audio.preferredPlayer
|
||||
onTextChanged: {
|
||||
@@ -240,8 +240,8 @@ ColumnLayout {
|
||||
|
||||
NTextInput {
|
||||
id: blacklistInput
|
||||
label: "Blacklist player"
|
||||
description: "Substring, e.g. plex, shim, mpv."
|
||||
label: "Excluded player"
|
||||
description: "Add keywords for players you want the system to ignore. Each keyword should be on a new line."
|
||||
placeholderText: "type substring and press +"
|
||||
}
|
||||
|
||||
@@ -336,14 +336,14 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Audio Visualizer"
|
||||
label: "Audio visualizer"
|
||||
description: "Customize visual effects that respond to audio playback."
|
||||
}
|
||||
|
||||
// AudioService Visualizer section
|
||||
NComboBox {
|
||||
id: audioVisualizerCombo
|
||||
label: "Visualization Type"
|
||||
label: "Visualization type"
|
||||
description: "Choose a visualization type for media playback"
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
@@ -368,8 +368,8 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: "Frame Rate"
|
||||
description: "Target frame rate for audio visualizer."
|
||||
label: "Frame rate"
|
||||
description: "Higher rates are smoother but use more resources."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "30"
|
||||
|
||||
@@ -42,12 +42,12 @@ ColumnLayout {
|
||||
|
||||
NHeader {
|
||||
label: "Appearance"
|
||||
description: "Configure bar appearance and positioning."
|
||||
description: "Customize the bar's appearance and position."
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: "Bar Position"
|
||||
label: "Bar position"
|
||||
description: "Choose where to place the bar on the screen."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
@@ -73,8 +73,8 @@ ColumnLayout {
|
||||
|
||||
NComboBox {
|
||||
Layout.fillWidth: true
|
||||
label: "Bar Density"
|
||||
description: "Choose the density of the bar."
|
||||
label: "Bar density"
|
||||
description: "Adjust the bar's padding for a compact or spacious look."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "compact"
|
||||
@@ -98,7 +98,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Background Opacity"
|
||||
label: "Background opacity"
|
||||
description: "Adjust the background opacity of the bar."
|
||||
}
|
||||
|
||||
@@ -115,16 +115,16 @@ ColumnLayout {
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: "Show Capsule"
|
||||
description: "Adds a capsule behind each widget to improve readability on transparent bars."
|
||||
label: "Show capsule"
|
||||
description: "Show widget backgrounds."
|
||||
checked: Settings.data.bar.showCapsule
|
||||
onToggled: checked => Settings.data.bar.showCapsule = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
Layout.fillWidth: true
|
||||
label: "Floating Bar"
|
||||
description: "Make the bar float with rounded corners and margins. Screen corners will move to screen edges."
|
||||
label: "Floating bar"
|
||||
description: "Displays the bar as a floating 'pill'. Note: This will move the screen corners to the edges."
|
||||
checked: Settings.data.bar.floating
|
||||
onToggled: checked => Settings.data.bar.floating = checked
|
||||
}
|
||||
@@ -192,47 +192,13 @@ ColumnLayout {
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Monitor Configuration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Monitors Configuration"
|
||||
description: "Show bar on specific monitors. Defaults to all if none are chosen."
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || "Unknown"
|
||||
description: `${modelData.model} - ${modelData.width}x${modelData.height} [x:${modelData.x} y:${modelData.y}]`
|
||||
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name)
|
||||
} else {
|
||||
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Widgets Management Section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Widgets Positioning"
|
||||
label: "Widgets positioning"
|
||||
description: "Drag and drop widgets to reorder them within each section, or use the add/remove buttons to manage widgets."
|
||||
}
|
||||
|
||||
@@ -293,6 +259,40 @@ ColumnLayout {
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Monitor Configuration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Monitor display"
|
||||
description: "Show bar on specific monitors. Defaults to all if none are chosen."
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || "Unknown"
|
||||
description: `${modelData.model} (${modelData.width}x${modelData.height})`
|
||||
checked: (Settings.data.bar.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.bar.monitors = addMonitor(Settings.data.bar.monitors, modelData.name)
|
||||
} else {
|
||||
Settings.data.bar.monitors = removeMonitor(Settings.data.bar.monitors, modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// Signal functions
|
||||
// ---------------------------------
|
||||
|
||||
@@ -106,14 +106,14 @@ ColumnLayout {
|
||||
|
||||
// Main Toggles - Dark Mode / Matugen
|
||||
NHeader {
|
||||
label: "Behavior"
|
||||
label: "Color Source"
|
||||
description: "Main settings for Noctalia's colors."
|
||||
}
|
||||
|
||||
// Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants)
|
||||
NToggle {
|
||||
label: "Dark Mode"
|
||||
description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available."
|
||||
label: "Dark mode"
|
||||
description: "Switches to a darker theme for easier viewing at night."
|
||||
checked: Settings.data.colorSchemes.darkMode
|
||||
enabled: true
|
||||
onToggled: checked => Settings.data.colorSchemes.darkMode = checked
|
||||
@@ -152,7 +152,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Predefined Color Schemes"
|
||||
label: "Predefined color schemes"
|
||||
description: "To use these color schemes, you must turn off Matugen. With Matugen enabled, colors are automatically generated from your wallpaper."
|
||||
}
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Monitor-specific configuration"
|
||||
description: "Configure scaling and brightness settings individually for each connected display."
|
||||
label: "Per-monitor settings"
|
||||
description: "Adjust scaling and brightness for each display."
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
@@ -89,7 +89,7 @@ ColumnLayout {
|
||||
|
||||
NLabel {
|
||||
label: modelData.name || "Unknown"
|
||||
description: `${modelData.model} - ${modelData.width}x${modelData.height} [x:${modelData.x} y:${modelData.y}]`
|
||||
description: `${modelData.model} (${modelData.width}x${modelData.height})`
|
||||
}
|
||||
|
||||
// Scale
|
||||
@@ -204,7 +204,7 @@ ColumnLayout {
|
||||
|
||||
NSpinBox {
|
||||
Layout.fillWidth: true
|
||||
label: "Brightness Step Size"
|
||||
label: "Brightness step size"
|
||||
description: "Adjust the step size for brightness changes (scroll wheel and keyboard shortcuts)."
|
||||
minimum: 1
|
||||
maximum: 50
|
||||
@@ -228,13 +228,13 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Night Light"
|
||||
label: "Night light"
|
||||
description: "Reduce blue light emission to help you sleep better and reduce eye strain."
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Enable Night Light"
|
||||
label: "Enable night light"
|
||||
description: "Apply a warm color filter to reduce blue light emission."
|
||||
checked: Settings.data.nightLight.enabled
|
||||
onToggled: checked => {
|
||||
@@ -257,7 +257,7 @@ ColumnLayout {
|
||||
|
||||
NLabel {
|
||||
label: "Color temperature"
|
||||
description: "Choose two temperatures in Kelvin."
|
||||
description: "Set the color warmth for nighttime and daytime."
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -373,7 +373,7 @@ ColumnLayout {
|
||||
// Force activation toggle
|
||||
NToggle {
|
||||
label: "Force activation"
|
||||
description: "Immediately apply night temperature without scheduling or fade."
|
||||
description: "Ignores the schedule and applies the night filter immediately."
|
||||
checked: Settings.data.nightLight.forced
|
||||
onToggled: checked => {
|
||||
Settings.data.nightLight.forced = checked
|
||||
|
||||
@@ -25,7 +25,7 @@ ColumnLayout {
|
||||
|
||||
NHeader {
|
||||
label: "Appearance"
|
||||
description: "Configure dock behavior and appearance."
|
||||
description: "Customize the dock's behavior and appearance."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
@@ -36,8 +36,8 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Exclusive Zone"
|
||||
description: "Ensure windows don't open underneath."
|
||||
label: "Exclusive zone"
|
||||
description: "Prevent window overlap."
|
||||
checked: Settings.data.dock.exclusive
|
||||
onToggled: checked => Settings.data.dock.exclusive = checked
|
||||
}
|
||||
@@ -46,8 +46,8 @@ ColumnLayout {
|
||||
spacing: Style.marginXXS * scaling
|
||||
Layout.fillWidth: true
|
||||
NLabel {
|
||||
label: "Background Opacity"
|
||||
description: "Adjust the background opacity."
|
||||
label: "Background opacity"
|
||||
description: "Adjust the dock's background opacity."
|
||||
}
|
||||
NValueSlider {
|
||||
Layout.fillWidth: true
|
||||
@@ -65,7 +65,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Dock Floating Distance"
|
||||
label: "Dock floating distance"
|
||||
description: "Adjust the floating distance from the screen edge."
|
||||
}
|
||||
|
||||
@@ -92,8 +92,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Monitors Configuration"
|
||||
description: "Show dock on specific monitors."
|
||||
label: "Monitor display"
|
||||
description: "Choose which monitor to display the dock on."
|
||||
}
|
||||
|
||||
Repeater {
|
||||
@@ -101,7 +101,7 @@ ColumnLayout {
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || "Unknown"
|
||||
description: `${modelData.model} - ${modelData.width}x${modelData.height} [x:${modelData.x} y:${modelData.y}]`
|
||||
description: `${modelData.model} (${modelData.width}x${modelData.height})`
|
||||
checked: (Settings.data.dock.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
|
||||
@@ -11,7 +11,7 @@ ColumnLayout {
|
||||
|
||||
NHeader {
|
||||
label: "Profile"
|
||||
description: "Configure your user profile and avatar settings."
|
||||
description: "Edit your user details and avatar."
|
||||
}
|
||||
|
||||
// Profile section
|
||||
@@ -54,12 +54,12 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "User Interface"
|
||||
description: "Main settings for the user interface."
|
||||
label: "User interface"
|
||||
description: "Customize the look, feel, and behavior of the interface."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Dim Desktop"
|
||||
label: "Dim desktop"
|
||||
description: "Dim the desktop when panels or menus are open."
|
||||
checked: Settings.data.general.dimDesktop
|
||||
onToggled: checked => Settings.data.general.dimDesktop = checked
|
||||
@@ -71,7 +71,7 @@ ColumnLayout {
|
||||
|
||||
NLabel {
|
||||
label: "Border radius"
|
||||
description: "Adjust the rounded border of all UI elements."
|
||||
description: "Controls the corner roundness of windows, buttons, and other elements."
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
@@ -91,7 +91,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Animation Speed"
|
||||
label: "Animation speed"
|
||||
description: "Adjust global animation speed."
|
||||
}
|
||||
|
||||
@@ -117,21 +117,22 @@ ColumnLayout {
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Screen Corners"
|
||||
label: "Screen corners"
|
||||
description: "Customize screen corner rounding and visual effects."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show Screen Corners"
|
||||
label: "Show screen corners"
|
||||
description: "Display rounded corners on the edge of the screen."
|
||||
checked: Settings.data.general.showScreenCorners
|
||||
onToggled: checked => Settings.data.general.showScreenCorners = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Solid Black Corners"
|
||||
description: "Force screen corners to always render as solid black."
|
||||
label: "Solid black corners"
|
||||
description: "Use solid black instead of the bar background color."
|
||||
checked: Settings.data.general.forceBlackScreenCorners
|
||||
onToggled: checked => Settings.data.general.forceBlackScreenCorners = checked
|
||||
}
|
||||
@@ -141,7 +142,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Screen Corners Radius"
|
||||
label: "Screen corners radius"
|
||||
description: "Adjust the rounded corners of the screen."
|
||||
}
|
||||
|
||||
@@ -169,7 +170,7 @@ ColumnLayout {
|
||||
|
||||
NHeader {
|
||||
label: "Fonts"
|
||||
description: "Configure interface typography."
|
||||
description: "Choose the fonts used throughout the interface."
|
||||
}
|
||||
|
||||
// Font configuration section
|
||||
@@ -178,7 +179,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NSearchableComboBox {
|
||||
label: "Default Font"
|
||||
label: "Default font"
|
||||
description: "Main font used throughout the interface."
|
||||
model: FontService.availableFonts
|
||||
currentKey: Settings.data.ui.fontDefault
|
||||
@@ -192,8 +193,8 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
NSearchableComboBox {
|
||||
label: "Fixed Width Font"
|
||||
description: "Monospace font used for terminal and code display."
|
||||
label: "Monospaced font"
|
||||
description: "Monospaced font used for numbers and stats display."
|
||||
model: FontService.monospaceFonts
|
||||
currentKey: Settings.data.ui.fontFixed
|
||||
placeholder: "Select monospace font..."
|
||||
@@ -206,8 +207,8 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
NSearchableComboBox {
|
||||
label: "Billboard Font"
|
||||
description: "Large font used for clocks and prominent displays."
|
||||
label: "Accent font"
|
||||
description: "Large font used for prominent displays."
|
||||
model: FontService.displayFonts
|
||||
currentKey: Settings.data.ui.fontBillboard
|
||||
placeholder: "Select display font..."
|
||||
|
||||
@@ -11,13 +11,13 @@ ColumnLayout {
|
||||
width: root.width
|
||||
|
||||
NHeader {
|
||||
label: "System Hooks"
|
||||
label: "System hooks"
|
||||
description: "Configure commands to be executed when system events occur."
|
||||
}
|
||||
|
||||
// Enable/Disable Toggle
|
||||
NToggle {
|
||||
label: "Enable Hooks"
|
||||
label: "Enable hooks"
|
||||
description: "Enable or disable all hook commands."
|
||||
checked: Settings.data.hooks.enabled
|
||||
onToggled: checked => Settings.data.hooks.enabled = checked
|
||||
|
||||
@@ -11,7 +11,7 @@ ColumnLayout {
|
||||
|
||||
NHeader {
|
||||
label: "Appearance"
|
||||
description: "Configure the launcher behavior and appearance."
|
||||
description: "Customize the launcher's behavior and appearance."
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
@@ -26,27 +26,27 @@ ColumnLayout {
|
||||
}
|
||||
ListElement {
|
||||
key: "top_left"
|
||||
name: "Top Left"
|
||||
name: "Top left"
|
||||
}
|
||||
ListElement {
|
||||
key: "top_right"
|
||||
name: "Top Right"
|
||||
name: "Top right"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom_left"
|
||||
name: "Bottom Left"
|
||||
name: "Bottom left"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom_right"
|
||||
name: "Bottom Right"
|
||||
name: "Bottom right"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom_center"
|
||||
name: "Bottom Center"
|
||||
name: "Bottom center"
|
||||
}
|
||||
ListElement {
|
||||
key: "top_center"
|
||||
name: "Top Center"
|
||||
name: "Top center"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.appLauncher.position
|
||||
@@ -60,7 +60,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NText {
|
||||
text: "Background Opacity"
|
||||
text: "Background opacity"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
@@ -87,15 +87,22 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Enable Clipboard History"
|
||||
description: "Show clipboard history in the launcher."
|
||||
label: "Enable clipboard history"
|
||||
description: "Access previously copied items from the launcher."
|
||||
checked: Settings.data.appLauncher.enableClipboardHistory
|
||||
onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use App2Unit for Launching"
|
||||
description: "Use app2unit -- 'desktop-entry' when launching applications for better systemd integration."
|
||||
label: "Sort by most used"
|
||||
description: "When enabled, frequently launched apps appear first in the list."
|
||||
checked: Settings.data.appLauncher.sortByMostUsed
|
||||
onToggled: checked => Settings.data.appLauncher.sortByMostUsed = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use App2Unit to launch applications"
|
||||
description: "Uses an alternative launch method to better manage app processes and prevent issues."
|
||||
checked: Settings.data.appLauncher.useApp2Unit
|
||||
onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Your Location"
|
||||
description: "Set your location for weather, time zones, and scheduling."
|
||||
label: "Your location"
|
||||
description: "Get accurate weather and night light scheduling by setting your location."
|
||||
}
|
||||
|
||||
// Location section
|
||||
@@ -20,8 +20,8 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NTextInput {
|
||||
label: "Location name"
|
||||
description: "Choose a known location near you."
|
||||
label: "Search for a location"
|
||||
description: "e.g., Toronto, ON"
|
||||
text: Settings.data.location.name || Settings.defaultLocation
|
||||
placeholderText: "Enter the location name"
|
||||
onEditingFinished: {
|
||||
@@ -65,11 +65,11 @@ ColumnLayout {
|
||||
|
||||
NHeader {
|
||||
label: "Weather"
|
||||
description: "Configure weather display preferences and temperature units."
|
||||
description: "Choose your preferred temperature unit."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use Fahrenheit"
|
||||
label: "Display temperature in Fahrenheit (°F)"
|
||||
description: "Display temperature in Fahrenheit instead of Celsius."
|
||||
checked: Settings.data.location.useFahrenheit
|
||||
onToggled: checked => Settings.data.location.useFahrenheit = checked
|
||||
@@ -81,4 +81,42 @@ ColumnLayout {
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Weather section
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Date & time"
|
||||
description: "Customize how date and time appear."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use 12-hour time format"
|
||||
description: "On for AM/PM format (e.g., 8:00 PM), off for 24-hour format (e.g., 20:00)."
|
||||
checked: Settings.data.location.use12hourFormat
|
||||
onToggled: checked => Settings.data.location.use12hourFormat = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show month before day"
|
||||
description: "On for 09/17/2025, off for 17/09/2025."
|
||||
checked: Settings.data.location.monthBeforeDay
|
||||
onToggled: checked => Settings.data.location.monthBeforeDay = checked
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Show week numbers"
|
||||
description: "Displays the week of the year (e.g., Week 38) in the calendar."
|
||||
checked: Settings.data.location.showWeekNumberInCalendar
|
||||
onToggled: checked => Settings.data.location.showWeekNumberInCalendar = checked
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
}
|
||||
@@ -12,20 +12,17 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Network Settings"
|
||||
description: "Configure Wi-Fi and Bluetooth connectivity options."
|
||||
label: "Manage Wi-Fi and Bluetooth connections."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Enable Wi-Fi"
|
||||
description: "Enable Wi-Fi connectivity."
|
||||
checked: Settings.data.network.wifiEnabled
|
||||
onToggled: checked => NetworkService.setWifiEnabled(checked)
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Enable Bluetooth"
|
||||
description: "Enable Bluetooth connectivity."
|
||||
checked: Settings.data.network.bluetoothEnabled
|
||||
onToggled: checked => BluetoothService.setBluetoothEnabled(checked)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Do Not Disturb"
|
||||
label: "Do not disturb"
|
||||
description: "Disable all notification popups when enabled."
|
||||
checked: Settings.data.notifications.doNotDisturb
|
||||
onToggled: checked => Settings.data.notifications.doNotDisturb = checked
|
||||
@@ -46,47 +46,13 @@ ColumnLayout {
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Monitor Configuration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Monitors Configuration"
|
||||
description: "Show bar on specific monitors. Defaults to all if none are chosen."
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || "Unknown"
|
||||
description: `${modelData.model} - ${modelData.width}x${modelData.height} [x:${modelData.x} y:${modelData.y}]`
|
||||
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors, modelData.name)
|
||||
} else {
|
||||
Settings.data.notifications.monitors = removeMonitor(Settings.data.notifications.monitors, modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Notification Duration Settings
|
||||
ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Notification Duration"
|
||||
label: "Notification duration"
|
||||
description: "Configure how long notifications stay visible based on their urgency level."
|
||||
}
|
||||
|
||||
@@ -96,7 +62,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Low Urgency Duration"
|
||||
label: "Low urgency"
|
||||
description: "How long low priority notifications stay visible."
|
||||
}
|
||||
|
||||
@@ -117,7 +83,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Normal Urgency Duration"
|
||||
label: "Normal urgency"
|
||||
description: "How long normal priority notifications stay visible."
|
||||
}
|
||||
|
||||
@@ -138,7 +104,7 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NLabel {
|
||||
label: "Critical Urgency Duration"
|
||||
label: "Critical urgency"
|
||||
description: "How long critical priority notifications stay visible."
|
||||
}
|
||||
|
||||
@@ -159,4 +125,38 @@ ColumnLayout {
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Monitor Configuration
|
||||
ColumnLayout {
|
||||
spacing: Style.marginM * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Monitors display"
|
||||
description: "Show notification on specific monitors. Defaults to all if none are chosen."
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Quickshell.screens || []
|
||||
delegate: NCheckbox {
|
||||
Layout.fillWidth: true
|
||||
label: modelData.name || "Unknown"
|
||||
description: `${modelData.model} (${modelData.width}x${modelData.height})`
|
||||
checked: (Settings.data.notifications.monitors || []).indexOf(modelData.name) !== -1
|
||||
onToggled: checked => {
|
||||
if (checked) {
|
||||
Settings.data.notifications.monitors = addMonitor(Settings.data.notifications.monitors, modelData.name)
|
||||
} else {
|
||||
Settings.data.notifications.monitors = removeMonitor(Settings.data.notifications.monitors, modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
}
|
||||
@@ -11,18 +11,18 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "General Settings"
|
||||
description: "Configure screen recording output and content."
|
||||
label: "General settings"
|
||||
description: "Manage screen recording output and content."
|
||||
}
|
||||
|
||||
// Output Directory
|
||||
// Output Folder
|
||||
ColumnLayout {
|
||||
spacing: Style.marginS * scaling
|
||||
Layout.fillWidth: true
|
||||
|
||||
NTextInput {
|
||||
label: "Output Directory"
|
||||
description: "Directory where screen recordings will be saved."
|
||||
label: "Output folder"
|
||||
description: "Folder where screen recordings will be saved."
|
||||
placeholderText: "/home/xxx/Videos"
|
||||
text: Settings.data.screenRecorder.directory
|
||||
onEditingFinished: {
|
||||
@@ -38,7 +38,7 @@ ColumnLayout {
|
||||
Layout.topMargin: Style.marginM * scaling
|
||||
// Show Cursor
|
||||
NToggle {
|
||||
label: "Show Cursor"
|
||||
label: "Show cursor"
|
||||
description: "Record mouse cursor in the video."
|
||||
checked: Settings.data.screenRecorder.showCursor
|
||||
onToggled: checked => Settings.data.screenRecorder.showCursor = checked
|
||||
@@ -58,12 +58,12 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Video Settings"
|
||||
label: "Video settings"
|
||||
}
|
||||
|
||||
// Source
|
||||
NComboBox {
|
||||
label: "Video Source"
|
||||
label: "Video source"
|
||||
description: "Portal is recommended, if you get artifacts try Screen."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
@@ -81,7 +81,7 @@ ColumnLayout {
|
||||
|
||||
// Frame Rate
|
||||
NComboBox {
|
||||
label: "Frame Rate"
|
||||
label: "Frame rate"
|
||||
description: "Target frame rate for screen recordings."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
@@ -119,7 +119,7 @@ ColumnLayout {
|
||||
|
||||
// Video Quality
|
||||
NComboBox {
|
||||
label: "Video Quality"
|
||||
label: "Video quality"
|
||||
description: "Higher quality results in larger file sizes."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
@@ -132,7 +132,7 @@ ColumnLayout {
|
||||
}
|
||||
ListElement {
|
||||
key: "very_high"
|
||||
name: "Very High"
|
||||
name: "Very high"
|
||||
}
|
||||
ListElement {
|
||||
key: "ultra"
|
||||
@@ -145,7 +145,7 @@ ColumnLayout {
|
||||
|
||||
// Video Codec
|
||||
NComboBox {
|
||||
label: "Video Codec"
|
||||
label: "Video codec"
|
||||
description: "h264 is the most common codec."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
@@ -175,7 +175,7 @@ ColumnLayout {
|
||||
|
||||
// Color Range
|
||||
NComboBox {
|
||||
label: "Color Range"
|
||||
label: "Color range"
|
||||
description: "Limited is recommended for better compatibility."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
@@ -204,25 +204,25 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Audio Settings"
|
||||
label: "Audio settings"
|
||||
}
|
||||
|
||||
// Audio Source
|
||||
NComboBox {
|
||||
label: "Audio Source"
|
||||
label: "Audio source"
|
||||
description: "Audio source to capture during recording."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "default_output"
|
||||
name: "System Output"
|
||||
name: "System output"
|
||||
}
|
||||
ListElement {
|
||||
key: "default_input"
|
||||
name: "Microphone Input"
|
||||
name: "Microphone input"
|
||||
}
|
||||
ListElement {
|
||||
key: "both"
|
||||
name: "System Output + Microphone Input"
|
||||
name: "System output + Microphone input"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.screenRecorder.audioSource
|
||||
@@ -231,7 +231,7 @@ ColumnLayout {
|
||||
|
||||
// Audio Codec
|
||||
NComboBox {
|
||||
label: "Audio Codec"
|
||||
label: "Audio codec"
|
||||
description: "Opus is recommended for best performance and smallest audio size."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Qt.labs.folderlistmodel
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
width: parent.width
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
property list<string> wallpapersList: []
|
||||
property string currentWallpaper: ""
|
||||
|
||||
Component.onCompleted: {
|
||||
wallpapersList = screen ? WallpaperService.getWallpapersList(screen.name) : []
|
||||
currentWallpaper = screen ? WallpaperService.getWallpaper(screen.name) : ""
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: WallpaperService
|
||||
function onWallpaperChanged(screenName, path) {
|
||||
if (screenName === screen.name) {
|
||||
currentWallpaper = WallpaperService.getWallpaper(screen.name)
|
||||
}
|
||||
}
|
||||
function onWallpaperDirectoryChanged(screenName, directory) {
|
||||
if (screenName === screen.name) {
|
||||
wallpapersList = WallpaperService.getWallpapersList(screen.name)
|
||||
currentWallpaper = WallpaperService.getWallpaper(screen.name)
|
||||
}
|
||||
}
|
||||
function onWallpaperListChanged(screenName, count) {
|
||||
if (screenName === screen.name) {
|
||||
wallpapersList = WallpaperService.getWallpapersList(screen.name)
|
||||
currentWallpaper = WallpaperService.getWallpaper(screen.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Current wallpaper display
|
||||
NHeader {
|
||||
label: "Current Wallpaper"
|
||||
description: "Preview and manage your desktop background."
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 140 * scaling
|
||||
radius: Style.radiusM * scaling
|
||||
color: Color.transparent
|
||||
|
||||
NImageRounded {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginXS * scaling
|
||||
imagePath: currentWallpaper
|
||||
fallbackIcon: "image"
|
||||
imageRadius: Style.radiusM * scaling
|
||||
borderColor: Color.mSecondary
|
||||
borderWidth: Style.borderL * 2 * scaling
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
|
||||
// Wallpaper selector
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
// Wallpaper grid
|
||||
NHeader {
|
||||
label: "Wallpaper Selector"
|
||||
description: "Click on a wallpaper to set it as your current wallpaper."
|
||||
}
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: "Refresh wallpaper list"
|
||||
onClicked: {
|
||||
WallpaperService.refreshWallpapersList()
|
||||
}
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Apply to all monitors"
|
||||
description: "Apply selected wallpaper to all monitors at once."
|
||||
checked: Settings.data.wallpaper.setWallpaperOnAllMonitors
|
||||
onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked
|
||||
visible: (wallpapersList.length > 0)
|
||||
}
|
||||
|
||||
// Wallpaper grid container
|
||||
Item {
|
||||
visible: !WallpaperService.scanning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: {
|
||||
return Math.ceil(wallpapersList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: wallpaperGridView
|
||||
anchors.fill: parent
|
||||
model: wallpapersList
|
||||
|
||||
interactive: false
|
||||
clip: true
|
||||
|
||||
property int columns: 4
|
||||
property int itemSize: Math.floor((width - leftMargin - rightMargin - (4 * Style.marginS * scaling)) / columns)
|
||||
|
||||
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
|
||||
cellHeight: Math.floor(itemSize * 0.67) + Style.marginS * scaling
|
||||
|
||||
leftMargin: Style.marginS * scaling
|
||||
rightMargin: Style.marginS * scaling
|
||||
topMargin: Style.marginS * scaling
|
||||
bottomMargin: Style.marginS * scaling
|
||||
|
||||
delegate: Rectangle {
|
||||
id: wallpaperItem
|
||||
|
||||
property string wallpaperPath: modelData
|
||||
property bool isSelected: screen ? (wallpaperPath === currentWallpaper) : false
|
||||
|
||||
width: wallpaperGridView.itemSize
|
||||
height: Math.round(wallpaperGridView.itemSize * 0.67)
|
||||
color: Color.transparent
|
||||
|
||||
// NImageCached relies on the image being visible to work properly.
|
||||
// MultiEffect relies on the image being invisible to apply effects.
|
||||
// That's why we don't have rounded corners here, as we don't want to bring back qt5compat.
|
||||
NImageCached {
|
||||
id: img
|
||||
imagePath: wallpaperPath
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
// Borders on top
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.transparent
|
||||
border.color: isSelected ? Color.mSecondary : Color.mSurface
|
||||
border.width: Math.max(1, Style.borderL * 1.5 * scaling)
|
||||
}
|
||||
|
||||
// Selection tick-mark
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Style.marginS * scaling
|
||||
width: 28 * scaling
|
||||
height: 28 * scaling
|
||||
radius: width / 2
|
||||
color: Color.mSecondary
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: isSelected
|
||||
|
||||
NIcon {
|
||||
icon: "check"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSecondary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
// Hover effect
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.mSurface
|
||||
opacity: (mouseArea.containsMouse || isSelected) ? 0 : 0.3
|
||||
radius: parent.radius
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
onPressed: {
|
||||
if (Settings.data.wallpaper.setWallpaperOnAllMonitors) {
|
||||
WallpaperService.changeWallpaper(wallpaperPath, undefined)
|
||||
} else if (screen) {
|
||||
WallpaperService.changeWallpaper(wallpaperPath, screen.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state
|
||||
Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusM * scaling
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: wallpapersList.length === 0 || WallpaperService.scanning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 130 * scaling
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
visible: WallpaperService.scanning
|
||||
NBusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
visible: wallpapersList.length === 0 && !WallpaperService.scanning
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
|
||||
NIcon {
|
||||
icon: "folder-open"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "No wallpaper found."
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Make sure your wallpaper directory is configured and contains image files."
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Style.marginXL * scaling
|
||||
Layout.bottomMargin: Style.marginXL * scaling
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,12 @@ ColumnLayout {
|
||||
spacing: Style.marginL * scaling
|
||||
|
||||
NHeader {
|
||||
label: "Wallpaper Settings"
|
||||
label: "Wallpaper settings"
|
||||
description: "Control how wallpapers are managed and displayed."
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Enable Wallpaper Management"
|
||||
label: "Enable wallpaper management"
|
||||
description: "Manage wallpapers with Noctalia. (Uncheck if you prefer using another application)."
|
||||
checked: Settings.data.wallpaper.enabled
|
||||
onToggled: checked => Settings.data.wallpaper.enabled = checked
|
||||
@@ -30,8 +30,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NTextInput {
|
||||
label: "Wallpaper Directory"
|
||||
description: "Path to your common wallpaper directory."
|
||||
label: "Wallpaper folder"
|
||||
description: "Path to your main wallpaper folder."
|
||||
text: Settings.data.wallpaper.directory
|
||||
onEditingFinished: {
|
||||
Settings.data.wallpaper.directory = text
|
||||
@@ -42,7 +42,7 @@ ColumnLayout {
|
||||
// Monitor-specific directories
|
||||
NToggle {
|
||||
label: "Monitor-specific directories"
|
||||
description: "Enable multi-monitor wallpaper directory management."
|
||||
description: "Set a different wallpaper folder for each monitor."
|
||||
checked: Settings.data.wallpaper.enableMultiMonitorDirectories
|
||||
onToggled: checked => Settings.data.wallpaper.enableMultiMonitorDirectories = checked
|
||||
}
|
||||
@@ -97,12 +97,12 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
NHeader {
|
||||
label: "Look & Feel"
|
||||
label: "Look & feel"
|
||||
}
|
||||
|
||||
// Fill Mode
|
||||
NComboBox {
|
||||
label: "Fill Mode"
|
||||
label: "Fill mode"
|
||||
description: "Select how the image should scale to match your monitor's resolution."
|
||||
model: WallpaperService.fillModeModel
|
||||
currentKey: Settings.data.wallpaper.fillMode
|
||||
@@ -111,7 +111,7 @@ ColumnLayout {
|
||||
|
||||
RowLayout {
|
||||
NLabel {
|
||||
label: "Fill Color"
|
||||
label: "Fill color"
|
||||
description: "Choose a fill color that may appear behind the wallpaper."
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
@@ -124,7 +124,7 @@ ColumnLayout {
|
||||
|
||||
// Transition Type
|
||||
NComboBox {
|
||||
label: "Transition Type"
|
||||
label: "Transition type"
|
||||
description: "Animation type when switching between wallpapers."
|
||||
model: WallpaperService.transitionsModel
|
||||
currentKey: Settings.data.wallpaper.transitionType
|
||||
@@ -134,7 +134,7 @@ ColumnLayout {
|
||||
// Transition Duration
|
||||
ColumnLayout {
|
||||
NLabel {
|
||||
label: "Transition Duration"
|
||||
label: "Transition duration"
|
||||
description: "Duration of transition animations in seconds."
|
||||
}
|
||||
|
||||
@@ -152,8 +152,8 @@ ColumnLayout {
|
||||
// Edge Smoothness
|
||||
ColumnLayout {
|
||||
NLabel {
|
||||
label: "Transition Edge Smoothness"
|
||||
description: "Duration of transition animations in seconds."
|
||||
label: "Soften transition edge"
|
||||
description: "Applies a soft, feathered effect to the edge of transitions."
|
||||
}
|
||||
|
||||
NValueSlider {
|
||||
@@ -185,7 +185,7 @@ ColumnLayout {
|
||||
|
||||
// Random Wallpaper
|
||||
NToggle {
|
||||
label: "Random Wallpaper"
|
||||
label: "Random wallpaper"
|
||||
description: "Schedule random wallpaper changes at regular intervals."
|
||||
checked: Settings.data.wallpaper.randomEnabled
|
||||
onToggled: checked => Settings.data.wallpaper.randomEnabled = checked
|
||||
@@ -196,7 +196,7 @@ ColumnLayout {
|
||||
visible: Settings.data.wallpaper.randomEnabled
|
||||
RowLayout {
|
||||
NLabel {
|
||||
label: "Wallpaper Interval"
|
||||
label: "Wallpaper interval"
|
||||
description: "How often to change wallpapers automatically."
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
@@ -262,7 +262,7 @@ ColumnLayout {
|
||||
Layout.topMargin: Style.marginS * scaling
|
||||
|
||||
NTextInput {
|
||||
label: "Custom Interval"
|
||||
label: "Custom interval"
|
||||
description: "Enter time as HH:MM (e.g., 01:30)."
|
||||
text: {
|
||||
const s = Settings.data.wallpaper.randomIntervalSec
|
||||
|
||||
@@ -25,45 +25,33 @@ NBox {
|
||||
}
|
||||
// Performance
|
||||
NIconButton {
|
||||
icon: "performance"
|
||||
tooltipText: "Set performance power profile."
|
||||
icon: PowerProfileService.getIcon(PowerProfile.Performance)
|
||||
tooltipText: `Set "${PowerProfileService.getName(PowerProfile.Performance)}" power profile.`
|
||||
enabled: hasPP
|
||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||
colorBg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
if (enabled) {
|
||||
PowerProfileService.setProfile(PowerProfile.Performance)
|
||||
}
|
||||
}
|
||||
onClicked: PowerProfileService.setProfile(PowerProfile.Performance)
|
||||
}
|
||||
// Balanced
|
||||
NIconButton {
|
||||
icon: "balanced"
|
||||
tooltipText: "Set balanced power profile."
|
||||
icon: PowerProfileService.getIcon(PowerProfile.Balanced)
|
||||
tooltipText: `Set "${PowerProfileService.getName(PowerProfile.Balanced)}" power profile.`
|
||||
enabled: hasPP
|
||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||
colorBg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
if (enabled) {
|
||||
PowerProfileService.setProfile(PowerProfile.Balanced)
|
||||
}
|
||||
}
|
||||
onClicked: PowerProfileService.setProfile(PowerProfile.Balanced)
|
||||
}
|
||||
// Eco
|
||||
NIconButton {
|
||||
icon: "powersaver"
|
||||
tooltipText: "Set eco power profile."
|
||||
icon: PowerProfileService.getIcon(PowerProfile.PowerSaver)
|
||||
tooltipText: `Set "${PowerProfileService.getName(PowerProfile.PowerSaver)}" power profile.`
|
||||
enabled: hasPP
|
||||
opacity: enabled ? Style.opacityFull : Style.opacityMedium
|
||||
colorBg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mPrimary : Color.mSurfaceVariant
|
||||
colorFg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mOnPrimary : Color.mPrimary
|
||||
onClicked: {
|
||||
if (enabled) {
|
||||
PowerProfileService.setProfile(PowerProfile.PowerSaver)
|
||||
}
|
||||
}
|
||||
onClicked: PowerProfileService.setProfile(PowerProfile.PowerSaver)
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -55,14 +55,8 @@ NBox {
|
||||
visible: Settings.data.wallpaper.enabled
|
||||
icon: "wallpaper-selector"
|
||||
tooltipText: "Left click: Open wallpaper selector.\nRight click: Set random wallpaper."
|
||||
onClicked: {
|
||||
var settingsPanel = PanelService.getPanel("settingsPanel")
|
||||
settingsPanel.requestedTab = SettingsPanel.Tab.WallpaperSelector
|
||||
settingsPanel.open()
|
||||
}
|
||||
onRightClicked: {
|
||||
WallpaperService.setRandomWallpaper()
|
||||
}
|
||||
onClicked: PanelService.getPanel("wallpaperSelector")?.toggle(this)
|
||||
onRightClicked: WallpaperService.setRandomWallpaper()
|
||||
}
|
||||
|
||||
Item {
|
||||
|
||||
@@ -15,14 +15,12 @@ Rectangle {
|
||||
|
||||
signal hidden
|
||||
|
||||
width: Math.min(500 * scaling, parent.width * 0.8)
|
||||
height: Math.max(60 * scaling, contentLayout.implicitHeight + Style.marginL * 2 * scaling)
|
||||
width: parent.width
|
||||
height: Math.round(contentLayout.implicitHeight + Style.marginL * 2 * scaling)
|
||||
radius: Style.radiusL * scaling
|
||||
visible: false
|
||||
opacity: 0
|
||||
scale: initialScale
|
||||
|
||||
// Clean surface background like NToast
|
||||
color: Color.mSurface
|
||||
|
||||
// Colored border based on type
|
||||
@@ -67,6 +65,12 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup on destruction
|
||||
Component.onDestruction: {
|
||||
hideTimer.stop()
|
||||
hideAnimation.stop()
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: contentLayout
|
||||
anchors.fill: parent
|
||||
@@ -125,24 +129,9 @@ Rectangle {
|
||||
visible: text.length > 0
|
||||
}
|
||||
}
|
||||
|
||||
// Close button
|
||||
NIconButton {
|
||||
id: closeButton
|
||||
icon: "close"
|
||||
|
||||
colorBg: Color.mSurfaceVariant
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.mOutline
|
||||
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
onClicked: root.hide()
|
||||
}
|
||||
}
|
||||
|
||||
// Click anywhere dismiss the toast
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
@@ -151,6 +140,10 @@ Rectangle {
|
||||
}
|
||||
|
||||
function show(msg, desc, msgType, msgDuration) {
|
||||
// Stop all timers first
|
||||
hideTimer.stop()
|
||||
hideAnimation.stop()
|
||||
|
||||
message = msg
|
||||
description = desc || ""
|
||||
type = msgType || "notice"
|
||||
@@ -167,10 +160,12 @@ Rectangle {
|
||||
hideTimer.stop()
|
||||
opacity = 0
|
||||
scale = initialScale
|
||||
hideAnimation.start()
|
||||
hideAnimation.restart()
|
||||
}
|
||||
|
||||
function hideImmediately() {
|
||||
hideTimer.stop()
|
||||
hideAnimation.stop()
|
||||
opacity = 0
|
||||
scale = initialScale
|
||||
root.visible = false
|
||||
|
||||
@@ -6,12 +6,12 @@ import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Loader {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property ShellScreen screen
|
||||
required property real scaling
|
||||
required property bool active
|
||||
property bool active: false
|
||||
|
||||
// Local queue for this screen only
|
||||
property var messageQueue: []
|
||||
@@ -44,16 +44,26 @@ Loader {
|
||||
}
|
||||
}
|
||||
|
||||
// Clear queue on component destruction to prevent orphaned toasts
|
||||
Component.onDestruction: {
|
||||
messageQueue = []
|
||||
isShowingToast = false
|
||||
hideTimer.stop()
|
||||
quickSwitchTimer.stop()
|
||||
}
|
||||
|
||||
function enqueueToast(toastData) {
|
||||
Logger.log("ToastScreen", "Queuing:", toastData.message, toastData.description, toastData.type)
|
||||
|
||||
if (replaceOnNew && isShowingToast) {
|
||||
// Cancel current toast and clear queue for latest toast
|
||||
messageQueue = [] // Clear existing queue
|
||||
messageQueue.push(toastData)
|
||||
|
||||
// Hide current toast immediately
|
||||
if (item) {
|
||||
if (windowLoader.item) {
|
||||
hideTimer.stop()
|
||||
item.hideToast() // Need to add this method to PanelWindow
|
||||
windowLoader.item.hideToast()
|
||||
}
|
||||
|
||||
// Process new toast after a brief delay
|
||||
@@ -73,20 +83,30 @@ Loader {
|
||||
}
|
||||
|
||||
function processQueue() {
|
||||
if (!active || !item || messageQueue.length === 0 || isShowingToast) {
|
||||
if (!active || messageQueue.length === 0 || isShowingToast) {
|
||||
return
|
||||
}
|
||||
|
||||
var data = messageQueue.shift()
|
||||
isShowingToast = true
|
||||
|
||||
// Show the toast
|
||||
item.showToast(data.message, data.description, data.type, data.duration)
|
||||
// Activate the loader and show toast
|
||||
windowLoader.active = true
|
||||
// Need a small delay to ensure the window is created
|
||||
Qt.callLater(function () {
|
||||
if (windowLoader.item) {
|
||||
windowLoader.item.showToast(data.message, data.description, data.type, data.duration)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onToastHidden() {
|
||||
isShowingToast = false
|
||||
// Small delay before next toast
|
||||
|
||||
// Deactivate the loader to completely remove the window
|
||||
windowLoader.active = false
|
||||
|
||||
// Small delay before processing next toast
|
||||
hideTimer.restart()
|
||||
}
|
||||
|
||||
@@ -96,48 +116,55 @@ Loader {
|
||||
onTriggered: root.processQueue()
|
||||
}
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
id: panel
|
||||
// The loader that creates/destroys the PanelWindow as needed
|
||||
Loader {
|
||||
id: windowLoader
|
||||
active: false // Only active when showing a toast
|
||||
|
||||
screen: root.screen
|
||||
sourceComponent: PanelWindow {
|
||||
id: panel
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
}
|
||||
property alias toastItem: toastItem
|
||||
|
||||
implicitWidth: 500 * root.scaling
|
||||
implicitHeight: Math.round(toastItem.visible ? toastItem.height + Style.marginM * root.scaling : 1)
|
||||
screen: root.screen
|
||||
|
||||
// Set margins based on bar position
|
||||
margins.top: {
|
||||
switch (Settings.data.bar.position) {
|
||||
case "top":
|
||||
return (Style.barHeight + Style.marginS) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0)
|
||||
default:
|
||||
return Style.marginL * scaling
|
||||
anchors {
|
||||
top: true
|
||||
}
|
||||
}
|
||||
|
||||
color: Color.transparent
|
||||
implicitWidth: 420 * root.scaling
|
||||
implicitHeight: toastItem.height
|
||||
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
exclusionMode: PanelWindow.ExclusionMode.Ignore
|
||||
// Set margins based on bar position
|
||||
margins.top: {
|
||||
switch (Settings.data.bar.position) {
|
||||
case "top":
|
||||
return (Style.barHeight + Style.marginS) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0)
|
||||
default:
|
||||
return Style.marginL * scaling
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(message, description, type, duration) {
|
||||
toastItem.show(message, description, type, duration)
|
||||
}
|
||||
color: Color.transparent
|
||||
|
||||
// Add method to immediately hide toast
|
||||
function hideToast() {
|
||||
toastItem.hideImmediately()
|
||||
}
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
exclusionMode: PanelWindow.ExclusionMode.Ignore
|
||||
|
||||
SimpleToast {
|
||||
id: toastItem
|
||||
function showToast(message, description, type, duration) {
|
||||
toastItem.show(message, description, type, duration)
|
||||
}
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onHidden: root.onToastHidden()
|
||||
function hideToast() {
|
||||
toastItem.hideImmediately()
|
||||
}
|
||||
|
||||
SimpleToast {
|
||||
id: toastItem
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onHidden: root.onToastHidden()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
353
Modules/WallpaperSelector/WallpaperSelector.qml
Normal file
353
Modules/WallpaperSelector/WallpaperSelector.qml
Normal file
@@ -0,0 +1,353 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import "../../Helpers/FuzzySort.js" as FuzzySort
|
||||
|
||||
NPanel {
|
||||
id: root
|
||||
|
||||
preferredWidth: 640
|
||||
preferredHeight: 480
|
||||
preferredWidthRatio: 0.4
|
||||
preferredHeightRatio: 0.52
|
||||
panelAnchorHorizontalCenter: true
|
||||
panelAnchorVerticalCenter: true
|
||||
panelKeyboardFocus: true
|
||||
draggable: true
|
||||
|
||||
panelContent: Rectangle {
|
||||
// Local reactive state
|
||||
property list<string> wallpapersList: []
|
||||
property string currentWallpaper: ""
|
||||
property string filterText: ""
|
||||
property list<string> filteredWallpapers: []
|
||||
|
||||
Component.onCompleted: {
|
||||
refreshWallpaperScreenData()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: WallpaperService
|
||||
function onWallpaperChanged(screenName, path) {
|
||||
if (screen !== null && screenName === screen.name) {
|
||||
currentWallpaper = WallpaperService.getWallpaper(screen.name)
|
||||
}
|
||||
}
|
||||
function onWallpaperDirectoryChanged(screenName, directory) {
|
||||
if (screen !== null && screenName === screen.name) {
|
||||
refreshWallpaperScreenData()
|
||||
}
|
||||
}
|
||||
function onWallpaperListChanged(screenName, count) {
|
||||
if (screen !== null && screenName === screen.name) {
|
||||
refreshWallpaperScreenData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function refreshWallpaperScreenData() {
|
||||
if (screen === null) {
|
||||
return
|
||||
}
|
||||
wallpapersList = WallpaperService.getWallpapersList(screen.name)
|
||||
currentWallpaper = WallpaperService.getWallpaper(screen.name)
|
||||
updateFiltered()
|
||||
}
|
||||
|
||||
function updateFiltered() {
|
||||
if (!filterText || filterText.trim().length === 0) {
|
||||
filteredWallpapers = wallpapersList
|
||||
return
|
||||
}
|
||||
// Build objects with basename for ranking
|
||||
const items = wallpapersList.map(function (p) {
|
||||
return {
|
||||
"path": p,
|
||||
"name": p.split('/').pop()
|
||||
}
|
||||
})
|
||||
const results = FuzzySort.go(filterText.trim(), items, {
|
||||
"key": 'name',
|
||||
"limit": 200
|
||||
})
|
||||
// Map back to path list
|
||||
filteredWallpapers = results.map(function (r) {
|
||||
return r.obj.path
|
||||
})
|
||||
}
|
||||
|
||||
color: Color.transparent
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginL * scaling
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Header
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "settings-wallpaper-selector"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Color.mPrimary
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Wallpaper Selector"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSurface
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "refresh"
|
||||
tooltipText: "Refresh wallpaper list."
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: WallpaperService.refreshWallpapersList()
|
||||
}
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: "Close."
|
||||
baseSize: Style.baseWidgetSize * 0.8
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Apply to all monitors"
|
||||
description: "Apply selected wallpaper to all monitors at once."
|
||||
checked: Settings.data.wallpaper.setWallpaperOnAllMonitors
|
||||
onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked
|
||||
visible: (wallpapersList.length > 0)
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
// Filter input
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
NText {
|
||||
text: "Search:"
|
||||
color: Color.mOnSurface
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
Layout.preferredWidth: implicitWidth
|
||||
}
|
||||
|
||||
NTextInput {
|
||||
id: searchInput
|
||||
placeholderText: "Type to filter wallpapers..."
|
||||
text: filterText
|
||||
onTextChanged: {
|
||||
filterText = text
|
||||
updateFiltered()
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Component.onCompleted: {
|
||||
if (searchInput.inputItem && searchInput.inputItem.visible) {
|
||||
searchInput.inputItem.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll container for wallpaper grid only
|
||||
Flickable {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
pressDelay: 200
|
||||
|
||||
NScrollView {
|
||||
id: scrollView
|
||||
anchors.fill: parent
|
||||
horizontalPolicy: ScrollBar.AlwaysOff
|
||||
verticalPolicy: ScrollBar.AsNeeded
|
||||
padding: Style.marginL * 0 * scaling
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
width: scrollView.availableWidth
|
||||
spacing: Style.marginM * scaling
|
||||
|
||||
// Grid container
|
||||
Item {
|
||||
visible: !WallpaperService.scanning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.ceil(filteredWallpapers.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight
|
||||
|
||||
GridView {
|
||||
id: wallpaperGridView
|
||||
anchors.fill: parent
|
||||
model: filteredWallpapers
|
||||
interactive: false
|
||||
|
||||
property int columns: 4
|
||||
property int itemSize: Math.floor((width - leftMargin - rightMargin - (columns * Style.marginS * scaling)) / columns)
|
||||
|
||||
cellWidth: Math.floor((width - leftMargin - rightMargin) / columns)
|
||||
cellHeight: Math.floor(itemSize * 0.7) + Style.marginXS * scaling + Style.fontSizeXS * scaling + Style.marginM * scaling
|
||||
|
||||
leftMargin: Style.marginS * scaling
|
||||
rightMargin: Style.marginS * scaling
|
||||
topMargin: Style.marginS * scaling
|
||||
bottomMargin: Style.marginS * scaling
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: wallpaperItem
|
||||
|
||||
property string wallpaperPath: modelData
|
||||
property bool isSelected: (wallpaperPath === currentWallpaper)
|
||||
property string filename: wallpaperPath.split('/').pop()
|
||||
|
||||
width: wallpaperGridView.itemSize
|
||||
spacing: Style.marginXS * scaling
|
||||
|
||||
Rectangle {
|
||||
id: imageContainer
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(wallpaperGridView.itemSize * 0.67)
|
||||
color: Color.transparent
|
||||
|
||||
NImageCached {
|
||||
id: img
|
||||
imagePath: wallpaperPath
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.transparent
|
||||
border.color: isSelected ? Color.mSecondary : Color.mSurface
|
||||
border.width: Math.max(1, Style.borderL * 1.5 * scaling)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Style.marginS * scaling
|
||||
width: 28 * scaling
|
||||
height: 28 * scaling
|
||||
radius: width / 2
|
||||
color: Color.mSecondary
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: isSelected
|
||||
|
||||
NIcon {
|
||||
icon: "check"
|
||||
font.pointSize: Style.fontSizeM * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
color: Color.mOnSecondary
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.mSurface
|
||||
opacity: (mouseArea.containsMouse || isSelected) ? 0 : 0.3
|
||||
radius: parent.radius
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
onPressed: {
|
||||
if (Settings.data.wallpaper.setWallpaperOnAllMonitors) {
|
||||
WallpaperService.changeWallpaper(wallpaperPath, undefined)
|
||||
} else {
|
||||
WallpaperService.changeWallpaper(wallpaperPath, Screen.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NText {
|
||||
text: filename
|
||||
color: Color.mOnSurfaceVariant
|
||||
opacity: 0.5
|
||||
font.pointSize: Style.fontSizeXS * scaling
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Style.marginS * scaling
|
||||
Layout.rightMargin: Style.marginS * scaling
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty / scanning state
|
||||
Rectangle {
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusM * scaling
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
visible: (filteredWallpapers.length === 0 && !WallpaperService.scanning) || WallpaperService.scanning
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 130 * scaling
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
visible: WallpaperService.scanning
|
||||
NBusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
visible: filteredWallpapers.length === 0 && !WallpaperService.scanning
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
NIcon {
|
||||
icon: "folder-open"
|
||||
font.pointSize: Style.fontSizeXXL * scaling
|
||||
color: Color.mOnSurface
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
NText {
|
||||
text: (filterText && filterText.length > 0) ? "No match found." : "No wallpaper found."
|
||||
color: Color.mOnSurface
|
||||
font.weight: Style.fontWeightBold
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
NText {
|
||||
text: (filterText && filterText.length > 0) ? "Try a different search query." : "Configure your wallpaper directory with images."
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
337
README.md
337
README.md
@@ -1,31 +1,29 @@
|
||||
<p align="center">
|
||||
<img src="https://assets.noctalia.dev/noctalia-logo.png" alt="Noctalia Logo" width="124" />
|
||||
</p>
|
||||
|
||||
# Noctalia
|
||||
|
||||
**_quiet by design_**
|
||||
|
||||
<p align="center">
|
||||
<img src="https://assets.noctalia.dev/noctalia-logo.png" alt="Noctalia Logo" width="124" />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/noctalia-dev/noctalia-shell/commits">
|
||||
<img src="https://img.shields.io/github/last-commit/noctalia-dev/noctalia-shell?style=for-the-badge&labelColor=0C0D11&color=A8AEFF" alt="Last commit" />
|
||||
<img src="https://img.shields.io/github/last-commit/noctalia-dev/noctalia-shell?style=for-the-badge&labelColor=0C0D11&color=A8AEFF&logo=git&logoColor=FFFFFF&label=commit" alt="Last commit" />
|
||||
</a>
|
||||
<a href="https://github.com/noctalia-dev/noctalia-shell/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/noctalia-dev/noctalia-shell?style=for-the-badge&labelColor=0C0D11&color=A8AEFF" alt="GitHub stars" />
|
||||
<img src="https://img.shields.io/github/stars/noctalia-dev/noctalia-shell?style=for-the-badge&labelColor=0C0D11&color=A8AEFF&logo=github&logoColor=FFFFFF" alt="GitHub stars" />
|
||||
</a>
|
||||
<a href="https://github.com/noctalia-dev/noctalia-shell/graphs/contributors">
|
||||
<img src="https://img.shields.io/github/contributors/noctalia-dev/noctalia-shell?style=for-the-badge&labelColor=0C0D11&color=A8AEFF" alt="GitHub contributors" />
|
||||
<a href="https://docs.noctalia.dev">
|
||||
<img src="https://img.shields.io/badge/docs-A8AEFF?style=for-the-badge&logo=gitbook&logoColor=FFFFFF&labelColor=0C0D11" alt="Documentation" />
|
||||
</a>
|
||||
<a href="https://discord.noctalia.dev">
|
||||
<img src="https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&labelColor=0C0D11&color=A8AEFF&logo=discord&logoColor=white" alt="Discord" />
|
||||
<img src="https://img.shields.io/badge/discord-A8AEFF?style=for-the-badge&labelColor=0C0D11&logo=discord&logoColor=FFFFFF" alt="Discord" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
A sleek and minimal desktop shell thoughtfully crafted for Wayland, built with Quickshell.
|
||||
|
||||
Features a modern modular architecture with a status bar, notification system, control panel, comprehensive system integration, and more — all styled with a warm lavender palette, or your favorite color scheme!
|
||||
A beautiful, minimal desktop shell for Wayland that actually gets out of your way. Built on Quickshell with a warm lavender aesthetic that you can easily customize to match your vibe.
|
||||
|
||||
## Preview
|
||||
|
||||
@@ -46,298 +44,34 @@ https://github.com/user-attachments/assets/72c6d6dc-48b0-48a0-bd8b-c7e70990edc4
|
||||
|
||||
---
|
||||
|
||||
> ⚠️ **Note:**
|
||||
> This shell currently supports **Niri** and **Hyprland** compositors. For other compositors, you will need to implement custom workspace logic in the CompositorService.
|
||||
## 🚀 Getting Started
|
||||
|
||||
**New to Noctalia?**
|
||||
Check out our documentation & installation guide to get started!
|
||||
|
||||
<a href="https://docs.noctalia.dev/getting-started/installation">
|
||||
<img src="https://img.shields.io/badge/⚡%20Quick%20Install-Get%20Started-A8AEFF?style=for-the-badge&logoColor=FFFFFF&labelColor=0C0D11" alt="Quick Install" />
|
||||
</a>
|
||||
|
||||
|
||||
**Need help?** Join our [Discord community](https://discord.noctalia.dev) or browse the [FAQ](https://docs.noctalia.dev/getting-started/faq/).
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
## 🖥️ Wayland Compositors
|
||||
|
||||
- **Status Bar:** Modular bar with workspace indicators, system monitors, clock, and quick access controls.
|
||||
- **Workspace Management:** Dynamic workspace switching with visual indicators and active window tracking.
|
||||
- **Notifications:** Rich notification system with history panel.
|
||||
- **Application Launcher:** Stylized launcher with favorites, recent apps, and special commands (calc, clipboard).
|
||||
- **Side Panel:** Quick access panel with media controls, weather, power profiles, and system utilities.
|
||||
- **Settings Panel:** Comprehensive configuration interface for all shell components and preferences.
|
||||
- **Lock Screen:** Secure lock experience with PAM authentication, time display, and animated background.
|
||||
- **Audio Integration:** Volume controls, media playback, and audio visualizer (cava-based).
|
||||
- **Connectivity:** WiFi and Bluetooth management with device pairing and network status.
|
||||
- **Power Management:** Battery monitoring, brightness control, power profile switching, power menu, and idle inhibition.
|
||||
- **System Monitoring:** CPU, memory, and network usage monitoring with visual indicators.
|
||||
- **Tray System:** Application tray with menu support and system integration.
|
||||
- **Background Management:** Wallpaper management with effects and dynamic theming support.
|
||||
- **Color Schemes:** Catppuccin, Dracula, Gruvbox, Noctalia, Nord, Rosépine, Solarized, Tokyo night or generated from your wallpaper.
|
||||
- **Scaling:** Per monitor scaling for maximum control.
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Required
|
||||
|
||||
- `quickshell-git` - Core shell framework
|
||||
- `ttf-roboto` - The default font used for most of the UI
|
||||
- `inter-font` - The default font used for Headers (ex: clock on the LockScreen)
|
||||
- `gpu-screen-recorder` - Screen recording functionality (Flatpak also supported)
|
||||
- `brightnessctl` - For internal/laptop monitor brightness
|
||||
- `ddcutil` - For desktop monitor brightness (might introduce some system instability with certain monitors)
|
||||
|
||||
|
||||
### Optional
|
||||
|
||||
- `cliphist` - For clipboard history support
|
||||
- `matugen` - Material You color scheme generation
|
||||
- `cava` - Audio visualizer component
|
||||
- `wlsunset` - To be able to use NightLight
|
||||
|
||||
> There is one more optional dependency.
|
||||
> `xdg-desktop-portal` to be able to use the "Portal" option from the screenRecorder.
|
||||
Noctalia provides native support for **Niri** and **Hyprland**. Other Wayland compositors will work but may require additional workspace logic configuration.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
## 🤝 Contributing
|
||||
|
||||
### Installation
|
||||
We welcome contributions of any size - bug fixes, new features, documentation improvements, or custom themes and configs.
|
||||
|
||||
#### Arch Linux
|
||||
|
||||
<details>
|
||||
<summary><strong>AUR</strong></summary>
|
||||
|
||||
You can install Noctalia from the [AUR](https://aur.archlinux.org/packages/noctalia-shell). This method will install the shell system-wide.
|
||||
|
||||
```bash
|
||||
paru -S noctalia-shell
|
||||
```
|
||||
|
||||
If you want the latest development version directly from the git repository, you can use the `noctalia-shell-git` package:
|
||||
|
||||
```bash
|
||||
paru -S noctalia-shell-git
|
||||
```
|
||||
This will always pull the most recent commit from the Noctalia repository. Note that it may be less stable than the release version.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Manual Installation</strong></summary>
|
||||
|
||||
This method installs the shell to your local user configuration.
|
||||
|
||||
Make sure you have Quickshell installed:
|
||||
```bash
|
||||
paru -S quickshell-git
|
||||
```
|
||||
|
||||
Download and install Noctalia (latest release):
|
||||
```bash
|
||||
mkdir -p ~/.config/quickshell/noctalia-shell && curl -sL https://github.com/noctalia-dev/noctalia-shell/releases/latest/download/noctalia-latest.tar.gz | tar -xz --strip-components=1 -C ~/.config/quickshell/noctalia-shell
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Nix
|
||||
|
||||
<details>
|
||||
<summary><strong>Nix Installation</strong></summary>
|
||||
|
||||
You can run Noctalia directly using the `nix run` command:
|
||||
```bash
|
||||
nix run github:noctalia-dev/noctalia-shell
|
||||
```
|
||||
|
||||
Alternatively, you can add it to your NixOS configuration or flake:
|
||||
|
||||
**Step 1**: Add Quickshell and Noctalia flakes to your `flake.nix`:
|
||||
```nix
|
||||
{
|
||||
description = "Example Nix flake with Noctalia + Quickshell";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
# you need nixpkgs unstable
|
||||
|
||||
quickshell = {
|
||||
url = "github:outfoxxed/quickshell";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
noctalia = {
|
||||
url = "github:noctalia-dev/noctalia-shell";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.quickshell.follows = "quickshell";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, noctalia, quickshell, ... }:
|
||||
{
|
||||
nixosConfigurations.my-host = nixpkgs.lib.nixosSystem {
|
||||
modules = [
|
||||
./configuration.nix
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2**: Add the packages to your `configuration.nix`:
|
||||
```nix
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
inputs.noctalia.packages.${system}.default
|
||||
inputs.quickshell.packages.${system}.default
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Usage
|
||||
Start the Shell with: `qs -c noctalia-shell`
|
||||
|
||||
`noctalia-shell` offers many IPC calls for your convenience, so you can add them to your favorite keybinds or scripts.
|
||||
|
||||
*If you're using the Flake installation on NixOS, replace `qs -c noctalia-shell` with `noctalia-shell`*
|
||||
|
||||
*If you're using the manual install (`git clone...` and have it in `~/.config/quickshell/`) you can just use `qs ipc call...`*
|
||||
|
||||
| Action | Command* |
|
||||
| --------------------------- | -------------------------------------------------------------- |
|
||||
| Start the Shell | `qs -c noctalia-shell` |
|
||||
| Toggle Application Launcher | `qs -c noctalia-shell ipc call launcher toggle` |
|
||||
| Toggle Side Panel | `qs -c noctalia-shell ipc call sidePanel toggle` |
|
||||
| Open Clipboard History | `qs -c noctalia-shell ipc call launcher clipboard` |
|
||||
| Open Calculator | `qs -c noctalia-shell ipc call launcher calculator` |
|
||||
| Increase Brightness | `qs -c noctalia-shell ipc call brightness increase` |
|
||||
| Decrease Brightness | `qs -c noctalia-shell ipc call brightness decrease` |
|
||||
| Increase Output Volume | `qs -c noctalia-shell ipc call volume increase` |
|
||||
| Decrease Output Volume | `qs -c noctalia-shell ipc call volume decrease` |
|
||||
| Toggle Mute Audio Output | `qs -c noctalia-shell ipc call volume muteOutput` |
|
||||
| Toggle Mute Audio Input | `qs -c noctalia-shell ipc call volume muteInput` |
|
||||
| Toggle Power Panel | `qs -c noctalia-shell ipc call powerPanel toggle` |
|
||||
| Toggle Idle Inhibitor | `qs -c noctalia-shell ipc call idleInhibitor toggle` |
|
||||
| Toggle Settings Window | `qs -c noctalia-shell ipc call settings toggle` |
|
||||
| Toggle Lock Screen | `qs -c noctalia-shell ipc call lockScreen toggle` |
|
||||
| Toggle Notification History | `qs -c noctalia-shell ipc call notifications toggleHistory` |
|
||||
| Toggle Notification DND | `qs -c noctalia-shell ipc call notifications toggleDND` |
|
||||
| Change Wallpaper | `qs -c noctalia-shell ipc call wallpaper set $path $monitor` |
|
||||
| Assign a Random Wallpaper | `qs -c noctalia-shell ipc call wallpaper random` |
|
||||
| Toggle Dark Mode | `qs -c noctalia-shell ipc call darkMode toggle` |
|
||||
| Set Dark Mode | `qs -c noctalia-shell ipc call darkMode setDark` |
|
||||
| Set Light Mode | `qs -c noctalia-shell ipc call darkMode setLight` |
|
||||
|
||||
### Configuration
|
||||
|
||||
Access settings through the side panel (top right button) to configure weather, wallpapers, screen recording, audio, network, and theme options.
|
||||
Configuration is usually stored in ~/.config/noctalia.
|
||||
|
||||
### Some of my app icons are missing!
|
||||
|
||||
The issue is most likely that you did not set up your environment variables properly.
|
||||
Example environment variables that you can use one of the following:
|
||||
|
||||
If you already have an icon theme set for GTK then you can use this one:
|
||||
- `QT_QPA_PLATFORMTHEME=gtk3`
|
||||
|
||||
You can also use Qt6ct to set your icon theme, for that you can use:
|
||||
- `QT_QPA_PLATFORMTHEME=qt6ct`
|
||||
|
||||
If you don't have either of those set then you can just use:
|
||||
- `QS_ICON_THEME="youricontheme"`
|
||||
|
||||
**Any of these environment variables should go into `/etc/environment` (you need to reboot afterwards). For NixOS you can use `environment.variables` or `home.sessionVariables`.**
|
||||
|
||||
|
||||
### Application Launcher
|
||||
|
||||
The launcher supports special commands for enhanced functionality:
|
||||
- `>calc` - Simple mathematical calculations
|
||||
- `>clip` - Clipboard history management
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary><strong>Theme Colors</strong></summary>
|
||||
|
||||
| Color Role | Color | Description |
|
||||
| -------------------- | ----------- | -------------------------- |
|
||||
| Primary | `#c7a1d8` | Soft lavender purple |
|
||||
| On Primary | `#1a151f` | Dark text on primary |
|
||||
| Secondary | `#a984c4` | Muted lavender |
|
||||
| On Secondary | `#f3edf7` | Light text on secondary |
|
||||
| Tertiary | `#e0b7c9` | Warm pink-lavender |
|
||||
| On Tertiary | `#20161f` | Dark text on tertiary |
|
||||
| Surface | `#1c1822` | Dark purple-tinted surface |
|
||||
| On Surface | `#e9e4f0` | Light text on surface |
|
||||
| Surface Variant | `#262130` | Elevated surface variant |
|
||||
| On Surface Variant | `#a79ab0` | Muted text on surface variant |
|
||||
| Error | `#e9899d` | Soft rose red |
|
||||
| On Error | `#1e1418` | Dark text on error |
|
||||
| Outline | `#4d445a` | Purple-tinted outline |
|
||||
| Shadow | `#120f18` | Deep purple-tinted shadow |
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Recommended Compositor Settings
|
||||
|
||||
For Niri:
|
||||
|
||||
```
|
||||
debug {
|
||||
honor-xdg-activation-with-invalid-serial
|
||||
}
|
||||
|
||||
window-rule {
|
||||
geometry-corner-radius 20
|
||||
clip-to-geometry true
|
||||
}
|
||||
|
||||
layer-rule {
|
||||
match namespace="^quickshell-overview$"
|
||||
place-within-backdrop true
|
||||
}
|
||||
```
|
||||
`honor-xdg-activation-with-invalid-serial` allows notification actions (like view etc) to work.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
Noctalia/
|
||||
├── shell.qml # Main shell entry point
|
||||
├── Modules/ # UI components
|
||||
│ ├── Bar/ # Status bar components
|
||||
│ ├── Dock/ # Application launcher
|
||||
│ ├── SidePanel/ # Quick access panel
|
||||
│ ├── SettingsPanel/ # Configuration interface
|
||||
│ └── ...
|
||||
├── Services/ # Backend services
|
||||
│ ├── CompositorService.qml
|
||||
│ ├── WorkspacesService.qml
|
||||
│ ├── AudioService.qml
|
||||
│ └── ...
|
||||
├── Widgets/ # Reusable UI components
|
||||
├── Commons/ # Shared utilities
|
||||
├── Assets/ # Static assets
|
||||
└── Bin/ # Utility scripts
|
||||
```
|
||||
|
||||
### Contributing
|
||||
|
||||
1. Follow the existing code style and patterns
|
||||
2. Use the modular architecture for new features
|
||||
3. Implement proper error handling and logging
|
||||
4. Test with both Hyprland and Niri compositors (if applicable)
|
||||
|
||||
Contributions are welcome! Don't worry about being perfect - every contribution helps! Whether it's fixing a small bug, adding a new feature, or improving documentation, we welcome all contributions. Feel free to open an issue to discuss ideas or ask questions before diving in. For feature requests and ideas, you can also use our discussions page.
|
||||
**Get involved:**
|
||||
- **Found a bug?** [Open an issue](https://github.com/noctalia-dev/noctalia-shell/issues/new)
|
||||
- **Want to code?** Check out our [development guidelines](https://docs.noctalia.dev/development/guideline)
|
||||
- **Need help?** Join our [Discord](https://discord.noctalia.dev)
|
||||
|
||||
---
|
||||
|
||||
@@ -347,13 +81,13 @@ A heartfelt thank you to our incredible community of [**contributors**](https://
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgment
|
||||
## 🙏 Acknowledgment
|
||||
|
||||
Special thanks to the creators of [**Caelestia**](https://github.com/caelestia-dots/shell) and [**DankMaterialShell**](https://github.com/AvengeMedia/DankMaterialShell) for their inspirational designs and clever implementation techniques.
|
||||
|
||||
---
|
||||
|
||||
#### Donation
|
||||
## ☕ Donation
|
||||
|
||||
While all donations are greatly appreciated, they are completely voluntary.
|
||||
|
||||
@@ -361,13 +95,14 @@ While all donations are greatly appreciated, they are completely voluntary.
|
||||
<img src="https://img.shields.io/badge/donate-ko--fi-A8AEFF?style=for-the-badge&logo=kofi&logoColor=FFFFFF&labelColor=0C0D11" alt="Ko-Fi" />
|
||||
</a>
|
||||
|
||||
#### Thank you to everyone who supports the project 💜!
|
||||
### Thank you to everyone who supports the project 💜!
|
||||
* Gohma
|
||||
* <a href="https://pika-os.com/" target="_blank">PikaOS</a>
|
||||
* DiscoCevapi
|
||||
* <a href="https://pika-os.com/" target="_blank">PikaOS</a>
|
||||
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the terms of the [MIT License](./LICENSE).
|
||||
MIT License - see [LICENSE](./LICENSE) for details.
|
||||
|
||||
171
Services/BarService.qml
Normal file
171
Services/BarService.qml
Normal file
@@ -0,0 +1,171 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Registry to store actual widget instances
|
||||
// Key format: "screenName|section|widgetId|index"
|
||||
property var widgetInstances: ({})
|
||||
// Register a widget instance
|
||||
function registerWidget(screenName, section, widgetId, index, instance) {
|
||||
const key = [screenName, section, widgetId, index].join("|")
|
||||
widgetInstances[key] = {
|
||||
"key": key,
|
||||
"screenName": screenName,
|
||||
"section": section,
|
||||
"widgetId": widgetId,
|
||||
"index": index,
|
||||
"instance": instance
|
||||
}
|
||||
Logger.log("BarService", "Registered widget:", key)
|
||||
}
|
||||
|
||||
// Unregister a widget instance
|
||||
function unregisterWidget(screenName, section, widgetId, index) {
|
||||
const key = [screenName, section, widgetId, index].join("|")
|
||||
delete widgetInstances[key]
|
||||
Logger.log("BarService", "Unregistered widget:", key)
|
||||
}
|
||||
|
||||
// Lookup a specific widget instance (returns the actual QML instance)
|
||||
function lookupWidget(widgetId, screenName = null, section = null) {
|
||||
// If looking for a specific instance
|
||||
if (screenName && section !== null) {
|
||||
for (var key in widgetInstances) {
|
||||
var widget = widgetInstances[key]
|
||||
if (widget.widgetId === widgetId && widget.screenName === screenName && widget.section === section) {
|
||||
return widget.instance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return first match if no specific screen/section specified
|
||||
for (var key in widgetInstances) {
|
||||
var widget = widgetInstances[key]
|
||||
if (widget.widgetId === widgetId) {
|
||||
if (!screenName || widget.screenName === screenName) {
|
||||
if (section === null || widget.section === section) {
|
||||
return widget.instance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Get all instances of a widget type
|
||||
function getAllWidgetInstances(widgetId = null, screenName = null, section = null) {
|
||||
var instances = []
|
||||
|
||||
for (var key in widgetInstances) {
|
||||
var widget = widgetInstances[key]
|
||||
|
||||
var matches = true
|
||||
if (widgetId && widget.widgetId !== widgetId)
|
||||
matches = false
|
||||
if (screenName && widget.screenName !== screenName)
|
||||
matches = false
|
||||
if (section !== null && widget.section !== section)
|
||||
matches = false
|
||||
|
||||
if (matches) {
|
||||
instances.push(widget.instance)
|
||||
}
|
||||
}
|
||||
|
||||
return instances
|
||||
}
|
||||
|
||||
// Get widget with full metadata
|
||||
function getWidgetWithMetadata(widgetId, screenName = null, section = null) {
|
||||
for (var key in widgetInstances) {
|
||||
var widget = widgetInstances[key]
|
||||
if (widget.widgetId === widgetId) {
|
||||
if (!screenName || widget.screenName === screenName) {
|
||||
if (section === null || widget.section === section) {
|
||||
return widget
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Get all widgets in a specific section
|
||||
function getWidgetsBySection(section, screenName = null) {
|
||||
var widgets = []
|
||||
|
||||
for (var key in widgetInstances) {
|
||||
var widget = widgetInstances[key]
|
||||
if (widget.section === section) {
|
||||
if (!screenName || widget.screenName === screenName) {
|
||||
widgets.push(widget.instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by index to maintain order
|
||||
widgets.sort(function (a, b) {
|
||||
var aWidget = getWidgetWithMetadata(a.widgetId, a.screen?.name, a.section)
|
||||
var bWidget = getWidgetWithMetadata(b.widgetId, b.screen?.name, b.section)
|
||||
return (aWidget?.index || 0) - (bWidget?.index || 0)
|
||||
})
|
||||
|
||||
return widgets
|
||||
}
|
||||
|
||||
// Get all registered widgets (for debugging)
|
||||
function getAllRegisteredWidgets() {
|
||||
var result = []
|
||||
for (var key in widgetInstances) {
|
||||
result.push({
|
||||
"key": key,
|
||||
"widgetId": widgetInstances[key].widgetId,
|
||||
"section": widgetInstances[key].section,
|
||||
"screenName": widgetInstances[key].screenName,
|
||||
"index": widgetInstances[key].index
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Check if a widget type exists in a section
|
||||
function hasWidget(widgetId, section = null, screenName = null) {
|
||||
for (var key in widgetInstances) {
|
||||
var widget = widgetInstances[key]
|
||||
if (widget.widgetId === widgetId) {
|
||||
if (section === null || widget.section === section) {
|
||||
if (!screenName || widget.screenName === screenName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get pill direction for a widget instance
|
||||
function getPillDirection(widgetInstance) {
|
||||
try {
|
||||
if (widgetInstance.section === "left") {
|
||||
return true
|
||||
} else if (widgetInstance.section === "right") {
|
||||
return false
|
||||
} else {
|
||||
// middle section
|
||||
if (widgetInstance.sectionWidgetIndex < widgetInstance.sectionWidgetsCount / 2) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error(e)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -52,9 +52,7 @@ Singleton {
|
||||
},
|
||||
"Clock": {
|
||||
"allowUserSettings": true,
|
||||
"displayFormat": "time-date-short",
|
||||
"use12HourClock": false,
|
||||
"reverseDayMonth": true
|
||||
"displayFormat": "time-date-short"
|
||||
},
|
||||
"CustomButton": {
|
||||
"allowUserSettings": true,
|
||||
@@ -206,24 +204,4 @@ Singleton {
|
||||
function widgetHasUserSettings(id) {
|
||||
return (widgetMetadata[id] !== undefined) && (widgetMetadata[id].allowUserSettings === true)
|
||||
}
|
||||
|
||||
function getPillDirection(widget) {
|
||||
try {
|
||||
if (widget.section === "left") {
|
||||
return true
|
||||
} else if (widget.section === "right") {
|
||||
return false
|
||||
} else {
|
||||
// middle section
|
||||
if (widget.sectionWidgetIndex < widget.sectionWidgetsCount / 2) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.error(e)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ Singleton {
|
||||
id: root
|
||||
|
||||
readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter
|
||||
readonly property bool available: adapter !== null
|
||||
readonly property bool enabled: (adapter && adapter.enabled) ?? false
|
||||
readonly property bool available: (adapter !== null)
|
||||
readonly property bool discovering: (adapter && adapter.discovering) ?? false
|
||||
readonly property var devices: adapter ? adapter.devices : null
|
||||
readonly property var pairedDevices: {
|
||||
@@ -30,37 +29,64 @@ Singleton {
|
||||
})
|
||||
}
|
||||
|
||||
property bool lastAdapterState: false
|
||||
|
||||
function init() {
|
||||
Logger.log("Bluetooth", "Service initialized")
|
||||
delaySyncState.running = true
|
||||
syncStateTimer.running = true
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: delaySyncState
|
||||
id: syncStateTimer
|
||||
interval: 1000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
Settings.data.network.bluetoothEnabled = adapter.enabled
|
||||
lastAdapterState = Settings.data.network.bluetoothEnabled = adapter.enabled
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: delayDiscovery
|
||||
id: discoveryTimer
|
||||
interval: 1000
|
||||
repeat: false
|
||||
onTriggered: adapter.discovering = true
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: stateDebounceTimer
|
||||
interval: 200
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (!adapter) {
|
||||
Logger.warn("Bluetooth", "State debouncer", "No adapter available")
|
||||
return
|
||||
}
|
||||
if (lastAdapterState === adapter.enabled) {
|
||||
return
|
||||
}
|
||||
lastAdapterState = adapter.enabled
|
||||
if (adapter.enabled) {
|
||||
ToastService.showNotice("Bluetooth", "Enabled")
|
||||
} else {
|
||||
ToastService.showNotice("Bluetooth", "Disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: adapter
|
||||
function onEnabledChanged() {
|
||||
if (!adapter) {
|
||||
Logger.warn("Bluetooth", "onEnabledChanged", "No adapter available")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.log("Bluetooth", "onEnableChanged", adapter.enabled)
|
||||
Settings.data.network.bluetoothEnabled = adapter.enabled
|
||||
stateDebounceTimer.restart()
|
||||
if (adapter.enabled) {
|
||||
ToastService.showNotice("Bluetooth", "Enabled")
|
||||
// Using a timer to give a little time so the adapter is really enabled
|
||||
delayDiscovery.running = true
|
||||
} else {
|
||||
ToastService.showNotice("Bluetooth", "Disabled")
|
||||
discoveryTimer.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,12 +257,13 @@ Singleton {
|
||||
device.forget()
|
||||
}
|
||||
|
||||
function setBluetoothEnabled(enabled) {
|
||||
function setBluetoothEnabled(state) {
|
||||
if (!adapter) {
|
||||
Logger.warn("Bluetooth", "No adapter available")
|
||||
return
|
||||
}
|
||||
|
||||
adapter.enabled = enabled
|
||||
Logger.log("Bluetooth", "SetBluetoothEnabled", state)
|
||||
adapter.enabled = state
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,9 +197,11 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real stepSize: Settings.data.brightness.brightnessStep / 100.0
|
||||
|
||||
// Timer for debouncing rapid changes
|
||||
readonly property Timer timer: Timer {
|
||||
interval: 200
|
||||
interval: 100
|
||||
onTriggered: {
|
||||
if (!isNaN(monitor.queuedBrightness)) {
|
||||
monitor.setBrightness(monitor.queuedBrightness)
|
||||
@@ -208,14 +210,19 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function setBrightnessDebounced(value: real): void {
|
||||
monitor.queuedBrightness = value
|
||||
timer.start()
|
||||
}
|
||||
|
||||
function increaseBrightness(): void {
|
||||
var stepSize = Settings.data.brightness.brightnessStep / 100.0
|
||||
setBrightnessDebounced(monitor.brightness + stepSize)
|
||||
const value = !isNaN(monitor.queuedBrightness) ? monitor.queuedBrightness : monitor.brightness
|
||||
setBrightnessDebounced(value + stepSize)
|
||||
}
|
||||
|
||||
function decreaseBrightness(): void {
|
||||
var stepSize = Settings.data.brightness.brightnessStep / 100.0
|
||||
setBrightnessDebounced(monitor.brightness - stepSize)
|
||||
const value = !isNaN(monitor.queuedBrightness) ? monitor.queuedBrightness : monitor.brightness
|
||||
setBrightnessDebounced(value - stepSize)
|
||||
}
|
||||
|
||||
function setBrightness(value: real): void {
|
||||
@@ -225,7 +232,7 @@ Singleton {
|
||||
if (Math.round(monitor.brightness * 100) === rounded)
|
||||
return
|
||||
|
||||
if (isDdc && timer.running) {
|
||||
if (timer.running) {
|
||||
monitor.queuedBrightness = value
|
||||
return
|
||||
}
|
||||
@@ -247,11 +254,6 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function setBrightnessDebounced(value: real): void {
|
||||
monitor.queuedBrightness = value
|
||||
timer.restart()
|
||||
}
|
||||
|
||||
function initBrightness(): void {
|
||||
if (isAppleDisplay) {
|
||||
initProc.command = ["asdbctl", "get"]
|
||||
|
||||
@@ -163,13 +163,13 @@ Singleton {
|
||||
if (activeInhibitors.includes("manual")) {
|
||||
removeInhibitor("manual")
|
||||
Settings.data.ui.idleInhibitorEnabled = false
|
||||
ToastService.showNotice("Keep Awake", "Disabled", false, 3000)
|
||||
ToastService.showNotice("Keep Awake", "Disabled")
|
||||
Logger.log("IdleInhibitor", "Manual inhibition disabled and saved to settings")
|
||||
return false
|
||||
} else {
|
||||
addInhibitor("manual", "Manually activated by user")
|
||||
Settings.data.ui.idleInhibitorEnabled = true
|
||||
ToastService.showNotice("Keep Awake", "Enabled", false, 3000)
|
||||
ToastService.showNotice("Keep Awake", "Enabled")
|
||||
Logger.log("IdleInhibitor", "Manual inhibition enabled and saved to settings")
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -47,6 +47,11 @@ Singleton {
|
||||
|
||||
// Generate colors using current wallpaper and settings
|
||||
function generateFromWallpaper() {
|
||||
if (!Settings.isLoaded) {
|
||||
Logger.log("Matugen", "Settings not loaded yet, skipping wallpaper color generation")
|
||||
return
|
||||
}
|
||||
|
||||
Logger.log("Matugen", "Generating from wallpaper on screen:", Screen.name)
|
||||
var wp = WallpaperService.getWallpaper(Screen.name).replace(/'/g, "'\\''")
|
||||
if (wp === "") {
|
||||
|
||||
@@ -13,17 +13,40 @@ Singleton {
|
||||
readonly property bool available: powerProfiles && powerProfiles.hasPerformanceProfile
|
||||
property int profile: powerProfiles ? powerProfiles.profile : PowerProfile.Balanced
|
||||
|
||||
function profileName(p) {
|
||||
const prof = (p !== undefined) ? p : profile
|
||||
function getName(p) {
|
||||
if (!available)
|
||||
return "Unknown"
|
||||
if (prof === PowerProfile.Performance)
|
||||
|
||||
const prof = (p !== undefined) ? p : profile
|
||||
|
||||
switch (prof) {
|
||||
case PowerProfile.Performance:
|
||||
return "Performance"
|
||||
if (prof === PowerProfile.Balanced)
|
||||
case PowerProfile.Balanced:
|
||||
return "Balanced"
|
||||
if (prof === PowerProfile.PowerSaver)
|
||||
case PowerProfile.PowerSaver:
|
||||
return "Power Saver"
|
||||
return "Unknown"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
function getIcon(p) {
|
||||
if (!available)
|
||||
return "balanced"
|
||||
|
||||
const prof = (p !== undefined) ? p : profile
|
||||
|
||||
switch (prof) {
|
||||
case PowerProfile.Performance:
|
||||
return "performance"
|
||||
case PowerProfile.Balanced:
|
||||
return "balanced"
|
||||
case PowerProfile.PowerSaver:
|
||||
return "powersaver"
|
||||
default:
|
||||
return "balanced"
|
||||
}
|
||||
}
|
||||
|
||||
function setProfile(p) {
|
||||
@@ -53,9 +76,9 @@ Singleton {
|
||||
function onProfileChanged() {
|
||||
root.profile = powerProfiles.profile
|
||||
// Only show toast if we have a valid profile name (not "Unknown")
|
||||
const profileName = root.profileName()
|
||||
const profileName = root.getName()
|
||||
if (profileName !== "Unknown") {
|
||||
ToastService.showNotice("Power Profile", profileName)
|
||||
ToastService.showNotice("Power Profile Changed", `"${profileName}"`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ Singleton {
|
||||
id: root
|
||||
|
||||
// Public properties
|
||||
property string baseVersion: "2.10.0"
|
||||
property bool isDevelopment: false
|
||||
property string baseVersion: "2.12.0"
|
||||
property bool isDevelopment: true
|
||||
|
||||
property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}`
|
||||
|
||||
|
||||
@@ -9,6 +9,20 @@ import qs.Commons
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// Public init to rehydrate cache after Settings load
|
||||
function init() {
|
||||
// Rebuild cache from persisted settings
|
||||
var monitors = Settings.data.wallpaper.monitors || []
|
||||
currentWallpapers = ({})
|
||||
for (var i = 0; i < monitors.length; i++) {
|
||||
if (monitors[i].name && monitors[i].wallpaper) {
|
||||
currentWallpapers[monitors[i].name] = monitors[i].wallpaper
|
||||
// Notify listeners so Background updates immediately after settings load
|
||||
root.wallpaperChanged(monitors[i].name, monitors[i].wallpaper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.log("Wallpaper", "Service started")
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ ColumnLayout {
|
||||
text: root.label
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
font.capitalization: Font.Capitalize
|
||||
color: Color.mSecondary
|
||||
visible: root.title !== ""
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ ColumnLayout {
|
||||
text: label
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
font.weight: Style.fontWeightBold
|
||||
font.capitalization: Font.Capitalize
|
||||
color: labelColor
|
||||
visible: label !== ""
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -16,6 +16,7 @@ Loader {
|
||||
property real preferredWidthRatio
|
||||
property real preferredHeightRatio
|
||||
property color panelBackgroundColor: Color.mSurface
|
||||
property bool draggable: false
|
||||
|
||||
property bool panelAnchorHorizontalCenter: false
|
||||
property bool panelAnchorVerticalCenter: false
|
||||
@@ -38,6 +39,7 @@ Loader {
|
||||
readonly property real originalOpacity: 0.0
|
||||
property real scaleValue: originalScale
|
||||
property real opacityValue: originalOpacity
|
||||
property real dimmingOpacity: 0
|
||||
|
||||
property alias isClosing: hideTimer.running
|
||||
|
||||
@@ -108,6 +110,7 @@ Loader {
|
||||
|
||||
// -----------------------------------------
|
||||
function close() {
|
||||
dimmingOpacity = 0
|
||||
scaleValue = originalScale
|
||||
opacityValue = originalOpacity
|
||||
hideTimer.start()
|
||||
@@ -149,6 +152,7 @@ Loader {
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.log("NPanel", "Opened", root.objectName)
|
||||
dimmingOpacity = Style.opacityHeavy
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -174,17 +178,14 @@ Loader {
|
||||
|
||||
visible: true
|
||||
|
||||
// No dimming here
|
||||
color: Color.transparent
|
||||
|
||||
WlrLayershell.layer: Settings.data.general.dimDesktop ? WlrLayer.Overlay : WlrLayer.Top
|
||||
color: Settings.data.general.dimDesktop ? Qt.alpha(Color.mShadow, dimmingOpacity) : Color.transparent
|
||||
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.namespace: "noctalia-panel"
|
||||
WlrLayershell.keyboardFocus: root.panelKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Style.animationNormal
|
||||
duration: Style.animationSlow
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,12 +209,18 @@ Loader {
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
// The actual panel's content
|
||||
Rectangle {
|
||||
id: panelBackground
|
||||
color: panelBackgroundColor
|
||||
radius: Style.radiusL * scaling
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
// Dragging support
|
||||
property bool draggable: root.draggable
|
||||
property bool isDragged: false
|
||||
property real manualX: 0
|
||||
property real manualY: 0
|
||||
width: {
|
||||
var w
|
||||
if (preferredWidthRatio !== undefined) {
|
||||
@@ -238,8 +245,8 @@ Loader {
|
||||
|
||||
scale: root.scaleValue
|
||||
opacity: root.opacityValue
|
||||
x: calculatedX
|
||||
y: calculatedY
|
||||
x: isDragged ? manualX : calculatedX
|
||||
y: isDragged ? manualY : calculatedY
|
||||
|
||||
// ---------------------------------------------
|
||||
// Does not account for corners are they are negligible and helps keep the code clean.
|
||||
@@ -372,6 +379,14 @@ Loader {
|
||||
root.opacityValue = 1.0
|
||||
}
|
||||
|
||||
// Reset drag position when panel closes
|
||||
Connections {
|
||||
target: root
|
||||
function onClosed() {
|
||||
panelBackground.isDragged = false
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent closing when clicking in the panel bg
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
@@ -397,6 +412,79 @@ Loader {
|
||||
anchors.fill: parent
|
||||
sourceComponent: root.panelContent
|
||||
}
|
||||
|
||||
// Handle drag move on the whole panel area
|
||||
DragHandler {
|
||||
id: dragHandler
|
||||
target: null
|
||||
enabled: panelBackground.draggable
|
||||
property real dragStartX: 0
|
||||
property real dragStartY: 0
|
||||
onActiveChanged: {
|
||||
if (active) {
|
||||
// Capture current position into manual coordinates BEFORE toggling isDragged
|
||||
panelBackground.manualX = panelBackground.x
|
||||
panelBackground.manualY = panelBackground.y
|
||||
dragStartX = panelBackground.x
|
||||
dragStartY = panelBackground.y
|
||||
panelBackground.isDragged = true
|
||||
if (root.enableBackgroundClick)
|
||||
root.disableBackgroundClick()
|
||||
} else {
|
||||
// Keep isDragged true so we continue using the manual x/y after release
|
||||
if (root.enableBackgroundClick)
|
||||
root.enableBackgroundClick()
|
||||
}
|
||||
}
|
||||
onTranslationChanged: {
|
||||
// Proposed new coordinates from fixed drag origin
|
||||
var nx = dragStartX + translation.x
|
||||
var ny = dragStartY + translation.y
|
||||
|
||||
// Calculate gaps so we never overlap the bar on any side
|
||||
var baseGap = Style.marginS * scaling
|
||||
var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL * scaling : 0
|
||||
var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL * scaling : 0
|
||||
|
||||
var insetLeft = baseGap + ((barIsVisible && barPosition === "left") ? (Style.barHeight * scaling + floatExtraH) : 0)
|
||||
var insetRight = baseGap + ((barIsVisible && barPosition === "right") ? (Style.barHeight * scaling + floatExtraH) : 0)
|
||||
var insetTop = baseGap + ((barIsVisible && barPosition === "top") ? (Style.barHeight * scaling + floatExtraV) : 0)
|
||||
var insetBottom = baseGap + ((barIsVisible && barPosition === "bottom") ? (Style.barHeight * scaling + floatExtraV) : 0)
|
||||
|
||||
// Clamp within screen bounds accounting for insets
|
||||
var maxX = panelWindow.width - panelBackground.width - insetRight
|
||||
var minX = insetLeft
|
||||
var maxY = panelWindow.height - panelBackground.height - insetBottom
|
||||
var minY = insetTop
|
||||
|
||||
panelBackground.manualX = Math.round(Math.max(minX, Math.min(nx, maxX)))
|
||||
panelBackground.manualY = Math.round(Math.max(minY, Math.min(ny, maxY)))
|
||||
}
|
||||
}
|
||||
|
||||
// Drag indicator border
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
color: Color.transparent
|
||||
border.color: Color.mPrimary
|
||||
border.width: Math.max(2, Style.borderL * scaling)
|
||||
radius: parent.radius
|
||||
visible: panelBackground.isDragged && dragHandler.active
|
||||
opacity: 0.8
|
||||
z: 3000
|
||||
|
||||
// Subtle glow effect
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 0
|
||||
color: Color.transparent
|
||||
border.color: Color.mPrimary
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
radius: parent.radius
|
||||
opacity: 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
flake.nix
12
flake.nix
@@ -40,6 +40,7 @@
|
||||
libnotify
|
||||
matugen
|
||||
networkmanager
|
||||
wlsunset
|
||||
wl-clipboard
|
||||
] ++ lib.optionals (pkgs.stdenv.hostPlatform.isx86_64)
|
||||
[ gpu-screen-recorder ];
|
||||
@@ -79,6 +80,17 @@
|
||||
});
|
||||
|
||||
defaultPackage = eachSystem (system: self.packages.${system}.default);
|
||||
|
||||
nixosModules = {
|
||||
noctalia = { pkgs, lib, ... }: {
|
||||
environment.systemPackages = [
|
||||
self.packages.${pkgs.system}.default
|
||||
];
|
||||
# Required services for proper functionality
|
||||
services.power-profiles-daemon.enable = true; # Power profile switching support
|
||||
services.upower.enable = true; # Battery monitoring & power management
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import qs.Modules.PowerPanel
|
||||
import qs.Modules.SidePanel
|
||||
import qs.Modules.Toast
|
||||
import qs.Modules.WiFiPanel
|
||||
import qs.Modules.WallpaperSelector
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
@@ -37,7 +38,6 @@ ShellRoot {
|
||||
Background {}
|
||||
Overview {}
|
||||
ScreenCorners {}
|
||||
Dimmer {}
|
||||
Bar {}
|
||||
Dock {}
|
||||
|
||||
@@ -95,6 +95,11 @@ ShellRoot {
|
||||
objectName: "bluetoothPanel"
|
||||
}
|
||||
|
||||
WallpaperSelector {
|
||||
id: wallpaperSelector
|
||||
objectName: "wallpaperSelector"
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Save a ref. to our lockScreen so we can access it easily
|
||||
PanelService.lockScreen = lockScreen
|
||||
|
||||
Reference in New Issue
Block a user