diff options
| author | Tim Hämisch <haemtim@gmail.com> | 2025-06-13 17:49:49 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-06-13 17:49:49 +0200 |
| commit | 5fb727a4c8d9e89acc63a1c353202423bc7b120e (patch) | |
| tree | 28839211a383025df53168993d090cdccaa9bf7a | |
| parent | Add shutdown, reboot, and logout actions to launcher (diff) | |
| parent | docs: add contributing.md (diff) | |
| download | caelestia-shell-5fb727a4c8d9e89acc63a1c353202423bc7b120e.tar.gz caelestia-shell-5fb727a4c8d9e89acc63a1c353202423bc7b120e.tar.bz2 caelestia-shell-5fb727a4c8d9e89acc63a1c353202423bc7b120e.zip | |
Merge branch 'caelestia-dots:main' into betteractions
| -rw-r--r-- | .github/CONTRIBUTING.md | 21 | ||||
| -rw-r--r-- | modules/Shortcuts.qml | 9 | ||||
| -rw-r--r-- | modules/background/Wallpaper.qml | 14 | ||||
| -rw-r--r-- | modules/dashboard/dash/User.qml | 4 | ||||
| -rw-r--r-- | modules/drawers/Interactions.qml | 86 | ||||
| -rwxr-xr-x | run.fish | 3 | ||||
| -rw-r--r-- | services/Thumbnailer.qml | 72 | ||||
| -rw-r--r-- | utils/Paths.qml | 16 | ||||
| -rw-r--r-- | widgets/CachingImage.qml | 42 |
9 files changed, 173 insertions, 94 deletions
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..d0239c0 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,21 @@ +# Contributing + +There are only a few rules: +- Follow the commit convention as follows: + - The name of the commit should be `module: change` + - Try to be consistent with the module names; you can look at existing commits for the module names I use + - If there is more than one change, the change in the commit name should be the most impactful change + - Put other changes in the description +- Format your code + - I use the vscode qml extension with default arguments to format the code, however you do not have to use it + - Just try to follow the code style of the rest of the code and ensure that there is: + - no trailing whitespace on any lines + - a single space between operators +- No AI slop allowed + - AI readme/docs slop = instant block +- PLEASE TEST YOUR PRS + - I can't believe I have to put this here, but please test your PRs before submitting them + - Your PR must not break anything currently existing, or specify in the description if it does +- PR descriptions should be descriptive + - Please explain what the PR does and how to use it in your PR description + - Also include any breaking changes and/or side effects of the PR diff --git a/modules/Shortcuts.qml b/modules/Shortcuts.qml index bed620c..3d665ff 100644 --- a/modules/Shortcuts.qml +++ b/modules/Shortcuts.qml @@ -9,6 +9,15 @@ Scope { property bool launcherInterrupted CustomShortcut { + name: "showall" + description: "Toggle launcher, dashboard and osd" + onPressed: { + const v = Visibilities.getForActive(); + v.launcher = v.dashboard = v.osd = !(v.launcher || v.dashboard || v.osd); + } + } + + CustomShortcut { name: "session" description: "Toggle session menu" onPressed: { diff --git a/modules/background/Wallpaper.qml b/modules/background/Wallpaper.qml index 2734034..f4e3243 100644 --- a/modules/background/Wallpaper.qml +++ b/modules/background/Wallpaper.qml @@ -8,7 +8,7 @@ import QtQuick Item { id: root - property url source: Wallpapers.current ? `file://${Wallpapers.current}` : "" + property string source: Wallpapers.current property Image current: one anchors.fill: parent @@ -32,20 +32,14 @@ Item { id: img function update(): void { - const srcPath = `${root.source}`.slice(7); - if (thumbnail.originalPath === srcPath) { + if (path === root.source) root.current = this; - } else - path = srcPath; + else + path = root.source; } anchors.fill: parent - loadOriginal: true - asynchronous: true - cache: false - fillMode: Image.PreserveAspectCrop - opacity: 0 scale: Wallpapers.showPreview ? 1 : 0.8 diff --git a/modules/dashboard/dash/User.qml b/modules/dashboard/dash/User.qml index c7d2bfd..f1b9b18 100644 --- a/modules/dashboard/dash/User.qml +++ b/modules/dashboard/dash/User.qml @@ -10,13 +10,13 @@ Row { id: root padding: Appearance.padding.large - spacing: Appearance.spacing.large + spacing: Appearance.spacing.normal StyledClippingRect { implicitWidth: info.implicitHeight implicitHeight: info.implicitHeight - radius: Appearance.rounding.full + radius: Appearance.rounding.large color: Colours.palette.m3surfaceContainerHigh MaterialIcon { diff --git a/modules/drawers/Interactions.qml b/modules/drawers/Interactions.qml index 3ec522b..10e37a8 100644 --- a/modules/drawers/Interactions.qml +++ b/modules/drawers/Interactions.qml @@ -16,6 +16,8 @@ MouseArea { property bool osdHovered property point dragStart + property bool dashboardShortcutActive + property bool osdShortcutActive function withinPanelHeight(panel: Item, x: real, y: real): bool { const panelY = BorderConfig.thickness + panel.y; @@ -37,9 +39,14 @@ MouseArea { onPressed: event => dragStart = Qt.point(event.x, event.y) onContainsMouseChanged: { if (!containsMouse) { - visibilities.osd = false; - osdHovered = false; - visibilities.dashboard = false; + // Only hide if not activated by shortcut + if (!osdShortcutActive) { + visibilities.osd = false; + osdHovered = false; + } + if (!dashboardShortcutActive) { + visibilities.dashboard = false; + } popouts.hasCurrent = false; } } @@ -47,8 +54,16 @@ MouseArea { onPositionChanged: ({x, y}) => { // Show osd on hover const showOsd = inRightPanel(panels.osd, x, y); - visibilities.osd = showOsd; - osdHovered = showOsd; + + // Always update visibility based on hover if not in shortcut mode + if (!osdShortcutActive) { + visibilities.osd = showOsd; + osdHovered = showOsd; + } else if (showOsd) { + // If hovering over OSD area while in shortcut mode, transition to hover control + osdShortcutActive = false; + osdHovered = true; + } // Show/hide session on drag if (pressed && withinPanelHeight(panels.session, x, y)) { @@ -60,7 +75,15 @@ MouseArea { } // Show dashboard on hover - visibilities.dashboard = inTopPanel(panels.dashboard, x, y); + const showDashboard = inTopPanel(panels.dashboard, x, y); + + // Always update visibility based on hover if not in shortcut mode + if (!dashboardShortcutActive) { + visibilities.dashboard = showDashboard; + } else if (showDashboard) { + // If hovering over dashboard area while in shortcut mode, transition to hover control + dashboardShortcutActive = false; + } // Show popouts on hover const popout = panels.popouts; @@ -75,6 +98,57 @@ MouseArea { popouts.hasCurrent = false; } + // Monitor individual visibility changes + Connections { + target: root.visibilities + + function onLauncherChanged() { + // If launcher is hidden, clear shortcut flags for dashboard and OSD + if (!root.visibilities.launcher) { + root.dashboardShortcutActive = false; + root.osdShortcutActive = false; + + // Also hide dashboard and OSD if they're not being hovered + const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY); + const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); + + if (!inDashboardArea) { + root.visibilities.dashboard = false; + } + if (!inOsdArea) { + root.visibilities.osd = false; + root.osdHovered = false; + } + } + } + + function onDashboardChanged() { + if (root.visibilities.dashboard) { + // Dashboard became visible, immediately check if this should be shortcut mode + const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY); + if (!inDashboardArea) { + root.dashboardShortcutActive = true; + } + } else { + // Dashboard hidden, clear shortcut flag + root.dashboardShortcutActive = false; + } + } + + function onOsdChanged() { + if (root.visibilities.osd) { + // OSD became visible, immediately check if this should be shortcut mode + const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); + if (!inOsdArea) { + root.osdShortcutActive = true; + } + } else { + // OSD hidden, clear shortcut flag + root.osdShortcutActive = false; + } + } + } + Osd.Interactions { screen: root.screen visibilities: root.visibilities @@ -4,5 +4,6 @@ set -l dbus 'quickshell.dbus.properties.warning = false;quickshell.dbus.dbusmenu set -l notifs 'quickshell.service.notifications.warning = false' # Notification server warnings on reload set -l sni 'quickshell.service.sni.host.warning = false' # StatusNotifierItem warnings on reload set -l process 'QProcess: Destroyed while process' # Long running processes on reload +set -l cache "Cannot open: file://$XDG_CACHE_HOME/caelestia/imagecache/" -qs -p (dirname (status filename)) --log-rules "$dbus;$notifs;$sni" | grep -vE -e $process +qs -p (dirname (status filename)) --log-rules "$dbus;$notifs;$sni" | grep -vF -e $process -e $cache diff --git a/services/Thumbnailer.qml b/services/Thumbnailer.qml deleted file mode 100644 index 6a6066c..0000000 --- a/services/Thumbnailer.qml +++ /dev/null @@ -1,72 +0,0 @@ -pragma Singleton -pragma ComponentBehavior: Bound - -import "root:/utils" -import Quickshell -import Quickshell.Io -import QtQuick - -Singleton { - id: root - - readonly property string thumbDir: `${Paths.cache}/thumbnails`.slice(7) - - function go(obj: var): var { - return thumbComp.createObject(obj, { - originalPath: obj.path, - width: obj.width, - height: obj.height, - loadOriginal: obj.loadOriginal - }); - } - - component Thumbnail: QtObject { - id: obj - - required property string originalPath - required property int width - required property int height - required property bool loadOriginal - - property string path - - readonly property Process proc: Process { - running: true - command: ["fish", "-c", ` -set -l path "${root.thumbDir}/$(sha1sum ${obj.originalPath} | cut -d ' ' -f 1)@${obj.width}x${obj.height}-exact.png" -if test -f $path - echo $path -else - echo 'start' - set -l size (identify -ping -format '%w\n%h' ${obj.originalPath}) - if test $size[1] -gt ${obj.width} -o $size[2] -gt ${obj.height} - magick ${obj.originalPath} -${obj.width > 1024 || obj.height > 1024 ? "resize" : "thumbnail"} ${obj.width}x${obj.height}^ -background none -gravity center -extent ${obj.width}x${obj.height} -unsharp 0x.5 $path - else - cp ${obj.originalPath} $path - end - echo $path -end`] - stdout: SplitParser { - onRead: data => { - if (data === "start") { - if (obj.loadOriginal) - obj.path = obj.originalPath; - } else { - obj.path = data; - } - } - } - } - - function reload(): void { - proc.signal(9); - proc.running = true; - } - } - - Component { - id: thumbComp - - Thumbnail {} - } -} diff --git a/utils/Paths.qml b/utils/Paths.qml index 011bb1f..88267ea 100644 --- a/utils/Paths.qml +++ b/utils/Paths.qml @@ -1,6 +1,7 @@ pragma Singleton import Quickshell +import Quickshell.Io import Qt.labs.platform Singleton { @@ -12,4 +13,19 @@ Singleton { readonly property url data: `${StandardPaths.standardLocations(StandardPaths.GenericDataLocation)[0]}/caelestia` readonly property url state: `${StandardPaths.standardLocations(StandardPaths.GenericStateLocation)[0]}/caelestia` readonly property url cache: `${StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]}/caelestia` + + readonly property url imagecache: `${cache}/imagecache` + + function mkdir(path: url): void { + mkdirProc.path = path.toString().replace("file://", ""); + mkdirProc.startDetached(); + } + + Process { + id: mkdirProc + + property string path + + command: ["mkdir", "-p", path] + } } diff --git a/widgets/CachingImage.qml b/widgets/CachingImage.qml index cd164b4..3382615 100644 --- a/widgets/CachingImage.qml +++ b/widgets/CachingImage.qml @@ -1,14 +1,50 @@ import "root:/services" +import "root:/utils" +import Quickshell.Io import QtQuick Image { id: root property string path - property bool loadOriginal - readonly property Thumbnailer.Thumbnail thumbnail: Thumbnailer.go(this) + property string hash + readonly property string cachePath: `${Paths.imagecache}/${hash}@${width}x${height}.png`.slice(7) - source: thumbnail.path ? `file://${thumbnail.path}` : "" asynchronous: true + cache: false fillMode: Image.PreserveAspectCrop + sourceSize.width: width + sourceSize.height: height + + onPathChanged: { + shaProc.signal(9); + shaProc.path = path.replace("file://", ""); + shaProc.running = true; + } + + onCachePathChanged: { + if (hash) + source = cachePath; + } + + onStatusChanged: { + if (source == cachePath && status === Image.Error) + source = path; + else if (source == path && status === Image.Ready) { + Paths.mkdir(Paths.imagecache); + const grabPath = cachePath; + grabToImage(res => res.saveToFile(grabPath)); + } + } + + Process { + id: shaProc + + property string path + + command: ["sha256sum", path] + stdout: SplitParser { + onRead: data => root.hash = data.split(" ")[0] + } + } } |