mirror of
https://github.com/tuxdotrs/tshell.git
synced 2026-09-19 18:49:02 +05:30
Compare commits
15 Commits
e95f62579b
...
4453c280bf
| Author | SHA1 | Date | |
|---|---|---|---|
|
4453c280bf
|
|||
|
51906373aa
|
|||
|
8f8239727e
|
|||
|
5299945cfb
|
|||
|
abd48a9c66
|
|||
|
28aa79dd13
|
|||
|
dfab12482a
|
|||
|
33c19e35a5
|
|||
|
198ce4ff67
|
|||
|
e1906e5528
|
|||
|
5d84d68e26
|
|||
|
d53f375fa7
|
|||
|
adfd353e85
|
|||
|
ca7ac0696b
|
|||
|
de3c9274b5
|
10
AGENTS.md
10
AGENTS.md
@@ -32,17 +32,19 @@ shell.qml Entry point: Scope { Bar {}; Notification {} }
|
|||||||
│ └── Notification.qml Popup notification stack window
|
│ └── Notification.qml Popup notification stack window
|
||||||
├── modules/ Feature widgets, grouped by surface
|
├── modules/ Feature widgets, grouped by surface
|
||||||
│ ├── bar/ Widgets placed in the bar (Clock, Workspaces,
|
│ ├── bar/ Widgets placed in the bar (Clock, Workspaces,
|
||||||
│ │ BatteryIndicator, CavaVisualizer, SystemTray,
|
│ │ BatteryIndicator, AudioWidget, CavaVisualizer,
|
||||||
│ │ LauncherButton, PowerProfileWidget, GhostButton)
|
│ │ SystemTray, LauncherButton, PowerProfileWidget,
|
||||||
|
│ │ GhostButton)
|
||||||
│ └── osd/ NotificationCard.qml
|
│ └── osd/ NotificationCard.qml
|
||||||
├── services/ QML Singletons exposing system state
|
├── services/ QML Singletons exposing system state
|
||||||
│ ├── Time.qml SystemClock → formatted time string
|
│ ├── Time.qml SystemClock → formatted time string
|
||||||
│ ├── Battery.qml UPower display device → percentage/icon
|
│ ├── Battery.qml UPower display device → percentage/icon
|
||||||
|
│ ├── Audio.qml PipeWire default sink/source → device names/icons
|
||||||
│ ├── PowerProfile.qml UPower power profiles → name/icon
|
│ ├── PowerProfile.qml UPower power profiles → name/icon
|
||||||
│ ├── Cava.qml Spawns `cava`, parses stdout into `values[]`
|
│ ├── Cava.qml Spawns `cava`, parses stdout into `values[]`
|
||||||
│ └── Notifications.qml NotificationServer: popups, DND flag, history
|
│ └── Notifications.qml NotificationServer: popups, DND flag, history
|
||||||
├── config/ QML Singletons for configuration/theming
|
├── config/ QML Singletons for configuration/theming
|
||||||
│ ├── Appearance.qml colors, font, margin/radius/spacing/padding/duration
|
│ ├── Theme.qml base16 palettes (Theme.colors.base00-0F + semantic aliases), font, margin/radius/spacing/padding/duration, per-widget sizes
|
||||||
│ └── BarLayout.qml Which widgets go in left/center/right bar sections
|
│ └── BarLayout.qml Which widgets go in left/center/right bar sections
|
||||||
├── ui/ Shared reusable primitives
|
├── ui/ Shared reusable primitives
|
||||||
│ ├── BarButton.qml Themed button used by bar widgets
|
│ ├── BarButton.qml Themed button used by bar widgets
|
||||||
@@ -64,6 +66,6 @@ shell.qml Entry point: Scope { Bar {}; Notification {} }
|
|||||||
|
|
||||||
- Start every QML file with `pragma ComponentBehavior: Bound` where bindings reference outer ids.
|
- Start every QML file with `pragma ComponentBehavior: Bound` where bindings reference outer ids.
|
||||||
- Indentation is inconsistent across the repo (2-space in newer UI/window files, 4-space in some services); match the surrounding file when editing.
|
- Indentation is inconsistent across the repo (2-space in newer UI/window files, 4-space in some services); match the surrounding file when editing.
|
||||||
- Prefer `readonly property` for derived values; keep widgets presentational and pull all theme values from `Appearance.*` rather than hardcoding colors/sizes/fonts.
|
- Prefer `readonly property` for derived values; keep widgets presentational and pull all theme values from `Theme.*` rather than hardcoding colors/sizes/fonts.
|
||||||
- Access asset paths via `Quickshell.shellPath(...)` (see `Battery.qml`, `NotificationCard.qml`) so paths work regardless of install location.
|
- Access asset paths via `Quickshell.shellPath(...)` (see `Battery.qml`, `NotificationCard.qml`) so paths work regardless of install location.
|
||||||
- Commit messages follow Conventional Commits style (`feat(notifications): ...`, `refactor(bar): ...`).
|
- Commit messages follow Conventional Commits style (`feat(notifications): ...`, `refactor(bar): ...`).
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[general]
|
[general]
|
||||||
bars = 12
|
bars = 14
|
||||||
framerate = 60
|
framerate = 60
|
||||||
|
|
||||||
[input]
|
[input]
|
||||||
|
|||||||
5
assets/icons/microphone.svg
Normal file
5
assets/icons/microphone.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 9C4.41421 9 4.75 9.33579 4.75 9.75V10.75C4.75 14.7541 7.99594 18 12 18C16.0041 18 19.25 14.7541 19.25 10.75V9.75C19.25 9.33579 19.5858 9 20 9C20.4142 9 20.75 9.33579 20.75 9.75V10.75C20.75 15.3298 17.2314 19.0879 12.75 19.4683V21.75C12.75 22.1642 12.4142 22.5 12 22.5C11.5858 22.5 11.25 22.1642 11.25 21.75V19.4683C6.7686 19.0879 3.25 15.3298 3.25 10.75V9.75C3.25 9.33579 3.58579 9 4 9Z" fill="#1C274C"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C8.82436 2 6.25 4.57436 6.25 7.75V10.75C6.25 13.9256 8.82436 16.5 12 16.5C15.1756 16.5 17.75 13.9256 17.75 10.75V7.75C17.75 4.57436 15.1756 2 12 2ZM14 11.5C14.4142 11.5 14.75 11.1642 14.75 10.75C14.75 10.3358 14.4142 10 14 10H10C9.58579 10 9.25 10.3358 9.25 10.75C9.25 11.1642 9.58579 11.5 10 11.5H14ZM13.75 7.75C13.75 8.16421 13.4142 8.5 13 8.5H11C10.5858 8.5 10.25 8.16421 10.25 7.75C10.25 7.33579 10.5858 7 11 7H13C13.4142 7 13.75 7.33579 13.75 7.75Z" fill="#1C274C"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
5
assets/icons/speaker.svg
Normal file
5
assets/icons/speaker.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 10.75C10.2051 10.75 8.75 12.2051 8.75 14C8.75 15.7949 10.2051 17.25 12 17.25C13.7949 17.25 15.25 15.7949 15.25 14C15.25 12.2051 13.7949 10.75 12 10.75Z" fill="#1C274C"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 10C4 6.22876 4 4.34315 5.17157 3.17157C6.34315 2 8.22876 2 12 2C15.7712 2 17.6569 2 18.8284 3.17157C20 4.34315 20 6.22876 20 10V14C20 17.7712 20 19.6569 18.8284 20.8284C17.6569 22 15.7712 22 12 22C8.22876 22 6.34315 22 5.17157 20.8284C4 19.6569 4 17.7712 4 14V10ZM7.25 14C7.25 11.3766 9.37665 9.25 12 9.25C14.6234 9.25 16.75 11.3766 16.75 14C16.75 16.6234 14.6234 18.75 12 18.75C9.37665 18.75 7.25 16.6234 7.25 14ZM10 5.25C9.58579 5.25 9.25 5.58579 9.25 6C9.25 6.41421 9.58579 6.75 10 6.75H14C14.4142 6.75 14.75 6.41421 14.75 6C14.75 5.58579 14.4142 5.25 14 5.25H10Z" fill="#1C274C"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -1,35 +0,0 @@
|
|||||||
pragma Singleton
|
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
|
|
||||||
Singleton {
|
|
||||||
id: root
|
|
||||||
property QtObject colors
|
|
||||||
property QtObject font
|
|
||||||
|
|
||||||
property int margin
|
|
||||||
property int radius
|
|
||||||
property int spacing
|
|
||||||
property int padding
|
|
||||||
property int duration
|
|
||||||
|
|
||||||
margin: 8
|
|
||||||
radius: 8
|
|
||||||
spacing: 8
|
|
||||||
padding: 8
|
|
||||||
duration: 150
|
|
||||||
|
|
||||||
font: QtObject {
|
|
||||||
property string family: "FiraCode Nerd Font Mono SemBd"
|
|
||||||
property int pointSize: 9
|
|
||||||
}
|
|
||||||
|
|
||||||
colors: QtObject {
|
|
||||||
property color accent: "#6791c9"
|
|
||||||
property color foreground: "#ffffff"
|
|
||||||
property color background: "#101213"
|
|
||||||
property color inActive: "#1b1d1e"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,28 +5,15 @@ import qs.ui
|
|||||||
import qs.services
|
import qs.services
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
function widget(name: string): url {
|
function widget(name: string): url {
|
||||||
return `${Quickshell.shellPath("modules/bar")}/${name}.qml`;
|
return `${Quickshell.shellPath("modules/bar")}/${name}.qml`;
|
||||||
}
|
}
|
||||||
|
|
||||||
property var left: [
|
property var left: [root.widget("LauncherButton"), root.widget("PowerProfileWidget"), root.widget("HomeWidget"), root.widget("MusicWidget")]
|
||||||
root.widget("LauncherButton"),
|
|
||||||
root.widget("PowerProfileWidget"),
|
|
||||||
root.widget("HomeWidget"),
|
|
||||||
]
|
|
||||||
|
|
||||||
property var center: [
|
property var center: [root.widget("CavaVisualizer"), root.widget("Workspaces"), root.widget("CavaVisualizer"),]
|
||||||
root.widget("CavaVisualizer"),
|
|
||||||
root.widget("Workspaces"),
|
|
||||||
root.widget("CavaVisualizer"),
|
|
||||||
]
|
|
||||||
|
|
||||||
property var right: [
|
property var right: [root.widget("AudioWidget"), root.widget("BatteryIndicator"), root.widget("SystemTrayWidget"), root.widget("ClockWidget"), root.widget("GhostButton")]
|
||||||
root.widget("BatteryIndicator"),
|
|
||||||
root.widget("SystemTrayWidget"),
|
|
||||||
root.widget("ClockWidget"),
|
|
||||||
root.widget("GhostButton")
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
150
config/Theme.qml
Normal file
150
config/Theme.qml
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string name: "poimandres"
|
||||||
|
|
||||||
|
readonly property Base16 scheme: {
|
||||||
|
switch (root.name) {
|
||||||
|
case "poimandres":
|
||||||
|
return poimandres;
|
||||||
|
default:
|
||||||
|
return gruvbox;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component Base16: QtObject {
|
||||||
|
property color base00
|
||||||
|
property color base01
|
||||||
|
property color base02
|
||||||
|
property color base03
|
||||||
|
property color base04
|
||||||
|
property color base05
|
||||||
|
property color base06
|
||||||
|
property color base07
|
||||||
|
property color base08
|
||||||
|
property color base09
|
||||||
|
property color base0A
|
||||||
|
property color base0B
|
||||||
|
property color base0C
|
||||||
|
property color base0D
|
||||||
|
property color base0E
|
||||||
|
property color base0F
|
||||||
|
}
|
||||||
|
|
||||||
|
Base16 {
|
||||||
|
id: poimandres
|
||||||
|
|
||||||
|
base00: "#0f0f0f"
|
||||||
|
base01: "#181818"
|
||||||
|
base02: "#202020"
|
||||||
|
base03: "#292929"
|
||||||
|
base04: "#3f3f3f"
|
||||||
|
base05: "#E4F0FB"
|
||||||
|
base06: "#b6d7f4"
|
||||||
|
base07: "#ffffff"
|
||||||
|
base08: "#D0679D"
|
||||||
|
base09: "#91B4D5"
|
||||||
|
base0A: "#FFFAC2"
|
||||||
|
base0B: "#5FB3A1"
|
||||||
|
base0C: "#5DE4C7"
|
||||||
|
base0D: "#89DDFF"
|
||||||
|
base0E: "#A6ACCD"
|
||||||
|
base0F: "#FCC5E9"
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property QtObject colors: QtObject {
|
||||||
|
readonly property color base00: root.scheme.base00
|
||||||
|
readonly property color base01: root.scheme.base01
|
||||||
|
readonly property color base02: root.scheme.base02
|
||||||
|
readonly property color base03: root.scheme.base03
|
||||||
|
readonly property color base04: root.scheme.base04
|
||||||
|
readonly property color base05: root.scheme.base05
|
||||||
|
readonly property color base06: root.scheme.base06
|
||||||
|
readonly property color base07: root.scheme.base07
|
||||||
|
readonly property color base08: root.scheme.base08
|
||||||
|
readonly property color base09: root.scheme.base09
|
||||||
|
readonly property color base0A: root.scheme.base0A
|
||||||
|
readonly property color base0B: root.scheme.base0B
|
||||||
|
readonly property color base0C: root.scheme.base0C
|
||||||
|
readonly property color base0D: root.scheme.base0D
|
||||||
|
readonly property color base0E: root.scheme.base0E
|
||||||
|
readonly property color base0F: root.scheme.base0F
|
||||||
|
|
||||||
|
readonly property color accent: base0D
|
||||||
|
readonly property color foreground: base05
|
||||||
|
readonly property color background: base00
|
||||||
|
readonly property color inActive: base01
|
||||||
|
readonly property color hover: base02
|
||||||
|
readonly property color border: base03
|
||||||
|
readonly property color secondaryForeground: base04
|
||||||
|
readonly property color error: base08
|
||||||
|
readonly property color warning: base0A
|
||||||
|
readonly property color success: base0B
|
||||||
|
readonly property color info: base0C
|
||||||
|
}
|
||||||
|
|
||||||
|
property int margin: 8
|
||||||
|
property int radius: 8
|
||||||
|
property int spacing: 8
|
||||||
|
property int padding: 8
|
||||||
|
property int duration: 150
|
||||||
|
property int borderWidth: 1
|
||||||
|
|
||||||
|
property QtObject font: QtObject {
|
||||||
|
property string family: "FiraCode Nerd Font Mono SemBd"
|
||||||
|
property int pointSize: 9
|
||||||
|
readonly property int body: pointSize - 1
|
||||||
|
readonly property int caption: pointSize - 2
|
||||||
|
}
|
||||||
|
|
||||||
|
property QtObject bar: QtObject {
|
||||||
|
property int thickness: 10
|
||||||
|
property int height: 50
|
||||||
|
}
|
||||||
|
|
||||||
|
property QtObject workspaces: QtObject {
|
||||||
|
property int height: 15
|
||||||
|
property int radius: 1000
|
||||||
|
property real activeWidthRatio: 2.3
|
||||||
|
}
|
||||||
|
|
||||||
|
property QtObject cava: QtObject {
|
||||||
|
property int width: 96
|
||||||
|
property int height: 32
|
||||||
|
property int innerMargin: 8
|
||||||
|
property int barSpacing: 3
|
||||||
|
property int barMinHeight: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
property QtObject button: QtObject {
|
||||||
|
property int iconSize: 14
|
||||||
|
property int verticalPadding: 4
|
||||||
|
property int borderWidth: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
property QtObject tray: QtObject {
|
||||||
|
property int iconSize: 16
|
||||||
|
}
|
||||||
|
|
||||||
|
property QtObject notification: QtObject {
|
||||||
|
property int width: 420
|
||||||
|
property int iconSize: 40
|
||||||
|
property int textSpacing: 2
|
||||||
|
property real bodyOpacity: 0.8
|
||||||
|
}
|
||||||
|
|
||||||
|
property QtObject volume: QtObject {
|
||||||
|
property int width: 300
|
||||||
|
property int height: 48
|
||||||
|
property int iconSize: 22
|
||||||
|
property int barHeight: 10
|
||||||
|
property int timeout: 1500
|
||||||
|
property real bottomMarginRatio: 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
10
flake.nix
10
flake.nix
@@ -38,12 +38,20 @@
|
|||||||
|
|
||||||
programs = {
|
programs = {
|
||||||
nixfmt.enable = true;
|
nixfmt.enable = true;
|
||||||
|
qmlformat.enable = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
rec {
|
||||||
formatter.${system} = treefmtEval.config.build.wrapper;
|
formatter.${system} = treefmtEval.config.build.wrapper;
|
||||||
|
|
||||||
|
packages.${system} = {
|
||||||
|
tshell = pkgs.callPackage ./package.nix {
|
||||||
|
inherit quickshellWithModules;
|
||||||
|
};
|
||||||
|
default = packages.${system}.tshell;
|
||||||
|
};
|
||||||
|
|
||||||
devShells.${system}.default = pkgs.mkShell {
|
devShells.${system}.default = pkgs.mkShell {
|
||||||
packages = [
|
packages = [
|
||||||
quickshellWithModules
|
quickshellWithModules
|
||||||
|
|||||||
21
modules/bar/AudioWidget.qml
Normal file
21
modules/bar/AudioWidget.qml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.ui
|
||||||
|
import qs.services
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
BarButton {
|
||||||
|
hoverHighlight: false
|
||||||
|
pointerCursor: false
|
||||||
|
label: Audio.inputName
|
||||||
|
iconSource: Audio.inputIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
BarButton {
|
||||||
|
hoverHighlight: false
|
||||||
|
pointerCursor: false
|
||||||
|
label: Audio.outputName
|
||||||
|
iconSource: Audio.outputIcon
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ import qs.ui
|
|||||||
import qs.services
|
import qs.services
|
||||||
|
|
||||||
BarButton {
|
BarButton {
|
||||||
hoverHighlight: false
|
hoverHighlight: false
|
||||||
pointerCursor: false
|
pointerCursor: false
|
||||||
label: `${Battery.percentage}% ${Battery.energyRate}W`
|
label: `${Battery.percentage}% ${Battery.energyRate}W`
|
||||||
iconSource: Battery.icon
|
iconSource: Battery.icon
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,47 +5,41 @@ import qs.config
|
|||||||
import qs.services
|
import qs.services
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int barCount: 14
|
property int barCount: 14
|
||||||
property real maxValue: 100
|
property real maxValue: 100
|
||||||
readonly property bool shouldVisualize: barCount > 0 && Cava.values.some(v => v >= 0.001)
|
readonly property bool shouldVisualize: barCount > 0 && Cava.values.some(v => v >= 0.001)
|
||||||
|
|
||||||
implicitWidth: 96
|
implicitWidth: Theme.cava.width
|
||||||
implicitHeight: 32
|
implicitHeight: Theme.cava.height
|
||||||
radius: Appearance.radius
|
radius: Theme.radius
|
||||||
color: Appearance.colors.inActive
|
color: Theme.colors.inActive
|
||||||
visible: shouldVisualize
|
visible: shouldVisualize
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: barRow
|
id: barRow
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 8
|
anchors.margins: Theme.cava.innerMargin
|
||||||
spacing: 3
|
spacing: Theme.cava.barSpacing
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.barCount
|
model: root.barCount
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: bar
|
id: bar
|
||||||
|
|
||||||
required property int index
|
required property int index
|
||||||
readonly property real value: Cava.values[index] ?? 0
|
readonly property real value: Cava.values[index] ?? 0
|
||||||
|
|
||||||
width: (barRow.width - barRow.spacing * (root.barCount - 1)) / root.barCount
|
width: (barRow.width - barRow.spacing * (root.barCount - 1)) / root.barCount
|
||||||
height: Math.max(2, (bar.value / root.maxValue) * barRow.height)
|
height: Math.max(Theme.cava.barMinHeight, (bar.value / root.maxValue) * barRow.height)
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
radius: bar.width / 2
|
radius: bar.width / 2
|
||||||
color: Appearance.colors.accent
|
color: Theme.colors.accent
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
|
}
|
||||||
Behavior on height {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Appearance.duration
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import qs.ui
|
|||||||
import qs.services
|
import qs.services
|
||||||
|
|
||||||
BarButton {
|
BarButton {
|
||||||
hoverHighlight: false
|
hoverHighlight: false
|
||||||
pointerCursor: false
|
pointerCursor: false
|
||||||
label: Time.time
|
label: Time.time
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Quickshell
|
|||||||
import qs.ui
|
import qs.ui
|
||||||
|
|
||||||
BarButton {
|
BarButton {
|
||||||
iconOnly: true
|
iconOnly: true
|
||||||
iconSource: `${Quickshell.shellPath("assets")}/icons/ghost.svg`
|
hoverHighlight: false
|
||||||
|
iconSource: `${Quickshell.shellPath("assets")}/icons/ghost.svg`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import qs.ui
|
|||||||
import qs.services
|
import qs.services
|
||||||
|
|
||||||
BarButton {
|
BarButton {
|
||||||
hoverHighlight: false
|
hoverHighlight: false
|
||||||
pointerCursor: false
|
pointerCursor: false
|
||||||
label: Home.latency >= 0 ? `${Home.latency} ms` : "NA"
|
label: Home.latency >= 0 ? `${Home.latency} ms` : "NA"
|
||||||
iconSource: Home.icon
|
iconSource: Home.icon
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import Quickshell
|
|||||||
import qs.ui
|
import qs.ui
|
||||||
|
|
||||||
BarButton {
|
BarButton {
|
||||||
iconOnly: true
|
iconOnly: true
|
||||||
iconSource: `${Quickshell.shellPath("assets")}/icons/nix.svg`
|
hoverHighlight: false
|
||||||
onClicked: Quickshell.execDetached(["vicinae", "toggle"])
|
iconSource: `${Quickshell.shellPath("assets")}/icons/nix.svg`
|
||||||
|
onClicked: Quickshell.execDetached(["vicinae", "toggle"])
|
||||||
}
|
}
|
||||||
|
|||||||
17
modules/bar/MusicWidget.qml
Normal file
17
modules/bar/MusicWidget.qml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import qs.services
|
||||||
|
import qs.ui
|
||||||
|
|
||||||
|
BarButton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property string track: Media.trackArtist !== "" ? `${Media.trackTitle} — ${Media.trackArtist}` : Media.trackTitle
|
||||||
|
readonly property bool active: track !== ""
|
||||||
|
|
||||||
|
visible: active
|
||||||
|
hoverHighlight: false
|
||||||
|
pointerCursor: false
|
||||||
|
iconSource: Media.icon
|
||||||
|
label: `${Media.truncateText(root.track, 28)} ${Media.position} / ${Media.length}`
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ import qs.ui
|
|||||||
import qs.services
|
import qs.services
|
||||||
|
|
||||||
BarButton {
|
BarButton {
|
||||||
hoverHighlight: false
|
hoverHighlight: false
|
||||||
pointerCursor: false
|
pointerCursor: false
|
||||||
label: PowerProfile.name
|
label: PowerProfile.name
|
||||||
iconSource: PowerProfile.icon
|
iconSource: PowerProfile.icon
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,27 +6,25 @@ import qs.config
|
|||||||
import qs.ui
|
import qs.ui
|
||||||
|
|
||||||
BarButton {
|
BarButton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int iconSize: 16
|
hoverHighlight: false
|
||||||
|
pointerCursor: false
|
||||||
|
visible: SystemTray.items.values.length > 0
|
||||||
|
|
||||||
hoverHighlight: false
|
contentItem: Row {
|
||||||
pointerCursor: false
|
spacing: Theme.spacing
|
||||||
visible: SystemTray.items.values.length > 0
|
|
||||||
|
|
||||||
contentItem: Row {
|
Repeater {
|
||||||
spacing: Appearance.spacing
|
model: SystemTray.items.values
|
||||||
|
|
||||||
Repeater {
|
Image {
|
||||||
model: SystemTray.items.values
|
required property var modelData
|
||||||
|
|
||||||
Image {
|
source: modelData.icon
|
||||||
required property var modelData
|
width: Theme.tray.iconSize
|
||||||
|
height: Theme.tray.iconSize
|
||||||
source: modelData.icon
|
}
|
||||||
width: root.iconSize
|
}
|
||||||
height: root.iconSize
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,56 +5,56 @@ import Quickshell.Hyprland
|
|||||||
import qs.config
|
import qs.config
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int count: 7
|
property int count: 7
|
||||||
|
|
||||||
spacing: Appearance.spacing
|
spacing: Theme.spacing
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: root.count
|
model: root.count
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: pill
|
id: pill
|
||||||
|
|
||||||
required property int index
|
required property int index
|
||||||
property var ws: Hyprland.workspaces.values.find(w => w.id === pill.index + 1)
|
property var ws: Hyprland.workspaces.values.find(w => w.id === pill.index + 1)
|
||||||
property bool isActive: Hyprland.focusedWorkspace?.id === (pill.index + 1)
|
property bool isActive: Hyprland.focusedWorkspace?.id === (pill.index + 1)
|
||||||
|
|
||||||
radius: 1000
|
radius: Theme.workspaces.radius
|
||||||
implicitHeight: 15
|
implicitHeight: Theme.workspaces.height
|
||||||
implicitWidth: pill.isActive ? pill.implicitHeight * 2.3 : pill.implicitHeight
|
implicitWidth: pill.isActive ? pill.implicitHeight * Theme.workspaces.activeWidthRatio : pill.implicitHeight
|
||||||
color: {
|
color: {
|
||||||
if (handler.hovered)
|
if (handler.hovered)
|
||||||
return Appearance.colors.accent;
|
return Theme.colors.accent;
|
||||||
if (pill.isActive || pill.ws)
|
if (pill.isActive || pill.ws)
|
||||||
return Appearance.colors.accent;
|
return Theme.colors.accent;
|
||||||
return Appearance.colors.inActive;
|
return Theme.colors.inActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: Hyprland.dispatch("workspace " + (pill.index + 1))
|
onClicked: Hyprland.dispatch("workspace " + (pill.index + 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
id: handler
|
id: handler
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
duration: Appearance.duration
|
duration: Theme.duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.duration
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on implicitWidth {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Appearance.duration
|
|
||||||
easing.type: Easing.OutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,111 +7,110 @@ import Quickshell.Services.Notifications
|
|||||||
import qs.config
|
import qs.config
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property Notification notif
|
property Notification notif
|
||||||
readonly property bool critical: notif?.urgency === NotificationUrgency.Critical
|
readonly property bool critical: notif?.urgency === NotificationUrgency.Critical
|
||||||
readonly property int timeoutDuration: {
|
readonly property int timeoutDuration: {
|
||||||
const seconds = Math.round((notif?.expireTimeout ?? 0) / 1000);
|
const seconds = Math.round((notif?.expireTimeout ?? 0) / 1000);
|
||||||
return seconds > 0 ? seconds : 5;
|
return seconds > 0 ? seconds : 5;
|
||||||
}
|
}
|
||||||
readonly property string iconName: {
|
readonly property string iconName: {
|
||||||
if (!notif)
|
if (!notif)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
const hints = notif.hints ?? {};
|
const hints = notif.hints ?? {};
|
||||||
const hinted = String(hints["image-path"] ?? hints["image_path"] ?? hints["icon_path"] ?? "");
|
const hinted = String(hints["image-path"] ?? hints["image_path"] ?? hints["icon_path"] ?? "");
|
||||||
return notif.appIcon || notif.desktopEntry || hinted;
|
return notif.appIcon || notif.desktopEntry || hinted;
|
||||||
}
|
}
|
||||||
readonly property url themedIconUrl: iconName !== "" ? Quickshell.iconPath(iconName, false) : ""
|
readonly property url themedIconUrl: iconName !== "" ? Quickshell.iconPath(iconName, false) : ""
|
||||||
readonly property url iconUrl: `${Quickshell.shellPath("assets")}/icons/notification.svg`
|
readonly property url iconUrl: `${Quickshell.shellPath("assets")}/icons/notification.svg`
|
||||||
|
|
||||||
implicitWidth: 360
|
implicitWidth: Theme.notification.width
|
||||||
implicitHeight: content.implicitHeight + Appearance.padding * 2
|
implicitHeight: content.implicitHeight + Theme.padding * 2
|
||||||
radius: Appearance.radius
|
radius: Theme.radius
|
||||||
color: Appearance.colors.background
|
color: Theme.colors.background
|
||||||
border.width: critical ? 1 : 0
|
border.width: critical ? Theme.notification.borderWidth : 0
|
||||||
border.color: Appearance.colors.accent
|
border.color: Theme.colors.accent
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: countdown
|
id: countdown
|
||||||
|
|
||||||
interval: root.timeoutDuration * 1000
|
interval: root.timeoutDuration * 1000
|
||||||
repeat: false
|
repeat: false
|
||||||
running: !root.critical && !hovered.hovered && visible
|
running: !root.critical && !hovered.hovered && visible
|
||||||
onTriggered: root.notif.expire()
|
onTriggered: root.notif.expire()
|
||||||
}
|
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
id: hovered
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: content
|
|
||||||
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.margins: Appearance.padding
|
|
||||||
spacing: Appearance.spacing
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: 40
|
|
||||||
height: 40
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
|
|
||||||
Image {
|
|
||||||
anchors.fill: parent
|
|
||||||
source: root.iconUrl
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
smooth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
|
||||||
anchors.fill: parent
|
|
||||||
source: root.themedIconUrl
|
|
||||||
visible: root.themedIconUrl !== "" && status === Image.Ready
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
smooth: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
HoverHandler {
|
||||||
spacing: 2
|
id: hovered
|
||||||
Layout.fillWidth: true
|
}
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
|
RowLayout {
|
||||||
Text {
|
id: content
|
||||||
text: root.notif?.appName ?? ""
|
|
||||||
color: Appearance.colors.accent
|
anchors.left: parent.left
|
||||||
font.family: Appearance.font.family
|
anchors.right: parent.right
|
||||||
font.pointSize: Appearance.font.pointSize - 2
|
anchors.top: parent.top
|
||||||
elide: Text.ElideRight
|
anchors.margins: Theme.padding
|
||||||
Layout.fillWidth: true
|
spacing: Theme.spacing
|
||||||
}
|
|
||||||
Text {
|
Item {
|
||||||
text: root.notif?.summary ?? ""
|
width: Theme.notification.iconSize
|
||||||
color: Appearance.colors.foreground
|
height: Theme.notification.iconSize
|
||||||
font.family: Appearance.font.family
|
Layout.alignment: Qt.AlignTop
|
||||||
font.pointSize: Appearance.font.pointSize
|
|
||||||
font.weight: Font.DemiBold
|
Image {
|
||||||
elide: Text.ElideRight
|
anchors.fill: parent
|
||||||
Layout.fillWidth: true
|
source: root.iconUrl
|
||||||
}
|
fillMode: Image.PreserveAspectFit
|
||||||
Text {
|
smooth: true
|
||||||
text: root.notif?.body ?? ""
|
}
|
||||||
visible: text !== ""
|
|
||||||
color: Appearance.colors.foreground
|
Image {
|
||||||
opacity: 0.8
|
anchors.fill: parent
|
||||||
font.family: Appearance.font.family
|
source: root.themedIconUrl
|
||||||
font.pointSize: Appearance.font.pointSize - 1
|
visible: root.themedIconUrl !== "" && status === Image.Ready
|
||||||
wrapMode: Text.Wrap
|
fillMode: Image.PreserveAspectFit
|
||||||
maximumLineCount: 3
|
smooth: true
|
||||||
elide: Text.ElideRight
|
}
|
||||||
textFormat: Text.PlainText
|
}
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
ColumnLayout {
|
||||||
|
spacing: Theme.notification.textSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: root.notif?.appName ?? ""
|
||||||
|
color: Theme.colors.accent
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pointSize: Theme.font.caption
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: root.notif?.summary ?? ""
|
||||||
|
color: Theme.colors.foreground
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pointSize: Theme.font.pointSize
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: root.notif?.body ?? ""
|
||||||
|
visible: text !== ""
|
||||||
|
color: Theme.colors.foreground
|
||||||
|
opacity: Theme.notification.bodyOpacity
|
||||||
|
font.family: Theme.font.family
|
||||||
|
font.pointSize: Theme.font.body
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
maximumLineCount: 3
|
||||||
|
elide: Text.ElideRight
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
42
modules/osd/VolumeCard.qml
Normal file
42
modules/osd/VolumeCard.qml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.config
|
||||||
|
import qs.services
|
||||||
|
import qs.ui
|
||||||
|
|
||||||
|
BarButton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property real level: Math.max(0, Math.min(Audio.muted ? 0 : Audio.volume, 1))
|
||||||
|
readonly property url iconUrl: `${Quickshell.shellPath("assets")}/icons/speaker.svg`
|
||||||
|
|
||||||
|
hoverHighlight: false
|
||||||
|
pointerCursor: false
|
||||||
|
iconOnly: true
|
||||||
|
iconSource: root.iconUrl
|
||||||
|
icon.width: Theme.volume.iconSize
|
||||||
|
icon.height: Theme.volume.iconSize
|
||||||
|
|
||||||
|
implicitWidth: Theme.volume.width
|
||||||
|
implicitHeight: Theme.volume.height
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: fill
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
implicitWidth: parent.width * root.level
|
||||||
|
radius: Theme.radius
|
||||||
|
color: Audio.muted ? Theme.colors.error : Theme.colors.accent
|
||||||
|
opacity: 0.35
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
package.nix
Normal file
68
package.nix
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
makeWrapper,
|
||||||
|
makeFontsConf,
|
||||||
|
quickshellWithModules,
|
||||||
|
cava,
|
||||||
|
wallust,
|
||||||
|
nerd-fonts,
|
||||||
|
extraRuntimeDeps ? [ ],
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
version = "0.1.0";
|
||||||
|
|
||||||
|
runtimeDeps = [
|
||||||
|
cava
|
||||||
|
wallust
|
||||||
|
]
|
||||||
|
++ extraRuntimeDeps;
|
||||||
|
|
||||||
|
fontconfig = makeFontsConf {
|
||||||
|
fontDirectories = [ nerd-fonts.fira-code ];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
inherit version;
|
||||||
|
pname = "tshell";
|
||||||
|
src = lib.fileset.toSource {
|
||||||
|
root = ./.;
|
||||||
|
fileset = lib.fileset.unions [
|
||||||
|
./shell.qml
|
||||||
|
./assets
|
||||||
|
./config
|
||||||
|
./modules
|
||||||
|
./services
|
||||||
|
./ui
|
||||||
|
./windows
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = [ makeWrapper ];
|
||||||
|
buildInputs = [ quickshellWithModules ];
|
||||||
|
propagatedBuildInputs = runtimeDeps;
|
||||||
|
|
||||||
|
dontBuild = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
|
||||||
|
mkdir -p $out/bin $out/share/tshell
|
||||||
|
cp -r . $out/share/tshell
|
||||||
|
|
||||||
|
makeWrapper ${quickshellWithModules}/bin/qs $out/bin/tshell \
|
||||||
|
--prefix PATH : "${lib.makeBinPath runtimeDeps}" \
|
||||||
|
--set FONTCONFIG_FILE "${fontconfig}" \
|
||||||
|
--add-flags "-p $out/share/tshell"
|
||||||
|
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "tux's widgets for wayland";
|
||||||
|
homepage = "https://github.com/tuxdotrs/tshell";
|
||||||
|
license = lib.licenses.gpl3Only;
|
||||||
|
mainProgram = "tshell";
|
||||||
|
platforms = lib.platforms.linux;
|
||||||
|
};
|
||||||
|
}
|
||||||
30
services/Audio.qml
Normal file
30
services/Audio.qml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.Pipewire
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property string outputName: truncateText(root.displayName(Pipewire.defaultAudioSink), 14)
|
||||||
|
readonly property string inputName: truncateText(root.displayName(Pipewire.defaultAudioSource), 14)
|
||||||
|
readonly property url outputIcon: `${Quickshell.shellPath("assets")}/icons/speaker.svg`
|
||||||
|
readonly property url inputIcon: `${Quickshell.shellPath("assets")}/icons/microphone.svg`
|
||||||
|
readonly property real volume: Pipewire.defaultAudioSink?.audio.volume ?? 0
|
||||||
|
readonly property bool muted: Pipewire.defaultAudioSink?.audio.muted ?? false
|
||||||
|
|
||||||
|
function truncateText(text, maxLength) {
|
||||||
|
return text.length > maxLength ? text.slice(0, maxLength - 3) + "..." : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayName(node: PwNode): string {
|
||||||
|
if (!node)
|
||||||
|
return "none";
|
||||||
|
return node.description || node.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
PwObjectTracker {
|
||||||
|
objects: [Pipewire.defaultAudioSink, Pipewire.defaultAudioSource]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import Quickshell.Services.UPower
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
readonly property int percentage: Math.round(UPower.displayDevice.percentage * 100)
|
readonly property int percentage: Math.round(UPower.displayDevice.percentage * 100)
|
||||||
readonly property real energyRate: UPower.displayDevice.changeRate
|
readonly property real energyRate: UPower.displayDevice.changeRate.toFixed(1)
|
||||||
readonly property string iconName: root.percentage < 30 ? "battery-low.svg" : root.percentage < 70 ? "battery-medium.svg" : "battery-full.svg"
|
readonly property string iconName: root.percentage < 30 ? "battery-low.svg" : root.percentage < 70 ? "battery-medium.svg" : "battery-full.svg"
|
||||||
readonly property url icon: `${Quickshell.shellPath("assets")}/icons/${root.iconName}`
|
readonly property url icon: `${Quickshell.shellPath("assets")}/icons/${root.iconName}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,22 +5,22 @@ import Quickshell
|
|||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property string configPath: {
|
readonly property string configPath: {
|
||||||
const url = Quickshell.shellPath("assets/cava.conf").toString();
|
const url = Quickshell.shellPath("assets/cava.conf").toString();
|
||||||
return url.startsWith("file://") ? decodeURIComponent(url.substring(7)) : url;
|
return url.startsWith("file://") ? decodeURIComponent(url.substring(7)) : url;
|
||||||
}
|
}
|
||||||
property var values: []
|
property var values: []
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
running: true
|
running: true
|
||||||
command: ["cava", "-p", root.configPath]
|
command: ["cava", "-p", root.configPath]
|
||||||
|
|
||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
onRead: line => {
|
onRead: line => {
|
||||||
root.values = line.split(";").map(Number).filter(v => !Number.isNaN(v));
|
root.values = line.split(";").map(Number).filter(v => !Number.isNaN(v));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
43
services/Media.qml
Normal file
43
services/Media.qml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.Mpris
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property MprisPlayer player: {
|
||||||
|
const players = [...Mpris.players.values];
|
||||||
|
return players.find(p => p.playbackState === MprisPlaybackState.Playing) ?? players[0] ?? null;
|
||||||
|
}
|
||||||
|
readonly property bool playing: root.player?.isPlaying ?? false
|
||||||
|
readonly property string trackTitle: root.player?.trackTitle ?? ""
|
||||||
|
readonly property string trackArtist: root.player?.trackArtist ?? ""
|
||||||
|
readonly property url icon: {
|
||||||
|
const entry = root.player?.desktopEntry ?? "";
|
||||||
|
return entry !== "" ? Quickshell.iconPath(entry, false) : "";
|
||||||
|
}
|
||||||
|
readonly property string position: root.formatTime(root.player?.position ?? 0)
|
||||||
|
readonly property string length: root.formatTime(root.player?.length ?? 0)
|
||||||
|
|
||||||
|
function truncateText(text, maxLength) {
|
||||||
|
return text.length > maxLength ? text.slice(0, maxLength - 3) + "..." : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(seconds: real): string {
|
||||||
|
const total = Math.max(0, Math.floor(seconds));
|
||||||
|
const minutes = Math.floor(total / 60);
|
||||||
|
return `${minutes}:${(total % 60).toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `position` does not update reactively on its own; emit the changed
|
||||||
|
// signal every second while playing so bindings stay in sync.
|
||||||
|
Timer {
|
||||||
|
running: root.playing && (root.player?.positionSupported ?? false)
|
||||||
|
interval: 1000
|
||||||
|
repeat: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
onTriggered: root.player?.positionChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ import Quickshell.Services.UPower as UPower
|
|||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property bool performanceAvailable: UPower.PowerProfiles.hasPerformanceProfile
|
|
||||||
readonly property string name: {
|
readonly property string name: {
|
||||||
switch (UPower.PowerProfiles.profile) {
|
switch (UPower.PowerProfiles.profile) {
|
||||||
case UPower.PowerProfile.Performance:
|
case UPower.PowerProfile.Performance:
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import qs.windows
|
|||||||
Scope {
|
Scope {
|
||||||
Bar {}
|
Bar {}
|
||||||
Notification {}
|
Notification {}
|
||||||
|
Volume {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,42 +5,42 @@ import QtQuick.Controls
|
|||||||
import qs.config
|
import qs.config
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string label: ""
|
property string label: ""
|
||||||
property url iconSource: ""
|
property url iconSource: ""
|
||||||
property bool iconOnly: false
|
property bool iconOnly: false
|
||||||
property bool hoverHighlight: true
|
property bool hoverHighlight: true
|
||||||
property bool pointerCursor: true
|
property bool pointerCursor: true
|
||||||
|
|
||||||
padding: Appearance.padding
|
padding: Theme.padding
|
||||||
display: iconOnly ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
|
display: iconOnly ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
|
||||||
|
|
||||||
text: label
|
text: label
|
||||||
font.family: Appearance.font.family
|
font.family: Theme.font.family
|
||||||
font.pointSize: Appearance.font.pointSize
|
font.pointSize: Theme.font.pointSize
|
||||||
palette.buttonText: Appearance.colors.foreground
|
palette.buttonText: Theme.colors.foreground
|
||||||
icon.color: Appearance.colors.foreground
|
icon.color: Theme.colors.foreground
|
||||||
icon.source: iconSource
|
icon.source: iconSource
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
enabled: root.pointerCursor
|
enabled: root.pointerCursor
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: Appearance.radius
|
|
||||||
color: {
|
|
||||||
if (!root.hoverHighlight)
|
|
||||||
return Appearance.colors.inActive;
|
|
||||||
return root.hovered ? Appearance.colors.inActive : Appearance.colors.background;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
background: Rectangle {
|
||||||
ColorAnimation {
|
anchors.fill: parent
|
||||||
duration: Appearance.duration
|
radius: Theme.radius
|
||||||
}
|
color: {
|
||||||
|
if (!root.hoverHighlight)
|
||||||
|
return Theme.colors.inActive;
|
||||||
|
return root.hovered ? Theme.colors.inActive : Theme.colors.background;
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.duration
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import Quickshell
|
|||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
property string name
|
property string name
|
||||||
|
|
||||||
implicitWidth: 0
|
implicitWidth: 0
|
||||||
implicitHeight: 0
|
implicitHeight: 0
|
||||||
WlrLayershell.namespace: `quickshell:${name}ExclusionZone`
|
WlrLayershell.namespace: `quickshell:${name}ExclusionZone`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,61 +6,61 @@ import Quickshell.Widgets
|
|||||||
import qs.config
|
import qs.config
|
||||||
|
|
||||||
WrapperItem {
|
WrapperItem {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int corner
|
property int corner
|
||||||
property real radius: Appearance.radius
|
property real radius: Theme.radius
|
||||||
property color color
|
property color color
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
switch (corner) {
|
switch (corner) {
|
||||||
case 0:
|
case 0:
|
||||||
anchors.left = parent.left;
|
anchors.left = parent.left;
|
||||||
anchors.top = parent.top;
|
anchors.top = parent.top;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
anchors.top = parent.top;
|
anchors.top = parent.top;
|
||||||
anchors.right = parent.right;
|
anchors.right = parent.right;
|
||||||
rotation = 90;
|
rotation = 90;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
anchors.right = parent.right;
|
anchors.right = parent.right;
|
||||||
anchors.bottom = parent.bottom;
|
anchors.bottom = parent.bottom;
|
||||||
rotation = 180;
|
rotation = 180;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
anchors.left = parent.left;
|
anchors.left = parent.left;
|
||||||
anchors.bottom = parent.bottom;
|
anchors.bottom = parent.bottom;
|
||||||
rotation = -90;
|
rotation = -90;
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Shape {
|
Shape {
|
||||||
preferredRendererType: Shape.CurveRenderer
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
|
||||||
ShapePath {
|
ShapePath {
|
||||||
strokeWidth: 0
|
strokeWidth: 0
|
||||||
fillColor: root.color
|
fillColor: root.color
|
||||||
startX: root.radius
|
startX: root.radius
|
||||||
|
|
||||||
PathArc {
|
PathArc {
|
||||||
relativeX: -root.radius
|
relativeX: -root.radius
|
||||||
relativeY: root.radius
|
relativeY: root.radius
|
||||||
radiusX: root.radius
|
radiusX: root.radius
|
||||||
radiusY: radiusX
|
radiusY: radiusX
|
||||||
direction: PathArc.Counterclockwise
|
direction: PathArc.Counterclockwise
|
||||||
}
|
}
|
||||||
|
|
||||||
PathLine {
|
PathLine {
|
||||||
relativeX: 0
|
relativeX: 0
|
||||||
relativeY: -root.radius
|
relativeY: -root.radius
|
||||||
}
|
}
|
||||||
|
|
||||||
PathLine {
|
PathLine {
|
||||||
relativeX: root.radius
|
relativeX: root.radius
|
||||||
relativeY: 0
|
relativeY: 0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
268
windows/Bar.qml
268
windows/Bar.qml
@@ -8,154 +8,154 @@ import qs.config
|
|||||||
import qs.ui
|
import qs.ui
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: window
|
id: window
|
||||||
|
|
||||||
property color barColor: Appearance.colors.background
|
property color barColor: Theme.colors.background
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
exclusionMode: ExclusionMode.Ignore
|
|
||||||
mask: Region {
|
|
||||||
item: cornersArea
|
|
||||||
intersection: Intersection.Subtract
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
left: true
|
|
||||||
top: true
|
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
|
||||||
|
|
||||||
component BarSection: Rectangle {
|
|
||||||
id: section
|
|
||||||
|
|
||||||
property var widgets: []
|
|
||||||
property int alignment: Qt.AlignLeft
|
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
implicitHeight: parent.height
|
exclusionMode: ExclusionMode.Ignore
|
||||||
Layout.fillWidth: true
|
mask: Region {
|
||||||
|
item: cornersArea
|
||||||
|
intersection: Intersection.Subtract
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
anchors {
|
||||||
id: secRow
|
left: true
|
||||||
|
top: true
|
||||||
|
right: true
|
||||||
|
bottom: true
|
||||||
|
}
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
component BarSection: Rectangle {
|
||||||
x: section.alignment === Qt.AlignLeft ? 0 : section.alignment === Qt.AlignRight ? parent.width - width : (parent.width - width) / 2
|
id: section
|
||||||
spacing: Appearance.spacing
|
|
||||||
|
|
||||||
Repeater {
|
property var widgets: []
|
||||||
model: section.widgets
|
property int alignment: Qt.AlignLeft
|
||||||
|
|
||||||
Loader {
|
color: "transparent"
|
||||||
required property var modelData
|
implicitHeight: parent.height
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
source: modelData
|
RowLayout {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
id: secRow
|
||||||
|
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
x: section.alignment === Qt.AlignLeft ? 0 : section.alignment === Qt.AlignRight ? parent.width - width : (parent.width - width) / 2
|
||||||
|
spacing: Theme.spacing
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: section.widgets
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
source: modelData
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Exclusions
|
// Exclusions
|
||||||
Scope {
|
Scope {
|
||||||
ExclusionZone {
|
ExclusionZone {
|
||||||
name: "left"
|
name: "left"
|
||||||
exclusiveZone: leftBar.implicitWidth
|
exclusiveZone: leftBar.implicitWidth
|
||||||
anchors.left: true
|
anchors.left: true
|
||||||
|
}
|
||||||
|
ExclusionZone {
|
||||||
|
name: "top"
|
||||||
|
exclusiveZone: topBar.implicitHeight
|
||||||
|
anchors.top: true
|
||||||
|
}
|
||||||
|
ExclusionZone {
|
||||||
|
name: "right"
|
||||||
|
exclusiveZone: rightBar.implicitWidth
|
||||||
|
anchors.right: true
|
||||||
|
}
|
||||||
|
ExclusionZone {
|
||||||
|
name: "bottom"
|
||||||
|
exclusiveZone: bottomBar.implicitHeight
|
||||||
|
anchors.bottom: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ExclusionZone {
|
|
||||||
name: "top"
|
|
||||||
exclusiveZone: topBar.implicitHeight
|
|
||||||
anchors.top: true
|
|
||||||
}
|
|
||||||
ExclusionZone {
|
|
||||||
name: "right"
|
|
||||||
exclusiveZone: rightBar.implicitWidth
|
|
||||||
anchors.right: true
|
|
||||||
}
|
|
||||||
ExclusionZone {
|
|
||||||
name: "bottom"
|
|
||||||
exclusiveZone: bottomBar.implicitHeight
|
|
||||||
anchors.bottom: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bars
|
// Bars
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: leftBar
|
id: leftBar
|
||||||
|
|
||||||
implicitWidth: 10
|
implicitWidth: Theme.bar.thickness
|
||||||
implicitHeight: QsWindow.window?.height ?? 0
|
implicitHeight: QsWindow.window?.height ?? 0
|
||||||
color: window.barColor
|
|
||||||
anchors.left: parent.left
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
id: rightBar
|
|
||||||
|
|
||||||
implicitWidth: 10
|
|
||||||
implicitHeight: QsWindow.window?.height ?? 0
|
|
||||||
color: window.barColor
|
|
||||||
anchors.right: parent.right
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
id: bottomBar
|
|
||||||
|
|
||||||
implicitWidth: QsWindow.window?.width ?? 0
|
|
||||||
implicitHeight: 10
|
|
||||||
color: window.barColor
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
id: topBar
|
|
||||||
|
|
||||||
implicitWidth: QsWindow.window?.width ?? 0
|
|
||||||
implicitHeight: 50
|
|
||||||
color: window.barColor
|
|
||||||
anchors.top: parent.top
|
|
||||||
|
|
||||||
FlexboxLayout {
|
|
||||||
id: flexLayout
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Appearance.margin
|
|
||||||
|
|
||||||
wrap: FlexboxLayout.Wrap
|
|
||||||
direction: FlexboxLayout.Row
|
|
||||||
justifyContent: FlexboxLayout.JustifySpaceBetween
|
|
||||||
|
|
||||||
BarSection {
|
|
||||||
widgets: BarLayout.left
|
|
||||||
alignment: Qt.AlignLeft
|
|
||||||
}
|
|
||||||
BarSection {
|
|
||||||
widgets: BarLayout.center
|
|
||||||
alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
BarSection {
|
|
||||||
widgets: BarLayout.right
|
|
||||||
alignment: Qt.AlignRight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: cornersArea
|
|
||||||
|
|
||||||
implicitWidth: QsWindow.window?.width - (leftBar.implicitWidth + rightBar.implicitWidth)
|
|
||||||
implicitHeight: QsWindow.window?.height - (topBar.implicitHeight + bottomBar.implicitHeight)
|
|
||||||
color: "transparent"
|
|
||||||
x: leftBar.implicitWidth
|
|
||||||
y: topBar.implicitHeight
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: 4
|
|
||||||
|
|
||||||
RoundedCorner {
|
|
||||||
required property int modelData
|
|
||||||
|
|
||||||
corner: modelData
|
|
||||||
color: window.barColor
|
color: window.barColor
|
||||||
}
|
anchors.left: parent.left
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
id: rightBar
|
||||||
|
|
||||||
|
implicitWidth: Theme.bar.thickness
|
||||||
|
implicitHeight: QsWindow.window?.height ?? 0
|
||||||
|
color: window.barColor
|
||||||
|
anchors.right: parent.right
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
id: bottomBar
|
||||||
|
|
||||||
|
implicitWidth: QsWindow.window?.width ?? 0
|
||||||
|
implicitHeight: Theme.bar.thickness
|
||||||
|
color: window.barColor
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
id: topBar
|
||||||
|
|
||||||
|
implicitWidth: QsWindow.window?.width ?? 0
|
||||||
|
implicitHeight: Theme.bar.height
|
||||||
|
color: window.barColor
|
||||||
|
anchors.top: parent.top
|
||||||
|
|
||||||
|
FlexboxLayout {
|
||||||
|
id: flexLayout
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.margin
|
||||||
|
|
||||||
|
wrap: FlexboxLayout.Wrap
|
||||||
|
direction: FlexboxLayout.Row
|
||||||
|
justifyContent: FlexboxLayout.JustifySpaceBetween
|
||||||
|
|
||||||
|
BarSection {
|
||||||
|
widgets: BarLayout.left
|
||||||
|
alignment: Qt.AlignLeft
|
||||||
|
}
|
||||||
|
BarSection {
|
||||||
|
widgets: BarLayout.center
|
||||||
|
alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
BarSection {
|
||||||
|
widgets: BarLayout.right
|
||||||
|
alignment: Qt.AlignRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: cornersArea
|
||||||
|
|
||||||
|
implicitWidth: QsWindow.window?.width - (leftBar.implicitWidth + rightBar.implicitWidth)
|
||||||
|
implicitHeight: QsWindow.window?.height - (topBar.implicitHeight + bottomBar.implicitHeight)
|
||||||
|
color: "transparent"
|
||||||
|
x: leftBar.implicitWidth
|
||||||
|
y: topBar.implicitHeight
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: 4
|
||||||
|
|
||||||
|
RoundedCorner {
|
||||||
|
required property int modelData
|
||||||
|
|
||||||
|
corner: modelData
|
||||||
|
color: window.barColor
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,48 +8,46 @@ import qs.services
|
|||||||
import "../modules/osd"
|
import "../modules/osd"
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property int cardWidth: 420
|
color: "transparent"
|
||||||
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
visible: Notifications.popupCount > 0
|
||||||
|
implicitWidth: Theme.notification.width
|
||||||
|
implicitHeight: stack.implicitHeight
|
||||||
|
|
||||||
color: "transparent"
|
anchors {
|
||||||
exclusionMode: ExclusionMode.Ignore
|
top: true
|
||||||
visible: Notifications.popupCount > 0
|
right: true
|
||||||
implicitWidth: root.cardWidth
|
}
|
||||||
implicitHeight: stack.implicitHeight
|
margins {
|
||||||
|
top: Theme.bar.height + Theme.margin
|
||||||
anchors {
|
right: Theme.bar.thickness + Theme.margin
|
||||||
top: true
|
}
|
||||||
right: true
|
|
||||||
}
|
mask: Region {
|
||||||
margins {
|
item: stack
|
||||||
top: 50 + Appearance.margin
|
}
|
||||||
right: 10 + Appearance.margin
|
|
||||||
}
|
ColumnLayout {
|
||||||
|
id: stack
|
||||||
mask: Region {
|
|
||||||
item: stack
|
anchors.top: parent.top
|
||||||
}
|
anchors.right: parent.right
|
||||||
|
anchors.left: parent.left
|
||||||
ColumnLayout {
|
spacing: Theme.spacing
|
||||||
id: stack
|
|
||||||
|
Repeater {
|
||||||
anchors.top: parent.top
|
model: [...Notifications.popups.values].reverse()
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.left: parent.left
|
NotificationCard {
|
||||||
spacing: Appearance.spacing
|
id: card
|
||||||
|
|
||||||
Repeater {
|
required property var modelData
|
||||||
model: [...Notifications.popups.values].reverse()
|
|
||||||
|
notif: modelData
|
||||||
NotificationCard {
|
Layout.fillWidth: true
|
||||||
id: card
|
}
|
||||||
|
}
|
||||||
required property var modelData
|
|
||||||
|
|
||||||
notif: modelData
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
52
windows/Volume.qml
Normal file
52
windows/Volume.qml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import qs.config
|
||||||
|
import qs.services
|
||||||
|
import "../modules/osd"
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool shown: false
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
visible: shown
|
||||||
|
implicitWidth: card.implicitWidth
|
||||||
|
implicitHeight: card.implicitHeight
|
||||||
|
|
||||||
|
anchors.bottom: true
|
||||||
|
margins.bottom: screen.height * Theme.volume.bottomMarginRatio
|
||||||
|
|
||||||
|
// An empty click mask prevents the window from blocking mouse events.
|
||||||
|
mask: Region {}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Audio
|
||||||
|
|
||||||
|
function onVolumeChanged() {
|
||||||
|
root.shown = true;
|
||||||
|
hideTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMutedChanged() {
|
||||||
|
root.shown = true;
|
||||||
|
hideTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: hideTimer
|
||||||
|
|
||||||
|
interval: Theme.volume.timeout
|
||||||
|
onTriggered: root.shown = false
|
||||||
|
}
|
||||||
|
|
||||||
|
VolumeCard {
|
||||||
|
id: card
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user