From 7b9ecd048dee39d86eca059bab07915527de6c24 Mon Sep 17 00:00:00 2001 From: Kevin Diaz Date: Fri, 26 Sep 2025 19:18:16 -0400 Subject: [PATCH 01/14] CI: add GitHub Actions workflow to automate AUR package updates on release --- .github/workflows/update-aur-package.yml | 108 +++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 .github/workflows/update-aur-package.yml diff --git a/.github/workflows/update-aur-package.yml b/.github/workflows/update-aur-package.yml new file mode 100644 index 00000000..e221d3b6 --- /dev/null +++ b/.github/workflows/update-aur-package.yml @@ -0,0 +1,108 @@ +name: Update AUR Package + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + aur-sync: + name: Sync PKGBUILD with release + runs-on: ubuntu-latest + container: + image: archlinux:latest + defaults: + run: + shell: bash + env: + AUR_REPO: ssh://aur@aur.archlinux.org/noctalia-shell.git + GIT_SSH_COMMAND: ssh -i /root/.ssh/id_aur -o StrictHostKeyChecking=yes -o IdentitiesOnly=yes + PKGNAME: noctalia-shell + AUR_LINK: https://aur.archlinux.org/packages/noctalia-shell + + steps: + - name: Install dependencies + run: | + set -euo pipefail + pacman -Syu --noconfirm git base-devel pacman-contrib openssh + + - name: Create build user + run: | + set -euo pipefail + useradd -m builduser + echo 'builduser ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers + + - name: Configure SSH + env: + AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + run: | + set -euo pipefail + mkdir -p /root/.ssh + chmod 700 /root/.ssh + printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > /root/.ssh/id_aur + chmod 600 /root/.ssh/id_aur + ssh-keyscan aur.archlinux.org >> /root/.ssh/known_hosts + chmod 600 /root/.ssh/known_hosts + + - name: Determine version + id: vars + env: + TAG_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + PKGVER="${TAG_NAME#v}" + echo "pkgver=$PKGVER" >> "$GITHUB_OUTPUT" + + - name: Clone AUR repository + run: | + set -euo pipefail + git clone "$AUR_REPO" "$GITHUB_WORKSPACE/aur" + + - name: Update PKGBUILD + env: + PKGVER: ${{ steps.vars.outputs.pkgver }} + run: | + set -euo pipefail + cd "$GITHUB_WORKSPACE/aur" + sed -i "s/^pkgver=.*/pkgver=${PKGVER}/" PKGBUILD + sed -i "s/^pkgrel=.*/pkgrel=1/" PKGBUILD + + - name: Refresh checksums and metadata + run: | + set -euo pipefail + AUR_DIR="$GITHUB_WORKSPACE/aur" + chown -R builduser:builduser "$AUR_DIR" + su - builduser -c "cd $AUR_DIR && updpkgsums" + su - builduser -c "cd $AUR_DIR && makepkg --printsrcinfo > .SRCINFO" + + - name: Commit and push changes + env: + PKGVER: ${{ steps.vars.outputs.pkgver }} + run: | + set -euo pipefail + cd "$GITHUB_WORKSPACE/aur" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + if [[ -n "$(git status --porcelain)" ]]; then + git add PKGBUILD .SRCINFO + git commit -m "chore(package): release ${PKGVER}" + git push origin HEAD + else + echo "No updates necessary." + fi + + - name: Summarize update + env: + PKGNAME: noctalia-shell + PKGVER: ${{ steps.vars.outputs.pkgver }} + AUR_LINK: https://aur.archlinux.org/packages/noctalia-shell + run: | + set -euo pipefail + { + echo "## AUR Update" + echo "" + echo "- Package: ${PKGNAME}" + echo "- Updated version: ${PKGVER}" + echo "- AUR page: ${AUR_LINK}" + } >> "$GITHUB_STEP_SUMMARY" \ No newline at end of file From b7c99905f37ce4336519ee238a5e42388b93574b Mon Sep 17 00:00:00 2001 From: Kevin Diaz Date: Fri, 26 Sep 2025 20:00:56 -0400 Subject: [PATCH 02/14] fix(ci): not in a git directory --- .github/workflows/update-aur-package.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update-aur-package.yml b/.github/workflows/update-aur-package.yml index e221d3b6..7978e188 100644 --- a/.github/workflows/update-aur-package.yml +++ b/.github/workflows/update-aur-package.yml @@ -62,16 +62,17 @@ jobs: - name: Update PKGBUILD env: PKGVER: ${{ steps.vars.outputs.pkgver }} + working-directory: ${{ github.workspace }}/aur run: | set -euo pipefail - cd "$GITHUB_WORKSPACE/aur" sed -i "s/^pkgver=.*/pkgver=${PKGVER}/" PKGBUILD sed -i "s/^pkgrel=.*/pkgrel=1/" PKGBUILD - name: Refresh checksums and metadata + env: + AUR_DIR: ${{ github.workspace }}/aur run: | set -euo pipefail - AUR_DIR="$GITHUB_WORKSPACE/aur" chown -R builduser:builduser "$AUR_DIR" su - builduser -c "cd $AUR_DIR && updpkgsums" su - builduser -c "cd $AUR_DIR && makepkg --printsrcinfo > .SRCINFO" @@ -79,9 +80,10 @@ jobs: - name: Commit and push changes env: PKGVER: ${{ steps.vars.outputs.pkgver }} + working-directory: ${{ github.workspace }}/aur run: | set -euo pipefail - cd "$GITHUB_WORKSPACE/aur" + git config --global --add safe.directory "$PWD" git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" if [[ -n "$(git status --porcelain)" ]]; then From 96d3051151c7fb17cde87208af97565561654019 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 26 Sep 2025 23:18:35 -0400 Subject: [PATCH 03/14] Update service --- Services/UpdateService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index 6918614e..977cc142 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -8,7 +8,7 @@ Singleton { id: root // Public properties - property string baseVersion: "2.14.1" + property string baseVersion: "2.14.2" property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From f8ee0bb8dfe392c189736d6c831fa6d7e6f4b09a Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 26 Sep 2025 23:18:59 -0400 Subject: [PATCH 04/14] FilePicker: debugging and improvements. --- Commons/TablerIcons.qml | 3 +- .../WidgetSettings/ControlCenterSettings.qml | 3 +- Modules/Settings/Tabs/GeneralTab.qml | 3 +- Modules/Settings/Tabs/ScreenRecorderTab.qml | 3 +- Modules/Settings/Tabs/WallpaperTab.qml | 6 +- Widgets/NFilePicker.qml | 120 +++++++++++------- 6 files changed, 83 insertions(+), 55 deletions(-) diff --git a/Commons/TablerIcons.qml b/Commons/TablerIcons.qml index 2044503a..13b60e33 100644 --- a/Commons/TablerIcons.qml +++ b/Commons/TablerIcons.qml @@ -148,7 +148,8 @@ Singleton { "filepicker-file": "file", "filepicker-text": "file-text", "filepicker-eye": "eye", - "filepicker-eye-off": "eye-off" + "filepicker-eye-off": "eye-off", + "filepicker-folder-current": "checks" } // Fonts Codepoints - do not change! diff --git a/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml b/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml index 75aacb97..78b54210 100644 --- a/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/ControlCenterSettings.qml @@ -90,8 +90,7 @@ ColumnLayout { NFilePicker { id: imagePicker title: I18n.tr("bar.widget-settings.control-center.select-custom-icon") - selectFiles: true - selectFolders: false + selectionMode: "files" nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"] initialPath: Quickshell.env("HOME") onAccepted: paths => { diff --git a/Modules/Settings/Tabs/GeneralTab.qml b/Modules/Settings/Tabs/GeneralTab.qml index c2e08693..a4600281 100644 --- a/Modules/Settings/Tabs/GeneralTab.qml +++ b/Modules/Settings/Tabs/GeneralTab.qml @@ -49,8 +49,7 @@ ColumnLayout { NFilePicker { id: avatarPicker title: I18n.tr("settings.general.profile.select-avatar") - selectFiles: true - selectFolders: true + selectionMode: "files" initialPath: Settings.data.general.avatarImage.substr(0, Settings.data.general.avatarImage.lastIndexOf("/")) || Quickshell.env("HOME") nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"] onAccepted: paths => { diff --git a/Modules/Settings/Tabs/ScreenRecorderTab.qml b/Modules/Settings/Tabs/ScreenRecorderTab.qml index cdd2be99..957579bf 100644 --- a/Modules/Settings/Tabs/ScreenRecorderTab.qml +++ b/Modules/Settings/Tabs/ScreenRecorderTab.qml @@ -229,8 +229,7 @@ ColumnLayout { NFilePicker { id: folderPicker - selectFiles: false - selectFolders: true + selectionMode: "folders" title: I18n.tr("settings.screen-recorder.general.select-output-folder") initialPath: Settings.data.screenRecorder.directory || Quickshell.env("HOME") + "/Videos" onAccepted: paths => { diff --git a/Modules/Settings/Tabs/WallpaperTab.qml b/Modules/Settings/Tabs/WallpaperTab.qml index fa22c89a..742849b9 100644 --- a/Modules/Settings/Tabs/WallpaperTab.qml +++ b/Modules/Settings/Tabs/WallpaperTab.qml @@ -340,8 +340,7 @@ ColumnLayout { NFilePicker { id: mainFolderPicker - selectFiles: false - selectFolders: true + selectionMode: "folders" title: I18n.tr("settings.wallpaper.settings.select-folder") initialPath: Settings.data.wallpaper.directory || Quickshell.env("HOME") + "/Pictures" onAccepted: paths => { @@ -353,8 +352,7 @@ ColumnLayout { NFilePicker { id: monitorFolderPicker - selectFiles: false - selectFolders: true + selectionMode: "folders" title: I18n.tr("settings.wallpaper.settings.select-monitor-folder") initialPath: WallpaperService.getMonitorDirectory(specificFolderMonitorName) || Quickshell.env("HOME") + "/Pictures" onAccepted: paths => { diff --git a/Widgets/NFilePicker.qml b/Widgets/NFilePicker.qml index 87b0efb1..d2e2f766 100644 --- a/Widgets/NFilePicker.qml +++ b/Widgets/NFilePicker.qml @@ -15,8 +15,7 @@ Popup { // Properties property string title: "File Picker" property string initialPath: Quickshell.env("HOME") || "/home" - property bool selectFiles: true - property bool selectFolders: true + property string selectionMode: "files" // "files" or "folders" property var nameFilters: ["*"] property bool showDirs: true property bool showHiddenFiles: false @@ -117,8 +116,16 @@ Popup { const fileIsDir = folderModel.get(i, "fileIsDir") const fileSize = folderModel.get(i, "fileSize") - if (root.selectFolders && !root.selectFiles && !fileIsDir) + // Skip hidden items if showHiddenFiles is false + // This additional check ensures hidden files are properly filtered + if (!root.showHiddenFiles && fileName.startsWith(".")) { continue + } + + // In folder mode, hide files + if (root.selectionMode === "folders" && !fileIsDir) + continue + if (searchText === "" || fileName.toLowerCase().includes(searchText)) { filteredModel.append({ "fileName": fileName, @@ -193,10 +200,28 @@ Popup { color: Color.mPrimary Layout.fillWidth: true } + + // "Select Current" button only visible in folder selection mode + NButton { + text: "Select Current" + icon: "filepicker-folder-current" + visible: root.selectionMode === "folders" + onClicked: { + filePickerPanel.currentSelection = [root.currentPath] + root.confirmSelection() + } + } + NIconButton { icon: "filepicker-refresh" tooltipText: "Refresh" - onClicked: folderModel.refresh() + onClicked: { + // Force a proper refresh by resetting the folder + const currentFolder = folderModel.folder + folderModel.folder = "" + folderModel.folder = currentFolder + Qt.callLater(root.updateFilteredModel) + } } NIconButton { icon: "filepicker-close" @@ -301,7 +326,11 @@ Popup { baseSize: Style.baseWidgetSize * 0.8 onClicked: { root.showHiddenFiles = !root.showHiddenFiles - root.updateFilteredModel() + // Force model refresh by resetting the folder + const currentFolder = folderModel.folder + folderModel.folder = "" + folderModel.folder = currentFolder + Qt.callLater(root.updateFilteredModel) } } } @@ -375,15 +404,20 @@ Popup { FolderListModel { id: folderModel folder: "file://" + root.currentPath - nameFilters: root.nameFilters + // Use wildcard filters including hidden files when showHiddenFiles is true + nameFilters: root.showHiddenFiles ? ["*", ".*"] : root.nameFilters showDirs: root.showDirs - showHidden: root.showHiddenFiles + showHidden: true // Always true, we'll filter in updateFilteredModel + showDotAndDotDot: false sortField: FolderListModel.Name sortReversed: false + onFolderChanged: { root.currentPath = folder.toString().replace("file://", "") filePickerPanel.currentSelection = [] + Qt.callLater(root.updateFilteredModel) } + onStatusChanged: { if (status === FolderListModel.Error) { if (root.currentPath !== Quickshell.env("HOME")) { @@ -396,6 +430,14 @@ Popup { } } + // Update nameFilters when showHiddenFiles changes + Connections { + target: root + function onShowHiddenFilesChanged() { + folderModel.nameFilters = root.showHiddenFiles ? ["*", ".*"] : root.nameFilters + } + } + ListModel { id: filteredModel } @@ -618,15 +660,16 @@ Popup { onClicked: mouse => { if (mouse.button === Qt.LeftButton) { if (model.fileIsDir) { - if (root.selectFolders && !root.selectFiles) { + // In folder mode, single click selects the folder + if (root.selectionMode === "folders") { filePickerPanel.currentSelection = [model.filePath] - } else { - folderModel.folder = "file://" + model.filePath - root.currentPath = model.filePath } + // In file mode, single click on folder does nothing (must double-click to enter) } else { - if (root.selectFiles) - filePickerPanel.currentSelection = [model.filePath] + // Single click on file selects it (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.currentSelection = [model.filePath] + } } } } @@ -634,15 +677,12 @@ Popup { onDoubleClicked: mouse => { if (mouse.button === Qt.LeftButton) { if (model.fileIsDir) { - if (root.selectFolders && !root.selectFiles) { - filePickerPanel.currentSelection = [model.filePath] - root.confirmSelection() - } else { - folderModel.folder = "file://" + model.filePath - root.currentPath = model.filePath - } + // Double-click on folder always navigates into it + folderModel.folder = "file://" + model.filePath + root.currentPath = model.filePath } else { - if (root.selectFiles) { + // Double-click on file selects and confirms (only in file mode) + if (root.selectionMode === "files") { filePickerPanel.currentSelection = [model.filePath] root.confirmSelection() } @@ -719,15 +759,16 @@ Popup { onClicked: mouse => { if (mouse.button === Qt.LeftButton) { if (model.fileIsDir) { - if (root.selectFolders && !root.selectFiles) { + // In folder mode, single click selects the folder + if (root.selectionMode === "folders") { filePickerPanel.currentSelection = [model.filePath] - } else { - folderModel.folder = "file://" + model.filePath - root.currentPath = model.filePath } + // In file mode, single click on folder does nothing (must double-click to enter) } else { - if (root.selectFiles) - filePickerPanel.currentSelection = [model.filePath] + // Single click on file selects it (only in file mode) + if (root.selectionMode === "files") { + filePickerPanel.currentSelection = [model.filePath] + } } } } @@ -735,15 +776,12 @@ Popup { onDoubleClicked: mouse => { if (mouse.button === Qt.LeftButton) { if (model.fileIsDir) { - if (root.selectFolders && !root.selectFiles) { - filePickerPanel.currentSelection = [model.filePath] - root.confirmSelection() - } else { - folderModel.folder = "file://" + model.filePath - root.currentPath = model.filePath - } + // Double-click on folder always navigates into it + folderModel.folder = "file://" + model.filePath + root.currentPath = model.filePath } else { - if (root.selectFiles) { + // Double-click on file selects and confirms (only in file mode) + if (root.selectionMode === "files") { filePickerPanel.currentSelection = [model.filePath] root.confirmSelection() } @@ -765,7 +803,8 @@ Popup { if (filePickerPanel.searchText.length > 0) { return "Searching for: \"" + filePickerPanel.searchText + "\" (" + filteredModel.count + " matches)" } else if (filePickerPanel.currentSelection.length > 0) { - return filePickerPanel.currentSelection.length + " item(s) selected" + const selectedName = filePickerPanel.currentSelection[0].split('/').pop() + return "Selected: " + selectedName } else { return filteredModel.count + " items" } @@ -785,14 +824,7 @@ Popup { } NButton { - text: { - if (root.selectFolders && !root.selectFiles) - return "Select Folder" - else if (root.selectFiles && !root.selectFolders) - return "Select File" - else - return "Select" - } + text: root.selectionMode === "folders" ? "Select Folder" : "Select File" icon: "filepicker-check" enabled: filePickerPanel.currentSelection.length > 0 onClicked: root.confirmSelection() From e2f7012c5bf966fe72e28a4c676c6d175bb2e1e0 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 26 Sep 2025 23:35:05 -0400 Subject: [PATCH 05/14] NScrollView: properly disable horizontal scrrol when setting proper horizontalPolicy --- Widgets/NScrollView.qml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Widgets/NScrollView.qml b/Widgets/NScrollView.qml index 1edca1c1..63e28819 100644 --- a/Widgets/NScrollView.qml +++ b/Widgets/NScrollView.qml @@ -14,10 +14,44 @@ T.ScrollView { property real handleRadius: Style.radiusM * scaling property int verticalPolicy: ScrollBar.AsNeeded property int horizontalPolicy: ScrollBar.AsNeeded + property bool preventHorizontalScroll: horizontalPolicy === ScrollBar.AlwaysOff + property int boundsBehavior: Flickable.StopAtBounds + property int flickableDirection: Flickable.VerticalFlick implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding) implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding) + // Configure the internal flickable when it becomes available + Component.onCompleted: { + configureFlickable() + } + + // Function to configure the underlying Flickable + function configureFlickable() { + // Find the internal Flickable (it's usually the first child) + for (var i = 0; i < children.length; i++) { + var child = children[i] + if (child.toString().indexOf("Flickable") !== -1) { + // Configure the flickable to prevent horizontal scrolling + child.boundsBehavior = root.boundsBehavior + + if (root.preventHorizontalScroll) { + child.flickableDirection = Flickable.VerticalFlick + child.contentWidth = Qt.binding(() => child.width) + } else { + child.flickableDirection = root.flickableDirection + } + break + } + } + } + + // Watch for changes in horizontalPolicy + onHorizontalPolicyChanged: { + preventHorizontalScroll = (horizontalPolicy === ScrollBar.AlwaysOff) + configureFlickable() + } + ScrollBar.vertical: ScrollBar { parent: root x: root.mirrored ? 0 : root.width - width From 83d82a825bea7b60666ac986e06b664a58621d57 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 26 Sep 2025 23:46:25 -0400 Subject: [PATCH 06/14] v2.14.3 --- Services/UpdateService.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index 977cc142..d23879dd 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -8,8 +8,8 @@ Singleton { id: root // Public properties - property string baseVersion: "2.14.2" - property bool isDevelopment: true + property string baseVersion: "2.14.3" + property bool isDevelopment: false property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From dd29a739f3e6128bfcbe311a21e1e3cd1c07b1d2 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Fri, 26 Sep 2025 23:48:03 -0400 Subject: [PATCH 07/14] v2.14.3-dev --- Services/UpdateService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/UpdateService.qml b/Services/UpdateService.qml index d23879dd..7782e03b 100644 --- a/Services/UpdateService.qml +++ b/Services/UpdateService.qml @@ -9,7 +9,7 @@ Singleton { // Public properties property string baseVersion: "2.14.3" - property bool isDevelopment: false + property bool isDevelopment: true property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}` From 13e32dc11b4cf371afcada11cba421f2f38bc095 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 27 Sep 2025 08:58:48 -0400 Subject: [PATCH 08/14] Notifications test with a lot of actions --- Bin/notifications-test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Bin/notifications-test.sh b/Bin/notifications-test.sh index 32ecd9d2..789b036b 100755 --- a/Bin/notifications-test.sh +++ b/Bin/notifications-test.sh @@ -41,6 +41,6 @@ gdbus call --session \ "dialog-question" \ "Confirmation Required" \ "Do you want to proceed with the action?" \ - "['default', 'OK', 'cancel', 'Cancel']" \ + "['default', 'OK', 'cancel', 'Cancel', 'maybe', 'Maybe', 'undecided', 'Undecided']" \ "{}" \ - 5000 \ No newline at end of file + 5000 From fe2654268dd27c59b6124bd5e02c004ba4e9e81c Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 27 Sep 2025 15:14:44 +0200 Subject: [PATCH 09/14] NightLight: check if wlsunset exists, else dont enable NightLight SystemMonitorSettings: If RAM usage is not toggled, don't show % option Settings: remove NightLight from default bar widgets --- Assets/settings-default.json | 5 +---- Commons/Settings.qml | 6 ++---- Modules/Bar/Widgets/NightLight.qml | 6 ++++++ .../Settings/Bar/WidgetSettings/SystemMonitorSettings.qml | 1 + Services/ProgramCheckerService.qml | 4 +++- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 55a851c8..7ea8b3f7 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -1,5 +1,5 @@ { - "settingsVersion": 8, + "settingsVersion": 9, "bar": { "position": "top", "backgroundOpacity": 1, @@ -51,9 +51,6 @@ { "id": "Brightness" }, - { - "id": "NightLight" - }, { "id": "Clock" }, diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 819f11be..77623d7b 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -114,7 +114,7 @@ Singleton { JsonAdapter { id: adapter - property int settingsVersion: 8 + property int settingsVersion: 9 // bar property JsonObject bar: JsonObject { @@ -156,10 +156,8 @@ Singleton { "id": "Battery" }, { "id": "Volume" - }, { + }, { "id": "Brightness" - }, { - "id": "NightLight" }, { "id": "Clock" }, { diff --git a/Modules/Bar/Widgets/NightLight.qml b/Modules/Bar/Widgets/NightLight.qml index 3be8cf53..605f572a 100644 --- a/Modules/Bar/Widgets/NightLight.qml +++ b/Modules/Bar/Widgets/NightLight.qml @@ -24,6 +24,12 @@ NIconButton { icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off" tooltipText: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? I18n.tr("tooltips.night-light-forced") : I18n.tr("tooltips.night-light-enabled")) : I18n.tr("tooltips.night-light-disabled") onClicked: { + // Check if wlsunset is available before enabling night light + if (!ProgramCheckerService.wlsunsetAvailable) { + ToastService.showWarning(I18n.tr("settings.display.night-light.section.label"), I18n.tr("toast.night-light.not-installed")) + return + } + if (!Settings.data.nightLight.enabled) { Settings.data.nightLight.enabled = true Settings.data.nightLight.forced = false diff --git a/Modules/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml b/Modules/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml index b7524c86..06ba806d 100644 --- a/Modules/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml +++ b/Modules/Settings/Bar/WidgetSettings/SystemMonitorSettings.qml @@ -62,6 +62,7 @@ ColumnLayout { label: I18n.tr("bar.widget-settings.system-monitor.memory-percentage") checked: valueShowMemoryAsPercent onToggled: checked => valueShowMemoryAsPercent = checked + visible: valueShowMemoryUsage } NToggle { diff --git a/Services/ProgramCheckerService.qml b/Services/ProgramCheckerService.qml index f8e4fa98..a7d5f436 100644 --- a/Services/ProgramCheckerService.qml +++ b/Services/ProgramCheckerService.qml @@ -18,6 +18,7 @@ Singleton { property bool fuzzelAvailable: false property bool vesktopAvailable: false property bool gpuScreenRecorderAvailable: false + property bool wlsunsetAvailable: false // Signal emitted when all checks are complete signal checksCompleted @@ -31,7 +32,8 @@ Singleton { "footAvailable": ["which", "foot"], "fuzzelAvailable": ["which", "fuzzel"], "vesktopAvailable": ["which", "vesktop"], - "gpuScreenRecorderAvailable": ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"] + "gpuScreenRecorderAvailable": ["sh", "-c", "command -v gpu-screen-recorder >/dev/null 2>&1 || (command -v flatpak >/dev/null 2>&1 && flatpak list --app | grep -q 'com.dec05eba.gpu_screen_recorder')"], + "wlsunsetAvailable": ["which", "wlsunset"] }) // Internal tracking From 65cd95c62b2300810cb1780965b6e76046db3eb9 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 27 Sep 2025 09:17:23 -0400 Subject: [PATCH 10/14] Notifications: properly handle large/many action buttons. Fix #379 --- Modules/Notification/Notification.qml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index 3cf73d3f..b7f48abe 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -341,11 +341,14 @@ Variants { } // Notification actions - RowLayout { + Flow { Layout.fillWidth: true spacing: Style.marginS * scaling Layout.topMargin: Style.marginM * scaling + flow: Flow.LeftToRight + layoutDirection: Qt.LeftToRight + // Store the notification ID for access in button delegates property string parentNotificationId: notificationId @@ -378,17 +381,12 @@ Variants { textColor: hovered ? Color.mOnTertiary : Color.mOnPrimary hoverColor: Color.mTertiary outlined: false - Layout.preferredHeight: 24 * scaling + implicitHeight: 24 * scaling onClicked: { NotificationService.invokeAction(parent.parentNotificationId, actionData.identifier) } } } - - // Spacer to push buttons to the left - Item { - Layout.fillWidth: true - } } } } From afce091473f35d37206bc97ce9e2bc57c46bfbc3 Mon Sep 17 00:00:00 2001 From: ItsLemmy Date: Sat, 27 Sep 2025 11:03:52 -0400 Subject: [PATCH 11/14] Bluetooth: simplify the way we handle adapter state vs settings value. --- Services/BluetoothService.qml | 58 +++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/Services/BluetoothService.qml b/Services/BluetoothService.qml index 43ac4dd8..ccfc0439 100644 --- a/Services/BluetoothService.qml +++ b/Services/BluetoothService.qml @@ -10,6 +10,7 @@ Singleton { readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter readonly property bool available: (adapter !== null) + readonly property bool enabled: adapter?.enabled ?? false readonly property bool discovering: (adapter && adapter.discovering) ?? false readonly property var devices: adapter ? adapter.devices : null readonly property var pairedDevices: { @@ -30,18 +31,28 @@ Singleton { } property bool lastAdapterState: false + property bool restoringState: false function init() { Logger.log("Bluetooth", "Service initialized") - syncStateTimer.running = true + // Try to restore saved state if needed + if (Settings.data.network.bluetoothEnabled !== undefined && adapter) { + restoringState = true + adapter.enabled = Settings.data.network.bluetoothEnabled + restoringState = false + } + + // Need to delay a bit for the adapter to be ready + initTimer.running = true } Timer { - id: syncStateTimer + id: initTimer interval: 1000 repeat: false onTriggered: { - lastAdapterState = Settings.data.network.bluetoothEnabled = adapter.enabled + lastAdapterState = adapter?.enabled ?? false + Logger.log("Bluetooth", "LastAdapterState:", lastAdapterState) } } @@ -52,27 +63,6 @@ Singleton { 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(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.enabled")) - } else { - ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.disabled")) - } - } - } - Connections { target: adapter function onEnabledChanged() { @@ -82,11 +72,21 @@ Singleton { } Logger.log("Bluetooth", "onEnableChanged", adapter.enabled) - Settings.data.network.bluetoothEnabled = adapter.enabled - stateDebounceTimer.restart() - if (adapter.enabled) { - // Using a timer to give a little time so the adapter is really enabled - discoveryTimer.running = true + + // Only save to settings if this is a user-initiated change + if (!restoringState) { + Settings.data.network.bluetoothEnabled = adapter.enabled + } + + // Show toast only for actual state changes + if (lastAdapterState !== adapter.enabled) { + lastAdapterState = adapter.enabled + if (adapter.enabled) { + ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.enabled")) + discoveryTimer.running = true + } else { + ToastService.showNotice(I18n.tr("bluetooth.panel.title"), I18n.tr("toast.bluetooth.disabled")) + } } } } From 35bf30ef5ed93800c5b39a7dc1a4a32c9339d83d Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 27 Sep 2025 17:16:00 +0200 Subject: [PATCH 12/14] ColorSchemeTab: add matugen type option --- Assets/Translations/de.json | 4 +++ Assets/Translations/en.json | 4 +++ Assets/Translations/es.json | 4 +++ Assets/Translations/fr.json | 4 +++ Assets/Translations/pt.json | 4 +++ Assets/Translations/zh.json | 4 +++ Commons/Settings.qml | 3 +- Modules/Settings/Tabs/ColorSchemeTab.qml | 43 ++++++++++++++++++++++++ Services/MatugenService.qml | 6 ++-- 9 files changed, 72 insertions(+), 4 deletions(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index d3678692..a07cc9e8 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -478,6 +478,10 @@ "enable-matugen": { "label": "Matugen aktivieren", "description": "Automatisch Farben basierend auf Ihrem aktiven Hintergrundbild generieren." + }, + "matugen-scheme-type": { + "label": "Matugen-Schema-Typ", + "description": "Wähle einen Farbstil für Matugen aus." } }, "predefined": { diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 18bb5ce1..4a903bc2 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -478,6 +478,10 @@ "enable-matugen": { "label": "Enable Matugen", "description": "Automatically generate colors based on your active wallpaper." + }, + "matugen-scheme-type": { + "label": "Matugen scheme type", + "description": "Choose the color scheme generation algorithm for Matugen." } }, "predefined": { diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 574484ba..0c23929f 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -476,6 +476,10 @@ "enable-matugen": { "label": "Activar Matugen", "description": "Genera colores automáticamente basados en tu fondo de pantalla activo." + }, + "matugen-scheme-type": { + "label": "Tipo de esquema Matugen", + "description": "Elige el algoritmo de generación de esquema de colores para Matugen." } }, "predefined": { diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 5c302a1d..5a77f950 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -476,6 +476,10 @@ "enable-matugen": { "label": "Activer Matugen", "description": "Générez automatiquement des couleurs en fonction de votre fond d'écran actif." + }, + "matugen-scheme-type": { + "label": "Type de schéma Matugen", + "description": "Choisissez l'algorithme de génération de schéma de couleurs pour Matugen." } }, "predefined": { diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 93e83768..cc913f62 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -442,6 +442,10 @@ "enable-matugen": { "label": "Ativar Matugen", "description": "Gera cores automaticamente com base no seu papel de parede ativo." + }, + "matugen-scheme-type": { + "label": "Tipo de esquema Matugen", + "description": "Escolha o algoritmo de geração de esquema de cores para Matugen." } }, "predefined": { diff --git a/Assets/Translations/zh.json b/Assets/Translations/zh.json index 7ab9b8e7..f4d3dd0a 100644 --- a/Assets/Translations/zh.json +++ b/Assets/Translations/zh.json @@ -476,6 +476,10 @@ "enable-matugen": { "label": "启用 Matugen", "description": "根据您的活动壁纸自动生成颜色。" + }, + "matugen-scheme-type": { + "label": "Matugen 配色方案类型", + "description": "为 Matugen 选择配色方案生成算法。" } }, "predefined": { diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 77623d7b..9ccf3c9f 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -156,7 +156,7 @@ Singleton { "id": "Battery" }, { "id": "Volume" - }, { + }, { "id": "Brightness" }, { "id": "Clock" @@ -291,6 +291,7 @@ Singleton { property bool useWallpaperColors: false property string predefinedScheme: "Noctalia (default)" property bool darkMode: true + property string matugenSchemeType: "scheme-fruit-salad" } // matugen templates toggles diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 443d01be..1407235e 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -140,6 +140,49 @@ ColumnLayout { } } + // Matugen Scheme Type Selection + NComboBox { + label: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.label") + description: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.description") + enabled: Settings.data.colorSchemes.useWallpaperColors + opacity: Settings.data.colorSchemes.useWallpaperColors ? 1.0 : 0.6 + + model: [{ + "key": "scheme-content", + "name": "Content" + }, { + "key": "scheme-expressive", + "name": "Expressive" + }, { + "key": "scheme-fidelity", + "name": "Fidelity" + }, { + "key": "scheme-fruit-salad", + "name": "Fruit Salad" + }, { + "key": "scheme-monochrome", + "name": "Monochrome" + }, { + "key": "scheme-neutral", + "name": "Neutral" + }, { + "key": "scheme-rainbow", + "name": "Rainbow" + }, { + "key": "scheme-tonal-spot", + "name": "Tonal Spot" + }] + + currentKey: Settings.data.colorSchemes.matugenSchemeType + + onSelected: key => { + Settings.data.colorSchemes.matugenSchemeType = key + if (Settings.data.colorSchemes.useWallpaperColors) { + MatugenService.generateFromWallpaper() + } + } + } + NDivider { Layout.fillWidth: true Layout.topMargin: Style.marginXL * scaling diff --git a/Services/MatugenService.qml b/Services/MatugenService.qml index c2ebcd53..e3410d57 100644 --- a/Services/MatugenService.qml +++ b/Services/MatugenService.qml @@ -66,14 +66,14 @@ Singleton { var extraUser = (Settings.configDir + "matugen.d").replace(/'/g, "'\\''") // Build the main script - var script = "cat > '" + pathEsc + "' << 'EOF'\n" + content + "EOF\n" + "for d in '" + extraRepo + "' '" + extraUser + "'; do\n" + " if [ -d \"$d\" ]; then\n" + " for f in \"$d\"/*.toml; do\n" + " [ -f \"$f\" ] && { echo; echo \"# extra: $f\"; cat \"$f\"; } >> '" + pathEsc + "'\n" + " done\n" + " fi\n" + "done\n" + "matugen image '" - + wp + "' --config '" + pathEsc + "' --mode " + mode + var script = "cat > '" + pathEsc + "' << 'EOF'\n" + content + "EOF\n" + "for d in '" + extraRepo + "' '" + extraUser + "'; do\n" + " if [ -d \"$d\" ]; then\n" + " for f in \"$d\"/*.toml; do\n" + " [ -f \"$f\" ] && { echo; echo \"# extra: $f\"; cat \"$f\"; } >> '" + pathEsc + "'\n" + " done\n" + " fi\n" + + "done\n" + "matugen image '" + wp + "' --config '" + pathEsc + "' --mode " + mode + " --type " + Settings.data.colorSchemes.matugenSchemeType // Add user config execution if enabled if (Settings.data.matugen.enableUserTemplates) { var userConfigDir = (Quickshell.env("HOME") + "/.config/matugen/").replace(/'/g, "'\\''") script += "\n# Execute user config if it exists\nif [ -f '" + userConfigDir + "config.toml' ]; then\n" - script += " matugen image '" + wp + "' --config '" + userConfigDir + "config.toml' --mode " + mode + "\n" + script += " matugen image '" + wp + "' --config '" + userConfigDir + "config.toml' --mode " + mode + " --type " + Settings.data.colorSchemes.matugenSchemeType + "\n" script += "fi" } From 50e2a95f52ee9a0865204a6c1c260c2ee909cbe7 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 27 Sep 2025 17:16:37 +0200 Subject: [PATCH 13/14] Update settings-default --- Assets/settings-default.json | 5 +++-- Commons/Settings.qml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index 7ea8b3f7..e4005902 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -1,5 +1,5 @@ { - "settingsVersion": 9, + "settingsVersion": 10, "bar": { "position": "top", "backgroundOpacity": 1, @@ -158,7 +158,8 @@ "colorSchemes": { "useWallpaperColors": false, "predefinedScheme": "Noctalia (default)", - "darkMode": true + "darkMode": true, + "matugenSchemeType": "scheme-fruit-salad" }, "matugen": { "gtk4": false, diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 9ccf3c9f..dd9bb779 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -114,7 +114,7 @@ Singleton { JsonAdapter { id: adapter - property int settingsVersion: 9 + property int settingsVersion: 10 // bar property JsonObject bar: JsonObject { From d4dd3b17341c2dce3111eb6dd8ebb11efed7443b Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Sat, 27 Sep 2025 17:20:47 +0200 Subject: [PATCH 14/14] ColorSchemeTab: hide matugen scheme type when Matugen is disabled --- Modules/Settings/Tabs/ColorSchemeTab.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/Settings/Tabs/ColorSchemeTab.qml b/Modules/Settings/Tabs/ColorSchemeTab.qml index 1407235e..be5ed002 100644 --- a/Modules/Settings/Tabs/ColorSchemeTab.qml +++ b/Modules/Settings/Tabs/ColorSchemeTab.qml @@ -146,6 +146,7 @@ ColumnLayout { description: I18n.tr("settings.color-scheme.color-source.matugen-scheme-type.description") enabled: Settings.data.colorSchemes.useWallpaperColors opacity: Settings.data.colorSchemes.useWallpaperColors ? 1.0 : 0.6 + visible: Settings.data.colorSchemes.useWallpaperColors model: [{ "key": "scheme-content",