mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 18:43:59 +00:00
Merge branch 'noctalia-dev:main' into Centered-Circles
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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 }}
|
||||
working-directory: ${{ github.workspace }}/aur
|
||||
run: |
|
||||
set -euo pipefail
|
||||
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
|
||||
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 }}
|
||||
working-directory: ${{ github.workspace }}/aur
|
||||
run: |
|
||||
set -euo pipefail
|
||||
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
|
||||
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"
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -476,6 +476,10 @@
|
||||
"enable-matugen": {
|
||||
"label": "启用 Matugen",
|
||||
"description": "根据您的活动壁纸自动生成颜色。"
|
||||
},
|
||||
"matugen-scheme-type": {
|
||||
"label": "Matugen 配色方案类型",
|
||||
"description": "为 Matugen 选择配色方案生成算法。"
|
||||
}
|
||||
},
|
||||
"predefined": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"settingsVersion": 8,
|
||||
"settingsVersion": 10,
|
||||
"bar": {
|
||||
"position": "top",
|
||||
"backgroundOpacity": 1,
|
||||
@@ -51,9 +51,6 @@
|
||||
{
|
||||
"id": "Brightness"
|
||||
},
|
||||
{
|
||||
"id": "NightLight"
|
||||
},
|
||||
{
|
||||
"id": "Clock"
|
||||
},
|
||||
@@ -161,7 +158,8 @@
|
||||
"colorSchemes": {
|
||||
"useWallpaperColors": false,
|
||||
"predefinedScheme": "Noctalia (default)",
|
||||
"darkMode": true
|
||||
"darkMode": true,
|
||||
"matugenSchemeType": "scheme-fruit-salad"
|
||||
},
|
||||
"matugen": {
|
||||
"gtk4": false,
|
||||
|
||||
@@ -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
|
||||
5000
|
||||
|
||||
@@ -114,7 +114,7 @@ Singleton {
|
||||
JsonAdapter {
|
||||
id: adapter
|
||||
|
||||
property int settingsVersion: 8
|
||||
property int settingsVersion: 10
|
||||
|
||||
// bar
|
||||
property JsonObject bar: JsonObject {
|
||||
@@ -158,8 +158,6 @@ Singleton {
|
||||
"id": "Volume"
|
||||
}, {
|
||||
"id": "Brightness"
|
||||
}, {
|
||||
"id": "NightLight"
|
||||
}, {
|
||||
"id": "Clock"
|
||||
}, {
|
||||
@@ -293,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
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -62,6 +62,7 @@ ColumnLayout {
|
||||
label: I18n.tr("bar.widget-settings.system-monitor.memory-percentage")
|
||||
checked: valueShowMemoryAsPercent
|
||||
onToggled: checked => valueShowMemoryAsPercent = checked
|
||||
visible: valueShowMemoryUsage
|
||||
}
|
||||
|
||||
NToggle {
|
||||
|
||||
@@ -140,6 +140,50 @@ 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
|
||||
visible: Settings.data.colorSchemes.useWallpaperColors
|
||||
|
||||
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
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,7 +8,7 @@ Singleton {
|
||||
id: root
|
||||
|
||||
// Public properties
|
||||
property string baseVersion: "2.14.1"
|
||||
property string baseVersion: "2.14.3"
|
||||
property bool isDevelopment: true
|
||||
|
||||
property string currentVersion: `v${!isDevelopment ? baseVersion : baseVersion + "-dev"}`
|
||||
|
||||
+76
-44
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user