diff options
| author | Freya Murphy <freya@freyacat.org> | 2026-05-29 14:29:48 -0400 |
|---|---|---|
| committer | Freya Murphy <freya@freyacat.org> | 2026-05-29 14:29:48 -0400 |
| commit | be2d03fef0c560f8bcd2a3be9bd806b1e3d118b6 (patch) | |
| tree | 51a27813f670fc04174bdf6f6b4b89d9ea257711 | |
| parent | remove tertiary color (diff) | |
| download | caelestia-shell-be2d03fef0c560f8bcd2a3be9bd806b1e3d118b6.tar.gz caelestia-shell-be2d03fef0c560f8bcd2a3be9bd806b1e3d118b6.tar.bz2 caelestia-shell-be2d03fef0c560f8bcd2a3be9bd806b1e3d118b6.zip | |
remove a lot
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 Binary files differdeleted file mode 100644 index f960fec..0000000 --- a/assets/bongocat.gif +++ /dev/null diff --git a/assets/dino.png b/assets/dino.png Binary files differdeleted file mode 100644 index b5bc7bb..0000000 --- a/assets/dino.png +++ /dev/null diff --git a/assets/kurukuru.gif b/assets/kurukuru.gif Binary files differdeleted file mode 100644 index 38d203d..0000000 --- a/assets/kurukuru.gif +++ /dev/null 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 Binary files differdeleted file mode 100644 index 7bf97c2..0000000 --- a/assets/shaders/opacitymask.frag.qsb +++ /dev/null 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; - } - } } |