summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2026-05-29 14:29:48 -0400
committerFreya Murphy <freya@freyacat.org>2026-05-29 14:29:48 -0400
commitbe2d03fef0c560f8bcd2a3be9bd806b1e3d118b6 (patch)
tree51a27813f670fc04174bdf6f6b4b89d9ea257711
parentremove tertiary color (diff)
downloadcaelestia-shell-be2d03fef0c560f8bcd2a3be9bd806b1e3d118b6.tar.gz
caelestia-shell-be2d03fef0c560f8bcd2a3be9bd806b1e3d118b6.tar.bz2
caelestia-shell-be2d03fef0c560f8bcd2a3be9bd806b1e3d118b6.zip
remove a lot
-rw-r--r--.github/CONTRIBUTING.md21
-rw-r--r--.github/FUNDING.yml15
-rw-r--r--.github/ISSUE_TEMPLATE/config.yml1
-rw-r--r--.github/ISSUE_TEMPLATE/feature.yml24
-rw-r--r--.github/ISSUE_TEMPLATE/issue.yml56
-rw-r--r--.github/workflows/release.yml37
-rw-r--r--.github/workflows/update-flake-inputs.yml85
-rw-r--r--.vscode/settings.json6
-rw-r--r--assets/bongocat.gifbin7350 -> 0 bytes
-rw-r--r--assets/dino.pngbin9441 -> 0 bytes
-rw-r--r--assets/kurukuru.gifbin99006 -> 0 bytes
-rw-r--r--assets/shaders/opacitymask.frag19
-rw-r--r--assets/shaders/opacitymask.frag.qsbbin1337 -> 0 bytes
-rw-r--r--components/ConnectionHeader.qml31
-rw-r--r--components/ConnectionInfoSection.qml59
-rw-r--r--components/PropertyRow.qml26
-rw-r--r--components/SectionContainer.qml32
-rw-r--r--components/SectionHeader.qml27
-rw-r--r--components/containers/StyledFlickable.qml14
-rw-r--r--components/controls/CollapsibleSection.qml132
-rw-r--r--components/controls/CustomSpinBox.qml170
-rw-r--r--components/controls/SpinBoxRow.qml52
-rw-r--r--components/controls/SplitButton.qml164
-rw-r--r--components/controls/SplitButtonRow.qml62
-rw-r--r--components/controls/StyledInputField.qml79
-rw-r--r--components/controls/SwitchRow.qml48
-rw-r--r--components/controls/ToggleButton.qml124
-rw-r--r--components/controls/ToggleRow.qml28
-rw-r--r--components/controls/Tooltip.qml185
-rw-r--r--components/effects/InnerBorder.qml44
-rw-r--r--components/effects/OpacityMask.qml9
-rw-r--r--components/misc/Ref.qml8
-rw-r--r--config/AppearanceConfig.qml1
-rw-r--r--config/Config.qml52
-rw-r--r--config/ControlCenterConfig.qml10
-rw-r--r--config/DashboardConfig.qml35
-rw-r--r--config/GeneralConfig.qml9
-rw-r--r--config/ServiceConfig.qml12
-rw-r--r--config/SidebarConfig.qml11
-rw-r--r--config/ToastsConfig.qml2
-rw-r--r--config/UserPaths.qml3
-rw-r--r--modules/Shortcuts.qml29
-rw-r--r--modules/dashboard/Background.qml66
-rw-r--r--modules/dashboard/Content.qml175
-rw-r--r--modules/dashboard/Media.qml403
-rw-r--r--modules/dashboard/Performance.qml831
-rw-r--r--modules/dashboard/Tabs.qml244
-rw-r--r--modules/dashboard/Weather.qml280
-rw-r--r--modules/dashboard/Wrapper.qml93
-rw-r--r--modules/drawers/Backgrounds.qml24
-rw-r--r--modules/drawers/Drawers.qml9
-rw-r--r--modules/drawers/Interactions.qml128
-rw-r--r--modules/drawers/Panels.qml29
-rw-r--r--modules/launcher/Wrapper.qml7
-rw-r--r--modules/launcher/services/Apps.qml2
-rw-r--r--modules/notifications/Background.qml7
-rw-r--r--modules/notifications/Wrapper.qml11
-rw-r--r--modules/sidebar/Background.qml43
-rw-r--r--modules/sidebar/Content.qml40
-rw-r--r--modules/sidebar/Notif.qml164
-rw-r--r--modules/sidebar/NotifActionList.qml200
-rw-r--r--modules/sidebar/NotifDock.qml207
-rw-r--r--modules/sidebar/NotifDockList.qml167
-rw-r--r--modules/sidebar/NotifGroup.qml269
-rw-r--r--modules/sidebar/NotifGroupList.qml213
-rw-r--r--modules/sidebar/Props.qml7
-rw-r--r--modules/sidebar/Wrapper.qml68
-rw-r--r--nix/default.nix15
-rw-r--r--plugin/src/Caelestia/CMakeLists.txt6
-rw-r--r--plugin/src/Caelestia/Internal/CMakeLists.txt3
-rw-r--r--plugin/src/Caelestia/Internal/arcgauge.cpp119
-rw-r--r--plugin/src/Caelestia/Internal/arcgauge.hpp61
-rw-r--r--plugin/src/Caelestia/Internal/circularbuffer.cpp94
-rw-r--r--plugin/src/Caelestia/Internal/circularbuffer.hpp44
-rw-r--r--plugin/src/Caelestia/Internal/sparklineitem.cpp212
-rw-r--r--plugin/src/Caelestia/Internal/sparklineitem.hpp90
-rw-r--r--plugin/src/Caelestia/Services/CMakeLists.txt9
-rw-r--r--plugin/src/Caelestia/Services/audiocollector.cpp246
-rw-r--r--plugin/src/Caelestia/Services/audiocollector.hpp76
-rw-r--r--plugin/src/Caelestia/Services/audioprovider.cpp78
-rw-r--r--plugin/src/Caelestia/Services/audioprovider.hpp48
-rw-r--r--plugin/src/Caelestia/Services/beattracker.cpp58
-rw-r--r--plugin/src/Caelestia/Services/beattracker.hpp49
-rw-r--r--plugin/src/Caelestia/Services/cavaprovider.cpp140
-rw-r--r--plugin/src/Caelestia/Services/cavaprovider.hpp64
-rw-r--r--plugin/src/Caelestia/Services/serviceref.cpp36
-rw-r--r--plugin/src/Caelestia/Services/serviceref.hpp28
-rw-r--r--plugin/src/Caelestia/requests.cpp35
-rw-r--r--plugin/src/Caelestia/requests.hpp23
-rw-r--r--services/Audio.qml13
-rw-r--r--services/NetworkUsage.qml229
-rw-r--r--services/Notifs.qml37
-rw-r--r--services/Players.qml126
-rw-r--r--services/SystemUsage.qml329
-rw-r--r--services/Weather.qml206
-rw-r--r--utils/SysInfo.qml34
96 files changed, 30 insertions, 7887 deletions
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
deleted file mode 100644
index d0239c0..0000000
--- a/.github/CONTRIBUTING.md
+++ /dev/null
@@ -1,21 +0,0 @@
-# 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/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index 30f44f7..0000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-# These are supported funding model platforms
-
-github: soramanew
-patreon: # Replace with a single Patreon username
-open_collective: # Replace with a single Open Collective username
-ko_fi: soramane
-tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
-community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
-liberapay: # Replace with a single Liberapay username
-issuehunt: # Replace with a single IssueHunt username
-lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
-polar: # Replace with a single Polar username
-buy_me_a_coffee: soramane
-thanks_dev: # Replace with a single thanks.dev username
-custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index 3ba13e0..0000000
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1 +0,0 @@
-blank_issues_enabled: false
diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml
deleted file mode 100644
index c5caffa..0000000
--- a/.github/ISSUE_TEMPLATE/feature.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-name: Feature request
-description: Suggest a new feature
-labels: ["enhancement"]
-type: "Feature"
-title: "[FEATURE] "
-body:
- - type: markdown
- attributes:
- value: "NOTE: Please write in **English**."
-
- - type: textarea
- attributes:
- label: "What would you like to be added?"
- description: "Can be a suggestion for an existing feature. You can suggest a widget, minor user interaction changes.. whatever."
-
- - type: textarea
- attributes:
- label: "How will it help?"
- description: "It's helpful to include examples (like in your use case)."
-
- - type: textarea
- attributes:
- label: "Extra info"
- description: "If you want a new widget, a pic of the inspiration (if available) would be awesome."
diff --git a/.github/ISSUE_TEMPLATE/issue.yml b/.github/ISSUE_TEMPLATE/issue.yml
deleted file mode 100644
index b767104..0000000
--- a/.github/ISSUE_TEMPLATE/issue.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-name: Issue
-description: Report an issue with the dots
-labels: ["bug"]
-type: "Bug"
-title: "[BUG] "
-body:
- - type: markdown
- attributes:
- value: "**Welcome to submit a new issue!**\n- It takes only 3 steps, so please be patient :)\n- Tip: If your issue is not a feature request and is not an issue with the dots (e.g. \"how do I use X feature\"), please use [Discussions](https://github.com/caelestia-dots/shell/discussions) instead."
- - type: checkboxes
- attributes:
- label: "Step 1. Before you submit"
- description: "Hint: The 2nd and 3rd checkbox is **not** forcely required as you may have failed to do so."
- options:
- - label: I have read the above instructions and am sure that this is supposed to be posted here.
- required: true
- - label: I've successfully updated to the latest versions following the [updating guide](https://github.com/caelestia-dots/caelestia?tab=readme-ov-file#updating).
- required: false # Not required cuz user may have failed to do so
- - label: I've successfully updated the system packages to the latest.
- required: false # Not required cuz user may have failed to do so
- - label: I've ticked the checkboxes without reading their contents
- required: false # Obviously
-
- - type: textarea
- attributes:
- label: "Step 2. Version info"
- description: "Run `caelestia -v` and paste the result below."
- value: "<details><summary>Version info</summary>\n\n```\n<!-- Run `caelestia -v` and paste the result here! -->\n```\n\n</details>"
- validations:
- required: true
-
- - type: markdown
- attributes:
- value: |
- **Tips for the following Step 3**
- 1. Use `LANG=C LC_ALL=C` to get the output of a command in English, eg. `LANG=C LC_ALL=C date` displays time in English.
- 2. If it throws errors, **PLEASE**, attach logs and describe in detail if possible.
- - Something happened to the shell (bar, dashboard, etc)? Run `caelestia shell -l` WITHOUT exiting the shell for logs.
- - Installation failed? Run installation again for logs.
- - You may use more code blocks when needed.
- 3. In case you are confused, the `<details>`, `<summary>`, `</summary>`, `</details>` are HTML tags for folding the logs (typically very long) inside. Please do not touch them (unless you know what you are doing).
- 4. If the logs are suuuuuuper long, consider using an online pastebin service instead.
-
- - type: textarea
- attributes:
- label: "Step 3. Describe the issue"
- value: "\n<!-- Firstly describe your issue here! -->\n\n<details><summary>Logs</summary>\n\n```\n<!-- Put your log content here!-->\n```\n\n</details>"
- validations:
- required: true
-
- - type: checkboxes
- attributes:
- label: Reminder
- options:
- - label: I agree that it's usually impossible for others to help me without my logs.
- required: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
deleted file mode 100644
index ac43772..0000000
--- a/.github/workflows/release.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-name: Create release
-
-on:
- push:
- tags:
- - 'v*'
-
-jobs:
- build-and-release:
- runs-on: ubuntu-latest
-
- permissions:
- contents: write
-
- steps:
- - uses: actions/checkout@v4
-
- - name: Create packages
- run: |
- mkdir -p release
- rsync -av \
- --exclude='release' \
- --exclude='.*' \
- --exclude='nix' \
- --exclude='flake.lock' \
- --exclude='flake.nix' \
- . release
- tar -czf caelestia-shell-${{ github.ref_name }}.tar.gz release
- cp caelestia-shell-${{ github.ref_name }}.tar.gz caelestia-shell-latest.tar.gz
-
- - name: Create release
- uses: softprops/action-gh-release@v2
- with:
- files: |
- caelestia-shell-${{ github.ref_name }}.tar.gz
- caelestia-shell-latest.tar.gz
- generate_release_notes: true
diff --git a/.github/workflows/update-flake-inputs.yml b/.github/workflows/update-flake-inputs.yml
deleted file mode 100644
index 1a8bd07..0000000
--- a/.github/workflows/update-flake-inputs.yml
+++ /dev/null
@@ -1,85 +0,0 @@
-name: Update flake inputs
-
-on:
- workflow_dispatch:
- schedule:
- - cron: '0 0 * * 0'
-
-jobs:
- update-flake:
- runs-on: ubuntu-latest
-
- permissions:
- contents: write
-
- steps:
- - uses: actions/checkout@v4
-
- - name: Install Nix
- uses: nixbuild/nix-quick-install-action@v31
- with:
- nix_conf: |
- keep-env-derivations = true
- keep-outputs = true
-
- - name: Restore and save Nix store
- uses: nix-community/cache-nix-action@v6
- with:
- # restore and save a cache using this key
- primary-key: nix-${{ hashFiles('**/*.nix', '**/flake.lock') }}
- # if there's no cache hit, restore a cache by this prefix
- restore-prefixes-first-match: nix-
- # collect garbage until the Nix store size (in bytes) is at most this number
- # before trying to save a new cache
- # 1G = 1073741824
- gc-max-store-size-linux: 1G
- # do purge caches
- purge: true
- # purge all versions of the cache
- purge-prefixes: nix-
- # created more than this number of seconds ago
- purge-created: 0
- # or, last accessed more than this number of seconds ago
- # relative to the start of the `Post Restore and save Nix store` phase
- purge-last-accessed: 0
- # except any version with the key that is the same as the `primary-key`
- purge-primary-key: never
-
- - name: Update flake inputs
- run: nix flake update
-
- - name: Attempt to build flake
- run: nix build
-
- - name: Test on Sway
- env:
- XDG_RUNTIME_DIR: /home/runner/runtime
- WLR_BACKENDS: headless
- WLR_LIBINPUT_NO_DEVICES: 1
- WAYLAND_DISPLAY: wayland-1
- run: |
- mkdir $XDG_RUNTIME_DIR
- chown $USER $XDG_RUNTIME_DIR
- chmod 0700 $XDG_RUNTIME_DIR
-
- nix profile install 'nixpkgs#sway'
- sway &
- sleep 3 # Give Sway some time to start
- result/bin/caelestia-shell -d
- sleep 3 # Give the shell some time to start (and die)
- pgrep .quickshell-wra # Fail job if shell died
-
- result/bin/caelestia-shell kill
- killall sway # Screw using IPC
-
- - name: Check for changes
- id: check
- run: echo modified=$(git diff --exit-code flake.lock &>/dev/null && echo 'false' || echo 'true') >> $GITHUB_OUTPUT
-
- - name: Commit and push changes
- if: steps.check.outputs.modified == 'true'
- uses: EndBug/add-and-commit@v9
- with:
- add: flake.lock
- default_author: github_actions
- message: "[CI] chore: update flake"
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 13763e4..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "editor.defaultFormatter": "theqtcompany.qt-qml",
- "[cpp]": {
- "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
- }
-}
diff --git a/assets/bongocat.gif b/assets/bongocat.gif
deleted file mode 100644
index f960fec..0000000
--- a/assets/bongocat.gif
+++ /dev/null
Binary files differ
diff --git a/assets/dino.png b/assets/dino.png
deleted file mode 100644
index b5bc7bb..0000000
--- a/assets/dino.png
+++ /dev/null
Binary files differ
diff --git a/assets/kurukuru.gif b/assets/kurukuru.gif
deleted file mode 100644
index 38d203d..0000000
--- a/assets/kurukuru.gif
+++ /dev/null
Binary files differ
diff --git a/assets/shaders/opacitymask.frag b/assets/shaders/opacitymask.frag
deleted file mode 100644
index 94a80b8..0000000
--- a/assets/shaders/opacitymask.frag
+++ /dev/null
@@ -1,19 +0,0 @@
-#version 440
-
-layout(location = 0) in vec2 qt_TexCoord0;
-layout(location = 0) out vec4 fragColor;
-
-layout(std140, binding = 0) uniform buf {
- // qt_Matrix and qt_Opacity must always be both present
- // if the built-in vertex shader is used.
- mat4 qt_Matrix;
- float qt_Opacity;
-};
-
-layout(binding = 1) uniform sampler2D source;
-layout(binding = 2) uniform sampler2D maskSource;
-
-void main()
-{
- fragColor = texture(source, qt_TexCoord0.st) * (texture(maskSource, qt_TexCoord0.st).a) * qt_Opacity;
-}
diff --git a/assets/shaders/opacitymask.frag.qsb b/assets/shaders/opacitymask.frag.qsb
deleted file mode 100644
index 7bf97c2..0000000
--- a/assets/shaders/opacitymask.frag.qsb
+++ /dev/null
Binary files differ
diff --git a/components/ConnectionHeader.qml b/components/ConnectionHeader.qml
deleted file mode 100644
index 12b4276..0000000
--- a/components/ConnectionHeader.qml
+++ /dev/null
@@ -1,31 +0,0 @@
-import qs.components
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-ColumnLayout {
- id: root
-
- required property string icon
- required property string title
-
- spacing: Appearance.spacing.normal
- Layout.alignment: Qt.AlignHCenter
-
- MaterialIcon {
- Layout.alignment: Qt.AlignHCenter
- animate: true
- text: root.icon
- font.pointSize: Appearance.font.size.extraLarge * 3
- font.bold: true
- }
-
- StyledText {
- Layout.alignment: Qt.AlignHCenter
- animate: true
- text: root.title
- font.pointSize: Appearance.font.size.large
- font.bold: true
- }
-}
diff --git a/components/ConnectionInfoSection.qml b/components/ConnectionInfoSection.qml
deleted file mode 100644
index 927ef28..0000000
--- a/components/ConnectionInfoSection.qml
+++ /dev/null
@@ -1,59 +0,0 @@
-import qs.components
-import qs.components.effects
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-ColumnLayout {
- id: root
-
- required property var deviceDetails
-
- spacing: Appearance.spacing.small / 2
-
- StyledText {
- text: qsTr("IP Address")
- }
-
- StyledText {
- text: root.deviceDetails?.ipAddress || qsTr("Not available")
- color: Colours.palette.m3outline
- font.pointSize: Appearance.font.size.small
- }
-
- StyledText {
- Layout.topMargin: Appearance.spacing.normal
- text: qsTr("Subnet Mask")
- }
-
- StyledText {
- text: root.deviceDetails?.subnet || qsTr("Not available")
- color: Colours.palette.m3outline
- font.pointSize: Appearance.font.size.small
- }
-
- StyledText {
- Layout.topMargin: Appearance.spacing.normal
- text: qsTr("Gateway")
- }
-
- StyledText {
- text: root.deviceDetails?.gateway || qsTr("Not available")
- color: Colours.palette.m3outline
- font.pointSize: Appearance.font.size.small
- }
-
- StyledText {
- Layout.topMargin: Appearance.spacing.normal
- text: qsTr("DNS Servers")
- }
-
- StyledText {
- text: (root.deviceDetails && root.deviceDetails.dns && root.deviceDetails.dns.length > 0) ? root.deviceDetails.dns.join(", ") : qsTr("Not available")
- color: Colours.palette.m3outline
- font.pointSize: Appearance.font.size.small
- wrapMode: Text.Wrap
- Layout.maximumWidth: parent.width
- }
-}
diff --git a/components/PropertyRow.qml b/components/PropertyRow.qml
deleted file mode 100644
index 640d5f7..0000000
--- a/components/PropertyRow.qml
+++ /dev/null
@@ -1,26 +0,0 @@
-import qs.components
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-ColumnLayout {
- id: root
-
- required property string label
- required property string value
- property bool showTopMargin: false
-
- spacing: Appearance.spacing.small / 2
-
- StyledText {
- Layout.topMargin: root.showTopMargin ? Appearance.spacing.normal : 0
- text: root.label
- }
-
- StyledText {
- text: root.value
- color: Colours.palette.m3outline
- font.pointSize: Appearance.font.size.small
- }
-}
diff --git a/components/SectionContainer.qml b/components/SectionContainer.qml
deleted file mode 100644
index 2b653a5..0000000
--- a/components/SectionContainer.qml
+++ /dev/null
@@ -1,32 +0,0 @@
-import qs.components
-import qs.components.effects
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-StyledRect {
- id: root
-
- default property alias content: contentColumn.data
- property real contentSpacing: Appearance.spacing.larger
- property bool alignTop: false
-
- Layout.fillWidth: true
- implicitHeight: contentColumn.implicitHeight + Appearance.padding.large * 2
-
- radius: Appearance.rounding.normal
- color: Colours.transparency.enabled ? Colours.layer(Colours.palette.m3surfaceContainer, 2) : Colours.palette.m3surfaceContainerHigh
-
- ColumnLayout {
- id: contentColumn
-
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: root.alignTop ? parent.top : undefined
- anchors.verticalCenter: root.alignTop ? undefined : parent.verticalCenter
- anchors.margins: Appearance.padding.large
-
- spacing: root.contentSpacing
- }
-}
diff --git a/components/SectionHeader.qml b/components/SectionHeader.qml
deleted file mode 100644
index 502e918..0000000
--- a/components/SectionHeader.qml
+++ /dev/null
@@ -1,27 +0,0 @@
-import qs.components
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-ColumnLayout {
- id: root
-
- required property string title
- property string description: ""
-
- spacing: 0
-
- StyledText {
- Layout.topMargin: Appearance.spacing.large
- text: root.title
- font.pointSize: Appearance.font.size.larger
- font.weight: 500
- }
-
- StyledText {
- visible: root.description !== ""
- text: root.description
- color: Colours.palette.m3outline
- }
-}
diff --git a/components/containers/StyledFlickable.qml b/components/containers/StyledFlickable.qml
deleted file mode 100644
index bc6ae0f..0000000
--- a/components/containers/StyledFlickable.qml
+++ /dev/null
@@ -1,14 +0,0 @@
-import ".."
-import QtQuick
-
-Flickable {
- id: root
-
- maximumFlickVelocity: 3000
-
- rebound: Transition {
- Anim {
- properties: "x,y"
- }
- }
-}
diff --git a/components/controls/CollapsibleSection.qml b/components/controls/CollapsibleSection.qml
deleted file mode 100644
index e3d8eef..0000000
--- a/components/controls/CollapsibleSection.qml
+++ /dev/null
@@ -1,132 +0,0 @@
-import ".."
-import qs.components
-import qs.components.effects
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-ColumnLayout {
- id: root
-
- required property string title
- property string description: ""
- property bool expanded: false
- property bool showBackground: false
- property bool nested: false
-
- signal toggleRequested
-
- spacing: Appearance.spacing.small
- Layout.fillWidth: true
-
- Item {
- id: sectionHeaderItem
- Layout.fillWidth: true
- Layout.preferredHeight: Math.max(titleRow.implicitHeight + Appearance.padding.normal * 2, 48)
-
- RowLayout {
- id: titleRow
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- anchors.leftMargin: Appearance.padding.normal
- anchors.rightMargin: Appearance.padding.normal
- spacing: Appearance.spacing.normal
-
- StyledText {
- text: root.title
- font.pointSize: Appearance.font.size.larger
- font.weight: 500
- }
-
- Item {
- Layout.fillWidth: true
- }
-
- MaterialIcon {
- text: "expand_more"
- rotation: root.expanded ? 180 : 0
- color: Colours.palette.m3onSurfaceVariant
- font.pointSize: Appearance.font.size.normal
- Behavior on rotation {
- Anim {
- duration: Appearance.anim.durations.small
- easing.bezierCurve: Appearance.anim.curves.standard
- }
- }
- }
- }
-
- StateLayer {
- anchors.fill: parent
- color: Colours.palette.m3onSurface
- radius: Appearance.rounding.normal
- showHoverBackground: false
- function onClicked(): void {
- root.toggleRequested();
- root.expanded = !root.expanded;
- }
- }
- }
-
- default property alias content: contentColumn.data
-
- Item {
- id: contentWrapper
- Layout.fillWidth: true
- Layout.preferredHeight: root.expanded ? (contentColumn.implicitHeight + Appearance.spacing.small * 2) : 0
- clip: true
-
- Behavior on Layout.preferredHeight {
- Anim {
- easing.bezierCurve: Appearance.anim.curves.standard
- }
- }
-
- StyledRect {
- id: backgroundRect
- anchors.fill: parent
- radius: Appearance.rounding.normal
- color: Colours.transparency.enabled ? Colours.layer(Colours.palette.m3surfaceContainer, root.nested ? 3 : 2) : (root.nested ? Colours.palette.m3surfaceContainerHigh : Colours.palette.m3surfaceContainer)
- opacity: root.showBackground && root.expanded ? 1.0 : 0.0
- visible: root.showBackground
-
- Behavior on opacity {
- Anim {
- easing.bezierCurve: Appearance.anim.curves.standard
- }
- }
- }
-
- ColumnLayout {
- id: contentColumn
- anchors.left: parent.left
- anchors.right: parent.right
- y: Appearance.spacing.small
- anchors.leftMargin: Appearance.padding.normal
- anchors.rightMargin: Appearance.padding.normal
- anchors.bottomMargin: Appearance.spacing.small
- spacing: Appearance.spacing.small
- opacity: root.expanded ? 1.0 : 0.0
-
- Behavior on opacity {
- Anim {
- easing.bezierCurve: Appearance.anim.curves.standard
- }
- }
-
- StyledText {
- id: descriptionText
- Layout.fillWidth: true
- Layout.topMargin: root.description !== "" ? Appearance.spacing.smaller : 0
- Layout.bottomMargin: root.description !== "" ? Appearance.spacing.small : 0
- visible: root.description !== ""
- text: root.description
- color: Colours.palette.m3onSurfaceVariant
- font.pointSize: Appearance.font.size.small
- wrapMode: Text.Wrap
- }
- }
- }
-}
diff --git a/components/controls/CustomSpinBox.qml b/components/controls/CustomSpinBox.qml
deleted file mode 100644
index 438dc08..0000000
--- a/components/controls/CustomSpinBox.qml
+++ /dev/null
@@ -1,170 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import ".."
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-RowLayout {
- id: root
-
- property real value
- property real max: Infinity
- property real min: -Infinity
- property real step: 1
- property alias repeatRate: timer.interval
-
- signal valueModified(value: real)
-
- spacing: Appearance.spacing.small
-
- property bool isEditing: false
- property string displayText: root.value.toString()
-
- onValueChanged: {
- if (!root.isEditing) {
- root.displayText = root.value.toString();
- }
- }
-
- StyledTextField {
- id: textField
-
- inputMethodHints: Qt.ImhFormattedNumbersOnly
- text: root.isEditing ? text : root.displayText
- validator: DoubleValidator {
- bottom: root.min
- top: root.max
- decimals: root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0
- }
- onActiveFocusChanged: {
- if (activeFocus) {
- root.isEditing = true;
- } else {
- root.isEditing = false;
- root.displayText = root.value.toString();
- }
- }
- onAccepted: {
- const numValue = parseFloat(text);
- if (!isNaN(numValue)) {
- const clampedValue = Math.max(root.min, Math.min(root.max, numValue));
- root.value = clampedValue;
- root.displayText = clampedValue.toString();
- root.valueModified(clampedValue);
- } else {
- text = root.displayText;
- }
- root.isEditing = false;
- }
- onEditingFinished: {
- if (text !== root.displayText) {
- const numValue = parseFloat(text);
- if (!isNaN(numValue)) {
- const clampedValue = Math.max(root.min, Math.min(root.max, numValue));
- root.value = clampedValue;
- root.displayText = clampedValue.toString();
- root.valueModified(clampedValue);
- } else {
- text = root.displayText;
- }
- }
- root.isEditing = false;
- }
-
- padding: Appearance.padding.small
- leftPadding: Appearance.padding.normal
- rightPadding: Appearance.padding.normal
-
- background: StyledRect {
- implicitWidth: 100
- radius: Appearance.rounding.small
- color: Colours.tPalette.m3surfaceContainerHigh
- }
- }
-
- StyledRect {
- radius: Appearance.rounding.small
- color: Colours.palette.m3primary
-
- implicitWidth: implicitHeight
- implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2
-
- StateLayer {
- id: upState
-
- color: Colours.palette.m3onPrimary
-
- onPressAndHold: timer.start()
- onReleased: timer.stop()
-
- function onClicked(): void {
- let newValue = Math.min(root.max, root.value + root.step);
- // Round to avoid floating point precision errors
- const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0;
- newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals);
- root.value = newValue;
- root.displayText = newValue.toString();
- root.valueModified(newValue);
- }
- }
-
- MaterialIcon {
- id: upIcon
-
- anchors.centerIn: parent
- text: "keyboard_arrow_up"
- color: Colours.palette.m3onPrimary
- }
- }
-
- StyledRect {
- radius: Appearance.rounding.small
- color: Colours.palette.m3primary
-
- implicitWidth: implicitHeight
- implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2
-
- StateLayer {
- id: downState
-
- color: Colours.palette.m3onPrimary
-
- onPressAndHold: timer.start()
- onReleased: timer.stop()
-
- function onClicked(): void {
- let newValue = Math.max(root.min, root.value - root.step);
- // Round to avoid floating point precision errors
- const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0;
- newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals);
- root.value = newValue;
- root.displayText = newValue.toString();
- root.valueModified(newValue);
- }
- }
-
- MaterialIcon {
- id: downIcon
-
- anchors.centerIn: parent
- text: "keyboard_arrow_down"
- color: Colours.palette.m3onPrimary
- }
- }
-
- Timer {
- id: timer
-
- interval: 100
- repeat: true
- triggeredOnStart: true
- onTriggered: {
- if (upState.pressed)
- upState.onClicked();
- else if (downState.pressed)
- downState.onClicked();
- }
- }
-}
diff --git a/components/controls/SpinBoxRow.qml b/components/controls/SpinBoxRow.qml
deleted file mode 100644
index fe6a198..0000000
--- a/components/controls/SpinBoxRow.qml
+++ /dev/null
@@ -1,52 +0,0 @@
-import ".."
-import qs.components
-import qs.components.effects
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-StyledRect {
- id: root
-
- required property string label
- required property real value
- required property real min
- required property real max
- property real step: 1
- property var onValueModified: function (value) {}
-
- Layout.fillWidth: true
- implicitHeight: row.implicitHeight + Appearance.padding.large * 2
- radius: Appearance.rounding.normal
- color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
-
- Behavior on implicitHeight {
- Anim {}
- }
-
- RowLayout {
- id: row
-
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- anchors.margins: Appearance.padding.large
- spacing: Appearance.spacing.normal
-
- StyledText {
- Layout.fillWidth: true
- text: root.label
- }
-
- CustomSpinBox {
- min: root.min
- max: root.max
- step: root.step
- value: root.value
- onValueModified: value => {
- root.onValueModified(value);
- }
- }
- }
-}
diff --git a/components/controls/SplitButton.qml b/components/controls/SplitButton.qml
deleted file mode 100644
index c91474e..0000000
--- a/components/controls/SplitButton.qml
+++ /dev/null
@@ -1,164 +0,0 @@
-import ".."
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-Row {
- id: root
-
- enum Type {
- Filled,
- Tonal
- }
-
- property real horizontalPadding: Appearance.padding.normal
- property real verticalPadding: Appearance.padding.smaller
- property int type: SplitButton.Filled
- property bool disabled
- property bool menuOnTop
- property string fallbackIcon
- property string fallbackText
-
- property alias menuItems: menu.items
- property alias active: menu.active
- property alias expanded: menu.expanded
- property alias menu: menu
- property alias iconLabel: iconLabel
- property alias label: label
- property alias stateLayer: stateLayer
-
- property color colour: type == SplitButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
- property color textColour: type == SplitButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
- property color disabledColour: Qt.alpha(Colours.palette.m3onSurface, 0.1)
- property color disabledTextColour: Qt.alpha(Colours.palette.m3onSurface, 0.38)
-
- spacing: Math.floor(Appearance.spacing.small / 2)
-
- StyledRect {
- radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
- topRightRadius: Appearance.rounding.small / 2
- bottomRightRadius: Appearance.rounding.small / 2
- color: root.disabled ? root.disabledColour : root.colour
-
- implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2
- implicitHeight: expandBtn.implicitHeight
-
- StateLayer {
- id: stateLayer
-
- rect.topRightRadius: parent.topRightRadius
- rect.bottomRightRadius: parent.bottomRightRadius
- color: root.textColour
- disabled: root.disabled
-
- function onClicked(): void {
- root.active?.clicked();
- }
- }
-
- RowLayout {
- id: textRow
-
- anchors.centerIn: parent
- anchors.horizontalCenterOffset: Math.floor(root.verticalPadding / 4)
- spacing: Appearance.spacing.small
-
- MaterialIcon {
- id: iconLabel
-
- Layout.alignment: Qt.AlignVCenter
- animate: true
- text: root.active?.activeIcon ?? root.fallbackIcon
- color: root.disabled ? root.disabledTextColour : root.textColour
- fill: 1
- }
-
- StyledText {
- id: label
-
- Layout.alignment: Qt.AlignVCenter
- Layout.preferredWidth: implicitWidth
- animate: true
- text: root.active?.activeText ?? root.fallbackText
- color: root.disabled ? root.disabledTextColour : root.textColour
- clip: true
-
- Behavior on Layout.preferredWidth {
- Anim {
- easing.bezierCurve: Appearance.anim.curves.emphasized
- }
- }
- }
- }
- }
-
- StyledRect {
- id: expandBtn
-
- property real rad: root.expanded ? implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) : Appearance.rounding.small / 2
-
- radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
- topLeftRadius: rad
- bottomLeftRadius: rad
- color: root.disabled ? root.disabledColour : root.colour
-
- implicitWidth: implicitHeight
- implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2
-
- StateLayer {
- id: expandStateLayer
-
- rect.topLeftRadius: parent.topLeftRadius
- rect.bottomLeftRadius: parent.bottomLeftRadius
- color: root.textColour
- disabled: root.disabled
-
- function onClicked(): void {
- root.expanded = !root.expanded;
- }
- }
-
- MaterialIcon {
- id: expandIcon
-
- anchors.centerIn: parent
- anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
-
- text: "expand_more"
- color: root.disabled ? root.disabledTextColour : root.textColour
- rotation: root.expanded ? 180 : 0
-
- Behavior on anchors.horizontalCenterOffset {
- Anim {}
- }
-
- Behavior on rotation {
- Anim {}
- }
- }
-
- Behavior on rad {
- Anim {}
- }
-
- Menu {
- id: menu
-
- states: State {
- when: root.menuOnTop
-
- AnchorChanges {
- target: menu
- anchors.top: undefined
- anchors.bottom: expandBtn.top
- }
- }
-
- anchors.top: parent.bottom
- anchors.right: parent.right
- anchors.topMargin: Appearance.spacing.small
- anchors.bottomMargin: Appearance.spacing.small
- }
- }
-}
diff --git a/components/controls/SplitButtonRow.qml b/components/controls/SplitButtonRow.qml
deleted file mode 100644
index db9925f..0000000
--- a/components/controls/SplitButtonRow.qml
+++ /dev/null
@@ -1,62 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import ".."
-import qs.components
-import qs.components.effects
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-StyledRect {
- id: root
-
- required property string label
- property int expandedZ: 100
- property bool enabled: true
-
- property alias menuItems: splitButton.menuItems
- property alias active: splitButton.active
- property alias expanded: splitButton.expanded
- property alias type: splitButton.type
-
- signal selected(item: MenuItem)
-
- Layout.fillWidth: true
- implicitHeight: row.implicitHeight + Appearance.padding.large * 2
- radius: Appearance.rounding.normal
- color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
-
- clip: false
- z: splitButton.menu.implicitHeight > 0 ? expandedZ : 1
- opacity: enabled ? 1.0 : 0.5
-
- RowLayout {
- id: row
- anchors.fill: parent
- anchors.margins: Appearance.padding.large
- spacing: Appearance.spacing.normal
-
- StyledText {
- Layout.fillWidth: true
- text: root.label
- color: root.enabled ? Colours.palette.m3onSurface : Colours.palette.m3onSurfaceVariant
- }
-
- SplitButton {
- id: splitButton
- enabled: root.enabled
- type: SplitButton.Filled
-
- menu.z: 1
-
- stateLayer.onClicked: {
- splitButton.expanded = !splitButton.expanded;
- }
-
- menu.onItemSelected: item => {
- root.selected(item);
- }
- }
- }
-}
diff --git a/components/controls/StyledInputField.qml b/components/controls/StyledInputField.qml
deleted file mode 100644
index 0d199c7..0000000
--- a/components/controls/StyledInputField.qml
+++ /dev/null
@@ -1,79 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import ".."
-import qs.components
-import qs.services
-import qs.config
-import QtQuick
-
-Item {
- id: root
-
- property string text: ""
- property var validator: null
- property bool readOnly: false
- property int horizontalAlignment: TextInput.AlignHCenter
- property int implicitWidth: 70
- property bool enabled: true
-
- // Expose activeFocus through alias to avoid FINAL property override
- readonly property alias hasFocus: inputField.activeFocus
-
- signal textEdited(string text)
- signal editingFinished
-
- implicitHeight: inputField.implicitHeight + Appearance.padding.small * 2
-
- StyledRect {
- id: container
-
- anchors.fill: parent
- color: inputHover.containsMouse || inputField.activeFocus ? Colours.layer(Colours.palette.m3surfaceContainer, 3) : Colours.layer(Colours.palette.m3surfaceContainer, 2)
- radius: Appearance.rounding.small
- border.width: 1
- border.color: inputField.activeFocus ? Colours.palette.m3primary : Qt.alpha(Colours.palette.m3outline, 0.3)
- opacity: root.enabled ? 1 : 0.5
-
- Behavior on color {
- CAnim {}
- }
- Behavior on border.color {
- CAnim {}
- }
-
- MouseArea {
- id: inputHover
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.IBeamCursor
- acceptedButtons: Qt.NoButton
- enabled: root.enabled
- }
-
- StyledTextField {
- id: inputField
- anchors.centerIn: parent
- width: parent.width - Appearance.padding.normal
- horizontalAlignment: root.horizontalAlignment
- validator: root.validator
- readOnly: root.readOnly
- enabled: root.enabled
-
- Binding {
- target: inputField
- property: "text"
- value: root.text
- when: !inputField.activeFocus
- }
-
- onTextChanged: {
- root.text = text;
- root.textEdited(text);
- }
-
- onEditingFinished: {
- root.editingFinished();
- }
- }
- }
-}
diff --git a/components/controls/SwitchRow.qml b/components/controls/SwitchRow.qml
deleted file mode 100644
index 6dda3f0..0000000
--- a/components/controls/SwitchRow.qml
+++ /dev/null
@@ -1,48 +0,0 @@
-import ".."
-import qs.components
-import qs.components.effects
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-StyledRect {
- id: root
-
- required property string label
- required property bool checked
- property bool enabled: true
- property var onToggled: function (checked) {}
-
- Layout.fillWidth: true
- implicitHeight: row.implicitHeight + Appearance.padding.large * 2
- radius: Appearance.rounding.normal
- color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
-
- Behavior on implicitHeight {
- Anim {}
- }
-
- RowLayout {
- id: row
-
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- anchors.margins: Appearance.padding.large
- spacing: Appearance.spacing.normal
-
- StyledText {
- Layout.fillWidth: true
- text: root.label
- }
-
- StyledSwitch {
- checked: root.checked
- enabled: root.enabled
- onToggled: {
- root.onToggled(checked);
- }
- }
- }
-}
diff --git a/components/controls/ToggleButton.qml b/components/controls/ToggleButton.qml
deleted file mode 100644
index 98c7564..0000000
--- a/components/controls/ToggleButton.qml
+++ /dev/null
@@ -1,124 +0,0 @@
-import ".."
-import qs.components
-import qs.components.controls
-import qs.components.effects
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-StyledRect {
- id: root
-
- required property bool toggled
- property string icon
- property string label
- property string accent: "Secondary"
- property real iconSize: Appearance.font.size.large
- property real horizontalPadding: Appearance.padding.large
- property real verticalPadding: Appearance.padding.normal
- property string tooltip: ""
-
- property bool hovered: false
- signal clicked
-
- Component.onCompleted: {
- hovered = toggleStateLayer.containsMouse;
- }
-
- Connections {
- target: toggleStateLayer
- function onContainsMouseChanged() {
- const newHovered = toggleStateLayer.containsMouse;
- if (hovered !== newHovered) {
- hovered = newHovered;
- }
- }
- }
-
- Layout.preferredWidth: implicitWidth + (toggleStateLayer.pressed ? Appearance.padding.normal * 2 : toggled ? Appearance.padding.small * 2 : 0)
- implicitWidth: toggleBtnInner.implicitWidth + horizontalPadding * 2
- implicitHeight: toggleBtnIcon.implicitHeight + verticalPadding * 2
-
- radius: toggled || toggleStateLayer.pressed ? Appearance.rounding.small : Math.min(width, height) / 2 * Math.min(1, Appearance.rounding.scale)
- color: toggled ? Colours.palette[`m3${accent.toLowerCase()}`] : Colours.palette[`m3${accent.toLowerCase()}Container`]
-
- StateLayer {
- id: toggleStateLayer
-
- color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`]
-
- function onClicked(): void {
- root.clicked();
- }
- }
-
- RowLayout {
- id: toggleBtnInner
-
- anchors.centerIn: parent
- spacing: Appearance.spacing.normal
-
- MaterialIcon {
- id: toggleBtnIcon
-
- visible: !!text
- fill: root.toggled ? 1 : 0
- text: root.icon
- color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`]
- font.pointSize: root.iconSize
-
- Behavior on fill {
- Anim {}
- }
- }
-
- Loader {
- active: !!root.label
- visible: active
-
- sourceComponent: StyledText {
- text: root.label
- color: root.toggled ? Colours.palette[`m3on${root.accent}`] : Colours.palette[`m3on${root.accent}Container`]
- }
- }
- }
-
- Behavior on radius {
- Anim {
- duration: Appearance.anim.durations.expressiveFastSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
- }
- }
-
- Behavior on Layout.preferredWidth {
- Anim {
- duration: Appearance.anim.durations.expressiveFastSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
- }
- }
-
- // Tooltip - positioned absolutely, doesn't affect layout
- Loader {
- id: tooltipLoader
- active: root.tooltip !== ""
- z: 10000
- width: 0
- height: 0
- sourceComponent: Component {
- Tooltip {
- target: root
- text: root.tooltip
- }
- }
- // Completely remove from layout
- Layout.fillWidth: false
- Layout.fillHeight: false
- Layout.preferredWidth: 0
- Layout.preferredHeight: 0
- Layout.maximumWidth: 0
- Layout.maximumHeight: 0
- Layout.minimumWidth: 0
- Layout.minimumHeight: 0
- }
-}
diff --git a/components/controls/ToggleRow.qml b/components/controls/ToggleRow.qml
deleted file mode 100644
index 269d3d6..0000000
--- a/components/controls/ToggleRow.qml
+++ /dev/null
@@ -1,28 +0,0 @@
-import qs.components
-import qs.components.controls
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-RowLayout {
- id: root
-
- required property string label
- property alias checked: toggle.checked
- property alias toggle: toggle
-
- Layout.fillWidth: true
- spacing: Appearance.spacing.normal
-
- StyledText {
- Layout.fillWidth: true
- text: root.label
- }
-
- StyledSwitch {
- id: toggle
-
- cLayer: 2
- }
-}
diff --git a/components/controls/Tooltip.qml b/components/controls/Tooltip.qml
deleted file mode 100644
index b129a37..0000000
--- a/components/controls/Tooltip.qml
+++ /dev/null
@@ -1,185 +0,0 @@
-import ".."
-import qs.components.effects
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-
-Popup {
- id: root
-
- required property Item target
- required property string text
- property int delay: 500
- property int timeout: 0
-
- property bool tooltipVisible: false
- property Timer showTimer: Timer {
- interval: root.delay
- onTriggered: root.tooltipVisible = true
- }
- property Timer hideTimer: Timer {
- interval: root.timeout
- onTriggered: root.tooltipVisible = false
- }
-
- // Popup properties - doesn't affect layout
- parent: {
- let p = target;
- // Walk up to find the root Item (usually has anchors.fill: parent)
- while (p && p.parent) {
- const parentItem = p.parent;
- // Check if this looks like a root pane Item
- if (parentItem && parentItem.anchors && parentItem.anchors.fill !== undefined) {
- return parentItem;
- }
- p = parentItem;
- }
- // Fallback
- return target.parent?.parent?.parent ?? target.parent?.parent ?? target.parent ?? target;
- }
-
- visible: tooltipVisible
- modal: false
- closePolicy: Popup.NoAutoClose
- padding: 0
- margins: 0
- background: Item {}
-
- // Update position when target moves or tooltip becomes visible
- onTooltipVisibleChanged: {
- if (tooltipVisible) {
- Qt.callLater(updatePosition);
- }
- }
- Connections {
- target: root.target
- function onXChanged() {
- if (root.tooltipVisible)
- root.updatePosition();
- }
- function onYChanged() {
- if (root.tooltipVisible)
- root.updatePosition();
- }
- function onWidthChanged() {
- if (root.tooltipVisible)
- root.updatePosition();
- }
- function onHeightChanged() {
- if (root.tooltipVisible)
- root.updatePosition();
- }
- }
-
- function updatePosition() {
- if (!target || !parent)
- return;
-
- // Wait for tooltipRect to have its size calculated
- Qt.callLater(() => {
- if (!target || !parent || !tooltipRect)
- return;
-
- // Get target position in parent's coordinate system
- const targetPos = target.mapToItem(parent, 0, 0);
- const targetCenterX = targetPos.x + target.width / 2;
-
- // Get tooltip size (use width/height if available, otherwise implicit)
- const tooltipWidth = tooltipRect.width > 0 ? tooltipRect.width : tooltipRect.implicitWidth;
- const tooltipHeight = tooltipRect.height > 0 ? tooltipRect.height : tooltipRect.implicitHeight;
-
- // Center tooltip horizontally on target
- let newX = targetCenterX - tooltipWidth / 2;
-
- // Position tooltip above target
- let newY = targetPos.y - tooltipHeight - Appearance.spacing.small;
-
- // Keep within bounds
- const padding = Appearance.padding.normal;
- if (newX < padding) {
- newX = padding;
- } else if (newX + tooltipWidth > (parent.width - padding)) {
- newX = parent.width - tooltipWidth - padding;
- }
-
- // Update popup position
- x = newX;
- y = newY;
- });
- }
-
- enter: Transition {
- Anim {
- property: "opacity"
- from: 0
- to: 1
- duration: Appearance.anim.durations.expressiveFastSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
- }
- }
-
- exit: Transition {
- Anim {
- property: "opacity"
- from: 1
- to: 0
- duration: Appearance.anim.durations.expressiveFastSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
- }
- }
-
- // Monitor hover state
- Connections {
- target: root.target
- function onHoveredChanged() {
- if (target.hovered) {
- showTimer.start();
- if (timeout > 0) {
- hideTimer.stop();
- hideTimer.start();
- }
- } else {
- showTimer.stop();
- hideTimer.stop();
- tooltipVisible = false;
- }
- }
- }
-
- contentItem: StyledRect {
- id: tooltipRect
-
- implicitWidth: tooltipText.implicitWidth + Appearance.padding.normal * 2
- implicitHeight: tooltipText.implicitHeight + Appearance.padding.smaller * 2
-
- color: Colours.palette.m3surfaceContainerHighest
- radius: Appearance.rounding.small
- antialiasing: true
-
- // Add elevation for depth
- Elevation {
- anchors.fill: parent
- radius: parent.radius
- z: -1
- level: 3
- }
-
- StyledText {
- id: tooltipText
-
- anchors.centerIn: parent
-
- text: root.text
- color: Colours.palette.m3onSurface
- font.pointSize: Appearance.font.size.small
- }
- }
-
- Component.onCompleted: {
- if (tooltipVisible) {
- updatePosition();
- }
- }
-}
diff --git a/components/effects/InnerBorder.qml b/components/effects/InnerBorder.qml
deleted file mode 100644
index d4a751f..0000000
--- a/components/effects/InnerBorder.qml
+++ /dev/null
@@ -1,44 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import ".."
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Effects
-
-StyledRect {
- property alias innerRadius: maskInner.radius
- property alias thickness: maskInner.anchors.margins
- property alias leftThickness: maskInner.anchors.leftMargin
- property alias topThickness: maskInner.anchors.topMargin
- property alias rightThickness: maskInner.anchors.rightMargin
- property alias bottomThickness: maskInner.anchors.bottomMargin
-
- anchors.fill: parent
- color: Colours.tPalette.m3surfaceContainer
-
- layer.enabled: true
- layer.effect: MultiEffect {
- maskSource: mask
- maskEnabled: true
- maskInverted: true
- maskThresholdMin: 0.5
- maskSpreadAtMin: 1
- }
-
- Item {
- id: mask
-
- anchors.fill: parent
- layer.enabled: true
- visible: false
-
- Rectangle {
- id: maskInner
-
- anchors.fill: parent
- anchors.margins: Appearance.padding.normal
- radius: Appearance.rounding.small
- }
- }
-}
diff --git a/components/effects/OpacityMask.qml b/components/effects/OpacityMask.qml
deleted file mode 100644
index 22e4249..0000000
--- a/components/effects/OpacityMask.qml
+++ /dev/null
@@ -1,9 +0,0 @@
-import Quickshell
-import QtQuick
-
-ShaderEffect {
- required property Item source
- required property Item maskSource
-
- fragmentShader: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/shaders/opacitymask.frag.qsb`)
-}
diff --git a/components/misc/Ref.qml b/components/misc/Ref.qml
deleted file mode 100644
index 0a694a4..0000000
--- a/components/misc/Ref.qml
+++ /dev/null
@@ -1,8 +0,0 @@
-import QtQuick
-
-QtObject {
- required property var service
-
- Component.onCompleted: service.refCount++
- Component.onDestruction: service.refCount--
-}
diff --git a/config/AppearanceConfig.qml b/config/AppearanceConfig.qml
index 3d590dc..faa4f1b 100644
--- a/config/AppearanceConfig.qml
+++ b/config/AppearanceConfig.qml
@@ -80,7 +80,6 @@ JsonObject {
}
component Anim: JsonObject {
- property real mediaGifSpeedAdjustment: 300
property real sessionGifSpeed: 0.7
property AnimCurves curves: AnimCurves {}
property AnimDurations durations: AnimDurations {}
diff --git a/config/Config.qml b/config/Config.qml
index e5029b2..8944617 100644
--- a/config/Config.qml
+++ b/config/Config.qml
@@ -14,13 +14,10 @@ Singleton {
property alias background: adapter.background
property alias bar: adapter.bar
property alias border: adapter.border
- property alias dashboard: adapter.dashboard
- property alias controlCenter: adapter.controlCenter
property alias launcher: adapter.launcher
property alias notifs: adapter.notifs
property alias osd: adapter.osd
property alias session: adapter.session
- property alias sidebar: adapter.sidebar
property alias services: adapter.services
property alias toasts: adapter.toasts
property alias paths: adapter.paths
@@ -33,13 +30,10 @@ Singleton {
background: serializeBackground(),
bar: serializeBar(),
border: serializeBorder(),
- dashboard: serializeDashboard(),
- controlCenter: serializeControlCenter(),
launcher: serializeLauncher(),
notifs: serializeNotifs(),
osd: serializeOsd(),
session: serializeSession(),
- sidebar: serializeSidebar(),
services: serializeServices(),
toasts: serializeToasts(),
paths: serializePaths(),
@@ -69,7 +63,6 @@ Singleton {
}
},
anim: {
- mediaGifSpeedAdjustment: 300,
sessionGifSpeed: 0.7,
durations: {
scale: appearance.anim.durations.scale
@@ -87,12 +80,7 @@ Singleton {
return {
logo: general.logo,
excludedScreens: general.excludedScreens,
- apps: {
- terminal: general.apps.terminal,
- audio: general.apps.audio,
- playback: general.apps.playback,
- explorer: general.apps.explorer
- },
+ terminal: general.terminal,
battery: {
warnLevels: general.battery.warnLevels,
criticalLevel: general.battery.criticalLevel
@@ -173,24 +161,6 @@ Singleton {
};
}
- function serializeDashboard(): var {
- return {
- enabled: dashboard.enabled,
- showOnHover: dashboard.showOnHover,
- mediaUpdateInterval: dashboard.mediaUpdateInterval,
- resourceUpdateInterval: dashboard.resourceUpdateInterval,
- dragThreshold: dashboard.dragThreshold,
- performance: {
- showBattery: dashboard.performance.showBattery,
- showGpu: dashboard.performance.showGpu,
- showCpu: dashboard.performance.showCpu,
- showMemory: dashboard.performance.showMemory,
- showStorage: dashboard.performance.showStorage,
- showNetwork: dashboard.performance.showNetwork
- }
- };
- }
-
function serializeControlCenter(): var {
return {};
}
@@ -257,26 +227,12 @@ Singleton {
};
}
- function serializeSidebar(): var {
- return {
- enabled: sidebar.enabled,
- dragThreshold: sidebar.dragThreshold
- };
- }
-
function serializeServices(): var {
return {
- weatherLocation: services.weatherLocation,
- useFahrenheit: services.useFahrenheit,
- useFahrenheitPerformance: services.useFahrenheitPerformance,
useTwelveHourClock: services.useTwelveHourClock,
- gpuType: services.gpuType,
- visualiserBars: services.visualiserBars,
audioIncrement: services.audioIncrement,
brightnessIncrement: services.brightnessIncrement,
maxVolume: services.maxVolume,
- defaultPlayer: services.defaultPlayer,
- playerAliases: services.playerAliases
};
}
@@ -284,14 +240,12 @@ Singleton {
return {
configLoaded: toats.configLoaded,
chargingChanged: toats.chargingChanged,
- dndChanged: toats.dndChanged,
audioOutputChanged: toats.audioOutputChanged,
audioInputChanged: toats.audioInputChanged,
capsLockChanged: toats.capsLockChanged,
numLockChanged: toats.numLockChanged,
kbLayoutChanged: toats.kbLayoutChanged,
kbLimit: toats.kbLimit,
- nowPlaying: toats.nowPlaying,
};
}
@@ -299,7 +253,6 @@ Singleton {
return {
wallpaper: paths.wallpaper,
sessionGif: paths.sessionGif,
- mediaGif: paths.mediaGif
};
}
@@ -331,13 +284,10 @@ Singleton {
property BackgroundConfig background: BackgroundConfig {}
property BarConfig bar: BarConfig {}
property BorderConfig border: BorderConfig {}
- property DashboardConfig dashboard: DashboardConfig {}
- property ControlCenterConfig controlCenter: ControlCenterConfig {}
property LauncherConfig launcher: LauncherConfig {}
property NotifsConfig notifs: NotifsConfig {}
property OsdConfig osd: OsdConfig {}
property SessionConfig session: SessionConfig {}
- property SidebarConfig sidebar: SidebarConfig {}
property ServiceConfig services: ServiceConfig {}
property ToastsConfig toasts: ToastsConfig {}
property UserPaths paths: UserPaths {}
diff --git a/config/ControlCenterConfig.qml b/config/ControlCenterConfig.qml
deleted file mode 100644
index a588949..0000000
--- a/config/ControlCenterConfig.qml
+++ /dev/null
@@ -1,10 +0,0 @@
-import Quickshell.Io
-
-JsonObject {
- property Sizes sizes: Sizes {}
-
- component Sizes: JsonObject {
- property real heightMult: 0.7
- property real ratio: 16 / 9
- }
-}
diff --git a/config/DashboardConfig.qml b/config/DashboardConfig.qml
deleted file mode 100644
index 6ec1d97..0000000
--- a/config/DashboardConfig.qml
+++ /dev/null
@@ -1,35 +0,0 @@
-import Quickshell.Io
-
-JsonObject {
- property bool enabled: true
- property bool showOnHover: true
- property int mediaUpdateInterval: 500
- property int resourceUpdateInterval: 1000
- property int dragThreshold: 50
- property bool showDashboard: true
- property bool showMedia: true
- property bool showPerformance: true
- property bool showWeather: true
- property Sizes sizes: Sizes {}
- property Performance performance: Performance {}
-
- component Performance: JsonObject {
- property bool showBattery: true
- property bool showGpu: true
- property bool showCpu: true
- property bool showMemory: true
- property bool showStorage: true
- property bool showNetwork: true
- }
-
- component Sizes: JsonObject {
- readonly property int tabIndicatorHeight: 3
- readonly property int tabIndicatorSpacing: 5
- readonly property int mediaWidth: 200
- readonly property int mediaProgressSweep: 180
- readonly property int mediaProgressThickness: 8
- readonly property int resourceProgessThickness: 10
- readonly property int mediaCoverArtSize: 150
- readonly property int mediaVisualiserSize: 80
- }
-}
diff --git a/config/GeneralConfig.qml b/config/GeneralConfig.qml
index 37df57a..cad7914 100644
--- a/config/GeneralConfig.qml
+++ b/config/GeneralConfig.qml
@@ -3,16 +3,9 @@ import Quickshell.Io
JsonObject {
property string logo: ""
property list<string> excludedScreens: []
- property Apps apps: Apps {}
+ property list<string> terminal: ["foot"]
property Battery battery: Battery {}
- component Apps: JsonObject {
- property list<string> terminal: ["foot"]
- property list<string> audio: ["pavucontrol"]
- property list<string> playback: ["mpv"]
- property list<string> explorer: ["thunar"]
- }
-
component Battery: JsonObject {
property list<var> warnLevels: [
{
diff --git a/config/ServiceConfig.qml b/config/ServiceConfig.qml
index 458ada0..ddc41df 100644
--- a/config/ServiceConfig.qml
+++ b/config/ServiceConfig.qml
@@ -2,20 +2,8 @@ import Quickshell.Io
import QtQuick
JsonObject {
- property string weatherLocation: "" // A lat,long pair or empty for autodetection, e.g. "37.8267,-122.4233"
- property bool useFahrenheit: [Locale.ImperialUSSystem, Locale.ImperialSystem].includes(Qt.locale().measurementSystem)
- property bool useFahrenheitPerformance: [Locale.ImperialUSSystem, Locale.ImperialSystem].includes(Qt.locale().measurementSystem)
property bool useTwelveHourClock: Qt.locale().timeFormat(Locale.ShortFormat).toLowerCase().includes("a")
- property string gpuType: ""
- property int visualiserBars: 45
property real audioIncrement: 0.1
property real brightnessIncrement: 0.1
property real maxVolume: 1.0
- property string defaultPlayer: "Spotify"
- property list<var> playerAliases: [
- {
- "from": "com.github.th_ch.youtube_music",
- "to": "YT Music"
- }
- ]
}
diff --git a/config/SidebarConfig.qml b/config/SidebarConfig.qml
deleted file mode 100644
index a871562..0000000
--- a/config/SidebarConfig.qml
+++ /dev/null
@@ -1,11 +0,0 @@
-import Quickshell.Io
-
-JsonObject {
- property bool enabled: true
- property int dragThreshold: 80
- property Sizes sizes: Sizes {}
-
- component Sizes: JsonObject {
- property int width: 430
- }
-}
diff --git a/config/ToastsConfig.qml b/config/ToastsConfig.qml
index 5fed72f..3dd7859 100644
--- a/config/ToastsConfig.qml
+++ b/config/ToastsConfig.qml
@@ -3,12 +3,10 @@ import Quickshell.Io
JsonObject {
property bool configLoaded: true
property bool chargingChanged: true
- property bool dndChanged: true
property bool audioOutputChanged: true
property bool audioInputChanged: true
property bool capsLockChanged: true
property bool numLockChanged: true
property bool kbLayoutChanged: true
property bool kbLimit: true
- property bool nowPlaying: false
}
diff --git a/config/UserPaths.qml b/config/UserPaths.qml
index 1ff6697..926ff7d 100644
--- a/config/UserPaths.qml
+++ b/config/UserPaths.qml
@@ -2,7 +2,6 @@ import qs.utils
import Quickshell.Io
JsonObject {
- property string wallpaper: ""
property string sessionGif: "root:/assets/len.gif"
- property string mediaGif: "root:/assets/bongocat.gif"
+ property string wallpaper: ""
}
diff --git a/modules/Shortcuts.qml b/modules/Shortcuts.qml
index 348a119..eaea433 100644
--- a/modules/Shortcuts.qml
+++ b/modules/Shortcuts.qml
@@ -12,23 +12,12 @@ Scope {
CustomShortcut {
name: "showall"
- description: "Toggle launcher, dashboard and osd"
+ description: "Toggle launcher, osd"
onPressed: {
if (root.hasFullscreen)
return;
const v = Visibilities.getForActive();
- v.launcher = v.dashboard = v.osd = !(v.launcher || v.dashboard || v.osd);
- }
- }
-
- CustomShortcut {
- name: "dashboard"
- description: "Toggle dashboard"
- onPressed: {
- if (root.hasFullscreen)
- return;
- const visibilities = Visibilities.getForActive();
- visibilities.dashboard = !visibilities.dashboard;
+ v.launcher = v.osd = !(v.launcher || v.osd);
}
}
@@ -62,24 +51,12 @@ Scope {
onPressed: root.launcherInterrupted = true
}
-
- CustomShortcut {
- name: "sidebar"
- description: "Toggle sidebar"
- onPressed: {
- if (root.hasFullscreen)
- return;
- const visibilities = Visibilities.getForActive();
- visibilities.sidebar = !visibilities.sidebar;
- }
- }
-
IpcHandler {
target: "drawers"
function toggle(drawer: string): void {
if (list().split("\n").includes(drawer)) {
- if (root.hasFullscreen && ["launcher", "session", "dashboard"].includes(drawer))
+ if (root.hasFullscreen && ["launcher", "session"].includes(drawer))
return;
const visibilities = Visibilities.getForActive();
visibilities[drawer] = !visibilities[drawer];
diff --git a/modules/dashboard/Background.qml b/modules/dashboard/Background.qml
deleted file mode 100644
index e2a91f7..0000000
--- a/modules/dashboard/Background.qml
+++ /dev/null
@@ -1,66 +0,0 @@
-import qs.components
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Shapes
-
-ShapePath {
- id: root
-
- required property Wrapper wrapper
- readonly property real rounding: Config.border.rounding
- readonly property bool flatten: wrapper.height < rounding * 2
- readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
-
- strokeWidth: -1
- fillColor: Colours.palette.m3surface
-
- PathArc {
- relativeX: root.rounding
- relativeY: root.roundingY
- radiusX: root.rounding
- radiusY: Math.min(root.rounding, root.wrapper.height)
- }
-
- PathLine {
- relativeX: 0
- relativeY: root.wrapper.height - root.roundingY * 2
- }
-
- PathArc {
- relativeX: root.rounding
- relativeY: root.roundingY
- radiusX: root.rounding
- radiusY: Math.min(root.rounding, root.wrapper.height)
- direction: PathArc.Counterclockwise
- }
-
- PathLine {
- relativeX: root.wrapper.width - root.rounding * 2
- relativeY: 0
- }
-
- PathArc {
- relativeX: root.rounding
- relativeY: -root.roundingY
- radiusX: root.rounding
- radiusY: Math.min(root.rounding, root.wrapper.height)
- direction: PathArc.Counterclockwise
- }
-
- PathLine {
- relativeX: 0
- relativeY: -(root.wrapper.height - root.roundingY * 2)
- }
-
- PathArc {
- relativeX: root.rounding
- relativeY: -root.roundingY
- radiusX: root.rounding
- radiusY: Math.min(root.rounding, root.wrapper.height)
- }
-
- Behavior on fillColor {
- CAnim {}
- }
-}
diff --git a/modules/dashboard/Content.qml b/modules/dashboard/Content.qml
deleted file mode 100644
index 84ee0cf..0000000
--- a/modules/dashboard/Content.qml
+++ /dev/null
@@ -1,175 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.config
-import Quickshell
-import Quickshell.Widgets
-import QtQuick
-import QtQuick.Layouts
-
-Item {
- id: root
-
- required property PersistentProperties visibilities
- required property PersistentProperties state
-
- readonly property var dashboardTabs: {
- const allTabs = [
- {
- component: mediaComponent,
- iconName: "queue_music",
- text: qsTr("Media"),
- enabled: Config.dashboard.showMedia
- },
- {
- component: performanceComponent,
- iconName: "speed",
- text: qsTr("Performance"),
- enabled: Config.dashboard.showPerformance && (Config.dashboard.performance.showCpu || Config.dashboard.performance.showGpu || Config.dashboard.performance.showMemory || Config.dashboard.performance.showStorage || Config.dashboard.performance.showNetwork || Config.dashboard.performance.showBattery)
- },
- {
- component: weatherComponent,
- iconName: "cloud",
- text: qsTr("Weather"),
- enabled: Config.dashboard.showWeather
- }
- ];
- return allTabs.filter(tab => tab.enabled);
- }
-
- readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
- readonly property real nonAnimHeight: tabs.implicitHeight + tabs.anchors.topMargin + view.implicitHeight + viewWrapper.anchors.margins * 2
-
- implicitWidth: nonAnimWidth
- implicitHeight: nonAnimHeight
-
- Tabs {
- id: tabs
-
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.topMargin: Appearance.padding.normal
- anchors.margins: Appearance.padding.large
-
- nonAnimWidth: root.nonAnimWidth - anchors.margins * 2
- state: root.state
- tabs: root.dashboardTabs
- }
-
- ClippingRectangle {
- id: viewWrapper
-
- anchors.top: tabs.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- anchors.margins: Appearance.padding.large
-
- radius: Appearance.rounding.normal
- color: "transparent"
-
- Flickable {
- id: view
-
- readonly property int currentIndex: root.state.currentTab
- readonly property Item currentItem: row.children[currentIndex]
-
- anchors.fill: parent
-
- flickableDirection: Flickable.HorizontalFlick
-
- implicitWidth: currentItem.implicitWidth
- implicitHeight: currentItem.implicitHeight
-
- contentX: currentItem.x
- contentWidth: row.implicitWidth
- contentHeight: row.implicitHeight
-
- onContentXChanged: {
- if (!moving)
- return;
-
- const x = contentX - currentItem.x;
- if (x > currentItem.implicitWidth / 2)
- root.state.currentTab = Math.min(root.state.currentTab + 1, tabs.count - 1);
- else if (x < -currentItem.implicitWidth / 2)
- root.state.currentTab = Math.max(root.state.currentTab - 1, 0);
- }
-
- onDragEnded: {
- const x = contentX - currentItem.x;
- if (x > currentItem.implicitWidth / 10)
- root.state.currentTab = Math.min(root.state.currentTab + 1, tabs.count - 1);
- else if (x < -currentItem.implicitWidth / 10)
- root.state.currentTab = Math.max(root.state.currentTab - 1, 0);
- else
- contentX = Qt.binding(() => currentItem.x);
- }
-
- RowLayout {
- id: row
-
- Repeater {
- model: ScriptModel {
- values: root.dashboardTabs
- }
-
- delegate: Loader {
- id: paneLoader
-
- required property int index
- required property var modelData
-
- Layout.alignment: Qt.AlignTop
-
- sourceComponent: modelData.component
-
- Component.onCompleted: active = Qt.binding(() => {
- if (index === view.currentIndex)
- return true;
- const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth);
- const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth);
- return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
- })
- }
- }
- }
-
- Component {
- id: mediaComponent
- Media {
- visibilities: root.visibilities
- }
- }
-
- Component {
- id: performanceComponent
- Performance {}
- }
-
- Component {
- id: weatherComponent
- Weather {}
- }
-
- Behavior on contentX {
- Anim {}
- }
- }
- }
-
- Behavior on implicitWidth {
- Anim {
- duration: Appearance.anim.durations.large
- easing.bezierCurve: Appearance.anim.curves.emphasized
- }
- }
-
- Behavior on implicitHeight {
- Anim {
- duration: Appearance.anim.durations.large
- easing.bezierCurve: Appearance.anim.curves.emphasized
- }
- }
-}
diff --git a/modules/dashboard/Media.qml b/modules/dashboard/Media.qml
deleted file mode 100644
index 722bc93..0000000
--- a/modules/dashboard/Media.qml
+++ /dev/null
@@ -1,403 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.components.controls
-import qs.services
-import qs.utils
-import qs.config
-import Caelestia.Services
-import Quickshell
-import Quickshell.Services.Mpris
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Shapes
-
-Item {
- id: root
-
- required property PersistentProperties visibilities
-
- property real playerProgress: {
- const active = Players.active;
- return active?.length ? active.position / active.length : 0;
- }
-
- function lengthStr(length: int): string {
- if (length < 0)
- return "-1:-1";
-
- const hours = Math.floor(length / 3600);
- const mins = Math.floor((length % 3600) / 60);
- const secs = Math.floor(length % 60).toString().padStart(2, "0");
-
- if (hours > 0)
- return `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
- return `${mins}:${secs}`;
- }
-
- implicitWidth: cover.implicitWidth + Config.dashboard.sizes.mediaVisualiserSize * 2 + details.implicitWidth + details.anchors.leftMargin + bongocat.implicitWidth + bongocat.anchors.leftMargin * 2 + Appearance.padding.large * 2
- implicitHeight: Math.max(cover.implicitHeight + Config.dashboard.sizes.mediaVisualiserSize * 2, details.implicitHeight, bongocat.implicitHeight) + Appearance.padding.large * 2
-
- Behavior on playerProgress {
- Anim {
- duration: Appearance.anim.durations.large
- }
- }
-
- Timer {
- running: Players.active?.isPlaying ?? false
- interval: Config.dashboard.mediaUpdateInterval
- triggeredOnStart: true
- repeat: true
- onTriggered: Players.active?.positionChanged()
- }
-
- ServiceRef {
- service: Audio.cava
- }
-
- ServiceRef {
- service: Audio.beatTracker
- }
-
- Shape {
- id: visualiser
-
- readonly property real centerX: width / 2
- readonly property real centerY: height / 2
- readonly property real innerX: cover.implicitWidth / 2 + Appearance.spacing.small
- readonly property real innerY: cover.implicitHeight / 2 + Appearance.spacing.small
- property color colour: Colours.palette.m3primary
-
- anchors.fill: cover
- anchors.margins: -Config.dashboard.sizes.mediaVisualiserSize
-
- asynchronous: true
- preferredRendererType: Shape.CurveRenderer
- data: visualiserBars.instances
- }
-
- Variants {
- id: visualiserBars
-
- model: Array.from({
- length: Config.services.visualiserBars
- }, (_, i) => i)
-
- ShapePath {
- id: visualiserBar
-
- required property int modelData
- readonly property real value: Math.max(1e-3, Math.min(1, Audio.cava.values[modelData]))
-
- readonly property real angle: modelData * 2 * Math.PI / Config.services.visualiserBars
- readonly property real magnitude: value * Config.dashboard.sizes.mediaVisualiserSize
- readonly property real cos: Math.cos(angle)
- readonly property real sin: Math.sin(angle)
-
- capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
- strokeWidth: 360 / Config.services.visualiserBars - Appearance.spacing.small / 4
- strokeColor: Colours.palette.m3primary
-
- startX: visualiser.centerX + (visualiser.innerX + strokeWidth / 2) * cos
- startY: visualiser.centerY + (visualiser.innerY + strokeWidth / 2) * sin
-
- PathLine {
- x: visualiser.centerX + (visualiser.innerX + visualiserBar.strokeWidth / 2 + visualiserBar.magnitude) * visualiserBar.cos
- y: visualiser.centerY + (visualiser.innerY + visualiserBar.strokeWidth / 2 + visualiserBar.magnitude) * visualiserBar.sin
- }
-
- Behavior on strokeColor {
- CAnim {}
- }
- }
- }
-
- StyledClippingRect {
- id: cover
-
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: parent.left
- anchors.leftMargin: Appearance.padding.large + Config.dashboard.sizes.mediaVisualiserSize
-
- implicitWidth: Config.dashboard.sizes.mediaCoverArtSize
- implicitHeight: Config.dashboard.sizes.mediaCoverArtSize
-
- color: Colours.tPalette.m3surfaceContainerHigh
- radius: Infinity
-
- MaterialIcon {
- anchors.centerIn: parent
-
- grade: 200
- text: "art_track"
- color: Colours.palette.m3onSurfaceVariant
- font.pointSize: (parent.width * 0.4) || 1
- }
-
- Image {
- id: image
-
- anchors.fill: parent
-
- source: Players.active?.trackArtUrl ?? "" // qmllint disable incompatible-type
- asynchronous: true
- fillMode: Image.PreserveAspectCrop
- sourceSize.width: width
- sourceSize.height: height
- }
- }
-
- ColumnLayout {
- id: details
-
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: visualiser.right
- anchors.leftMargin: Appearance.spacing.normal
-
- spacing: Appearance.spacing.small
-
- StyledText {
- id: title
-
- Layout.fillWidth: true
- Layout.maximumWidth: parent.implicitWidth
-
- animate: true
- horizontalAlignment: Text.AlignHCenter
- text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
- color: Players.active ? Colours.palette.m3primary : Colours.palette.m3onSurface
- font.pointSize: Appearance.font.size.normal
- elide: Text.ElideRight
- }
-
- StyledText {
- id: album
-
- Layout.fillWidth: true
- Layout.maximumWidth: parent.implicitWidth
-
- animate: true
- horizontalAlignment: Text.AlignHCenter
- visible: !!Players.active
- text: Players.active?.trackAlbum || qsTr("Unknown album")
- color: Colours.palette.m3outline
- font.pointSize: Appearance.font.size.small
- elide: Text.ElideRight
- }
-
- StyledText {
- id: artist
-
- Layout.fillWidth: true
- Layout.maximumWidth: parent.implicitWidth
-
- animate: true
- horizontalAlignment: Text.AlignHCenter
- text: (Players.active?.trackArtist ?? qsTr("Play some music for stuff to show up here!")) || qsTr("Unknown artist")
- color: Players.active ? Colours.palette.m3secondary : Colours.palette.m3outline
- elide: Text.ElideRight
- wrapMode: Players.active ? Text.NoWrap : Text.WordWrap
- }
-
- RowLayout {
- id: controls
-
- Layout.alignment: Qt.AlignHCenter
- Layout.topMargin: Appearance.spacing.small
- Layout.bottomMargin: Appearance.spacing.smaller
-
- spacing: Appearance.spacing.small
-
- PlayerControl {
- type: IconButton.Text
- icon: "skip_previous"
- font.pointSize: Math.round(Appearance.font.size.large * 1.5)
- disabled: !Players.active?.canGoPrevious
- onClicked: Players.active?.previous()
- }
-
- PlayerControl {
- icon: Players.active?.isPlaying ? "pause" : "play_arrow"
- label.animate: true
- toggle: true
- padding: Appearance.padding.small / 2
- checked: Players.active?.isPlaying ?? false
- font.pointSize: Math.round(Appearance.font.size.large * 1.5)
- disabled: !Players.active?.canTogglePlaying
- onClicked: Players.active?.togglePlaying()
- }
-
- PlayerControl {
- type: IconButton.Text
- icon: "skip_next"
- font.pointSize: Math.round(Appearance.font.size.large * 1.5)
- disabled: !Players.active?.canGoNext
- onClicked: Players.active?.next()
- }
- }
-
- StyledSlider {
- id: slider
-
- enabled: !!Players.active
- implicitWidth: 280
- implicitHeight: Appearance.padding.normal * 3
-
- onMoved: {
- const active = Players.active;
- if (active?.canSeek && active?.positionSupported)
- active.position = value * active.length;
- }
-
- Binding {
- target: slider
- property: "value"
- value: root.playerProgress
- when: !slider.pressed
- }
-
- CustomMouseArea {
- anchors.fill: parent
- acceptedButtons: Qt.NoButton
-
- function onWheel(event: WheelEvent) {
- const active = Players.active;
- if (!active?.canSeek || !active?.positionSupported)
- return;
-
- event.accepted = true;
- const delta = event.angleDelta.y > 0 ? 10 : -10; // Time 10 seconds
- Qt.callLater(() => {
- active.position = Math.max(0, Math.min(active.length, active.position + delta));
- });
- }
- }
- }
-
- Item {
- Layout.fillWidth: true
- implicitHeight: Math.max(position.implicitHeight, length.implicitHeight)
-
- StyledText {
- id: position
-
- anchors.left: parent.left
-
- text: root.lengthStr(Players.active?.position ?? -1)
- color: Colours.palette.m3onSurfaceVariant
- font.pointSize: Appearance.font.size.small
- }
-
- StyledText {
- id: length
-
- anchors.right: parent.right
-
- text: root.lengthStr(Players.active?.length ?? -1)
- color: Colours.palette.m3onSurfaceVariant
- font.pointSize: Appearance.font.size.small
- }
- }
-
- RowLayout {
- Layout.alignment: Qt.AlignHCenter
- spacing: Appearance.spacing.small
-
- PlayerControl {
- type: IconButton.Text
- icon: "move_up"
- inactiveOnColour: Colours.palette.m3secondary
- padding: Appearance.padding.small
- font.pointSize: Appearance.font.size.large
- disabled: !Players.active?.canRaise
- onClicked: {
- Players.active?.raise();
- root.visibilities.dashboard = false;
- }
- }
-
- SplitButton {
- id: playerSelector
-
- disabled: !Players.list.length
- active: menuItems.find(m => m.modelData === Players.active) ?? menuItems[0] ?? null
- menu.onItemSelected: item => Players.manualActive = (item as PlayerItem).modelData
-
- menuItems: playerList.instances
- fallbackIcon: "music_off"
- fallbackText: qsTr("No players")
-
- label.Layout.maximumWidth: slider.implicitWidth * 0.28
- label.elide: Text.ElideRight
-
- stateLayer.disabled: true
- menuOnTop: true
-
- Variants {
- id: playerList
-
- model: Players.list
-
- PlayerItem {}
- }
- }
-
- PlayerControl {
- type: IconButton.Text
- icon: "delete"
- inactiveOnColour: Colours.palette.m3error
- padding: Appearance.padding.small
- font.pointSize: Appearance.font.size.large
- disabled: !Players.active?.canQuit
- onClicked: Players.active?.quit()
- }
- }
- }
-
- Item {
- id: bongocat
-
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: details.right
- anchors.leftMargin: Appearance.spacing.normal
-
- implicitWidth: visualiser.width
- implicitHeight: visualiser.height
-
- AnimatedImage {
- anchors.centerIn: parent
-
- width: visualiser.width * 0.75
- height: visualiser.height * 0.75
-
- playing: Players.active?.isPlaying ?? false
- speed: Audio.beatTracker.bpm / Appearance.anim.mediaGifSpeedAdjustment // qmllint disable unresolved-type
- source: Paths.absolutePath(Config.paths.mediaGif)
- asynchronous: true
- fillMode: AnimatedImage.PreserveAspectFit
- }
- }
-
- component PlayerItem: MenuItem {
- required property MprisPlayer modelData
-
- icon: modelData === Players.active ? "check" : ""
- text: Players.getIdentity(modelData)
- activeIcon: "animated_images"
- }
-
- component PlayerControl: IconButton {
- Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? Appearance.padding.large : internalChecked ? Appearance.padding.smaller : 0)
- radius: stateLayer.pressed ? Appearance.rounding.small / 2 : internalChecked ? Appearance.rounding.small : implicitHeight / 2
- radiusAnim.duration: Appearance.anim.durations.expressiveFastSpatial
- radiusAnim.easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
-
- Behavior on Layout.preferredWidth {
- Anim {
- duration: Appearance.anim.durations.expressiveFastSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
- }
- }
- }
-}
diff --git a/modules/dashboard/Performance.qml b/modules/dashboard/Performance.qml
deleted file mode 100644
index 5d57e83..0000000
--- a/modules/dashboard/Performance.qml
+++ /dev/null
@@ -1,831 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import Quickshell.Services.UPower
-import Caelestia.Internal
-import qs.components
-import qs.components.misc
-import qs.config
-import qs.services
-
-Item {
- id: root
-
- readonly property int minWidth: 400 + 400 + Appearance.spacing.normal + 120 + Appearance.padding.large * 2
-
- function displayTemp(temp: real): string {
- return `${Math.ceil(Config.services.useFahrenheitPerformance ? temp * 1.8 + 32 : temp)}°${Config.services.useFahrenheitPerformance ? "F" : "C"}`;
- }
-
- implicitWidth: Math.max(minWidth, content.implicitWidth)
- implicitHeight: placeholder.visible ? placeholder.height : content.implicitHeight
-
- StyledRect {
- id: placeholder
-
- anchors.centerIn: parent
- width: 400
- height: 350
- radius: Appearance.rounding.large
- color: Colours.tPalette.m3surfaceContainer
- visible: !Config.dashboard.performance.showCpu && !(Config.dashboard.performance.showGpu && SystemUsage.gpuType !== "NONE") && !Config.dashboard.performance.showMemory && !Config.dashboard.performance.showStorage && !Config.dashboard.performance.showNetwork && !(UPower.displayDevice.isLaptopBattery && Config.dashboard.performance.showBattery)
-
- ColumnLayout {
- anchors.centerIn: parent
- spacing: Appearance.spacing.normal
-
- MaterialIcon {
- Layout.alignment: Qt.AlignHCenter
- text: "tune"
- font.pointSize: Appearance.font.size.extraLarge * 2
- color: Colours.palette.m3onSurfaceVariant
- }
-
- StyledText {
- Layout.alignment: Qt.AlignHCenter
- text: qsTr("No widgets enabled")
- font.pointSize: Appearance.font.size.large
- color: Colours.palette.m3onSurface
- }
-
- StyledText {
- Layout.alignment: Qt.AlignHCenter
- text: qsTr("Enable widgets in dashboard settings")
- font.pointSize: Appearance.font.size.small
- color: Colours.palette.m3onSurfaceVariant
- }
- }
- }
-
- RowLayout {
- id: content
-
- anchors.left: parent.left
- anchors.right: parent.right
- spacing: Appearance.spacing.normal
- visible: !placeholder.visible
-
- Ref {
- service: SystemUsage
- }
-
- ColumnLayout {
- id: mainColumn
-
- Layout.fillWidth: true
- spacing: Appearance.spacing.normal
-
- RowLayout {
- Layout.fillWidth: true
- spacing: Appearance.spacing.normal
- visible: Config.dashboard.performance.showCpu || (Config.dashboard.performance.showGpu && SystemUsage.gpuType !== "NONE")
-
- HeroCard {
- Layout.fillWidth: true
- Layout.minimumWidth: 400
- Layout.preferredHeight: 200
- visible: Config.dashboard.performance.showCpu
- icon: "memory"
- title: SystemUsage.cpuName ? `CPU - ${SystemUsage.cpuName}` : qsTr("CPU")
- mainValue: `${Math.round(SystemUsage.cpuPerc * 100)}%`
- mainLabel: qsTr("Usage")
- secondaryValue: root.displayTemp(SystemUsage.cpuTemp)
- secondaryLabel: qsTr("Temp")
- usage: SystemUsage.cpuPerc
- temperature: SystemUsage.cpuTemp
- accentColor: Colours.palette.m3primary
- }
-
- HeroCard {
- Layout.fillWidth: true
- Layout.minimumWidth: 400
- Layout.preferredHeight: 200
- visible: Config.dashboard.performance.showGpu && SystemUsage.gpuType !== "NONE"
- icon: "desktop_windows"
- title: SystemUsage.gpuName ? `GPU - ${SystemUsage.gpuName}` : qsTr("GPU")
- mainValue: `${Math.round(SystemUsage.gpuPerc * 100)}%`
- mainLabel: qsTr("Usage")
- secondaryValue: root.displayTemp(SystemUsage.gpuTemp)
- secondaryLabel: qsTr("Temp")
- usage: SystemUsage.gpuPerc
- temperature: SystemUsage.gpuTemp
- accentColor: Colours.palette.m3secondary
- }
- }
-
- RowLayout {
- Layout.fillWidth: true
- spacing: Appearance.spacing.normal
- visible: Config.dashboard.performance.showMemory || Config.dashboard.performance.showStorage || Config.dashboard.performance.showNetwork
-
- GaugeCard {
- Layout.minimumWidth: 250
- Layout.preferredHeight: 220
- Layout.fillWidth: !Config.dashboard.performance.showStorage && !Config.dashboard.performance.showNetwork
- icon: "memory_alt"
- title: qsTr("Memory")
- percentage: SystemUsage.memPerc
- subtitle: {
- const usedFmt = SystemUsage.formatKib(SystemUsage.memUsed);
- const totalFmt = SystemUsage.formatKib(SystemUsage.memTotal);
- return `${usedFmt.value.toFixed(1)} / ${Math.floor(totalFmt.value)} ${totalFmt.unit}`;
- }
- accentColor: Colours.palette.m3primary
- visible: Config.dashboard.performance.showMemory
- }
-
- StorageGaugeCard {
- Layout.minimumWidth: 250
- Layout.preferredHeight: 220
- Layout.fillWidth: !Config.dashboard.performance.showNetwork
- visible: Config.dashboard.performance.showStorage
- }
-
- NetworkCard {
- Layout.fillWidth: true
- Layout.minimumWidth: 200
- Layout.preferredHeight: 220
- visible: Config.dashboard.performance.showNetwork
- }
- }
- }
-
- BatteryTank {
- Layout.preferredWidth: 120
- Layout.preferredHeight: mainColumn.implicitHeight
- visible: UPower.displayDevice.isLaptopBattery && Config.dashboard.performance.showBattery
- }
- }
-
- component BatteryTank: StyledClippingRect {
- id: batteryTank
-
- property real percentage: UPower.displayDevice.percentage
- property bool isCharging: UPower.displayDevice.state === UPowerDeviceState.Charging
- property color accentColor: Colours.palette.m3primary
- property real animatedPercentage: 0
-
- color: Colours.tPalette.m3surfaceContainer
- radius: Appearance.rounding.large
- Component.onCompleted: animatedPercentage = percentage
- onPercentageChanged: animatedPercentage = percentage
-
- // Background Fill
- StyledRect {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- height: parent.height * batteryTank.animatedPercentage
- color: Qt.alpha(batteryTank.accentColor, 0.15)
- }
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: Appearance.padding.large
- spacing: Appearance.spacing.small
-
- // Header Section
- ColumnLayout {
- Layout.fillWidth: true
- spacing: Appearance.spacing.small
-
- MaterialIcon {
- text: {
- if (!UPower.displayDevice.isLaptopBattery) {
- if (PowerProfiles.profile === PowerProfile.PowerSaver)
- return "energy_savings_leaf";
-
- if (PowerProfiles.profile === PowerProfile.Performance)
- return "rocket_launch";
-
- return "balance";
- }
- if (UPower.displayDevice.state === UPowerDeviceState.FullyCharged)
- return "battery_full";
-
- const perc = UPower.displayDevice.percentage;
- const charging = [UPowerDeviceState.Charging, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state);
- if (perc >= 0.99)
- return "battery_full";
-
- let level = Math.floor(perc * 7);
- if (charging && (level === 4 || level === 1))
- level--;
-
- return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`;
- }
- font.pointSize: Appearance.font.size.large
- color: batteryTank.accentColor
- }
-
- StyledText {
- Layout.fillWidth: true
- text: qsTr("Battery")
- font.pointSize: Appearance.font.size.normal
- color: Colours.palette.m3onSurface
- }
- }
-
- Item {
- Layout.fillHeight: true
- }
-
- // Bottom Info Section
- ColumnLayout {
- Layout.fillWidth: true
- spacing: -4
-
- StyledText {
- Layout.alignment: Qt.AlignRight
- text: `${Math.round(batteryTank.percentage * 100)}%`
- font.pointSize: Appearance.font.size.extraLarge
- font.weight: Font.Medium
- color: batteryTank.accentColor
- }
-
- StyledText {
- Layout.alignment: Qt.AlignRight
- text: {
- if (UPower.displayDevice.state === UPowerDeviceState.FullyCharged)
- return qsTr("Full");
-
- if (batteryTank.isCharging)
- return qsTr("Charging");
-
- const s = UPower.displayDevice.timeToEmpty;
- if (s === 0)
- return qsTr("...");
-
- const hr = Math.floor(s / 3600);
- const min = Math.floor((s % 3600) / 60);
- if (hr > 0)
- return `${hr}h ${min}m`;
-
- return `${min}m`;
- }
- font.pointSize: Appearance.font.size.smaller
- color: Colours.palette.m3onSurfaceVariant
- }
- }
- }
-
- Behavior on animatedPercentage {
- Anim {
- duration: Appearance.anim.durations.large
- }
- }
- }
-
- component CardHeader: RowLayout {
- property string icon
- property string title
- property color accentColor: Colours.palette.m3primary
-
- Layout.fillWidth: true
- spacing: Appearance.spacing.small
-
- MaterialIcon {
- text: parent.icon
- fill: 1
- color: parent.accentColor
- font.pointSize: Appearance.spacing.large
- }
-
- StyledText {
- Layout.fillWidth: true
- text: parent.title
- font.pointSize: Appearance.font.size.normal
- elide: Text.ElideRight
- }
- }
-
- component ProgressBar: StyledRect {
- id: progressBar
-
- property real value: 0
- property color fgColor: Colours.palette.m3primary
- property color bgColor: Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)
- property real animatedValue: 0
-
- color: bgColor
- radius: Appearance.rounding.full
- Component.onCompleted: animatedValue = value
- onValueChanged: animatedValue = value
-
- StyledRect {
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: parent.width * progressBar.animatedValue
- color: progressBar.fgColor
- radius: Appearance.rounding.full
- }
-
- Behavior on animatedValue {
- Anim {
- duration: Appearance.anim.durations.large
- }
- }
- }
-
- component HeroCard: StyledClippingRect {
- id: heroCard
-
- property string icon
- property string title
- property string mainValue
- property string mainLabel
- property string secondaryValue
- property string secondaryLabel
- property real usage: 0
- property real temperature: 0
- property color accentColor: Colours.palette.m3primary
- readonly property real maxTemp: 100
- readonly property real tempProgress: Math.min(1, Math.max(0, temperature / maxTemp))
- property real animatedUsage: 0
- property real animatedTemp: 0
-
- color: Colours.tPalette.m3surfaceContainer
- radius: Appearance.rounding.large
- Component.onCompleted: {
- animatedUsage = usage;
- animatedTemp = tempProgress;
- }
- onUsageChanged: animatedUsage = usage
- onTempProgressChanged: animatedTemp = tempProgress
-
- StyledRect {
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: parent.width * heroCard.animatedUsage
- color: Qt.alpha(heroCard.accentColor, 0.15)
- }
-
- ColumnLayout {
- anchors.fill: parent
- anchors.leftMargin: Appearance.padding.large
- anchors.rightMargin: Appearance.padding.large
- anchors.topMargin: Appearance.padding.normal
- anchors.bottomMargin: Appearance.padding.normal
- spacing: Appearance.spacing.small
-
- CardHeader {
- icon: heroCard.icon
- title: heroCard.title
- accentColor: heroCard.accentColor
- }
-
- RowLayout {
- Layout.fillWidth: true
- Layout.fillHeight: true
- spacing: Appearance.spacing.normal
-
- Column {
- Layout.alignment: Qt.AlignBottom
- Layout.fillWidth: true
- spacing: Appearance.spacing.small
-
- Row {
- spacing: Appearance.spacing.small
-
- StyledText {
- text: heroCard.secondaryValue
- font.pointSize: Appearance.font.size.normal
- font.weight: Font.Medium
- }
-
- StyledText {
- text: heroCard.secondaryLabel
- font.pointSize: Appearance.font.size.small
- color: Colours.palette.m3onSurfaceVariant
- anchors.baseline: parent.children[0].baseline
- }
- }
-
- ProgressBar {
- width: parent.width * 0.5
- height: 6
- value: heroCard.tempProgress
- fgColor: heroCard.accentColor
- bgColor: Qt.alpha(heroCard.accentColor, 0.2)
- }
- }
-
- Item {
- Layout.fillWidth: true
- }
- }
- }
-
- Column {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- anchors.margins: Appearance.padding.large
- anchors.rightMargin: 32
- spacing: 0
-
- StyledText {
- anchors.right: parent.right
- text: heroCard.mainLabel
- font.pointSize: Appearance.font.size.normal
- color: Colours.palette.m3onSurfaceVariant
- }
-
- StyledText {
- anchors.right: parent.right
- text: heroCard.mainValue
- font.pointSize: Appearance.font.size.extraLarge
- font.weight: Font.Medium
- color: heroCard.accentColor
- }
- }
-
- Behavior on animatedUsage {
- Anim {
- duration: Appearance.anim.durations.large
- }
- }
-
- Behavior on animatedTemp {
- Anim {
- duration: Appearance.anim.durations.large
- }
- }
- }
-
- component GaugeCard: StyledRect {
- id: gaugeCard
-
- property string icon
- property string title
- property real percentage: 0
- property string subtitle
- property color accentColor: Colours.palette.m3primary
- readonly property real arcStartAngle: 0.75 * Math.PI
- readonly property real arcSweep: 1.5 * Math.PI
- property real animatedPercentage: 0
-
- color: Colours.tPalette.m3surfaceContainer
- radius: Appearance.rounding.large
- clip: true
- Component.onCompleted: animatedPercentage = percentage
- onPercentageChanged: animatedPercentage = percentage
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: Appearance.padding.large
- spacing: Appearance.spacing.smaller
-
- CardHeader {
- icon: gaugeCard.icon
- title: gaugeCard.title
- accentColor: gaugeCard.accentColor
- }
-
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- ArcGauge {
- anchors.centerIn: parent
- width: Math.min(parent.width, parent.height)
- height: width
- percentage: gaugeCard.animatedPercentage
- accentColor: gaugeCard.accentColor
- trackColor: Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)
- startAngle: gaugeCard.arcStartAngle
- sweepAngle: gaugeCard.arcSweep
- }
-
- StyledText {
- anchors.centerIn: parent
- text: `${Math.round(gaugeCard.percentage * 100)}%`
- font.pointSize: Appearance.font.size.extraLarge
- font.weight: Font.Medium
- color: gaugeCard.accentColor
- }
- }
-
- StyledText {
- Layout.alignment: Qt.AlignHCenter
- text: gaugeCard.subtitle
- font.pointSize: Appearance.font.size.smaller
- color: Colours.palette.m3onSurfaceVariant
- }
- }
-
- Behavior on animatedPercentage {
- Anim {
- duration: Appearance.anim.durations.large
- }
- }
- }
-
- component StorageGaugeCard: StyledRect {
- id: storageGaugeCard
-
- property int currentDiskIndex: 0
- readonly property var currentDisk: SystemUsage.disks.length > 0 ? SystemUsage.disks[currentDiskIndex] : null
- property int diskCount: 0
- readonly property real arcStartAngle: 0.75 * Math.PI
- readonly property real arcSweep: 1.5 * Math.PI
- property real animatedPercentage: 0
- property color accentColor: Colours.palette.m3secondary
-
- color: Colours.tPalette.m3surfaceContainer
- radius: Appearance.rounding.large
- clip: true
- Component.onCompleted: {
- diskCount = SystemUsage.disks.length;
- if (currentDisk)
- animatedPercentage = currentDisk.perc;
- }
- onCurrentDiskChanged: {
- if (currentDisk)
- animatedPercentage = currentDisk.perc;
- }
-
- // Update diskCount and animatedPercentage when disks data changes
- Connections {
- function onDisksChanged() {
- if (SystemUsage.disks.length !== storageGaugeCard.diskCount)
- storageGaugeCard.diskCount = SystemUsage.disks.length;
-
- // Update animated percentage when disk data refreshes
- if (storageGaugeCard.currentDisk)
- storageGaugeCard.animatedPercentage = storageGaugeCard.currentDisk.perc;
- }
-
- target: SystemUsage
- }
-
- MouseArea {
- anchors.fill: parent
- onWheel: wheel => {
- if (wheel.angleDelta.y > 0)
- storageGaugeCard.currentDiskIndex = (storageGaugeCard.currentDiskIndex - 1 + storageGaugeCard.diskCount) % storageGaugeCard.diskCount;
- else if (wheel.angleDelta.y < 0)
- storageGaugeCard.currentDiskIndex = (storageGaugeCard.currentDiskIndex + 1) % storageGaugeCard.diskCount;
- }
- }
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: Appearance.padding.large
- spacing: Appearance.spacing.smaller
-
- CardHeader {
- icon: "hard_disk"
- title: {
- const base = qsTr("Storage");
- if (!storageGaugeCard.currentDisk)
- return base;
-
- return `${base} - ${storageGaugeCard.currentDisk.mount}`;
- }
- accentColor: storageGaugeCard.accentColor
-
- // Scroll hint icon
- MaterialIcon {
- text: "unfold_more"
- color: Colours.palette.m3onSurfaceVariant
- font.pointSize: Appearance.font.size.normal
- visible: storageGaugeCard.diskCount > 1
- opacity: 0.7
- ToolTip.visible: hintHover.hovered
- ToolTip.text: qsTr("Scroll to switch disks")
- ToolTip.delay: 500
-
- HoverHandler {
- id: hintHover
- }
- }
- }
-
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- ArcGauge {
- anchors.centerIn: parent
- width: Math.min(parent.width, parent.height)
- height: width
- percentage: storageGaugeCard.animatedPercentage
- accentColor: storageGaugeCard.accentColor
- trackColor: Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)
- startAngle: storageGaugeCard.arcStartAngle
- sweepAngle: storageGaugeCard.arcSweep
- }
-
- StyledText {
- anchors.centerIn: parent
- text: storageGaugeCard.currentDisk ? `${Math.round(storageGaugeCard.currentDisk.perc * 100)}%` : "—"
- font.pointSize: Appearance.font.size.extraLarge
- font.weight: Font.Medium
- color: storageGaugeCard.accentColor
- }
- }
-
- StyledText {
- Layout.alignment: Qt.AlignHCenter
- text: {
- if (!storageGaugeCard.currentDisk)
- return "—";
-
- const usedFmt = SystemUsage.formatKib(storageGaugeCard.currentDisk.used);
- const totalFmt = SystemUsage.formatKib(storageGaugeCard.currentDisk.total);
- return `${usedFmt.value.toFixed(1)} / ${Math.floor(totalFmt.value)} ${totalFmt.unit}`;
- }
- font.pointSize: Appearance.font.size.smaller
- color: Colours.palette.m3onSurfaceVariant
- }
- }
-
- Behavior on animatedPercentage {
- Anim {
- duration: Appearance.anim.durations.large
- }
- }
- }
-
- component NetworkCard: StyledRect {
- id: networkCard
-
- property color accentColor: Colours.palette.m3primary
-
- color: Colours.tPalette.m3surfaceContainer
- radius: Appearance.rounding.large
- clip: true
-
- Ref {
- service: NetworkUsage
- }
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: Appearance.padding.large
- spacing: Appearance.spacing.small
-
- CardHeader {
- icon: "swap_vert"
- title: qsTr("Network")
- accentColor: networkCard.accentColor
- }
-
- // Sparkline graph
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- SparklineItem {
- id: sparkline
-
- property real targetMax: 1024
- property real smoothMax: targetMax
-
- anchors.fill: parent
- line1: NetworkUsage.uploadBuffer
- line1Color: Colours.palette.m3secondary
- line1FillAlpha: 0.15
- line2: NetworkUsage.downloadBuffer
- line2Color: Colours.palette.m3primary
- line2FillAlpha: 0.2
- maxValue: smoothMax
- historyLength: NetworkUsage.historyLength
-
- Connections {
- target: NetworkUsage.downloadBuffer
-
- function onValuesChanged(): void {
- sparkline.targetMax = Math.max(NetworkUsage.downloadBuffer.maximum, NetworkUsage.uploadBuffer.maximum, 1024);
- slideAnim.restart();
- }
- }
-
- NumberAnimation {
- id: slideAnim
-
- target: sparkline
- property: "slideProgress"
- from: 0
- to: 1
- duration: Config.dashboard.resourceUpdateInterval
- }
-
- Behavior on smoothMax {
- Anim {
- duration: Appearance.anim.durations.large
- }
- }
- }
-
- // "No data" placeholder
- StyledText {
- anchors.centerIn: parent
- text: qsTr("Collecting data...")
- font.pointSize: Appearance.font.size.small
- color: Colours.palette.m3onSurfaceVariant
- visible: NetworkUsage.downloadBuffer.count < 2
- opacity: 0.6
- }
- }
-
- // Download row
- RowLayout {
- Layout.fillWidth: true
- spacing: Appearance.spacing.normal
-
- MaterialIcon {
- text: "download"
- color: Colours.palette.m3primary
- font.pointSize: Appearance.font.size.normal
- }
-
- StyledText {
- text: qsTr("Download")
- font.pointSize: Appearance.font.size.small
- color: Colours.palette.m3onSurfaceVariant
- }
-
- Item {
- Layout.fillWidth: true
- }
-
- StyledText {
- text: {
- const fmt = NetworkUsage.formatBytes(NetworkUsage.downloadSpeed ?? 0);
- return fmt ? `${fmt.value.toFixed(1)} ${fmt.unit}` : "0.0 B/s";
- }
- font.pointSize: Appearance.font.size.normal
- font.weight: Font.Medium
- color: Colours.palette.m3primary
- }
- }
-
- // Upload row
- RowLayout {
- Layout.fillWidth: true
- spacing: Appearance.spacing.normal
-
- MaterialIcon {
- text: "upload"
- color: Colours.palette.m3secondary
- font.pointSize: Appearance.font.size.normal
- }
-
- StyledText {
- text: qsTr("Upload")
- font.pointSize: Appearance.font.size.small
- color: Colours.palette.m3onSurfaceVariant
- }
-
- Item {
- Layout.fillWidth: true
- }
-
- StyledText {
- text: {
- const fmt = NetworkUsage.formatBytes(NetworkUsage.uploadSpeed ?? 0);
- return fmt ? `${fmt.value.toFixed(1)} ${fmt.unit}` : "0.0 B/s";
- }
- font.pointSize: Appearance.font.size.normal
- font.weight: Font.Medium
- color: Colours.palette.m3secondary
- }
- }
-
- // Session totals
- RowLayout {
- Layout.fillWidth: true
- spacing: Appearance.spacing.normal
-
- MaterialIcon {
- text: "history"
- color: Colours.palette.m3onSurfaceVariant
- font.pointSize: Appearance.font.size.normal
- }
-
- StyledText {
- text: qsTr("Total")
- font.pointSize: Appearance.font.size.small
- color: Colours.palette.m3onSurfaceVariant
- }
-
- Item {
- Layout.fillWidth: true
- }
-
- StyledText {
- text: {
- const down = NetworkUsage.formatBytesTotal(NetworkUsage.downloadTotal ?? 0);
- const up = NetworkUsage.formatBytesTotal(NetworkUsage.uploadTotal ?? 0);
- return (down && up) ? `↓${down.value.toFixed(1)}${down.unit} ↑${up.value.toFixed(1)}${up.unit}` : "↓0.0B ↑0.0B";
- }
- font.pointSize: Appearance.font.size.small
- color: Colours.palette.m3onSurfaceVariant
- }
- }
- }
- }
-}
diff --git a/modules/dashboard/Tabs.qml b/modules/dashboard/Tabs.qml
deleted file mode 100644
index 6e09e76..0000000
--- a/modules/dashboard/Tabs.qml
+++ /dev/null
@@ -1,244 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.components.controls
-import qs.services
-import qs.config
-import Quickshell
-import Quickshell.Widgets
-import QtQuick
-import QtQuick.Controls
-
-Item {
- id: root
-
- required property real nonAnimWidth
- required property PersistentProperties state
- required property var tabs
-
- readonly property alias count: bar.count
-
- implicitHeight: bar.implicitHeight + indicator.implicitHeight + indicator.anchors.topMargin + separator.implicitHeight
-
- TabBar {
- id: bar
-
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: parent.top
-
- currentIndex: root.state.currentTab
- background: null
-
- onCurrentIndexChanged: root.state.currentTab = currentIndex
-
- Repeater {
- model: ScriptModel {
- values: root.tabs
- }
-
- delegate: Tab {
- required property var modelData
-
- iconName: modelData.iconName
- text: modelData.text
- }
- }
- }
-
- Item {
- id: indicator
-
- anchors.top: bar.bottom
- anchors.topMargin: 5
-
- implicitWidth: {
- const tab = bar.currentItem;
- if (tab)
- return tab.implicitWidth;
- const width = (root.nonAnimWidth - bar.spacing * (bar.count - 1)) / bar.count;
- return width;
- }
- implicitHeight: 3
-
- x: {
- const tab = bar.currentItem;
- const width = (root.nonAnimWidth - bar.spacing * (bar.count - 1)) / bar.count;
- const tabWidth = tab?.implicitWidth ?? width;
- return width * bar.currentIndex + (width - tabWidth) / 2;
- }
-
- clip: true
-
- StyledRect {
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- implicitHeight: parent.implicitHeight * 2
-
- color: Colours.palette.m3primary
- radius: Appearance.rounding.full
- }
-
- Behavior on x {
- Anim {}
- }
-
- Behavior on implicitWidth {
- Anim {}
- }
- }
-
- StyledRect {
- id: separator
-
- anchors.top: indicator.bottom
- anchors.left: parent.left
- anchors.right: parent.right
-
- implicitHeight: 1
- color: Colours.palette.m3outlineVariant
- }
-
- component Tab: TabButton {
- id: tab
-
- required property string iconName
- readonly property bool current: TabBar.tabBar.currentItem === this
-
- background: null
-
- contentItem: CustomMouseArea {
- id: mouse
-
- implicitWidth: Math.max(icon.width, label.width)
- implicitHeight: icon.height + label.height
-
- cursorShape: Qt.PointingHandCursor
-
- onPressed: event => {
- root.state.currentTab = tab.TabBar.index;
-
- const stateY = stateWrapper.y;
- rippleAnim.x = event.x;
- rippleAnim.y = event.y - stateY;
-
- const dist = (ox, oy) => ox * ox + oy * oy;
- rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y + stateY), dist(event.x, stateWrapper.height - event.y), dist(width - event.x, event.y + stateY), dist(width - event.x, stateWrapper.height - event.y)));
-
- rippleAnim.restart();
- }
-
- function onWheel(event: WheelEvent): void {
- if (event.angleDelta.y < 0)
- root.state.currentTab = Math.min(root.state.currentTab + 1, bar.count - 1);
- else if (event.angleDelta.y > 0)
- root.state.currentTab = Math.max(root.state.currentTab - 1, 0);
- }
-
- SequentialAnimation {
- id: rippleAnim
-
- property real x
- property real y
- property real radius
-
- PropertyAction {
- target: ripple
- property: "x"
- value: rippleAnim.x
- }
- PropertyAction {
- target: ripple
- property: "y"
- value: rippleAnim.y
- }
- PropertyAction {
- target: ripple
- property: "opacity"
- value: 0.08
- }
- Anim {
- target: ripple
- properties: "implicitWidth,implicitHeight"
- from: 0
- to: rippleAnim.radius * 2
- duration: Appearance.anim.durations.normal
- easing.bezierCurve: Appearance.anim.curves.standardDecel
- }
- Anim {
- target: ripple
- property: "opacity"
- to: 0
- duration: Appearance.anim.durations.normal
- easing.type: Easing.BezierSpline
- easing.bezierCurve: Appearance.anim.curves.standard
- }
- }
-
- ClippingRectangle {
- id: stateWrapper
-
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- implicitHeight: parent.height + Config.dashboard.sizes.tabIndicatorSpacing * 2
-
- color: "transparent"
- radius: Appearance.rounding.small
-
- StyledRect {
- id: stateLayer
-
- anchors.fill: parent
-
- color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurface
- opacity: mouse.pressed ? 0.1 : tab.hovered ? 0.08 : 0
-
- Behavior on opacity {
- Anim {}
- }
- }
-
- StyledRect {
- id: ripple
-
- radius: Appearance.rounding.full
- color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurface
- opacity: 0
-
- transform: Translate {
- x: -ripple.width / 2
- y: -ripple.height / 2
- }
- }
- }
-
- MaterialIcon {
- id: icon
-
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.bottom: label.top
-
- text: tab.iconName
- color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
- fill: tab.current ? 1 : 0
- font.pointSize: Appearance.font.size.large
-
- Behavior on fill {
- Anim {}
- }
- }
-
- StyledText {
- id: label
-
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.bottom: parent.bottom
-
- text: tab.text
- color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
- }
- }
- }
-}
diff --git a/modules/dashboard/Weather.qml b/modules/dashboard/Weather.qml
deleted file mode 100644
index 0e39f88..0000000
--- a/modules/dashboard/Weather.qml
+++ /dev/null
@@ -1,280 +0,0 @@
-import qs.components
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-Item {
- id: root
-
- implicitWidth: layout.implicitWidth > 800 ? layout.implicitWidth : 840
- implicitHeight: layout.implicitHeight
-
- readonly property var today: Weather.forecast && Weather.forecast.length > 0 ? Weather.forecast[0] : null
-
- Component.onCompleted: Weather.reload()
-
- ColumnLayout {
- id: layout
-
- anchors.fill: parent
- spacing: Appearance.spacing.smaller
-
- RowLayout {
- Layout.leftMargin: Appearance.padding.large
- Layout.rightMargin: Appearance.padding.large
- Layout.fillWidth: true
-
- Column {
- spacing: Appearance.spacing.small / 2
-
- StyledText {
- text: Weather.city || qsTr("Loading...")
- font.pointSize: Appearance.font.size.extraLarge
- font.weight: 600
- color: Colours.palette.m3onSurface
- }
-
- StyledText {
- text: new Date().toLocaleDateString(Qt.locale(), "dddd, MMMM d")
- font.pointSize: Appearance.font.size.small
- color: Colours.palette.m3onSurfaceVariant
- }
- }
-
- Item {
- Layout.fillWidth: true
- }
-
- Row {
- spacing: Appearance.spacing.large
-
- WeatherStat {
- icon: "wb_twilight"
- label: "Sunrise"
- value: Weather.sunrise
- colour: Colours.palette.m3secondary
- }
-
- WeatherStat {
- icon: "bedtime"
- label: "Sunset"
- value: Weather.sunset
- colour: Colours.palette.m3secondary
- }
- }
- }
-
- StyledRect {
- Layout.fillWidth: true
- implicitHeight: bigInfoRow.implicitHeight + Appearance.padding.small * 2
-
- radius: Appearance.rounding.large
- color: Colours.tPalette.m3surfaceContainer
-
- RowLayout {
- id: bigInfoRow
-
- anchors.centerIn: parent
- spacing: Appearance.spacing.large
-
- MaterialIcon {
- Layout.alignment: Qt.AlignVCenter
- text: Weather.icon
- font.pointSize: Appearance.font.size.extraLarge * 3
- color: Colours.palette.m3secondary
- animate: true
- }
-
- ColumnLayout {
- Layout.alignment: Qt.AlignVCenter
- spacing: -Appearance.spacing.small
-
- StyledText {
- text: Weather.temp
- font.pointSize: Appearance.font.size.extraLarge * 2
- font.weight: 500
- color: Colours.palette.m3primary
- }
-
- StyledText {
- Layout.leftMargin: Appearance.padding.small
- text: Weather.description
- font.pointSize: Appearance.font.size.normal
- color: Colours.palette.m3onSurfaceVariant
- }
- }
- }
- }
-
- RowLayout {
- Layout.fillWidth: true
- spacing: Appearance.spacing.smaller
-
- DetailCard {
- icon: "water_drop"
- label: "Humidity"
- value: Weather.humidity + "%"
- colour: Colours.palette.m3secondary
- }
- DetailCard {
- icon: "thermostat"
- label: "Feels Like"
- value: Weather.feelsLike
- colour: Colours.palette.m3secondary
- }
- DetailCard {
- icon: "air"
- label: "Wind"
- value: Weather.windSpeed ? Weather.windSpeed + " km/h" : "--"
- colour: Colours.palette.m3secondary
- }
- }
-
- StyledText {
- Layout.topMargin: Appearance.spacing.normal
- Layout.leftMargin: Appearance.padding.normal
- visible: forecastRepeater.count > 0
- text: qsTr("7-Day Forecast")
- font.pointSize: Appearance.font.size.normal
- font.weight: 600
- color: Colours.palette.m3onSurface
- }
-
- RowLayout {
- Layout.fillWidth: true
- spacing: Appearance.spacing.smaller
-
- Repeater {
- id: forecastRepeater
-
- model: Weather.forecast
-
- StyledRect {
- id: forecastItem
-
- required property int index
- required property var modelData
-
- Layout.fillWidth: true
- implicitHeight: forecastItemColumn.implicitHeight + Appearance.padding.normal * 2
-
- radius: Appearance.rounding.normal
- color: Colours.tPalette.m3surfaceContainer
-
- ColumnLayout {
- id: forecastItemColumn
-
- anchors.centerIn: parent
- spacing: Appearance.spacing.small
-
- StyledText {
- Layout.alignment: Qt.AlignHCenter
- text: forecastItem.index === 0 ? qsTr("Today") : new Date(forecastItem.modelData.date).toLocaleDateString(Qt.locale(), "ddd")
- font.pointSize: Appearance.font.size.normal
- font.weight: 600
- color: Colours.palette.m3primary
- }
-
- StyledText {
- Layout.topMargin: -Appearance.spacing.small / 2
- Layout.alignment: Qt.AlignHCenter
- text: new Date(forecastItem.modelData.date).toLocaleDateString(Qt.locale(), "MMM d")
- font.pointSize: Appearance.font.size.small
- opacity: 0.7
- color: Colours.palette.m3onSurfaceVariant
- }
-
- MaterialIcon {
- Layout.alignment: Qt.AlignHCenter
- text: forecastItem.modelData.icon
- font.pointSize: Appearance.font.size.extraLarge
- color: Colours.palette.m3secondary
- }
-
- StyledText {
- Layout.alignment: Qt.AlignHCenter
- text: Config.services.useFahrenheit ? forecastItem.modelData.maxTempF + "°" + " / " + forecastItem.modelData.minTempF + "°" : forecastItem.modelData.maxTempC + "°" + " / " + forecastItem.modelData.minTempC + "°"
- font.weight: 600
- color: Colours.palette.m3primary
- }
- }
- }
- }
- }
- }
-
- component DetailCard: StyledRect {
- id: detailRoot
-
- property string icon
- property string label
- property string value
- property color colour
-
- Layout.fillWidth: true
- Layout.preferredHeight: 60
- radius: Appearance.rounding.normal
- color: Colours.tPalette.m3surfaceContainer
-
- Row {
- anchors.centerIn: parent
- spacing: Appearance.spacing.normal
-
- MaterialIcon {
- text: detailRoot.icon
- color: detailRoot.colour
- font.pointSize: Appearance.font.size.large
- anchors.verticalCenter: parent.verticalCenter
- }
-
- Column {
- anchors.verticalCenter: parent.verticalCenter
- spacing: 0
-
- StyledText {
- text: detailRoot.label
- font.pointSize: Appearance.font.size.smaller
- opacity: 0.7
- horizontalAlignment: Text.AlignLeft
- }
- StyledText {
- text: detailRoot.value
- font.weight: 600
- horizontalAlignment: Text.AlignLeft
- }
- }
- }
- }
-
- component WeatherStat: Row {
- id: weatherStat
-
- property string icon
- property string label
- property string value
- property color colour
-
- spacing: Appearance.spacing.small
-
- MaterialIcon {
- text: weatherStat.icon
- font.pointSize: Appearance.font.size.extraLarge
- color: weatherStat.colour
- }
-
- Column {
- StyledText {
- text: weatherStat.label
- font.pointSize: Appearance.font.size.smaller
- color: Colours.palette.m3onSurfaceVariant
- }
- StyledText {
- text: weatherStat.value
- font.pointSize: Appearance.font.size.small
- font.weight: 600
- color: Colours.palette.m3onSurface
- }
- }
- }
-}
diff --git a/modules/dashboard/Wrapper.qml b/modules/dashboard/Wrapper.qml
deleted file mode 100644
index 3f86158..0000000
--- a/modules/dashboard/Wrapper.qml
+++ /dev/null
@@ -1,93 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.config
-import qs.utils
-import Caelestia
-import Quickshell
-import QtQuick
-
-Item {
- id: root
-
- required property PersistentProperties visibilities
- readonly property PersistentProperties dashState: PersistentProperties {
- property int currentTab
- property date currentDate: new Date()
-
- reloadableId: "dashboardState"
- }
-
- readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0
-
- visible: height > 0
- implicitHeight: 0
- implicitWidth: content.implicitWidth
-
- onStateChanged: {
- if (state === "visible" && timer.running) {
- timer.triggered();
- timer.stop();
- }
- }
-
- states: State {
- name: "visible"
- when: root.visibilities.dashboard && Config.dashboard.enabled
-
- PropertyChanges {
- root.implicitHeight: content.implicitHeight
- root.implicitWidth: content.implicitWidth
- }
- }
-
- transitions: [
- Transition {
- from: ""
- to: "visible"
-
- Anim {
- target: root
- property: "implicitHeight"
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- },
- Transition {
- from: "visible"
- to: ""
-
- Anim {
- target: root
- property: "implicitHeight"
- easing.bezierCurve: Appearance.anim.curves.emphasized
- }
- }
- ]
-
- Timer {
- id: timer
-
- running: true
- interval: Appearance.anim.durations.extraLarge
- onTriggered: {
- content.active = Qt.binding(() => (root.visibilities.dashboard && Config.dashboard.enabled) || root.visible);
- content.visible = true;
- }
- }
-
- Loader {
- id: content
-
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.bottom: parent.bottom
-
- visible: false
- active: true
-
- sourceComponent: Content {
- visibilities: root.visibilities
- state: root.dashState
- }
- }
-}
diff --git a/modules/drawers/Backgrounds.qml b/modules/drawers/Backgrounds.qml
index 1eed114..afa7d61 100644
--- a/modules/drawers/Backgrounds.qml
+++ b/modules/drawers/Backgrounds.qml
@@ -4,9 +4,7 @@ import qs.modules.osd as Osd
import qs.modules.notifications as Notifications
import qs.modules.session as Session
import qs.modules.launcher as Launcher
-import qs.modules.dashboard as Dashboard
import qs.modules.bar.popouts as BarPopouts
-import qs.modules.sidebar as Sidebar
import QtQuick
import QtQuick.Shapes
@@ -24,13 +22,12 @@ Shape {
Osd.Background {
wrapper: root.panels.osd
- startX: root.width - root.panels.session.width - root.panels.sidebar.width
+ startX: root.width - root.panels.session.width
startY: (root.height - wrapper.height) / 2 - rounding
}
Notifications.Background {
wrapper: root.panels.notifications
- sidebar: sidebar
startX: root.width
startY: 0
@@ -39,7 +36,7 @@ Shape {
Session.Background {
wrapper: root.panels.session
- startX: root.width - root.panels.sidebar.width
+ startX: root.width
startY: (root.height - wrapper.height) / 2 - rounding
}
@@ -50,13 +47,6 @@ Shape {
startY: root.height
}
- Dashboard.Background {
- wrapper: root.panels.dashboard
-
- startX: (root.width - wrapper.width) / 2 - rounding
- startY: 0
- }
-
BarPopouts.Background {
wrapper: root.panels.popouts
invertBottomRounding: wrapper.y + wrapper.height + 1 >= root.height
@@ -64,14 +54,4 @@ Shape {
startX: wrapper.x
startY: wrapper.y - rounding * sideRounding
}
-
- Sidebar.Background {
- id: sidebar
-
- wrapper: root.panels.sidebar
- panels: root.panels
-
- startX: root.width
- startY: root.panels.notifications.height
- }
}
diff --git a/modules/drawers/Drawers.qml b/modules/drawers/Drawers.qml
index 4ae0b92..d3c4769 100644
--- a/modules/drawers/Drawers.qml
+++ b/modules/drawers/Drawers.qml
@@ -39,7 +39,7 @@ Variants {
return 0;
const thresholds = [];
- for (const panel of ["dashboard", "launcher", "session", "sidebar"])
+ for (const panel of ["launcher", "session"])
if (Config[panel].enabled)
thresholds.push(Config[panel].dragThreshold);
return Math.max(...thresholds);
@@ -48,7 +48,6 @@ Variants {
onHasFullscreenChanged: {
visibilities.launcher = false;
visibilities.session = false;
- visibilities.dashboard = false;
}
screen: scope.modelData
@@ -90,13 +89,11 @@ Variants {
HyprlandFocusGrab {
id: focusGrab
- active: (visibilities.launcher && Config.launcher.enabled) || (visibilities.session && Config.session.enabled) || (visibilities.sidebar && Config.sidebar.enabled) || (!Config.dashboard.showOnHover && visibilities.dashboard && Config.dashboard.enabled) || (panels.popouts.currentName.startsWith("traymenu") && panels.popouts.current?.depth > 1)
+ active: (visibilities.launcher && Config.launcher.enabled) || (visibilities.session && Config.session.enabled) || (panels.popouts.currentName.startsWith("traymenu") && panels.popouts.current?.depth > 1)
windows: [win]
onCleared: {
visibilities.launcher = false;
visibilities.session = false;
- visibilities.sidebar = false;
- visibilities.dashboard = false;
panels.popouts.hasCurrent = false;
bar.closeTray();
}
@@ -139,8 +136,6 @@ Variants {
property bool osd
property bool session
property bool launcher
- property bool dashboard
- property bool sidebar
Component.onCompleted: Visibilities.load(scope.modelData, this)
}
diff --git a/modules/drawers/Interactions.qml b/modules/drawers/Interactions.qml
index 8c026ce..5c9beaf 100644
--- a/modules/drawers/Interactions.qml
+++ b/modules/drawers/Interactions.qml
@@ -14,7 +14,6 @@ CustomMouseArea {
required property Item bar
property point dragStart
- property bool dashboardShortcutActive
property bool osdShortcutActive
function withinPanelHeight(panel: Item, x: real, y: real): bool {
@@ -43,12 +42,6 @@ CustomMouseArea {
return y > root.height - Config.border.thickness - panel.height - Config.border.rounding && withinPanelWidth(panel, x, y);
}
- function onWheel(event: WheelEvent): void {
- if (event.x < bar.implicitWidth) {
- bar.handleWheel(event.y, event.angleDelta);
- }
- }
-
anchors.fill: parent
hoverEnabled: true
@@ -61,9 +54,6 @@ CustomMouseArea {
root.panels.osd.hovered = false;
}
- if (!dashboardShortcutActive)
- visibilities.dashboard = false;
-
if (!popouts.currentName.startsWith("traymenu") || (popouts.current?.depth ?? 0) <= 1) {
popouts.hasCurrent = false;
bar.closeTray();
@@ -80,74 +70,15 @@ CustomMouseArea {
const dragX = x - dragStart.x;
const dragY = y - dragStart.y;
- // Show bar in non-exclusive mode on hover
- if (!visibilities.bar && Config.bar.showOnHover && x < bar.implicitWidth)
- bar.isHovered = true;
-
- // Show/hide bar on drag
- if (pressed && dragStart.x < bar.implicitWidth) {
- if (dragX > Config.bar.dragThreshold)
- visibilities.bar = true;
- else if (dragX < -Config.bar.dragThreshold)
- visibilities.bar = false;
- }
-
- if (panels.sidebar.width === 0) {
- // Show osd on hover
- const showOsd = inRightPanel(panels.osd, x, y);
-
- // Always update visibility based on hover if not in shortcut mode
- if (!osdShortcutActive) {
- visibilities.osd = showOsd;
- root.panels.osd.hovered = showOsd;
- } else if (showOsd) {
- // If hovering over OSD area while in shortcut mode, transition to hover control
- osdShortcutActive = false;
- root.panels.osd.hovered = true;
- }
-
- const showSidebar = pressed && dragStart.x > bar.implicitWidth + panels.sidebar.x;
-
- // Show/hide session on drag
- if (pressed && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) {
- if (dragX < -Config.session.dragThreshold)
- visibilities.session = true;
- else if (dragX > Config.session.dragThreshold)
- visibilities.session = false;
-
- // Show sidebar on drag if in session area and session is nearly fully visible
- if (showSidebar && panels.session.width >= panels.session.nonAnimWidth && dragX < -Config.sidebar.dragThreshold)
- visibilities.sidebar = true;
- } else if (showSidebar && dragX < -Config.sidebar.dragThreshold) {
- // Show sidebar on drag if not in session area
- visibilities.sidebar = true;
- }
- } else {
- const outOfSidebar = x < width - panels.sidebar.width;
- // Show osd on hover
- const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y);
-
- // Always update visibility based on hover if not in shortcut mode
- if (!osdShortcutActive) {
- visibilities.osd = showOsd;
- root.panels.osd.hovered = showOsd;
- } else if (showOsd) {
- // If hovering over OSD area while in shortcut mode, transition to hover control
- osdShortcutActive = false;
- root.panels.osd.hovered = true;
- }
-
- // Show/hide session on drag
- if (pressed && outOfSidebar && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) {
- if (dragX < -Config.session.dragThreshold)
- visibilities.session = true;
- else if (dragX > Config.session.dragThreshold)
- visibilities.session = false;
- }
-
- // Hide sidebar on drag
- if (pressed && inRightPanel(panels.sidebar, dragStart.x, 0) && dragX > Config.sidebar.dragThreshold)
- visibilities.sidebar = false;
+ // Show osd on hover
+ const showOsd = inRightPanel(panels.osd, x, y) && !visibilities.session;
+ if (!osdShortcutActive) {
+ visibilities.osd = showOsd;
+ root.panels.osd.hovered = showOsd;
+ } else if (showOsd) {
+ // If hovering over OSD area while in shortcut mode, transition to hover control
+ osdShortcutActive = false;
+ root.panels.osd.hovered = true;
}
// Show launcher on hover, or show/hide on drag if hover is disabled
@@ -161,25 +92,6 @@ CustomMouseArea {
visibilities.launcher = false;
}
- // Show dashboard on hover
- const showDashboard = Config.dashboard.showOnHover && 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/hide dashboard on drag (for touchscreen devices)
- if (pressed && inTopPanel(panels.dashboard, dragStart.x, dragStart.y) && withinPanelWidth(panels.dashboard, x, y)) {
- if (dragY > Config.dashboard.dragThreshold)
- visibilities.dashboard = true;
- else if (dragY < -Config.dashboard.dragThreshold)
- visibilities.dashboard = false;
- }
-
// Show popouts on hover
if (x < bar.implicitWidth) {
bar.checkPopout(y);
@@ -194,18 +106,13 @@ CustomMouseArea {
target: root.visibilities
function onLauncherChanged() {
- // If launcher is hidden, clear shortcut flags for dashboard and OSD
+ // If launcher is hidden, clear shortcut flags for 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);
+ // Also hide OSD if not being hovered
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
- if (!inDashboardArea) {
- root.visibilities.dashboard = false;
- }
if (!inOsdArea) {
root.visibilities.osd = false;
root.panels.osd.hovered = false;
@@ -213,19 +120,6 @@ CustomMouseArea {
}
}
- 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
diff --git a/modules/drawers/Panels.qml b/modules/drawers/Panels.qml
index d459a04..2b7c0d8 100644
--- a/modules/drawers/Panels.qml
+++ b/modules/drawers/Panels.qml
@@ -3,10 +3,8 @@ import qs.modules.osd as Osd
import qs.modules.notifications as Notifications
import qs.modules.session as Session
import qs.modules.launcher as Launcher
-import qs.modules.dashboard as Dashboard
import qs.modules.bar.popouts as BarPopouts
import qs.modules.toasts as Toasts
-import qs.modules.sidebar as Sidebar
import Quickshell
import QtQuick
@@ -21,10 +19,8 @@ Item {
readonly property alias notifications: notifications
readonly property alias session: session
readonly property alias launcher: launcher
- readonly property alias dashboard: dashboard
readonly property alias popouts: popouts
readonly property alias toasts: toasts
- readonly property alias sidebar: sidebar
anchors.fill: parent
anchors.margins: Config.border.thickness
@@ -33,13 +29,11 @@ Item {
Osd.Wrapper {
id: osd
- clip: sidebar.width > 0
screen: root.screen
visibilities: root.visibilities
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
- anchors.rightMargin: sidebar.width
}
Notifications.Wrapper {
@@ -55,13 +49,11 @@ Item {
Session.Wrapper {
id: session
- clip: sidebar.width > 0
visibilities: root.visibilities
panels: root
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
- anchors.rightMargin: sidebar.width
}
@@ -76,15 +68,6 @@ Item {
anchors.bottom: parent.bottom
}
- Dashboard.Wrapper {
- id: dashboard
-
- visibilities: root.visibilities
-
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.top: parent.top
- }
-
BarPopouts.Wrapper {
id: popouts
@@ -103,17 +86,7 @@ Item {
id: toasts
anchors.bottom: parent.bottom
- anchors.right: sidebar.left
- anchors.margins: Appearance.padding.normal
- }
-
- Sidebar.Wrapper {
- id: sidebar
-
- visibilities: root.visibilities
- panels: root
-
- anchors.top: notifications.bottom
anchors.right: parent.right
+ anchors.margins: Appearance.padding.normal
}
}
diff --git a/modules/launcher/Wrapper.qml b/modules/launcher/Wrapper.qml
index d62d726..15042cf 100644
--- a/modules/launcher/Wrapper.qml
+++ b/modules/launcher/Wrapper.qml
@@ -15,12 +15,7 @@ Item {
readonly property bool shouldBeActive: visibilities.launcher && Config.launcher.enabled
property int contentHeight
- readonly property real maxHeight: {
- let max = screen.height - Config.border.thickness * 2 - Appearance.spacing.large;
- if (visibilities.dashboard)
- max -= panels.dashboard.nonAnimHeight;
- return max;
- }
+ readonly property real maxHeight: screen.height - Config.border.thickness * 2 - Appearance.spacing.large
onMaxHeightChanged: timer.start()
diff --git a/modules/launcher/services/Apps.qml b/modules/launcher/services/Apps.qml
index ce1e47a..2fb02e0 100644
--- a/modules/launcher/services/Apps.qml
+++ b/modules/launcher/services/Apps.qml
@@ -13,7 +13,7 @@ Searcher {
if (entry.runInTerminal)
Quickshell.execDetached({
- command: ["app2unit", "--", ...Config.general.apps.terminal, ...entry.command],
+ command: ["app2unit", "--", ...Config.general.terminal, ...entry.command],
workingDirectory: entry.workingDirectory
});
else
diff --git a/modules/notifications/Background.qml b/modules/notifications/Background.qml
index a44cb19..464d2c9 100644
--- a/modules/notifications/Background.qml
+++ b/modules/notifications/Background.qml
@@ -8,7 +8,6 @@ ShapePath {
id: root
required property Wrapper wrapper
- required property var sidebar
readonly property real rounding: Config.border.rounding
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
@@ -31,14 +30,14 @@ ShapePath {
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
- relativeX: root.sidebar.notifsRoundingX
+ relativeX: root.rounding
relativeY: root.roundingY
- radiusX: root.sidebar.notifsRoundingX
+ radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
}
PathLine {
- relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding - root.sidebar.notifsRoundingX : root.wrapper.width
+ relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding : root.wrapper.width
relativeY: 0
}
PathArc {
diff --git a/modules/notifications/Wrapper.qml b/modules/notifications/Wrapper.qml
index 61acc56..6a59308 100644
--- a/modules/notifications/Wrapper.qml
+++ b/modules/notifications/Wrapper.qml
@@ -9,18 +9,9 @@ Item {
required property Item panels
visible: height > 0
- implicitWidth: Math.max(panels.sidebar.width, content.implicitWidth)
+ implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
- states: State {
- name: "hidden"
- when: root.visibilities.sidebar && Config.sidebar.enabled
-
- PropertyChanges {
- root.implicitHeight: 0
- }
- }
-
transitions: Transition {
Anim {
target: root
diff --git a/modules/sidebar/Background.qml b/modules/sidebar/Background.qml
deleted file mode 100644
index 7300c57..0000000
--- a/modules/sidebar/Background.qml
+++ /dev/null
@@ -1,43 +0,0 @@
-import qs.components
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Shapes
-
-ShapePath {
- id: root
-
- required property Wrapper wrapper
- required property var panels
-
- readonly property real rounding: Config.border.rounding
-
- readonly property real notifsWidthDiff: panels.notifications.width - wrapper.width
- readonly property real notifsRoundingX: panels.notifications.height > 0 && notifsWidthDiff < rounding * 2 ? notifsWidthDiff / 2 : rounding
-
- strokeWidth: -1
- fillColor: Colours.palette.m3surface
-
- PathLine {
- relativeX: -root.wrapper.width - root.notifsRoundingX
- relativeY: 0
- }
- PathArc {
- relativeX: root.notifsRoundingX
- relativeY: root.rounding
- radiusX: root.notifsRoundingX
- radiusY: root.rounding
- }
- PathLine {
- relativeX: 0
- relativeY: root.wrapper.height - root.rounding * 2
- }
- PathLine {
- relativeX: root.wrapper.width + root.utilsRoundingX
- relativeY: 0
- }
-
- Behavior on fillColor {
- CAnim {}
- }
-}
diff --git a/modules/sidebar/Content.qml b/modules/sidebar/Content.qml
deleted file mode 100644
index 1b7feed..0000000
--- a/modules/sidebar/Content.qml
+++ /dev/null
@@ -1,40 +0,0 @@
-import qs.components
-import qs.services
-import qs.config
-import QtQuick
-import QtQuick.Layouts
-
-Item {
- id: root
-
- required property Props props
- required property var visibilities
-
- ColumnLayout {
- id: layout
-
- anchors.fill: parent
- spacing: Appearance.spacing.normal
-
- StyledRect {
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- radius: Appearance.rounding.normal
- color: Colours.tPalette.m3surfaceContainerLow
-
- NotifDock {
- props: root.props
- visibilities: root.visibilities
- }
- }
-
- StyledRect {
- Layout.topMargin: Appearance.padding.large - layout.spacing
- Layout.fillWidth: true
- implicitHeight: 1
-
- color: Colours.tPalette.m3outlineVariant
- }
- }
-}
diff --git a/modules/sidebar/Notif.qml b/modules/sidebar/Notif.qml
deleted file mode 100644
index 5a31764..0000000
--- a/modules/sidebar/Notif.qml
+++ /dev/null
@@ -1,164 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.services
-import qs.config
-import Quickshell
-import QtQuick
-import QtQuick.Layouts
-
-StyledRect {
- id: root
-
- required property Notifs.Notif modelData
- required property Props props
- required property bool expanded
- required property var visibilities
-
- readonly property StyledText body: expandedContent.item?.body ?? null
- readonly property real nonAnimHeight: expanded ? summary.implicitHeight + expandedContent.implicitHeight + expandedContent.anchors.topMargin + Appearance.padding.normal * 2 : summaryHeightMetrics.height
-
- implicitHeight: nonAnimHeight
-
- radius: Appearance.rounding.small
- color: {
- const c = root.modelData.urgency === "critical" ? Colours.palette.m3secondaryContainer : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2);
- return expanded ? c : Qt.alpha(c, 0);
- }
-
- states: State {
- name: "expanded"
- when: root.expanded
-
- PropertyChanges {
- summary.anchors.margins: Appearance.padding.normal
- dummySummary.anchors.margins: Appearance.padding.normal
- compactBody.anchors.margins: Appearance.padding.normal
- timeStr.anchors.margins: Appearance.padding.normal
- expandedContent.anchors.margins: Appearance.padding.normal
- summary.width: root.width - Appearance.padding.normal * 2 - timeStr.implicitWidth - Appearance.spacing.small
- summary.maximumLineCount: Number.MAX_SAFE_INTEGER
- }
- }
-
- transitions: Transition {
- Anim {
- properties: "margins,width,maximumLineCount"
- }
- }
-
- TextMetrics {
- id: summaryHeightMetrics
-
- font: summary.font
- text: " " // Use this height to prevent weird characters from changing the line height
- }
-
- StyledText {
- id: summary
-
- anchors.top: parent.top
- anchors.left: parent.left
-
- width: parent.width
- text: root.modelData.summary
- color: root.modelData.urgency === "critical" ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurface
- elide: Text.ElideRight
- wrapMode: Text.WordWrap
- maximumLineCount: 1
- }
-
- StyledText {
- id: dummySummary
-
- anchors.top: parent.top
- anchors.left: parent.left
-
- visible: false
- text: root.modelData.summary
- }
-
- WrappedLoader {
- id: compactBody
-
- shouldBeActive: !root.expanded
- anchors.top: parent.top
- anchors.left: dummySummary.right
- anchors.right: parent.right
- anchors.leftMargin: Appearance.spacing.small
-
- sourceComponent: StyledText {
- text: root.modelData.body.replace(/\n/g, " ")
- color: root.modelData.urgency === "critical" ? Colours.palette.m3secondary : Colours.palette.m3outline
- elide: Text.ElideRight
- }
- }
-
- WrappedLoader {
- id: timeStr
-
- shouldBeActive: root.expanded
- anchors.top: parent.top
- anchors.right: parent.right
-
- sourceComponent: StyledText {
- animate: true
- text: root.modelData.timeStr
- color: Colours.palette.m3outline
- font.pointSize: Appearance.font.size.small
- }
- }
-
- WrappedLoader {
- id: expandedContent
-
- shouldBeActive: root.expanded
- anchors.top: summary.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.topMargin: Appearance.spacing.small / 2
-
- sourceComponent: ColumnLayout {
- readonly property alias body: body
-
- spacing: Appearance.spacing.smaller
-
- StyledText {
- id: body
-
- Layout.fillWidth: true
- textFormat: Text.MarkdownText
- text: root.modelData.body.replace(/(.)\n(?!\n)/g, "$1\n\n") || qsTr("No body here! :/")
- color: root.modelData.urgency === "critical" ? Colours.palette.m3secondary : Colours.palette.m3outline
- wrapMode: Text.WordWrap
-
- onLinkActivated: link => {
- Quickshell.execDetached(["app2unit", "-O", "--", link]);
- root.visibilities.sidebar = false;
- }
- }
-
- NotifActionList {
- notif: root.modelData
- }
- }
- }
-
- Behavior on implicitHeight {
- Anim {
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- }
-
- component WrappedLoader: Loader {
- required property bool shouldBeActive
-
- opacity: shouldBeActive ? 1 : 0
- active: opacity > 0
-
- Behavior on opacity {
- Anim {}
- }
- }
-}
diff --git a/modules/sidebar/NotifActionList.qml b/modules/sidebar/NotifActionList.qml
deleted file mode 100644
index d1f1e1f..0000000
--- a/modules/sidebar/NotifActionList.qml
+++ /dev/null
@@ -1,200 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.components.containers
-import qs.components.effects
-import qs.services
-import qs.config
-import Quickshell
-import Quickshell.Widgets
-import QtQuick
-import QtQuick.Layouts
-
-Item {
- id: root
-
- required property Notifs.Notif notif
-
- Layout.fillWidth: true
- implicitHeight: flickable.contentHeight
-
- layer.enabled: true
- layer.smooth: true
- layer.effect: OpacityMask {
- maskSource: gradientMask
- }
-
- Item {
- id: gradientMask
-
- anchors.fill: parent
- layer.enabled: true
- visible: false
-
- Rectangle {
- anchors.fill: parent
-
- gradient: Gradient {
- orientation: Gradient.Horizontal
-
- GradientStop {
- position: 0
- color: Qt.rgba(0, 0, 0, 0)
- }
- GradientStop {
- position: 0.1
- color: Qt.rgba(0, 0, 0, 1)
- }
- GradientStop {
- position: 0.9
- color: Qt.rgba(0, 0, 0, 1)
- }
- GradientStop {
- position: 1
- color: Qt.rgba(0, 0, 0, 0)
- }
- }
- }
-
- Rectangle {
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- anchors.left: parent.left
-
- implicitWidth: parent.width / 2
- opacity: flickable.contentX > 0 ? 0 : 1
-
- Behavior on opacity {
- Anim {}
- }
- }
-
- Rectangle {
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- anchors.right: parent.right
-
- implicitWidth: parent.width / 2
- opacity: flickable.contentX < flickable.contentWidth - parent.width ? 0 : 1
-
- Behavior on opacity {
- Anim {}
- }
- }
- }
-
- StyledFlickable {
- id: flickable
-
- anchors.fill: parent
- contentWidth: Math.max(width, actionList.implicitWidth)
- contentHeight: actionList.implicitHeight
-
- RowLayout {
- id: actionList
-
- anchors.fill: parent
- spacing: Appearance.spacing.small
-
- Repeater {
- model: [
- {
- isClose: true
- },
- ...root.notif.actions,
- {
- isCopy: true
- }
- ]
-
- StyledRect {
- id: action
-
- required property var modelData
-
- Layout.fillWidth: true
- Layout.fillHeight: true
- implicitWidth: actionInner.implicitWidth + Appearance.padding.normal * 2
- implicitHeight: actionInner.implicitHeight + Appearance.padding.small * 2
-
- Layout.preferredWidth: implicitWidth + (actionStateLayer.pressed ? Appearance.padding.large : 0)
- radius: actionStateLayer.pressed ? Appearance.rounding.small / 2 : Appearance.rounding.small
- color: Colours.layer(Colours.palette.m3surfaceContainerHighest, 4)
-
- Timer {
- id: copyTimer
-
- interval: 3000
- onTriggered: actionInner.item.text = "content_copy"
- }
-
- StateLayer {
- id: actionStateLayer
-
- function onClicked(): void {
- if (action.modelData.isClose) {
- root.notif.close();
- } else if (action.modelData.isCopy) {
- Quickshell.clipboardText = root.notif.body;
- actionInner.item.text = "inventory";
- copyTimer.start();
- } else if (action.modelData.invoke) {
- action.modelData.invoke();
- } else if (!root.notif.resident) {
- root.notif.close();
- }
- }
- }
-
- Loader {
- id: actionInner
-
- anchors.centerIn: parent
- sourceComponent: action.modelData.isClose || action.modelData.isCopy ? iconBtn : root.notif.hasActionIcons ? iconComp : textComp
- }
-
- Component {
- id: iconBtn
-
- MaterialIcon {
- animate: action.modelData.isCopy ?? false
- text: action.modelData.isCopy ? "content_copy" : "close"
- color: Colours.palette.m3onSurfaceVariant
- }
- }
-
- Component {
- id: iconComp
-
- IconImage {
- source: Quickshell.iconPath(action.modelData.identifier)
- }
- }
-
- Component {
- id: textComp
-
- StyledText {
- text: action.modelData.text
- color: Colours.palette.m3onSurfaceVariant
- }
- }
-
- Behavior on Layout.preferredWidth {
- Anim {
- duration: Appearance.anim.durations.expressiveFastSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
- }
- }
-
- Behavior on radius {
- Anim {
- duration: Appearance.anim.durations.expressiveFastSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
- }
- }
- }
- }
- }
- }
-}
diff --git a/modules/sidebar/NotifDock.qml b/modules/sidebar/NotifDock.qml
deleted file mode 100644
index d039d15..0000000
--- a/modules/sidebar/NotifDock.qml
+++ /dev/null
@@ -1,207 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.components.controls
-import qs.components.containers
-import qs.components.effects
-import qs.services
-import qs.config
-import Quickshell
-import Quickshell.Widgets
-import QtQuick
-import QtQuick.Layouts
-
-Item {
- id: root
-
- required property Props props
- required property var visibilities
- readonly property int notifCount: Notifs.list.reduce((acc, n) => n.closed ? acc : acc + 1, 0)
-
- anchors.fill: parent
- anchors.margins: Appearance.padding.normal
-
- Component.onCompleted: Notifs.list.forEach(n => n.popup = false)
-
- Item {
- id: title
-
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.margins: Appearance.padding.small
-
- implicitHeight: Math.max(count.implicitHeight, titleText.implicitHeight)
-
- StyledText {
- id: count
-
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: parent.left
- anchors.leftMargin: root.notifCount > 0 ? 0 : -width - titleText.anchors.leftMargin
- opacity: root.notifCount > 0 ? 1 : 0
-
- text: root.notifCount
- color: Colours.palette.m3outline
- font.pointSize: Appearance.font.size.normal
- font.family: Appearance.font.family.mono
- font.weight: 500
-
- Behavior on anchors.leftMargin {
- Anim {}
- }
-
- Behavior on opacity {
- Anim {}
- }
- }
-
- StyledText {
- id: titleText
-
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: count.right
- anchors.right: parent.right
- anchors.leftMargin: Appearance.spacing.small
-
- text: root.notifCount > 0 ? qsTr("notification%1").arg(root.notifCount === 1 ? "" : "s") : qsTr("Notifications")
- color: Colours.palette.m3outline
- font.pointSize: Appearance.font.size.normal
- font.family: Appearance.font.family.mono
- font.weight: 500
- elide: Text.ElideRight
- }
- }
-
- ClippingRectangle {
- id: clipRect
-
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: title.bottom
- anchors.bottom: parent.bottom
- anchors.topMargin: Appearance.spacing.smaller
-
- radius: Appearance.rounding.small
- color: "transparent"
-
- Loader {
- anchors.centerIn: parent
- active: opacity > 0
- opacity: root.notifCount > 0 ? 0 : 1
-
- sourceComponent: ColumnLayout {
- spacing: Appearance.spacing.large
-
- Image {
- asynchronous: true
- source: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/dino.png`)
- fillMode: Image.PreserveAspectFit
- sourceSize.width: clipRect.width * 0.8
-
- layer.enabled: true
- layer.effect: Colouriser {
- colorizationColor: Colours.palette.m3outlineVariant
- brightness: 1
- }
- }
-
- StyledText {
- Layout.alignment: Qt.AlignHCenter
- text: qsTr("No Notifications")
- color: Colours.palette.m3outlineVariant
- font.pointSize: Appearance.font.size.large
- font.family: Appearance.font.family.mono
- font.weight: 500
- }
- }
-
- Behavior on opacity {
- Anim {
- duration: Appearance.anim.durations.extraLarge
- }
- }
- }
-
- StyledFlickable {
- id: view
-
- anchors.fill: parent
-
- flickableDirection: Flickable.VerticalFlick
- contentWidth: width
- contentHeight: notifList.implicitHeight
-
- StyledScrollBar.vertical: StyledScrollBar {
- flickable: view
- }
-
- NotifDockList {
- id: notifList
-
- props: root.props
- visibilities: root.visibilities
- container: view
- }
- }
- }
-
- Timer {
- id: clearTimer
-
- repeat: true
- interval: 50
- onTriggered: {
- let next = null;
- for (let i = 0; i < notifList.repeater.count; i++) {
- next = notifList.repeater.itemAt(i);
- if (!next?.closed)
- break;
- }
- if (next)
- next.closeAll();
- else
- stop();
- }
- }
-
- Loader {
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- anchors.margins: Appearance.padding.normal
-
- scale: root.notifCount > 0 ? 1 : 0.5
- opacity: root.notifCount > 0 ? 1 : 0
- active: opacity > 0
-
- sourceComponent: IconButton {
- id: clearBtn
-
- icon: "clear_all"
- radius: Appearance.rounding.normal
- padding: Appearance.padding.normal
- font.pointSize: Math.round(Appearance.font.size.large * 1.2)
- onClicked: clearTimer.start()
-
- Elevation {
- anchors.fill: parent
- radius: parent.radius
- z: -1
- level: clearBtn.stateLayer.containsMouse ? 4 : 3
- }
- }
-
- Behavior on scale {
- Anim {
- duration: Appearance.anim.durations.expressiveFastSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
- }
- }
-
- Behavior on opacity {
- Anim {
- duration: Appearance.anim.durations.expressiveFastSpatial
- }
- }
- }
-}
diff --git a/modules/sidebar/NotifDockList.qml b/modules/sidebar/NotifDockList.qml
deleted file mode 100644
index b927e91..0000000
--- a/modules/sidebar/NotifDockList.qml
+++ /dev/null
@@ -1,167 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.services
-import qs.config
-import Quickshell
-import QtQuick
-
-Item {
- id: root
-
- required property Props props
- required property Flickable container
- required property var visibilities
-
- readonly property alias repeater: repeater
- readonly property int spacing: Appearance.spacing.small
- property bool flag
-
- anchors.left: parent.left
- anchors.right: parent.right
- implicitHeight: {
- const item = repeater.itemAt(repeater.count - 1);
- return item ? item.y + item.implicitHeight : 0;
- }
-
- Repeater {
- id: repeater
-
- model: ScriptModel {
- values: {
- const map = new Map();
- for (const n of Notifs.notClosed)
- map.set(n.appName, null);
- for (const n of Notifs.list)
- map.set(n.appName, null);
- return [...map.keys()];
- }
- onValuesChanged: root.flagChanged()
- }
-
- MouseArea {
- id: notif
-
- required property int index
- required property string modelData
-
- readonly property bool closed: notifInner.notifCount === 0
- readonly property alias nonAnimHeight: notifInner.nonAnimHeight
- property int startY
-
- function closeAll(): void {
- for (const n of Notifs.notClosed.filter(n => n.appName === modelData))
- n.close();
- }
-
- y: {
- root.flag; // Force update
- let y = 0;
- for (let i = 0; i < index; i++) {
- const item = repeater.itemAt(i);
- if (!item.closed)
- y += item.nonAnimHeight + root.spacing;
- }
- return y;
- }
-
- containmentMask: QtObject {
- function contains(p: point): bool {
- if (!root.container.contains(notif.mapToItem(root.container, p)))
- return false;
- return notifInner.contains(p);
- }
- }
-
- implicitWidth: root.width
- implicitHeight: notifInner.implicitHeight
-
- hoverEnabled: true
- cursorShape: pressed ? Qt.ClosedHandCursor : undefined
- acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
- preventStealing: true
- enabled: !closed
-
- drag.target: this
- drag.axis: Drag.XAxis
-
- onPressed: event => {
- startY = event.y;
- if (event.button === Qt.RightButton)
- notifInner.toggleExpand(!notifInner.expanded);
- else if (event.button === Qt.MiddleButton)
- closeAll();
- }
- onPositionChanged: event => {
- if (pressed) {
- const diffY = event.y - startY;
- if (Math.abs(diffY) > Config.notifs.expandThreshold)
- notifInner.toggleExpand(diffY > 0);
- }
- }
- onReleased: event => {
- if (Math.abs(x) < width * Config.notifs.clearThreshold)
- x = 0;
- else
- closeAll();
- }
-
- ParallelAnimation {
- running: true
-
- Anim {
- target: notif
- property: "opacity"
- from: 0
- to: 1
- }
- Anim {
- target: notif
- property: "scale"
- from: 0
- to: 1
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- }
-
- ParallelAnimation {
- running: notif.closed
-
- Anim {
- target: notif
- property: "opacity"
- to: 0
- }
- Anim {
- target: notif
- property: "scale"
- to: 0.6
- }
- }
-
- NotifGroup {
- id: notifInner
-
- modelData: notif.modelData
- props: root.props
- container: root.container
- visibilities: root.visibilities
- }
-
- Behavior on x {
- Anim {
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- }
-
- Behavior on y {
- Anim {
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- }
- }
- }
-}
diff --git a/modules/sidebar/NotifGroup.qml b/modules/sidebar/NotifGroup.qml
deleted file mode 100644
index 2c032aa..0000000
--- a/modules/sidebar/NotifGroup.qml
+++ /dev/null
@@ -1,269 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.components.effects
-import qs.services
-import qs.config
-import qs.utils
-import Quickshell
-import Quickshell.Services.Notifications
-import QtQuick
-import QtQuick.Layouts
-
-StyledRect {
- id: root
-
- required property string modelData
- required property Props props
- required property Flickable container
- required property var visibilities
-
- readonly property list<var> notifs: Notifs.list.filter(n => n.appName === modelData)
- readonly property var groupProps: {
- let count = 0;
- let img = "";
- let icon = "";
- let hasCritical = false;
- let hasNormal = false;
- for (const n of notifs) {
- if (!n.closed) {
- count++;
- if (!img && n.image.length > 0)
- img = n.image;
- if (!icon && n.appIcon.length > 0)
- icon = n.appIcon;
- if (n.urgency === NotificationUrgency.Critical)
- hasCritical = true;
- else if (n.urgency === NotificationUrgency.Normal)
- hasNormal = true;
- }
- }
- return {
- count,
- img,
- icon,
- urgency: hasCritical ? NotificationUrgency.Critical : hasNormal ? NotificationUrgency.Normal : NotificationUrgency.Low
- };
- }
- readonly property int notifCount: groupProps.count
- readonly property string image: groupProps.img
- readonly property string appIcon: groupProps.icon
- readonly property int urgency: groupProps.urgency
-
- readonly property int nonAnimHeight: {
- const headerHeight = header.implicitHeight + (root.expanded ? Math.round(Appearance.spacing.small / 2) : 0);
- const columnHeight = headerHeight + notifList.nonAnimHeight + column.Layout.topMargin + column.Layout.bottomMargin;
- return Math.round(Math.max(Config.notifs.sizes.image, columnHeight) + Appearance.padding.normal * 2);
- }
- readonly property bool expanded: props.expandedNotifs.includes(modelData)
-
- function toggleExpand(expand: bool): void {
- if (expand) {
- if (!expanded)
- props.expandedNotifs.push(modelData);
- } else if (expanded) {
- props.expandedNotifs.splice(props.expandedNotifs.indexOf(modelData), 1);
- }
- }
-
- Component.onDestruction: {
- if (notifCount === 0 && expanded)
- props.expandedNotifs.splice(props.expandedNotifs.indexOf(modelData), 1);
- }
-
- anchors.left: parent?.left
- anchors.right: parent?.right
- implicitHeight: content.implicitHeight + Appearance.padding.normal * 2
-
- clip: true
- radius: Appearance.rounding.normal
- color: Colours.layer(Colours.palette.m3surfaceContainer, 2)
-
- RowLayout {
- id: content
-
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: parent.top
- anchors.margins: Appearance.padding.normal
-
- spacing: Appearance.spacing.normal
-
- Item {
- Layout.alignment: Qt.AlignLeft | Qt.AlignTop
- implicitWidth: Config.notifs.sizes.image
- implicitHeight: Config.notifs.sizes.image
-
- Component {
- id: imageComp
-
- Image {
- source: Qt.resolvedUrl(root.image)
- fillMode: Image.PreserveAspectCrop
- sourceSize.width: Config.notifs.sizes.image
- sourceSize.height: Config.notifs.sizes.image
- cache: false
- asynchronous: true
- width: Config.notifs.sizes.image
- height: Config.notifs.sizes.image
- }
- }
-
- Component {
- id: appIconComp
-
- ColouredIcon {
- implicitSize: Math.round(Config.notifs.sizes.image * 0.6)
- source: Quickshell.iconPath(root.appIcon)
- colour: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
- layer.enabled: root.appIcon.endsWith("symbolic")
- }
- }
-
- Component {
- id: materialIconComp
-
- MaterialIcon {
- text: Icons.getNotifIcon(root.notifs[0]?.summary, root.urgency)
- color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
- font.pointSize: Appearance.font.size.large
- }
- }
-
- StyledClippingRect {
- anchors.fill: parent
- color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.urgency === NotificationUrgency.Low ? Colours.layer(Colours.palette.m3surfaceContainerHigh, 3) : Colours.palette.m3secondaryContainer
- radius: Appearance.rounding.full
-
- Loader {
- anchors.centerIn: parent
- sourceComponent: root.image ? imageComp : root.appIcon ? appIconComp : materialIconComp
- }
- }
-
- Loader {
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- active: root.appIcon && root.image
-
- sourceComponent: StyledRect {
- implicitWidth: Config.notifs.sizes.badge
- implicitHeight: Config.notifs.sizes.badge
-
- color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : root.urgency === NotificationUrgency.Low ? Colours.palette.m3surfaceContainerHigh : Colours.palette.m3secondaryContainer
- radius: Appearance.rounding.full
-
- ColouredIcon {
- anchors.centerIn: parent
- implicitSize: Math.round(Config.notifs.sizes.badge * 0.6)
- source: Quickshell.iconPath(root.appIcon)
- colour: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : root.urgency === NotificationUrgency.Low ? Colours.palette.m3onSurface : Colours.palette.m3onSecondaryContainer
- layer.enabled: root.appIcon.endsWith("symbolic")
- }
- }
- }
- }
-
- ColumnLayout {
- id: column
-
- Layout.topMargin: -Appearance.padding.small
- Layout.bottomMargin: -Appearance.padding.small / 2
- Layout.fillWidth: true
- spacing: 0
-
- RowLayout {
- id: header
-
- Layout.bottomMargin: root.expanded ? Math.round(Appearance.spacing.small / 2) : 0
- Layout.fillWidth: true
- spacing: Appearance.spacing.smaller
-
- StyledText {
- Layout.fillWidth: true
- text: root.modelData
- color: Colours.palette.m3onSurfaceVariant
- font.pointSize: Appearance.font.size.small
- elide: Text.ElideRight
- }
-
- StyledText {
- animate: true
- text: root.notifs.find(n => !n.closed)?.timeStr ?? ""
- color: Colours.palette.m3outline
- font.pointSize: Appearance.font.size.small
- }
-
- StyledRect {
- implicitWidth: expandBtn.implicitWidth + Appearance.padding.smaller * 2
- implicitHeight: groupCount.implicitHeight + Appearance.padding.small
-
- color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3error : Colours.layer(Colours.palette.m3surfaceContainerHigh, 3)
- radius: Appearance.rounding.full
-
- StateLayer {
- color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurface
-
- function onClicked(): void {
- root.toggleExpand(!root.expanded);
- }
- }
-
- RowLayout {
- id: expandBtn
-
- anchors.centerIn: parent
- spacing: Appearance.spacing.small / 2
-
- StyledText {
- id: groupCount
-
- Layout.leftMargin: Appearance.padding.small / 2
- animate: true
- text: root.notifCount
- color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurface
- font.pointSize: Appearance.font.size.small
- }
-
- MaterialIcon {
- Layout.rightMargin: -Appearance.padding.small / 2
- text: "expand_more"
- color: root.urgency === NotificationUrgency.Critical ? Colours.palette.m3onError : Colours.palette.m3onSurface
- rotation: root.expanded ? 180 : 0
- Layout.topMargin: root.expanded ? -Math.floor(Appearance.padding.smaller / 2) : 0
-
- Behavior on rotation {
- Anim {
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- }
-
- Behavior on Layout.topMargin {
- Anim {
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- }
- }
- }
- }
-
- Behavior on Layout.bottomMargin {
- Anim {}
- }
- }
-
- NotifGroupList {
- id: notifList
-
- props: root.props
- notifs: root.notifs
- expanded: root.expanded
- container: root.container
- visibilities: root.visibilities
- onRequestToggleExpand: expand => root.toggleExpand(expand)
- }
- }
- }
-}
diff --git a/modules/sidebar/NotifGroupList.qml b/modules/sidebar/NotifGroupList.qml
deleted file mode 100644
index e586b5f..0000000
--- a/modules/sidebar/NotifGroupList.qml
+++ /dev/null
@@ -1,213 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.services
-import qs.config
-import Quickshell
-import QtQuick
-import QtQuick.Layouts
-
-Item {
- id: root
-
- required property Props props
- required property list<var> notifs
- required property bool expanded
- required property Flickable container
- required property var visibilities
-
- readonly property real nonAnimHeight: {
- let h = -root.spacing;
- for (let i = 0; i < repeater.count; i++) {
- const item = repeater.itemAt(i);
- if (!item.modelData.closed && !item.previewHidden)
- h += item.nonAnimHeight + root.spacing;
- }
- return h;
- }
-
- readonly property int spacing: Math.round(Appearance.spacing.small / 2)
- property bool showAllNotifs
- property bool flag
-
- signal requestToggleExpand(expand: bool)
-
- onExpandedChanged: {
- if (expanded) {
- clearTimer.stop();
- showAllNotifs = true;
- } else {
- clearTimer.start();
- }
- }
-
- Layout.fillWidth: true
- implicitHeight: nonAnimHeight
-
- Timer {
- id: clearTimer
-
- interval: Appearance.anim.durations.normal
- onTriggered: root.showAllNotifs = false
- }
-
- Repeater {
- id: repeater
-
- model: ScriptModel {
- values: root.showAllNotifs ? root.notifs : root.notifs.slice(0, Config.notifs.groupPreviewNum + 1)
- onValuesChanged: root.flagChanged()
- }
-
- MouseArea {
- id: notif
-
- required property int index
- required property Notifs.Notif modelData
-
- readonly property alias nonAnimHeight: notifInner.nonAnimHeight
- readonly property bool previewHidden: {
- if (root.expanded)
- return false;
-
- let extraHidden = 0;
- for (let i = 0; i < index; i++)
- if (root.notifs[i].closed)
- extraHidden++;
-
- return index >= Config.notifs.groupPreviewNum + extraHidden;
- }
- property int startY
-
- y: {
- root.flag; // Force update
- let y = 0;
- for (let i = 0; i < index; i++) {
- const item = repeater.itemAt(i);
- if (!item.modelData.closed && !item.previewHidden)
- y += item.nonAnimHeight + root.spacing;
- }
- return y;
- }
-
- containmentMask: QtObject {
- function contains(p: point): bool {
- if (!root.container.contains(notif.mapToItem(root.container, p)))
- return false;
- return notifInner.contains(p);
- }
- }
-
- opacity: previewHidden ? 0 : 1
- scale: previewHidden ? 0.7 : 1
-
- implicitWidth: root.width
- implicitHeight: notifInner.implicitHeight
-
- hoverEnabled: true
- cursorShape: notifInner.body?.hoveredLink ? Qt.PointingHandCursor : pressed ? Qt.ClosedHandCursor : undefined
- acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
- preventStealing: !root.expanded
- enabled: !modelData.closed
-
- drag.target: this
- drag.axis: Drag.XAxis
-
- onPressed: event => {
- startY = event.y;
- if (event.button === Qt.RightButton)
- root.requestToggleExpand(!root.expanded);
- else if (event.button === Qt.MiddleButton)
- modelData.close();
- }
- onPositionChanged: event => {
- if (pressed && !root.expanded) {
- const diffY = event.y - startY;
- if (Math.abs(diffY) > Config.notifs.expandThreshold)
- root.requestToggleExpand(diffY > 0);
- }
- }
- onReleased: event => {
- if (Math.abs(x) < width * Config.notifs.clearThreshold)
- x = 0;
- else
- modelData.close();
- }
-
- Component.onCompleted: modelData.lock(this)
- Component.onDestruction: modelData.unlock(this)
-
- ParallelAnimation {
- Component.onCompleted: running = !notif.previewHidden
-
- Anim {
- target: notif
- property: "opacity"
- from: 0
- to: 1
- }
- Anim {
- target: notif
- property: "scale"
- from: 0.7
- to: 1
- }
- }
-
- ParallelAnimation {
- running: notif.modelData.closed
- onFinished: notif.modelData.unlock(notif)
-
- Anim {
- target: notif
- property: "opacity"
- to: 0
- }
- Anim {
- target: notif
- property: "x"
- to: notif.x >= 0 ? notif.width : -notif.width
- }
- }
-
- Notif {
- id: notifInner
-
- anchors.fill: parent
- modelData: notif.modelData
- props: root.props
- expanded: root.expanded
- visibilities: root.visibilities
- }
-
- Behavior on opacity {
- Anim {}
- }
-
- Behavior on scale {
- Anim {}
- }
-
- Behavior on x {
- Anim {
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- }
-
- Behavior on y {
- Anim {
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- }
- }
- }
-
- Behavior on implicitHeight {
- Anim {
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- }
-}
diff --git a/modules/sidebar/Props.qml b/modules/sidebar/Props.qml
deleted file mode 100644
index 4613942..0000000
--- a/modules/sidebar/Props.qml
+++ /dev/null
@@ -1,7 +0,0 @@
-import Quickshell
-
-PersistentProperties {
- property list<string> expandedNotifs: []
-
- reloadableId: "sidebar"
-}
diff --git a/modules/sidebar/Wrapper.qml b/modules/sidebar/Wrapper.qml
deleted file mode 100644
index 9303c6b..0000000
--- a/modules/sidebar/Wrapper.qml
+++ /dev/null
@@ -1,68 +0,0 @@
-pragma ComponentBehavior: Bound
-
-import qs.components
-import qs.config
-import QtQuick
-
-Item {
- id: root
-
- required property var visibilities
- required property var panels
- readonly property Props props: Props {}
-
- visible: width > 0
- implicitWidth: 0
-
- states: State {
- name: "visible"
- when: root.visibilities.sidebar && Config.sidebar.enabled
-
- PropertyChanges {
- root.implicitWidth: Config.sidebar.sizes.width
- }
- }
-
- transitions: [
- Transition {
- from: ""
- to: "visible"
-
- Anim {
- target: root
- property: "implicitWidth"
- duration: Appearance.anim.durations.expressiveDefaultSpatial
- easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
- }
- },
- Transition {
- from: "visible"
- to: ""
-
- Anim {
- target: root
- property: "implicitWidth"
- easing.bezierCurve: root.panels.osd.width > 0 || root.panels.session.width > 0 ? Appearance.anim.curves.expressiveDefaultSpatial : Appearance.anim.curves.emphasized
- }
- }
- ]
-
- Loader {
- id: content
-
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.margins: Appearance.padding.large
- anchors.bottomMargin: 0
-
- active: true
- Component.onCompleted: active = Qt.binding(() => (root.visibilities.sidebar && Config.sidebar.enabled) || root.visible)
-
- sourceComponent: Content {
- implicitWidth: Config.sidebar.sizes.width - Appearance.padding.large * 2
- props: root.props
- visibilities: root.visibilities
- }
- }
-}
diff --git a/nix/default.nix b/nix/default.nix
index 0f2675a..80dc92f 100644
--- a/nix/default.nix
+++ b/nix/default.nix
@@ -4,25 +4,17 @@
stdenv,
makeWrapper,
makeFontsConf,
- fish,
ddcutil,
brightnessctl,
app2unit,
networkmanager,
- lm_sensors,
- swappy,
wl-clipboard,
- libqalculate,
- bash,
hyprland,
material-symbols,
rubik,
nerd-fonts,
qt6,
quickshell,
- aubio,
- libcava,
- fftw,
pipewire,
xkeyboard-config,
cmake,
@@ -37,16 +29,11 @@
runtimeDeps =
[
- fish
ddcutil
brightnessctl
app2unit
networkmanager
- lm_sensors
- swappy
wl-clipboard
- libqalculate
- bash
hyprland
]
++ extraRuntimeDeps
@@ -94,7 +81,7 @@
};
nativeBuildInputs = [cmake ninja pkg-config];
- buildInputs = [qt6.qtbase qt6.qtdeclarative libqalculate pipewire aubio libcava fftw];
+ buildInputs = [qt6.qtbase qt6.qtdeclarative pipewire];
dontWrapQtApps = true;
cmakeFlags =
diff --git a/plugin/src/Caelestia/CMakeLists.txt b/plugin/src/Caelestia/CMakeLists.txt
index c99dece..d957d6e 100644
--- a/plugin/src/Caelestia/CMakeLists.txt
+++ b/plugin/src/Caelestia/CMakeLists.txt
@@ -1,11 +1,6 @@
find_package(Qt6 REQUIRED COMPONENTS Core Qml Gui Quick Concurrent Sql Network DBus)
find_package(PkgConfig REQUIRED)
pkg_check_modules(Pipewire IMPORTED_TARGET libpipewire-0.3 REQUIRED)
-pkg_check_modules(Aubio IMPORTED_TARGET aubio REQUIRED)
-pkg_check_modules(Cava IMPORTED_TARGET libcava QUIET)
-if(NOT Cava_FOUND)
- pkg_check_modules(Cava IMPORTED_TARGET cava REQUIRED)
-endif()
set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml")
qt_standard_project_setup(REQUIRES 6.9)
@@ -44,7 +39,6 @@ qml_module(caelestia
SOURCES
cutils.hpp cutils.cpp
appdb.hpp appdb.cpp
- requests.hpp requests.cpp
toaster.hpp toaster.cpp
imageanalyser.hpp imageanalyser.cpp
LIBRARIES
diff --git a/plugin/src/Caelestia/Internal/CMakeLists.txt b/plugin/src/Caelestia/Internal/CMakeLists.txt
index dfa0915..d0b7548 100644
--- a/plugin/src/Caelestia/Internal/CMakeLists.txt
+++ b/plugin/src/Caelestia/Internal/CMakeLists.txt
@@ -1,12 +1,9 @@
qml_module(caelestia-internal
URI Caelestia.Internal
SOURCES
- arcgauge.hpp arcgauge.cpp
- circularbuffer.hpp circularbuffer.cpp
circularindicatormanager.hpp circularindicatormanager.cpp
hyprdevices.hpp hyprdevices.cpp
hyprextras.hpp hyprextras.cpp
- sparklineitem.hpp sparklineitem.cpp
LIBRARIES
Qt::Gui
Qt::Quick
diff --git a/plugin/src/Caelestia/Internal/arcgauge.cpp b/plugin/src/Caelestia/Internal/arcgauge.cpp
deleted file mode 100644
index d534f5f..0000000
--- a/plugin/src/Caelestia/Internal/arcgauge.cpp
+++ /dev/null
@@ -1,119 +0,0 @@
-#include "arcgauge.hpp"
-
-#include <QtMath>
-#include <qpainter.h>
-#include <qpen.h>
-
-namespace caelestia::internal {
-
-ArcGauge::ArcGauge(QQuickItem* parent)
- : QQuickPaintedItem(parent) {
- setAntialiasing(true);
-}
-
-void ArcGauge::paint(QPainter* painter) {
- const qreal w = width();
- const qreal h = height();
- const qreal side = qMin(w, h);
- const qreal radius = (side - m_lineWidth - 2.0) / 2.0;
- const qreal cx = w / 2.0;
- const qreal cy = h / 2.0;
-
- const QRectF arcRect(cx - radius, cy - radius, radius * 2.0, radius * 2.0);
-
- // Convert from Canvas convention (CW radians from 3 o'clock) to QPainter (CCW 1/16th degrees)
- const int startAngle16 = qRound(-(m_startAngle * 180.0 / M_PI) * 16.0);
- const int sweepAngle16 = qRound(-(m_sweepAngle * 180.0 / M_PI) * 16.0);
-
- painter->setRenderHint(QPainter::Antialiasing, true);
-
- // Draw track arc
- QPen trackPen(m_trackColor, m_lineWidth);
- trackPen.setCapStyle(Qt::RoundCap);
- painter->setPen(trackPen);
- painter->setBrush(Qt::NoBrush);
- painter->drawArc(arcRect, startAngle16, sweepAngle16);
-
- // Draw value arc
- if (m_percentage > 0.0) {
- const int valueSweep16 = qRound(static_cast<qreal>(sweepAngle16) * m_percentage);
- QPen valuePen(m_accentColor, m_lineWidth);
- valuePen.setCapStyle(Qt::RoundCap);
- painter->setPen(valuePen);
- painter->drawArc(arcRect, startAngle16, valueSweep16);
- }
-}
-
-qreal ArcGauge::percentage() const {
- return m_percentage;
-}
-
-void ArcGauge::setPercentage(qreal percentage) {
- if (qFuzzyCompare(m_percentage, percentage))
- return;
- m_percentage = percentage;
- emit percentageChanged();
- update();
-}
-
-QColor ArcGauge::accentColor() const {
- return m_accentColor;
-}
-
-void ArcGauge::setAccentColor(const QColor& color) {
- if (m_accentColor == color)
- return;
- m_accentColor = color;
- emit accentColorChanged();
- update();
-}
-
-QColor ArcGauge::trackColor() const {
- return m_trackColor;
-}
-
-void ArcGauge::setTrackColor(const QColor& color) {
- if (m_trackColor == color)
- return;
- m_trackColor = color;
- emit trackColorChanged();
- update();
-}
-
-qreal ArcGauge::startAngle() const {
- return m_startAngle;
-}
-
-void ArcGauge::setStartAngle(qreal angle) {
- if (qFuzzyCompare(m_startAngle, angle))
- return;
- m_startAngle = angle;
- emit startAngleChanged();
- update();
-}
-
-qreal ArcGauge::sweepAngle() const {
- return m_sweepAngle;
-}
-
-void ArcGauge::setSweepAngle(qreal angle) {
- if (qFuzzyCompare(m_sweepAngle, angle))
- return;
- m_sweepAngle = angle;
- emit sweepAngleChanged();
- update();
-}
-
-qreal ArcGauge::lineWidth() const {
- return m_lineWidth;
-}
-
-void ArcGauge::setLineWidth(qreal width) {
- if (qFuzzyCompare(m_lineWidth, width))
- return;
- m_lineWidth = width;
- emit lineWidthChanged();
- update();
-}
-
-} // namespace caelestia::internal
diff --git a/plugin/src/Caelestia/Internal/arcgauge.hpp b/plugin/src/Caelestia/Internal/arcgauge.hpp
deleted file mode 100644
index 4ccb1fd..0000000
--- a/plugin/src/Caelestia/Internal/arcgauge.hpp
+++ /dev/null
@@ -1,61 +0,0 @@
-#pragma once
-
-#include <qcolor.h>
-#include <qobject.h>
-#include <qqmlintegration.h>
-#include <qquickpainteditem.h>
-
-namespace caelestia::internal {
-
-class ArcGauge : public QQuickPaintedItem {
- Q_OBJECT
- QML_ELEMENT
-
- Q_PROPERTY(qreal percentage READ percentage WRITE setPercentage NOTIFY percentageChanged)
- Q_PROPERTY(QColor accentColor READ accentColor WRITE setAccentColor NOTIFY accentColorChanged)
- Q_PROPERTY(QColor trackColor READ trackColor WRITE setTrackColor NOTIFY trackColorChanged)
- Q_PROPERTY(qreal startAngle READ startAngle WRITE setStartAngle NOTIFY startAngleChanged)
- Q_PROPERTY(qreal sweepAngle READ sweepAngle WRITE setSweepAngle NOTIFY sweepAngleChanged)
- Q_PROPERTY(qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged)
-
-public:
- explicit ArcGauge(QQuickItem* parent = nullptr);
-
- void paint(QPainter* painter) override;
-
- [[nodiscard]] qreal percentage() const;
- void setPercentage(qreal percentage);
-
- [[nodiscard]] QColor accentColor() const;
- void setAccentColor(const QColor& color);
-
- [[nodiscard]] QColor trackColor() const;
- void setTrackColor(const QColor& color);
-
- [[nodiscard]] qreal startAngle() const;
- void setStartAngle(qreal angle);
-
- [[nodiscard]] qreal sweepAngle() const;
- void setSweepAngle(qreal angle);
-
- [[nodiscard]] qreal lineWidth() const;
- void setLineWidth(qreal width);
-
-signals:
- void percentageChanged();
- void accentColorChanged();
- void trackColorChanged();
- void startAngleChanged();
- void sweepAngleChanged();
- void lineWidthChanged();
-
-private:
- qreal m_percentage = 0.0;
- QColor m_accentColor;
- QColor m_trackColor;
- qreal m_startAngle = 0.75 * M_PI;
- qreal m_sweepAngle = 1.5 * M_PI;
- qreal m_lineWidth = 10.0;
-};
-
-} // namespace caelestia::internal
diff --git a/plugin/src/Caelestia/Internal/circularbuffer.cpp b/plugin/src/Caelestia/Internal/circularbuffer.cpp
deleted file mode 100644
index 9701e7f..0000000
--- a/plugin/src/Caelestia/Internal/circularbuffer.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-#include "circularbuffer.hpp"
-
-#include <algorithm>
-
-namespace caelestia::internal {
-
-CircularBuffer::CircularBuffer(QObject* parent)
- : QObject(parent) {}
-
-int CircularBuffer::capacity() const {
- return m_capacity;
-}
-
-void CircularBuffer::setCapacity(int capacity) {
- if (capacity < 0)
- capacity = 0;
- if (m_capacity == capacity)
- return;
-
- const auto old = values();
-
- m_capacity = capacity;
- m_data.resize(capacity);
- m_data.fill(0.0);
- m_head = 0;
- m_count = 0;
-
- // Re-push old values, keeping the most recent ones
- const auto start = old.size() > capacity ? old.size() - capacity : 0;
- for (auto i = start; i < old.size(); ++i) {
- m_data[m_head] = old[i];
- m_head = (m_head + 1) % m_capacity;
- m_count++;
- }
-
- emit capacityChanged();
- emit countChanged();
- emit valuesChanged();
-}
-
-int CircularBuffer::count() const {
- return m_count;
-}
-
-QList<qreal> CircularBuffer::values() const {
- QList<qreal> result;
- result.reserve(m_count);
- for (int i = 0; i < m_count; ++i)
- result.append(at(i));
- return result;
-}
-
-qreal CircularBuffer::maximum() const {
- if (m_count == 0)
- return 0.0;
-
- qreal maxVal = at(0);
- for (int i = 1; i < m_count; ++i)
- maxVal = std::max(maxVal, at(i));
- return maxVal;
-}
-
-void CircularBuffer::push(qreal value) {
- if (m_capacity <= 0)
- return;
-
- m_data[m_head] = value;
- m_head = (m_head + 1) % m_capacity;
- if (m_count < m_capacity) {
- m_count++;
- emit countChanged();
- }
- emit valuesChanged();
-}
-
-void CircularBuffer::clear() {
- if (m_count == 0)
- return;
-
- m_head = 0;
- m_count = 0;
- emit countChanged();
- emit valuesChanged();
-}
-
-qreal CircularBuffer::at(int index) const {
- if (index < 0 || index >= m_count)
- return 0.0;
-
- const int actualIndex = (m_head - m_count + index + m_capacity) % m_capacity;
- return m_data[actualIndex];
-}
-
-} // namespace caelestia::internal
diff --git a/plugin/src/Caelestia/Internal/circularbuffer.hpp b/plugin/src/Caelestia/Internal/circularbuffer.hpp
deleted file mode 100644
index ab2dba5..0000000
--- a/plugin/src/Caelestia/Internal/circularbuffer.hpp
+++ /dev/null
@@ -1,44 +0,0 @@
-#pragma once
-
-#include <qobject.h>
-#include <qqmlintegration.h>
-#include <qvector.h>
-
-namespace caelestia::internal {
-
-class CircularBuffer : public QObject {
- Q_OBJECT
- QML_ELEMENT
-
- Q_PROPERTY(int capacity READ capacity WRITE setCapacity NOTIFY capacityChanged)
- Q_PROPERTY(int count READ count NOTIFY countChanged)
- Q_PROPERTY(QList<qreal> values READ values NOTIFY valuesChanged)
- Q_PROPERTY(qreal maximum READ maximum NOTIFY valuesChanged)
-
-public:
- explicit CircularBuffer(QObject* parent = nullptr);
-
- [[nodiscard]] int capacity() const;
- void setCapacity(int capacity);
-
- [[nodiscard]] int count() const;
- [[nodiscard]] QList<qreal> values() const;
- [[nodiscard]] qreal maximum() const;
-
- Q_INVOKABLE void push(qreal value);
- Q_INVOKABLE void clear();
- Q_INVOKABLE [[nodiscard]] qreal at(int index) const;
-
-signals:
- void capacityChanged();
- void countChanged();
- void valuesChanged();
-
-private:
- QVector<qreal> m_data;
- int m_head = 0;
- int m_count = 0;
- int m_capacity = 0;
-};
-
-} // namespace caelestia::internal
diff --git a/plugin/src/Caelestia/Internal/sparklineitem.cpp b/plugin/src/Caelestia/Internal/sparklineitem.cpp
deleted file mode 100644
index b4938d1..0000000
--- a/plugin/src/Caelestia/Internal/sparklineitem.cpp
+++ /dev/null
@@ -1,212 +0,0 @@
-#include "sparklineitem.hpp"
-
-#include <qpainter.h>
-#include <qpainterpath.h>
-#include <qpen.h>
-
-namespace caelestia::internal {
-
-SparklineItem::SparklineItem(QQuickItem* parent)
- : QQuickPaintedItem(parent) {
- setAntialiasing(true);
-}
-
-void SparklineItem::paint(QPainter* painter) {
- const bool has1 = m_line1 && m_line1->count() >= 2;
- const bool has2 = m_line2 && m_line2->count() >= 2;
- if (!has1 && !has2)
- return;
-
- painter->setRenderHint(QPainter::Antialiasing, true);
-
- // Draw line1 first (behind), then line2 (in front)
- if (has1)
- drawLine(painter, m_line1, m_line1Color, m_line1FillAlpha);
- if (has2)
- drawLine(painter, m_line2, m_line2Color, m_line2FillAlpha);
-}
-
-void SparklineItem::drawLine(QPainter* painter, CircularBuffer* buffer, const QColor& color, qreal fillAlpha) {
- const qreal w = width();
- const qreal h = height();
- const int len = buffer->count();
- const qreal stepX = w / static_cast<qreal>(m_historyLength - 1);
- const qreal startX = w - (len - 1) * stepX - stepX * m_slideProgress + stepX;
-
- // Build line path
- QPainterPath linePath;
- linePath.moveTo(startX, h - (buffer->at(0) / m_maxValue) * h);
- for (int i = 1; i < len; ++i) {
- const qreal x = startX + i * stepX;
- const qreal y = h - (buffer->at(i) / m_maxValue) * h;
- linePath.lineTo(x, y);
- }
-
- // Stroke the line
- QPen pen(color, m_lineWidth);
- pen.setCapStyle(Qt::RoundCap);
- pen.setJoinStyle(Qt::RoundJoin);
- painter->setPen(pen);
- painter->setBrush(Qt::NoBrush);
- painter->drawPath(linePath);
-
- // Fill under the line
- QPainterPath fillPath = linePath;
- fillPath.lineTo(startX + (len - 1) * stepX, h);
- fillPath.lineTo(startX, h);
- fillPath.closeSubpath();
-
- QColor fillColor = color;
- fillColor.setAlphaF(static_cast<float>(fillAlpha));
- painter->setPen(Qt::NoPen);
- painter->setBrush(fillColor);
- painter->drawPath(fillPath);
-}
-
-void SparklineItem::connectBuffer(CircularBuffer* buffer) {
- if (!buffer)
- return;
-
- connect(buffer, &CircularBuffer::valuesChanged, this, [this]() {
- update();
- });
- connect(buffer, &QObject::destroyed, this, [this, buffer]() {
- if (m_line1 == buffer) {
- m_line1 = nullptr;
- emit line1Changed();
- }
- if (m_line2 == buffer) {
- m_line2 = nullptr;
- emit line2Changed();
- }
- update();
- });
-}
-
-CircularBuffer* SparklineItem::line1() const {
- return m_line1;
-}
-
-void SparklineItem::setLine1(CircularBuffer* buffer) {
- if (m_line1 == buffer)
- return;
- if (m_line1)
- disconnect(m_line1, nullptr, this, nullptr);
- m_line1 = buffer;
- connectBuffer(buffer);
- emit line1Changed();
- update();
-}
-
-CircularBuffer* SparklineItem::line2() const {
- return m_line2;
-}
-
-void SparklineItem::setLine2(CircularBuffer* buffer) {
- if (m_line2 == buffer)
- return;
- if (m_line2)
- disconnect(m_line2, nullptr, this, nullptr);
- m_line2 = buffer;
- connectBuffer(buffer);
- emit line2Changed();
- update();
-}
-
-QColor SparklineItem::line1Color() const {
- return m_line1Color;
-}
-
-void SparklineItem::setLine1Color(const QColor& color) {
- if (m_line1Color == color)
- return;
- m_line1Color = color;
- emit line1ColorChanged();
- update();
-}
-
-QColor SparklineItem::line2Color() const {
- return m_line2Color;
-}
-
-void SparklineItem::setLine2Color(const QColor& color) {
- if (m_line2Color == color)
- return;
- m_line2Color = color;
- emit line2ColorChanged();
- update();
-}
-
-qreal SparklineItem::line1FillAlpha() const {
- return m_line1FillAlpha;
-}
-
-void SparklineItem::setLine1FillAlpha(qreal alpha) {
- if (qFuzzyCompare(m_line1FillAlpha, alpha))
- return;
- m_line1FillAlpha = alpha;
- emit line1FillAlphaChanged();
- update();
-}
-
-qreal SparklineItem::line2FillAlpha() const {
- return m_line2FillAlpha;
-}
-
-void SparklineItem::setLine2FillAlpha(qreal alpha) {
- if (qFuzzyCompare(m_line2FillAlpha, alpha))
- return;
- m_line2FillAlpha = alpha;
- emit line2FillAlphaChanged();
- update();
-}
-
-qreal SparklineItem::maxValue() const {
- return m_maxValue;
-}
-
-void SparklineItem::setMaxValue(qreal value) {
- if (qFuzzyCompare(m_maxValue, value))
- return;
- m_maxValue = value;
- emit maxValueChanged();
- update();
-}
-
-qreal SparklineItem::slideProgress() const {
- return m_slideProgress;
-}
-
-void SparklineItem::setSlideProgress(qreal progress) {
- if (qFuzzyCompare(m_slideProgress, progress))
- return;
- m_slideProgress = progress;
- emit slideProgressChanged();
- update();
-}
-
-int SparklineItem::historyLength() const {
- return m_historyLength;
-}
-
-void SparklineItem::setHistoryLength(int length) {
- if (m_historyLength == length)
- return;
- m_historyLength = length;
- emit historyLengthChanged();
- update();
-}
-
-qreal SparklineItem::lineWidth() const {
- return m_lineWidth;
-}
-
-void SparklineItem::setLineWidth(qreal width) {
- if (qFuzzyCompare(m_lineWidth, width))
- return;
- m_lineWidth = width;
- emit lineWidthChanged();
- update();
-}
-
-} // namespace caelestia::internal
diff --git a/plugin/src/Caelestia/Internal/sparklineitem.hpp b/plugin/src/Caelestia/Internal/sparklineitem.hpp
deleted file mode 100644
index 945a1b3..0000000
--- a/plugin/src/Caelestia/Internal/sparklineitem.hpp
+++ /dev/null
@@ -1,90 +0,0 @@
-#pragma once
-
-#include <qcolor.h>
-#include <qobject.h>
-#include <qqmlintegration.h>
-#include <qquickpainteditem.h>
-
-#include "circularbuffer.hpp"
-
-namespace caelestia::internal {
-
-class SparklineItem : public QQuickPaintedItem {
- Q_OBJECT
- QML_ELEMENT
-
- Q_PROPERTY(CircularBuffer* line1 READ line1 WRITE setLine1 NOTIFY line1Changed)
- Q_PROPERTY(CircularBuffer* line2 READ line2 WRITE setLine2 NOTIFY line2Changed)
- Q_PROPERTY(QColor line1Color READ line1Color WRITE setLine1Color NOTIFY line1ColorChanged)
- Q_PROPERTY(QColor line2Color READ line2Color WRITE setLine2Color NOTIFY line2ColorChanged)
- Q_PROPERTY(qreal line1FillAlpha READ line1FillAlpha WRITE setLine1FillAlpha NOTIFY line1FillAlphaChanged)
- Q_PROPERTY(qreal line2FillAlpha READ line2FillAlpha WRITE setLine2FillAlpha NOTIFY line2FillAlphaChanged)
- Q_PROPERTY(qreal maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged)
- Q_PROPERTY(qreal slideProgress READ slideProgress WRITE setSlideProgress NOTIFY slideProgressChanged)
- Q_PROPERTY(int historyLength READ historyLength WRITE setHistoryLength NOTIFY historyLengthChanged)
- Q_PROPERTY(qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged)
-
-public:
- explicit SparklineItem(QQuickItem* parent = nullptr);
-
- void paint(QPainter* painter) override;
-
- [[nodiscard]] CircularBuffer* line1() const;
- void setLine1(CircularBuffer* buffer);
-
- [[nodiscard]] CircularBuffer* line2() const;
- void setLine2(CircularBuffer* buffer);
-
- [[nodiscard]] QColor line1Color() const;
- void setLine1Color(const QColor& color);
-
- [[nodiscard]] QColor line2Color() const;
- void setLine2Color(const QColor& color);
-
- [[nodiscard]] qreal line1FillAlpha() const;
- void setLine1FillAlpha(qreal alpha);
-
- [[nodiscard]] qreal line2FillAlpha() const;
- void setLine2FillAlpha(qreal alpha);
-
- [[nodiscard]] qreal maxValue() const;
- void setMaxValue(qreal value);
-
- [[nodiscard]] qreal slideProgress() const;
- void setSlideProgress(qreal progress);
-
- [[nodiscard]] int historyLength() const;
- void setHistoryLength(int length);
-
- [[nodiscard]] qreal lineWidth() const;
- void setLineWidth(qreal width);
-
-signals:
- void line1Changed();
- void line2Changed();
- void line1ColorChanged();
- void line2ColorChanged();
- void line1FillAlphaChanged();
- void line2FillAlphaChanged();
- void maxValueChanged();
- void slideProgressChanged();
- void historyLengthChanged();
- void lineWidthChanged();
-
-private:
- void drawLine(QPainter* painter, CircularBuffer* buffer, const QColor& color, qreal fillAlpha);
- void connectBuffer(CircularBuffer* buffer);
-
- CircularBuffer* m_line1 = nullptr;
- CircularBuffer* m_line2 = nullptr;
- QColor m_line1Color;
- QColor m_line2Color;
- qreal m_line1FillAlpha = 0.15;
- qreal m_line2FillAlpha = 0.2;
- qreal m_maxValue = 1024.0;
- qreal m_slideProgress = 0.0;
- int m_historyLength = 30;
- qreal m_lineWidth = 2.0;
-};
-
-} // namespace caelestia::internal
diff --git a/plugin/src/Caelestia/Services/CMakeLists.txt b/plugin/src/Caelestia/Services/CMakeLists.txt
index 8ce868b..182c30a 100644
--- a/plugin/src/Caelestia/Services/CMakeLists.txt
+++ b/plugin/src/Caelestia/Services/CMakeLists.txt
@@ -2,13 +2,4 @@ qml_module(caelestia-services
URI Caelestia.Services
SOURCES
service.hpp service.cpp
- serviceref.hpp serviceref.cpp
- beattracker.hpp beattracker.cpp
- audiocollector.hpp audiocollector.cpp
- audioprovider.hpp audioprovider.cpp
- cavaprovider.hpp cavaprovider.cpp
- LIBRARIES
- PkgConfig::Pipewire
- PkgConfig::Aubio
- PkgConfig::Cava
)
diff --git a/plugin/src/Caelestia/Services/audiocollector.cpp b/plugin/src/Caelestia/Services/audiocollector.cpp
deleted file mode 100644
index 1563405..0000000
--- a/plugin/src/Caelestia/Services/audiocollector.cpp
+++ /dev/null
@@ -1,246 +0,0 @@
-#include "audiocollector.hpp"
-
-#include "service.hpp"
-#include <algorithm>
-#include <pipewire/pipewire.h>
-#include <qdebug.h>
-#include <qmutex.h>
-#include <spa/param/audio/format-utils.h>
-#include <spa/param/latency-utils.h>
-#include <stop_token>
-#include <vector>
-
-namespace caelestia::services {
-
-PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector)
- : m_loop(nullptr)
- , m_stream(nullptr)
- , m_timer(nullptr)
- , m_idle(true)
- , m_token(token)
- , m_collector(collector) {
- pw_init(nullptr, nullptr);
-
- m_loop = pw_main_loop_new(nullptr);
- if (!m_loop) {
- qWarning() << "PipeWireWorker::init: failed to create PipeWire main loop";
- pw_deinit();
- return;
- }
-
- timespec timeout = { 0, 10 * SPA_NSEC_PER_MSEC };
- m_timer = pw_loop_add_timer(pw_main_loop_get_loop(m_loop), handleTimeout, this);
- pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false);
-
- auto props = pw_properties_new(
- PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Music", nullptr);
- pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true");
- pw_properties_setf(
- props, PW_KEY_NODE_LATENCY, "%u/%u", nextPowerOf2(512 * ac::SAMPLE_RATE / 48000), ac::SAMPLE_RATE);
- pw_properties_set(props, PW_KEY_NODE_PASSIVE, "true");
- pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
- pw_properties_set(props, PW_KEY_STREAM_DONT_REMIX, "false");
- pw_properties_set(props, "channelmix.upmix", "true");
-
- std::vector<uint8_t> buffer(ac::CHUNK_SIZE);
- spa_pod_builder b;
- spa_pod_builder_init(&b, buffer.data(), static_cast<quint32>(buffer.size()));
-
- spa_audio_info_raw info{};
- info.format = SPA_AUDIO_FORMAT_S16;
- info.rate = ac::SAMPLE_RATE;
- info.channels = 1;
-
- const spa_pod* params[1];
- params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info);
-
- pw_stream_events events{};
- events.state_changed = [](void* data, pw_stream_state, pw_stream_state state, const char*) {
- auto* self = static_cast<PipeWireWorker*>(data);
- self->streamStateChanged(state);
- };
- events.process = [](void* data) {
- auto* self = static_cast<PipeWireWorker*>(data);
- self->processStream();
- };
-
- m_stream = pw_stream_new_simple(pw_main_loop_get_loop(m_loop), "caelestia-shell", props, &events, this);
-
- const int success = pw_stream_connect(m_stream, PW_DIRECTION_INPUT, PW_ID_ANY,
- static_cast<pw_stream_flags>(
- PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS),
- params, 1);
- if (success < 0) {
- qWarning() << "PipeWireWorker::init: failed to connect stream";
- pw_stream_destroy(m_stream);
- pw_main_loop_destroy(m_loop);
- pw_deinit();
- return;
- }
-
- pw_main_loop_run(m_loop);
-
- pw_stream_destroy(m_stream);
- pw_main_loop_destroy(m_loop);
- pw_deinit();
-}
-
-void PipeWireWorker::handleTimeout(void* data, uint64_t expirations) {
- auto* self = static_cast<PipeWireWorker*>(data);
-
- if (self->m_token.stop_requested()) {
- pw_main_loop_quit(self->m_loop);
- return;
- }
-
- if (!self->m_idle) {
- if (expirations < 10) {
- self->m_collector->clearBuffer();
- } else {
- self->m_idle = true;
- timespec timeout = { 0, 500 * SPA_NSEC_PER_MSEC };
- pw_loop_update_timer(pw_main_loop_get_loop(self->m_loop), self->m_timer, &timeout, &timeout, false);
- }
- }
-}
-
-void PipeWireWorker::streamStateChanged(pw_stream_state state) {
- m_idle = false;
- switch (state) {
- case PW_STREAM_STATE_PAUSED: {
- timespec timeout = { 0, 10 * SPA_NSEC_PER_MSEC };
- pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false);
- break;
- }
- case PW_STREAM_STATE_STREAMING:
- pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, nullptr, nullptr, false);
- break;
- case PW_STREAM_STATE_ERROR:
- pw_main_loop_quit(m_loop);
- break;
- default:
- break;
- }
-}
-
-void PipeWireWorker::processStream() {
- if (m_token.stop_requested()) {
- pw_main_loop_quit(m_loop);
- return;
- }
-
- pw_buffer* buffer = pw_stream_dequeue_buffer(m_stream);
- if (buffer == nullptr) {
- return;
- }
-
- const spa_buffer* buf = buffer->buffer;
- const qint16* samples = reinterpret_cast<const qint16*>(buf->datas[0].data);
- if (samples == nullptr) {
- return;
- }
-
- const quint32 count = buf->datas[0].chunk->size / 2;
- m_collector->loadChunk(samples, count);
-
- pw_stream_queue_buffer(m_stream, buffer);
-}
-
-unsigned int PipeWireWorker::nextPowerOf2(unsigned int n) {
- if (n == 0) {
- return 1;
- }
-
- n--;
- n |= n >> 1;
- n |= n >> 2;
- n |= n >> 4;
- n |= n >> 8;
- n |= n >> 16;
- n++;
-
- return n;
-}
-
-AudioCollector& AudioCollector::instance() {
- static AudioCollector instance;
- return instance;
-}
-
-void AudioCollector::clearBuffer() {
- auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
- std::fill(writeBuffer->begin(), writeBuffer->end(), 0.0f);
-
- auto* oldRead = m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel);
- m_writeBuffer.store(oldRead, std::memory_order_release);
-}
-
-void AudioCollector::loadChunk(const qint16* samples, quint32 count) {
- if (count > ac::CHUNK_SIZE) {
- count = ac::CHUNK_SIZE;
- }
-
- auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
- std::transform(samples, samples + count, writeBuffer->begin(), [](qint16 sample) {
- return sample / 32768.0f;
- });
-
- auto* oldRead = m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel);
- m_writeBuffer.store(oldRead, std::memory_order_release);
-}
-
-quint32 AudioCollector::readChunk(float* out, quint32 count) {
- if (count == 0 || count > ac::CHUNK_SIZE) {
- count = ac::CHUNK_SIZE;
- }
-
- auto* readBuffer = m_readBuffer.load(std::memory_order_acquire);
- std::memcpy(out, readBuffer->data(), count * sizeof(float));
-
- return count;
-}
-
-quint32 AudioCollector::readChunk(double* out, quint32 count) {
- if (count == 0 || count > ac::CHUNK_SIZE) {
- count = ac::CHUNK_SIZE;
- }
-
- auto* readBuffer = m_readBuffer.load(std::memory_order_acquire);
- std::transform(readBuffer->begin(), readBuffer->begin() + count, out, [](float sample) {
- return static_cast<double>(sample);
- });
-
- return count;
-}
-
-AudioCollector::AudioCollector(QObject* parent)
- : Service(parent)
- , m_buffer1(ac::CHUNK_SIZE)
- , m_buffer2(ac::CHUNK_SIZE)
- , m_readBuffer(&m_buffer1)
- , m_writeBuffer(&m_buffer2) {}
-
-AudioCollector::~AudioCollector() {
- stop();
-}
-
-void AudioCollector::start() {
- if (m_thread.joinable()) {
- return;
- }
-
- clearBuffer();
-
- m_thread = std::jthread([this](std::stop_token token) {
- PipeWireWorker worker(token, this);
- });
-}
-
-void AudioCollector::stop() {
- if (m_thread.joinable()) {
- m_thread.request_stop();
- m_thread.join();
- }
-}
-
-} // namespace caelestia::services
diff --git a/plugin/src/Caelestia/Services/audiocollector.hpp b/plugin/src/Caelestia/Services/audiocollector.hpp
deleted file mode 100644
index cd63afa..0000000
--- a/plugin/src/Caelestia/Services/audiocollector.hpp
+++ /dev/null
@@ -1,76 +0,0 @@
-#pragma once
-
-#include "service.hpp"
-#include <atomic>
-#include <pipewire/pipewire.h>
-#include <qmutex.h>
-#include <qqmlintegration.h>
-#include <spa/param/audio/format-utils.h>
-#include <stop_token>
-#include <thread>
-#include <vector>
-
-namespace caelestia::services {
-
-namespace ac {
-
-constexpr quint32 SAMPLE_RATE = 44100;
-constexpr quint32 CHUNK_SIZE = 512;
-
-} // namespace ac
-
-class AudioCollector;
-
-class PipeWireWorker {
-public:
- explicit PipeWireWorker(std::stop_token token, AudioCollector* collector);
-
- void run();
-
-private:
- pw_main_loop* m_loop;
- pw_stream* m_stream;
- spa_source* m_timer;
- bool m_idle;
-
- std::stop_token m_token;
- AudioCollector* m_collector;
-
- static void handleTimeout(void* data, uint64_t expirations);
- void streamStateChanged(pw_stream_state state);
- void processStream();
-
- [[nodiscard]] unsigned int nextPowerOf2(unsigned int n);
-};
-
-class AudioCollector : public Service {
- Q_OBJECT
-
-public:
- AudioCollector(const AudioCollector&) = delete;
- AudioCollector& operator=(const AudioCollector&) = delete;
-
- static AudioCollector& instance();
-
- void clearBuffer();
- void loadChunk(const qint16* samples, quint32 count);
- quint32 readChunk(float* out, quint32 count = 0);
- quint32 readChunk(double* out, quint32 count = 0);
-
-private:
- explicit AudioCollector(QObject* parent = nullptr);
- ~AudioCollector();
-
- std::jthread m_thread;
- std::vector<float> m_buffer1;
- std::vector<float> m_buffer2;
- std::atomic<std::vector<float>*> m_readBuffer;
- std::atomic<std::vector<float>*> m_writeBuffer;
- quint32 m_sampleCount;
-
- void reload();
- void start() override;
- void stop() override;
-};
-
-} // namespace caelestia::services
diff --git a/plugin/src/Caelestia/Services/audioprovider.cpp b/plugin/src/Caelestia/Services/audioprovider.cpp
deleted file mode 100644
index 1fac9ee..0000000
--- a/plugin/src/Caelestia/Services/audioprovider.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-#include "audioprovider.hpp"
-
-#include "audiocollector.hpp"
-#include "service.hpp"
-#include <qdebug.h>
-#include <qthread.h>
-
-namespace caelestia::services {
-
-AudioProcessor::AudioProcessor(QObject* parent)
- : QObject(parent) {}
-
-AudioProcessor::~AudioProcessor() {
- stop();
-}
-
-void AudioProcessor::init() {
- m_timer = new QTimer(this);
- m_timer->setInterval(static_cast<int>(ac::CHUNK_SIZE * 1000.0 / ac::SAMPLE_RATE));
- connect(m_timer, &QTimer::timeout, this, &AudioProcessor::process);
-}
-
-void AudioProcessor::start() {
- QMetaObject::invokeMethod(&AudioCollector::instance(), &AudioCollector::ref, Qt::QueuedConnection, this);
- if (m_timer) {
- m_timer->start();
- }
-}
-
-void AudioProcessor::stop() {
- if (m_timer) {
- m_timer->stop();
- }
- QMetaObject::invokeMethod(&AudioCollector::instance(), &AudioCollector::unref, Qt::QueuedConnection, this);
-}
-
-AudioProvider::AudioProvider(QObject* parent)
- : Service(parent)
- , m_processor(nullptr)
- , m_thread(nullptr) {}
-
-AudioProvider::~AudioProvider() {
- if (m_thread) {
- m_thread->quit();
- m_thread->wait();
- }
-}
-
-void AudioProvider::init() {
- if (!m_processor) {
- qWarning() << "AudioProvider::init: attempted to init with no processor set";
- return;
- }
-
- m_thread = new QThread(this);
- m_processor->moveToThread(m_thread);
-
- connect(m_thread, &QThread::started, m_processor, &AudioProcessor::init);
- connect(m_thread, &QThread::finished, m_processor, &AudioProcessor::deleteLater);
- connect(m_thread, &QThread::finished, m_thread, &QThread::deleteLater);
-
- m_thread->start();
-}
-
-void AudioProvider::start() {
- if (m_processor) {
- AudioCollector::instance(); // Create instance on main thread
- QMetaObject::invokeMethod(m_processor, &AudioProcessor::start);
- }
-}
-
-void AudioProvider::stop() {
- if (m_processor) {
- QMetaObject::invokeMethod(m_processor, &AudioProcessor::stop);
- }
-}
-
-} // namespace caelestia::services
diff --git a/plugin/src/Caelestia/Services/audioprovider.hpp b/plugin/src/Caelestia/Services/audioprovider.hpp
deleted file mode 100644
index 5bf9bb0..0000000
--- a/plugin/src/Caelestia/Services/audioprovider.hpp
+++ /dev/null
@@ -1,48 +0,0 @@
-#pragma once
-
-#include "service.hpp"
-#include <qqmlintegration.h>
-#include <qtimer.h>
-
-namespace caelestia::services {
-
-class AudioProcessor : public QObject {
- Q_OBJECT
-
-public:
- explicit AudioProcessor(QObject* parent = nullptr);
- ~AudioProcessor();
-
- void init();
-
-public slots:
- void start();
- void stop();
-
-protected:
- virtual void process() = 0;
-
-private:
- QTimer* m_timer;
-};
-
-class AudioProvider : public Service {
- Q_OBJECT
-
-public:
- explicit AudioProvider(QObject* parent = nullptr);
- ~AudioProvider();
-
-protected:
- AudioProcessor* m_processor;
-
- void init();
-
-private:
- QThread* m_thread;
-
- void start() override;
- void stop() override;
-};
-
-} // namespace caelestia::services
diff --git a/plugin/src/Caelestia/Services/beattracker.cpp b/plugin/src/Caelestia/Services/beattracker.cpp
deleted file mode 100644
index 93addc6..0000000
--- a/plugin/src/Caelestia/Services/beattracker.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-#include "beattracker.hpp"
-
-#include "audiocollector.hpp"
-#include "audioprovider.hpp"
-#include <aubio/aubio.h>
-
-namespace caelestia::services {
-
-BeatProcessor::BeatProcessor(QObject* parent)
- : AudioProcessor(parent)
- , m_tempo(new_aubio_tempo("default", 1024, ac::CHUNK_SIZE, ac::SAMPLE_RATE))
- , m_in(new_fvec(ac::CHUNK_SIZE))
- , m_out(new_fvec(2)) {};
-
-BeatProcessor::~BeatProcessor() {
- if (m_tempo) {
- del_aubio_tempo(m_tempo);
- }
- if (m_in) {
- del_fvec(m_in);
- }
- del_fvec(m_out);
-}
-
-void BeatProcessor::process() {
- if (!m_tempo || !m_in) {
- return;
- }
-
- AudioCollector::instance().readChunk(m_in->data);
-
- aubio_tempo_do(m_tempo, m_in, m_out);
- if (!qFuzzyIsNull(m_out->data[0])) {
- emit beat(aubio_tempo_get_bpm(m_tempo));
- }
-}
-
-BeatTracker::BeatTracker(QObject* parent)
- : AudioProvider(parent)
- , m_bpm(120) {
- m_processor = new BeatProcessor();
- init();
-
- connect(static_cast<BeatProcessor*>(m_processor), &BeatProcessor::beat, this, &BeatTracker::updateBpm);
-}
-
-smpl_t BeatTracker::bpm() const {
- return m_bpm;
-}
-
-void BeatTracker::updateBpm(smpl_t bpm) {
- if (!qFuzzyCompare(bpm + 1.0f, m_bpm + 1.0f)) {
- m_bpm = bpm;
- emit bpmChanged();
- }
-}
-
-} // namespace caelestia::services
diff --git a/plugin/src/Caelestia/Services/beattracker.hpp b/plugin/src/Caelestia/Services/beattracker.hpp
deleted file mode 100644
index 94738ce..0000000
--- a/plugin/src/Caelestia/Services/beattracker.hpp
+++ /dev/null
@@ -1,49 +0,0 @@
-#pragma once
-
-#include "audioprovider.hpp"
-#include <aubio/aubio.h>
-#include <qqmlintegration.h>
-
-namespace caelestia::services {
-
-class BeatProcessor : public AudioProcessor {
- Q_OBJECT
-
-public:
- explicit BeatProcessor(QObject* parent = nullptr);
- ~BeatProcessor();
-
-signals:
- void beat(smpl_t bpm);
-
-protected:
- void process() override;
-
-private:
- aubio_tempo_t* m_tempo;
- fvec_t* m_in;
- fvec_t* m_out;
-};
-
-class BeatTracker : public AudioProvider {
- Q_OBJECT
- QML_ELEMENT
-
- Q_PROPERTY(smpl_t bpm READ bpm NOTIFY bpmChanged)
-
-public:
- explicit BeatTracker(QObject* parent = nullptr);
-
- [[nodiscard]] smpl_t bpm() const;
-
-signals:
- void bpmChanged();
- void beat(smpl_t bpm);
-
-private:
- smpl_t m_bpm;
-
- void updateBpm(smpl_t bpm);
-};
-
-} // namespace caelestia::services
diff --git a/plugin/src/Caelestia/Services/cavaprovider.cpp b/plugin/src/Caelestia/Services/cavaprovider.cpp
deleted file mode 100644
index 7b6cc1f..0000000
--- a/plugin/src/Caelestia/Services/cavaprovider.cpp
+++ /dev/null
@@ -1,140 +0,0 @@
-#include "cavaprovider.hpp"
-
-#include "audiocollector.hpp"
-#include "audioprovider.hpp"
-#include <cava/cavacore.h>
-#include <cstddef>
-#include <qdebug.h>
-
-namespace caelestia::services {
-
-CavaProcessor::CavaProcessor(QObject* parent)
- : AudioProcessor(parent)
- , m_plan(nullptr)
- , m_in(new double[ac::CHUNK_SIZE])
- , m_out(nullptr)
- , m_bars(0) {};
-
-CavaProcessor::~CavaProcessor() {
- cleanup();
- delete[] m_in;
-}
-
-void CavaProcessor::process() {
- if (!m_plan || m_bars == 0 || !m_out) {
- return;
- }
-
- const int count = static_cast<int>(AudioCollector::instance().readChunk(m_in));
-
- // Process in data via cava
- cava_execute(m_in, count, m_out, m_plan);
-
- // Apply monstercat filter
- QVector<double> values(m_bars);
-
- // Left to right pass
- const double inv = 1.0 / 1.5;
- double carry = 0.0;
- for (int i = 0; i < m_bars; ++i) {
- carry = std::max(m_out[i], carry * inv);
- values[i] = carry;
- }
-
- // Right to left pass and combine
- carry = 0.0;
- for (int i = m_bars - 1; i >= 0; --i) {
- carry = std::max(m_out[i], carry * inv);
- values[i] = std::max(values[i], carry);
- }
-
- // Update values
- if (values != m_values) {
- m_values = std::move(values);
- emit valuesChanged(m_values);
- }
-}
-
-void CavaProcessor::setBars(int bars) {
- if (bars < 0) {
- qWarning() << "CavaProcessor::setBars: bars must be greater than 0. Setting to 0.";
- bars = 0;
- }
-
- if (m_bars != bars) {
- m_bars = bars;
- reload();
- }
-}
-
-void CavaProcessor::reload() {
- cleanup();
- initCava();
-}
-
-void CavaProcessor::cleanup() {
- if (m_plan) {
- cava_destroy(m_plan);
- m_plan = nullptr;
- }
-
- if (m_out) {
- delete[] m_out;
- m_out = nullptr;
- }
-}
-
-void CavaProcessor::initCava() {
- if (m_plan || m_bars == 0) {
- return;
- }
-
- m_plan = cava_init(m_bars, ac::SAMPLE_RATE, 1, 1, 0.85, 50, 10000);
- m_out = new double[static_cast<size_t>(m_bars)];
-}
-
-CavaProvider::CavaProvider(QObject* parent)
- : AudioProvider(parent)
- , m_bars(0)
- , m_values(m_bars, 0.0) {
- m_processor = new CavaProcessor();
- init();
-
- connect(static_cast<CavaProcessor*>(m_processor), &CavaProcessor::valuesChanged, this, &CavaProvider::updateValues);
-}
-
-int CavaProvider::bars() const {
- return m_bars;
-}
-
-void CavaProvider::setBars(int bars) {
- if (bars < 0) {
- qWarning() << "CavaProvider::setBars: bars must be greater than 0. Setting to 0.";
- bars = 0;
- }
-
- if (m_bars == bars) {
- return;
- }
-
- m_values.resize(bars, 0.0);
- m_bars = bars;
- emit barsChanged();
- emit valuesChanged();
-
- QMetaObject::invokeMethod(
- static_cast<CavaProcessor*>(m_processor), &CavaProcessor::setBars, Qt::QueuedConnection, bars);
-}
-
-QVector<double> CavaProvider::values() const {
- return m_values;
-}
-
-void CavaProvider::updateValues(QVector<double> values) {
- if (values != m_values) {
- m_values = values;
- emit valuesChanged();
- }
-}
-
-} // namespace caelestia::services
diff --git a/plugin/src/Caelestia/Services/cavaprovider.hpp b/plugin/src/Caelestia/Services/cavaprovider.hpp
deleted file mode 100644
index c45e33f..0000000
--- a/plugin/src/Caelestia/Services/cavaprovider.hpp
+++ /dev/null
@@ -1,64 +0,0 @@
-#pragma once
-
-#include "audioprovider.hpp"
-#include <cava/cavacore.h>
-#include <qqmlintegration.h>
-
-namespace caelestia::services {
-
-class CavaProcessor : public AudioProcessor {
- Q_OBJECT
-
-public:
- explicit CavaProcessor(QObject* parent = nullptr);
- ~CavaProcessor();
-
- void setBars(int bars);
-
-signals:
- void valuesChanged(QVector<double> values);
-
-protected:
- void process() override;
-
-private:
- struct cava_plan* m_plan;
- double* m_in;
- double* m_out;
-
- int m_bars;
- QVector<double> m_values;
-
- void reload();
- void initCava();
- void cleanup();
-};
-
-class CavaProvider : public AudioProvider {
- Q_OBJECT
- QML_ELEMENT
-
- Q_PROPERTY(int bars READ bars WRITE setBars NOTIFY barsChanged)
-
- Q_PROPERTY(QVector<double> values READ values NOTIFY valuesChanged)
-
-public:
- explicit CavaProvider(QObject* parent = nullptr);
-
- [[nodiscard]] int bars() const;
- void setBars(int bars);
-
- [[nodiscard]] QVector<double> values() const;
-
-signals:
- void barsChanged();
- void valuesChanged();
-
-private:
- int m_bars;
- QVector<double> m_values;
-
- void updateValues(QVector<double> values);
-};
-
-} // namespace caelestia::services
diff --git a/plugin/src/Caelestia/Services/serviceref.cpp b/plugin/src/Caelestia/Services/serviceref.cpp
deleted file mode 100644
index db1a3f2..0000000
--- a/plugin/src/Caelestia/Services/serviceref.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-#include "serviceref.hpp"
-
-#include "service.hpp"
-
-namespace caelestia::services {
-
-ServiceRef::ServiceRef(Service* service, QObject* parent)
- : QObject(parent)
- , m_service(service) {
- if (m_service) {
- m_service->ref(this);
- }
-}
-
-Service* ServiceRef::service() const {
- return m_service;
-}
-
-void ServiceRef::setService(Service* service) {
- if (m_service == service) {
- return;
- }
-
- if (m_service) {
- m_service->unref(this);
- }
-
- m_service = service;
- emit serviceChanged();
-
- if (m_service) {
- m_service->ref(this);
- }
-}
-
-} // namespace caelestia::services
diff --git a/plugin/src/Caelestia/Services/serviceref.hpp b/plugin/src/Caelestia/Services/serviceref.hpp
deleted file mode 100644
index d4d305c..0000000
--- a/plugin/src/Caelestia/Services/serviceref.hpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#pragma once
-
-#include "service.hpp"
-#include <qpointer.h>
-#include <qqmlintegration.h>
-
-namespace caelestia::services {
-
-class ServiceRef : public QObject {
- Q_OBJECT
- QML_ELEMENT
-
- Q_PROPERTY(caelestia::services::Service* service READ service WRITE setService NOTIFY serviceChanged)
-
-public:
- explicit ServiceRef(Service* service = nullptr, QObject* parent = nullptr);
-
- [[nodiscard]] Service* service() const;
- void setService(Service* service);
-
-signals:
- void serviceChanged();
-
-private:
- QPointer<Service> m_service;
-};
-
-} // namespace caelestia::services
diff --git a/plugin/src/Caelestia/requests.cpp b/plugin/src/Caelestia/requests.cpp
deleted file mode 100644
index 2ceddb3..0000000
--- a/plugin/src/Caelestia/requests.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-#include "requests.hpp"
-
-#include <qnetworkaccessmanager.h>
-#include <qnetworkreply.h>
-#include <qnetworkrequest.h>
-
-namespace caelestia {
-
-Requests::Requests(QObject* parent)
- : QObject(parent)
- , m_manager(new QNetworkAccessManager(this)) {}
-
-void Requests::get(const QUrl& url, QJSValue onSuccess, QJSValue onError) const {
- if (!onSuccess.isCallable()) {
- qWarning() << "Requests::get: onSuccess is not callable";
- return;
- }
-
- QNetworkRequest request(url);
- auto reply = m_manager->get(request);
-
- QObject::connect(reply, &QNetworkReply::finished, [reply, onSuccess, onError]() {
- if (reply->error() == QNetworkReply::NoError) {
- onSuccess.call({ QString(reply->readAll()) });
- } else if (onError.isCallable()) {
- onError.call({ reply->errorString() });
- } else {
- qWarning() << "Requests::get: request failed with error" << reply->errorString();
- }
-
- reply->deleteLater();
- });
-}
-
-} // namespace caelestia
diff --git a/plugin/src/Caelestia/requests.hpp b/plugin/src/Caelestia/requests.hpp
deleted file mode 100644
index 1db2f4c..0000000
--- a/plugin/src/Caelestia/requests.hpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-#include <qnetworkaccessmanager.h>
-#include <qobject.h>
-#include <qqmlengine.h>
-
-namespace caelestia {
-
-class Requests : public QObject {
- Q_OBJECT
- QML_ELEMENT
- QML_SINGLETON
-
-public:
- explicit Requests(QObject* parent = nullptr);
-
- Q_INVOKABLE void get(const QUrl& url, QJSValue callback, QJSValue onError = QJSValue()) const;
-
-private:
- QNetworkAccessManager* m_manager;
-};
-
-} // namespace caelestia
diff --git a/services/Audio.qml b/services/Audio.qml
index 3bbd9c6..f255293 100644
--- a/services/Audio.qml
+++ b/services/Audio.qml
@@ -26,9 +26,6 @@ Singleton {
readonly property bool sourceMuted: !!source?.audio?.muted
readonly property real sourceVolume: source?.audio?.volume ?? 0
- readonly property alias cava: cava
- readonly property alias beatTracker: beatTracker
-
function setVolume(newVolume: real): void {
if (sink?.ready && sink?.audio) {
sink.audio.muted = false;
@@ -152,14 +149,4 @@ Singleton {
PwObjectTracker {
objects: [...root.sinks, ...root.sources, ...root.streams]
}
-
- CavaProvider {
- id: cava
-
- bars: Config.services.visualiserBars
- }
-
- BeatTracker {
- id: beatTracker
- }
}
diff --git a/services/NetworkUsage.qml b/services/NetworkUsage.qml
deleted file mode 100644
index 4518647..0000000
--- a/services/NetworkUsage.qml
+++ /dev/null
@@ -1,229 +0,0 @@
-pragma Singleton
-
-import qs.config
-
-import Quickshell
-import Quickshell.Io
-
-import Caelestia.Internal
-
-import QtQuick
-
-Singleton {
- id: root
-
- property int refCount: 0
-
- // Current speeds in bytes per second
- readonly property real downloadSpeed: _downloadSpeed
- readonly property real uploadSpeed: _uploadSpeed
-
- // Total bytes transferred since tracking started
- readonly property real downloadTotal: _downloadTotal
- readonly property real uploadTotal: _uploadTotal
-
- // History buffers for sparkline
- readonly property CircularBuffer downloadBuffer: _downloadBuffer
- readonly property CircularBuffer uploadBuffer: _uploadBuffer
- readonly property int historyLength: 30
-
- // Private properties
- property real _downloadSpeed: 0
- property real _uploadSpeed: 0
- property real _downloadTotal: 0
- property real _uploadTotal: 0
-
- // Previous readings for calculating speed
- property real _prevRxBytes: 0
- property real _prevTxBytes: 0
- property real _prevTimestamp: 0
-
- // Initial readings for calculating totals
- property real _initialRxBytes: 0
- property real _initialTxBytes: 0
- property bool _initialized: false
-
- function formatBytes(bytes: real): var {
- // Handle negative or invalid values
- if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) {
- return {
- value: 0,
- unit: "B/s"
- };
- }
-
- if (bytes < 1024) {
- return {
- value: bytes,
- unit: "B/s"
- };
- } else if (bytes < 1024 * 1024) {
- return {
- value: bytes / 1024,
- unit: "KB/s"
- };
- } else if (bytes < 1024 * 1024 * 1024) {
- return {
- value: bytes / (1024 * 1024),
- unit: "MB/s"
- };
- } else {
- return {
- value: bytes / (1024 * 1024 * 1024),
- unit: "GB/s"
- };
- }
- }
-
- function formatBytesTotal(bytes: real): var {
- // Handle negative or invalid values
- if (bytes < 0 || isNaN(bytes) || !isFinite(bytes)) {
- return {
- value: 0,
- unit: "B"
- };
- }
-
- if (bytes < 1024) {
- return {
- value: bytes,
- unit: "B"
- };
- } else if (bytes < 1024 * 1024) {
- return {
- value: bytes / 1024,
- unit: "KB"
- };
- } else if (bytes < 1024 * 1024 * 1024) {
- return {
- value: bytes / (1024 * 1024),
- unit: "MB"
- };
- } else {
- return {
- value: bytes / (1024 * 1024 * 1024),
- unit: "GB"
- };
- }
- }
-
- function parseNetDev(content: string): var {
- const lines = content.split("\n");
- let totalRx = 0;
- let totalTx = 0;
-
- for (let i = 2; i < lines.length; i++) {
- const line = lines[i].trim();
- if (!line)
- continue;
-
- const parts = line.split(/\s+/);
- if (parts.length < 10)
- continue;
-
- const iface = parts[0].replace(":", "");
- // Skip loopback interface
- if (iface === "lo")
- continue;
-
- const rxBytes = parseFloat(parts[1]) || 0;
- const txBytes = parseFloat(parts[9]) || 0;
-
- totalRx += rxBytes;
- totalTx += txBytes;
- }
-
- return {
- rx: totalRx,
- tx: totalTx
- };
- }
-
- CircularBuffer {
- id: _downloadBuffer
- capacity: root.historyLength + 1
- }
-
- CircularBuffer {
- id: _uploadBuffer
- capacity: root.historyLength + 1
- }
-
- FileView {
- id: netDevFile
- path: "/proc/net/dev"
- }
-
- Timer {
- interval: Config.dashboard.resourceUpdateInterval
- running: root.refCount > 0
- repeat: true
- triggeredOnStart: true
-
- onTriggered: {
- netDevFile.reload();
- const content = netDevFile.text();
- if (!content)
- return;
-
- const data = root.parseNetDev(content);
- const now = Date.now();
-
- if (!root._initialized) {
- root._initialRxBytes = data.rx;
- root._initialTxBytes = data.tx;
- root._prevRxBytes = data.rx;
- root._prevTxBytes = data.tx;
- root._prevTimestamp = now;
- root._initialized = true;
- return;
- }
-
- const timeDelta = (now - root._prevTimestamp) / 1000; // seconds
- if (timeDelta > 0) {
- // Calculate byte deltas
- let rxDelta = data.rx - root._prevRxBytes;
- let txDelta = data.tx - root._prevTxBytes;
-
- // Handle counter overflow (when counters wrap around from max to 0)
- // This happens when counters exceed 32-bit or 64-bit limits
- if (rxDelta < 0) {
- // Counter wrapped around - assume 64-bit counter
- rxDelta += Math.pow(2, 64);
- }
- if (txDelta < 0) {
- txDelta += Math.pow(2, 64);
- }
-
- // Calculate speeds
- root._downloadSpeed = rxDelta / timeDelta;
- root._uploadSpeed = txDelta / timeDelta;
-
- if (root._downloadSpeed >= 0 && isFinite(root._downloadSpeed))
- _downloadBuffer.push(root._downloadSpeed);
-
- if (root._uploadSpeed >= 0 && isFinite(root._uploadSpeed))
- _uploadBuffer.push(root._uploadSpeed);
- }
-
- // Calculate totals with overflow handling
- let downTotal = data.rx - root._initialRxBytes;
- let upTotal = data.tx - root._initialTxBytes;
-
- // Handle counter overflow for totals
- if (downTotal < 0) {
- downTotal += Math.pow(2, 64);
- }
- if (upTotal < 0) {
- upTotal += Math.pow(2, 64);
- }
-
- root._downloadTotal = downTotal;
- root._uploadTotal = upTotal;
-
- root._prevRxBytes = data.rx;
- root._prevTxBytes = data.tx;
- root._prevTimestamp = now;
- }
- }
-}
diff --git a/services/Notifs.qml b/services/Notifs.qml
index a702968..480b9cc 100644
--- a/services/Notifs.qml
+++ b/services/Notifs.qml
@@ -16,20 +16,9 @@ Singleton {
property list<Notif> list: []
readonly property list<Notif> notClosed: list.filter(n => !n.closed)
readonly property list<Notif> popups: list.filter(n => n.popup)
- property alias dnd: props.dnd
property bool loaded
- onDndChanged: {
- if (!Config.toasts.dndChanged)
- return;
-
- if (dnd)
- Toaster.toast(qsTr("Do not disturb enabled"), qsTr("Popup notifications are now disabled"), "do_not_disturb_on");
- else
- Toaster.toast(qsTr("Do not disturb disabled"), qsTr("Popup notifications are now enabled"), "do_not_disturb_off");
- }
-
onListChanged: {
if (loaded)
saveTimer.restart();
@@ -55,14 +44,6 @@ Singleton {
}))))
}
- PersistentProperties {
- id: props
-
- property bool dnd
-
- reloadableId: "notifs"
- }
-
NotificationServer {
id: server
@@ -78,7 +59,7 @@ Singleton {
notif.tracked = true;
const comp = notifComp.createObject(root, {
- popup: !props.dnd && ![...Visibilities.screens.values()].some(v => v.sidebar),
+ popup: true,
notification: notif
});
root.list = [comp, ...root.list];
@@ -120,22 +101,6 @@ Singleton {
for (const notif of root.list.slice())
notif.close();
}
-
- function isDndEnabled(): bool {
- return props.dnd;
- }
-
- function toggleDnd(): void {
- props.dnd = !props.dnd;
- }
-
- function enableDnd(): void {
- props.dnd = true;
- }
-
- function disableDnd(): void {
- props.dnd = false;
- }
}
component Notif: QtObject {
diff --git a/services/Players.qml b/services/Players.qml
deleted file mode 100644
index 742b4d0..0000000
--- a/services/Players.qml
+++ /dev/null
@@ -1,126 +0,0 @@
-pragma Singleton
-
-import qs.components.misc
-import qs.config
-import Quickshell
-import Quickshell.Io
-import Quickshell.Services.Mpris
-import QtQml
-import Caelestia
-
-Singleton {
- id: root
-
- readonly property list<MprisPlayer> list: Mpris.players.values
- readonly property MprisPlayer active: props.manualActive ?? list.find(p => getIdentity(p) === Config.services.defaultPlayer) ?? list[0] ?? null
- property alias manualActive: props.manualActive
-
- function getIdentity(player: MprisPlayer): string {
- const alias = Config.services.playerAliases.find(a => a.from === player.identity);
- return alias?.to ?? player.identity;
- }
-
- Connections {
- target: active
-
- function onPostTrackChanged() {
- if (!Config.toasts.nowPlaying) {
- return;
- }
- if (active.trackArtist != "" && active.trackTitle != "") {
- Toaster.toast(qsTr("Now Playing"), qsTr("%1 - %2").arg(active.trackArtist).arg(active.trackTitle), "music_note");
- }
- }
- }
-
- PersistentProperties {
- id: props
-
- property MprisPlayer manualActive
-
- reloadableId: "players"
- }
-
- CustomShortcut {
- name: "mediaToggle"
- description: "Toggle media playback"
- onPressed: {
- const active = root.active;
- if (active && active.canTogglePlaying)
- active.togglePlaying();
- }
- }
-
- CustomShortcut {
- name: "mediaPrev"
- description: "Previous track"
- onPressed: {
- const active = root.active;
- if (active && active.canGoPrevious)
- active.previous();
- }
- }
-
- CustomShortcut {
- name: "mediaNext"
- description: "Next track"
- onPressed: {
- const active = root.active;
- if (active && active.canGoNext)
- active.next();
- }
- }
-
- CustomShortcut {
- name: "mediaStop"
- description: "Stop media playback"
- onPressed: root.active?.stop()
- }
-
- IpcHandler {
- target: "mpris"
-
- function getActive(prop: string): string {
- const active = root.active;
- return active ? active[prop] ?? "Invalid property" : "No active player";
- }
-
- function list(): string {
- return root.list.map(p => root.getIdentity(p)).join("\n");
- }
-
- function play(): void {
- const active = root.active;
- if (active?.canPlay)
- active.play();
- }
-
- function pause(): void {
- const active = root.active;
- if (active?.canPause)
- active.pause();
- }
-
- function playPause(): void {
- const active = root.active;
- if (active?.canTogglePlaying)
- active.togglePlaying();
- }
-
- function previous(): void {
- const active = root.active;
- if (active?.canGoPrevious)
- active.previous();
- }
-
- function next(): void {
- const active = root.active;
- if (active?.canGoNext)
- active.next();
- }
-
- function stop(): void {
- root.active?.stop();
- }
- }
-}
diff --git a/services/SystemUsage.qml b/services/SystemUsage.qml
deleted file mode 100644
index 5085644..0000000
--- a/services/SystemUsage.qml
+++ /dev/null
@@ -1,329 +0,0 @@
-pragma Singleton
-
-import qs.config
-import Quickshell
-import Quickshell.Io
-import QtQuick
-
-Singleton {
- id: root
-
- // CPU properties
- property string cpuName: ""
- property real cpuPerc
- property real cpuTemp
-
- // GPU properties
- readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
- property string autoGpuType: "NONE"
- property string gpuName: ""
- property real gpuPerc
- property real gpuTemp
-
- // Memory properties
- property real memUsed
- property real memTotal
- readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0
-
- // Storage properties (aggregated)
- readonly property real storagePerc: {
- let totalUsed = 0;
- let totalSize = 0;
- for (const disk of disks) {
- totalUsed += disk.used;
- totalSize += disk.total;
- }
- return totalSize > 0 ? totalUsed / totalSize : 0;
- }
-
- // Individual disks: Array of { mount, used, total, free, perc }
- property var disks: []
-
- property real lastCpuIdle
- property real lastCpuTotal
-
- property int refCount
-
- function cleanCpuName(name: string): string {
- return name.replace(/\(R\)|\(TM\)|CPU|\d+(?:th|nd|rd|st) Gen |Core |Processor/gi, "").replace(/\s+/g, " ").trim();
- }
-
- function cleanGpuName(name: string): string {
- return name.replace(/\(R\)|\(TM\)|Graphics/gi, "").replace(/\s+/g, " ").trim();
- }
-
- function formatKib(kib: real): var {
- const mib = 1024;
- const gib = 1024 ** 2;
- const tib = 1024 ** 3;
-
- if (kib >= tib)
- return {
- value: kib / tib,
- unit: "TiB"
- };
- if (kib >= gib)
- return {
- value: kib / gib,
- unit: "GiB"
- };
- if (kib >= mib)
- return {
- value: kib / mib,
- unit: "MiB"
- };
- return {
- value: kib,
- unit: "KiB"
- };
- }
-
- Timer {
- running: root.refCount > 0
- interval: Config.dashboard.resourceUpdateInterval
- repeat: true
- triggeredOnStart: true
- onTriggered: {
- stat.reload();
- meminfo.reload();
- storage.running = true;
- gpuUsage.running = true;
- sensors.running = true;
- }
- }
-
- // One-time CPU info detection (name)
- FileView {
- id: cpuinfoInit
-
- path: "/proc/cpuinfo"
- onLoaded: {
- const nameMatch = text().match(/model name\s*:\s*(.+)/);
- if (nameMatch)
- root.cpuName = root.cleanCpuName(nameMatch[1]);
- }
- }
-
- FileView {
- id: stat
-
- path: "/proc/stat"
- onLoaded: {
- const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
- if (data) {
- const stats = data.slice(1).map(n => parseInt(n, 10));
- const total = stats.reduce((a, b) => a + b, 0);
- const idle = stats[3] + (stats[4] ?? 0);
-
- const totalDiff = total - root.lastCpuTotal;
- const idleDiff = idle - root.lastCpuIdle;
- root.cpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
-
- root.lastCpuTotal = total;
- root.lastCpuIdle = idle;
- }
- }
- }
-
- FileView {
- id: meminfo
-
- path: "/proc/meminfo"
- onLoaded: {
- const data = text();
- root.memTotal = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
- root.memUsed = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0;
- }
- }
-
- Process {
- id: storage
- // Get physical disks with aggregated usage from their partitions
- // -J triggers JSON output. -b triggers bytes.
- command: ["lsblk", "-J", "-b", "-o", "NAME,SIZE,TYPE,FSUSED,FSSIZE,MOUNTPOINT"]
-
- stdout: StdioCollector {
- onStreamFinished: {
- const data = JSON.parse(text);
- const diskList = [];
- const seenDevices = new Set();
-
- // Helper to recursively sum usage from children (partitions, crypt, lvm)
- const aggregateUsage = dev => {
- let used = 0;
- let size = 0;
- let isRoot = dev.mountpoint === "/" || (dev.mountpoints && dev.mountpoints.includes("/"));
-
- if (!seenDevices.has(dev.name)) {
- // lsblk returns null for empty/unformatted partitions, which parses to 0 here
- used = parseInt(dev.fsused) || 0;
- size = parseInt(dev.fssize) || 0;
- seenDevices.add(dev.name);
- }
-
- if (dev.children) {
- for (const child of dev.children) {
- const stats = aggregateUsage(child);
- used += stats.used;
- size += stats.size;
- if (stats.isRoot)
- isRoot = true;
- }
- }
- return {
- used,
- size,
- isRoot
- };
- };
-
- for (const dev of data.blockdevices) {
- // Only process physical disks at the top level
- if (dev.type === "disk" && !dev.name.startsWith("zram")) {
- const stats = aggregateUsage(dev);
-
- if (stats.size === 0) {
- continue;
- }
-
- const total = stats.size;
- const used = stats.used;
-
- diskList.push({
- mount: dev.name,
- used: used / 1024 // KiB
- ,
- total: total / 1024 // KiB
- ,
- free: (total - used) / 1024,
- perc: total > 0 ? used / total : 0,
- hasRoot: stats.isRoot
- });
- }
- }
-
- // Sort by putting the disk with root first, then sort the rest alphabetically
- root.disks = diskList.sort((a, b) => {
- if (a.hasRoot && !b.hasRoot)
- return -1;
- if (!a.hasRoot && b.hasRoot)
- return 1;
- return a.mount.localeCompare(b.mount);
- });
- }
- }
- }
-
- // GPU name detection (one-time)
- Process {
- id: gpuNameDetect
-
- running: true
- command: ["sh", "-c", "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null || glxinfo -B 2>/dev/null | grep 'Device:' | cut -d':' -f2 | cut -d'(' -f1 || lspci 2>/dev/null | grep -i 'vga\\|3d controller\\|display' | head -1"]
- stdout: StdioCollector {
- onStreamFinished: {
- const output = text.trim();
- if (!output)
- return;
-
- // Check if it's from nvidia-smi (clean GPU name)
- if (output.toLowerCase().includes("nvidia") || output.toLowerCase().includes("geforce") || output.toLowerCase().includes("rtx") || output.toLowerCase().includes("gtx")) {
- root.gpuName = root.cleanGpuName(output);
- } else if (output.toLowerCase().includes("rx")) {
- root.gpuName = root.cleanGpuName(output);
- } else {
- // Parse lspci output: extract name from brackets or after colon
- // Handles cases like [AMD/ATI] Navi 21 [Radeon RX 6800/6800 XT / 6900 XT] (rev c0)
- const bracketMatch = output.match(/\[([^\]]+)\][^\[]*$/);
- if (bracketMatch) {
- root.gpuName = root.cleanGpuName(bracketMatch[1]);
- } else {
- const colonMatch = output.match(/:\s*(.+)/);
- if (colonMatch)
- root.gpuName = root.cleanGpuName(colonMatch[1]);
- }
- }
- }
- }
- }
-
- Process {
- id: gpuTypeCheck
-
- running: !Config.services.gpuType
- command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"]
- stdout: StdioCollector {
- onStreamFinished: root.autoGpuType = text.trim()
- }
- }
-
- Process {
- id: gpuUsage
-
- command: root.gpuType === "GENERIC" ? ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"] : root.gpuType === "NVIDIA" ? ["nvidia-smi", "--query-gpu=utilization.gpu,temperature.gpu", "--format=csv,noheader,nounits"] : ["echo"]
- stdout: StdioCollector {
- onStreamFinished: {
- if (root.gpuType === "GENERIC") {
- const percs = text.trim().split("\n");
- const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
- root.gpuPerc = sum / percs.length / 100;
- } else if (root.gpuType === "NVIDIA") {
- const [usage, temp] = text.trim().split(",");
- root.gpuPerc = parseInt(usage, 10) / 100;
- root.gpuTemp = parseInt(temp, 10);
- } else {
- root.gpuPerc = 0;
- root.gpuTemp = 0;
- }
- }
- }
- }
-
- Process {
- id: sensors
-
- command: ["sensors"]
- environment: ({
- LANG: "C.UTF-8",
- LC_ALL: "C.UTF-8"
- })
- stdout: StdioCollector {
- onStreamFinished: {
- let cpuTemp = text.match(/(?:Package id [0-9]+|Tdie):\s+((\+|-)[0-9.]+)(°| )C/);
- if (!cpuTemp)
- // If AMD Tdie pattern failed, try fallback on Tctl
- cpuTemp = text.match(/Tctl:\s+((\+|-)[0-9.]+)(°| )C/);
-
- if (cpuTemp)
- root.cpuTemp = parseFloat(cpuTemp[1]);
-
- if (root.gpuType !== "GENERIC")
- return;
-
- let eligible = false;
- let sum = 0;
- let count = 0;
-
- for (const line of text.trim().split("\n")) {
- if (line === "Adapter: PCI adapter")
- eligible = true;
- else if (line === "")
- eligible = false;
- else if (eligible) {
- let match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
- if (!match)
- // Fall back to junction/mem if GPU doesn't have edge temp (for AMD GPUs)
- match = line.match(/^(junction|mem)+:\s+\+([0-9]+\.[0-9]+)(°| )C/);
-
- if (match) {
- sum += parseFloat(match[2]);
- count++;
- }
- }
- }
-
- root.gpuTemp = count > 0 ? sum / count : 0;
- }
- }
- }
-}
diff --git a/services/Weather.qml b/services/Weather.qml
deleted file mode 100644
index a309542..0000000
--- a/services/Weather.qml
+++ /dev/null
@@ -1,206 +0,0 @@
-pragma Singleton
-
-import qs.config
-import qs.utils
-import Caelestia
-import Quickshell
-import QtQuick
-
-Singleton {
- id: root
-
- property string city
- property string loc
- property var cc
- property list<var> forecast
- property list<var> hourlyForecast
-
- readonly property string icon: cc ? Icons.getWeatherIcon(cc.weatherCode) : "cloud_alert"
- readonly property string description: cc?.weatherDesc ?? qsTr("No weather")
- readonly property string temp: Config.services.useFahrenheit ? `${cc?.tempF ?? 0}°F` : `${cc?.tempC ?? 0}°C`
- readonly property string feelsLike: Config.services.useFahrenheit ? `${cc?.feelsLikeF ?? 0}°F` : `${cc?.feelsLikeC ?? 0}°C`
- readonly property int humidity: cc?.humidity ?? 0
- readonly property real windSpeed: cc?.windSpeed ?? 0
- readonly property string sunrise: cc ? Qt.formatDateTime(new Date(cc.sunrise), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--"
- readonly property string sunset: cc ? Qt.formatDateTime(new Date(cc.sunset), Config.services.useTwelveHourClock ? "h:mm A" : "h:mm") : "--:--"
-
- readonly property var cachedCities: new Map()
-
- function reload(): void {
- const configLocation = Config.services.weatherLocation;
-
- if (configLocation) {
- if (configLocation.indexOf(",") !== -1 && !isNaN(parseFloat(configLocation.split(",")[0]))) {
- loc = configLocation;
- fetchCityFromCoords(configLocation);
- } else {
- fetchCoordsFromCity(configLocation);
- }
- } else if (!loc || timer.elapsed() > 900) {
- Requests.get("https://ipinfo.io/json", text => {
- const response = JSON.parse(text);
- if (response.loc) {
- loc = response.loc;
- city = response.city ?? "";
- timer.restart();
- }
- });
- }
- }
-
- function fetchCityFromCoords(coords: string): void {
- if (cachedCities.has(coords)) {
- city = cachedCities.get(coords);
- return;
- }
-
- const [lat, lon] = coords.split(",");
- const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=geocodejson`;
- Requests.get(url, text => {
- const geo = JSON.parse(text).features?.[0]?.properties.geocoding;
- if (geo) {
- const geoCity = geo.type === "city" ? geo.name : geo.city;
- city = geoCity;
- cachedCities.set(coords, geoCity);
- } else {
- city = "Unknown City";
- }
- });
- }
-
- function fetchCoordsFromCity(cityName: string): void {
- const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(cityName)}&count=1&language=en&format=json`;
-
- Requests.get(url, text => {
- const json = JSON.parse(text);
- if (json.results && json.results.length > 0) {
- const result = json.results[0];
- loc = result.latitude + "," + result.longitude;
- city = result.name;
- } else {
- loc = "";
- reload();
- }
- });
- }
-
- function fetchWeatherData(): void {
- const url = getWeatherUrl();
- if (url === "")
- return;
-
- Requests.get(url, text => {
- const json = JSON.parse(text);
- if (!json.current || !json.daily)
- return;
-
- cc = {
- weatherCode: json.current.weather_code,
- weatherDesc: getWeatherCondition(json.current.weather_code),
- tempC: Math.round(json.current.temperature_2m),
- tempF: Math.round(toFahrenheit(json.current.temperature_2m)),
- feelsLikeC: Math.round(json.current.apparent_temperature),
- feelsLikeF: Math.round(toFahrenheit(json.current.apparent_temperature)),
- humidity: json.current.relative_humidity_2m,
- windSpeed: json.current.wind_speed_10m,
- isDay: json.current.is_day,
- sunrise: json.daily.sunrise[0],
- sunset: json.daily.sunset[0]
- };
-
- const forecastList = [];
- for (let i = 0; i < json.daily.time.length; i++)
- forecastList.push({
- date: json.daily.time[i],
- maxTempC: Math.round(json.daily.temperature_2m_max[i]),
- maxTempF: Math.round(toFahrenheit(json.daily.temperature_2m_max[i])),
- minTempC: Math.round(json.daily.temperature_2m_min[i]),
- minTempF: Math.round(toFahrenheit(json.daily.temperature_2m_min[i])),
- weatherCode: json.daily.weather_code[i],
- icon: Icons.getWeatherIcon(json.daily.weather_code[i])
- });
- forecast = forecastList;
-
- const hourlyList = [];
- const now = new Date();
- for (let i = 0; i < json.hourly.time.length; i++) {
- const time = new Date(json.hourly.time[i]);
- if (time < now)
- continue;
-
- hourlyList.push({
- timestamp: json.hourly.time[i],
- hour: time.getHours(),
- tempC: Math.round(json.hourly.temperature_2m[i]),
- tempF: Math.round(toFahrenheit(json.hourly.temperature_2m[i])),
- weatherCode: json.hourly.weather_code[i],
- icon: Icons.getWeatherIcon(json.hourly.weather_code[i])
- });
- }
- hourlyForecast = hourlyList;
- });
- }
-
- function toFahrenheit(celcius: real): real {
- return celcius * 9 / 5 + 32;
- }
-
- function getWeatherUrl(): string {
- if (!loc || loc.indexOf(",") === -1)
- return "";
-
- const [lat, lon] = loc.split(",");
- const baseUrl = "https://api.open-meteo.com/v1/forecast";
- const params = ["latitude=" + lat, "longitude=" + lon, "hourly=weather_code,temperature_2m", "daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset", "current=temperature_2m,relative_humidity_2m,apparent_temperature,is_day,weather_code,wind_speed_10m", "timezone=auto", "forecast_days=7"];
-
- return baseUrl + "?" + params.join("&");
- }
-
- function getWeatherCondition(code: string): string {
- const conditions = {
- "0": "Clear",
- "1": "Clear",
- "2": "Partly cloudy",
- "3": "Overcast",
- "45": "Fog",
- "48": "Fog",
- "51": "Drizzle",
- "53": "Drizzle",
- "55": "Drizzle",
- "56": "Freezing drizzle",
- "57": "Freezing drizzle",
- "61": "Light rain",
- "63": "Rain",
- "65": "Heavy rain",
- "66": "Light rain",
- "67": "Heavy rain",
- "71": "Light snow",
- "73": "Snow",
- "75": "Heavy snow",
- "77": "Snow",
- "80": "Light rain",
- "81": "Rain",
- "82": "Heavy rain",
- "85": "Light snow showers",
- "86": "Heavy snow showers",
- "95": "Thunderstorm",
- "96": "Thunderstorm with hail",
- "99": "Thunderstorm with hail"
- };
- return conditions[code] || "Unknown";
- }
-
- onLocChanged: fetchWeatherData()
-
- // Refresh current location hourly
- Timer {
- interval: 3600000 // 1 hour
- running: true
- repeat: true
- onTriggered: fetchWeatherData()
- }
-
- ElapsedTimer {
- id: timer
- }
-}
diff --git a/utils/SysInfo.qml b/utils/SysInfo.qml
index 19aa4a7..7d640d4 100644
--- a/utils/SysInfo.qml
+++ b/utils/SysInfo.qml
@@ -16,11 +16,6 @@ Singleton {
property string osLogo: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/logo.svg`)
property bool isDefaultLogo: true
- property string uptime
- readonly property string user: Quickshell.env("USER")
- readonly property string wm: Quickshell.env("XDG_CURRENT_DESKTOP") || Quickshell.env("XDG_SESSION_DESKTOP")
- readonly property string shell: Quickshell.env("SHELL").split("/").pop()
-
FileView {
id: osRelease
@@ -56,33 +51,4 @@ Singleton {
osRelease.reload();
}
}
-
- Timer {
- running: true
- repeat: true
- interval: 15000
- onTriggered: fileUptime.reload()
- }
-
- FileView {
- id: fileUptime
-
- path: "/proc/uptime"
- onLoaded: {
- const up = parseInt(text().split(" ")[0] ?? 0);
-
- const days = Math.floor(up / 86400);
- const hours = Math.floor((up % 86400) / 3600);
- const minutes = Math.floor((up % 3600) / 60);
-
- let str = "";
- if (days > 0)
- str += `${days} day${days === 1 ? "" : "s"}`;
- if (hours > 0)
- str += `${str ? ", " : ""}${hours} hour${hours === 1 ? "" : "s"}`;
- if (minutes > 0 || !str)
- str += `${str ? ", " : ""}${minutes} minute${minutes === 1 ? "" : "s"}`;
- root.uptime = str;
- }
- }
}