diff --git a/hypr b/hypr
deleted file mode 120000
index 0ee98bc..0000000
--- a/hypr
+++ /dev/null
@@ -1 +0,0 @@
-/home/doloro/.config/hypr
\ No newline at end of file
diff --git a/hypr_/hyprland.conf b/hypr_/hyprland.conf
new file mode 100644
index 0000000..6e9f31f
--- /dev/null
+++ b/hypr_/hyprland.conf
@@ -0,0 +1,304 @@
+# This is an example Hyprland config file.
+# Refer to the wiki for more information.
+# https://wiki.hypr.land/Configuring/
+
+# Please note not all available settings / options are set here.
+# For a full list, see the wiki
+
+# You can split this configuration into multiple files
+# Create your files separately and then link them to this file like this:
+# source = ~/.config/hypr/myColors.conf
+
+
+################
+### MONITORS ###
+################
+
+# See https://wiki.hypr.land/Configuring/Monitors/
+monitor=,preferred,auto,auto
+
+
+###################
+### MY PROGRAMS ###
+###################
+
+# See https://wiki.hypr.land/Configuring/Keywords/
+
+# Set programs that you use
+$terminal = foot
+$fileManager = dolphin
+$menu = wofi --show drun
+
+
+#################
+### AUTOSTART ###
+#################
+
+# Autostart necessary processes (like notifications daemons, status bars, etc.)
+# Or execute your favorite apps at launch like this:
+
+# exec-once = $terminal
+# exec-once = nm-applet &
+# exec-once = waybar & hyprpaper & firefox
+
+
+#############################
+### ENVIRONMENT VARIABLES ###
+#############################
+
+# See https://wiki.hypr.land/Configuring/Environment-variables/
+
+env = XCURSOR_SIZE,24
+env = HYPRCURSOR_SIZE,24
+
+
+###################
+### PERMISSIONS ###
+###################
+
+# See https://wiki.hypr.land/Configuring/Permissions/
+# Please note permission changes here require a Hyprland restart and are not applied on-the-fly
+# for security reasons
+
+# ecosystem {
+# enforce_permissions = 1
+# }
+
+# permission = /usr/(bin|local/bin)/grim, screencopy, allow
+# permission = /usr/(lib|libexec|lib64)/xdg-desktop-portal-hyprland, screencopy, allow
+# permission = /usr/(bin|local/bin)/hyprpm, plugin, allow
+
+
+#####################
+### LOOK AND FEEL ###
+#####################
+
+# Refer to https://wiki.hypr.land/Configuring/Variables/
+
+# https://wiki.hypr.land/Configuring/Variables/#general
+general {
+ gaps_in = 5
+ gaps_out = 20
+
+ border_size = 2
+
+ # https://wiki.hypr.land/Configuring/Variables/#variable-types for info about colors
+ col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg
+ col.inactive_border = rgba(595959aa)
+
+ # Set to true enable resizing windows by clicking and dragging on borders and gaps
+ resize_on_border = false
+
+ # Please see https://wiki.hypr.land/Configuring/Tearing/ before you turn this on
+ allow_tearing = false
+
+ layout = dwindle
+}
+
+# https://wiki.hypr.land/Configuring/Variables/#decoration
+decoration {
+ rounding = 10
+ rounding_power = 2
+
+ # Change transparency of focused and unfocused windows
+ active_opacity = 1.0
+ inactive_opacity = 1.0
+
+ shadow {
+ enabled = true
+ range = 4
+ render_power = 3
+ color = rgba(1a1a1aee)
+ }
+
+ # https://wiki.hypr.land/Configuring/Variables/#blur
+ blur {
+ enabled = true
+ size = 3
+ passes = 1
+
+ vibrancy = 0.1696
+ }
+}
+
+# https://wiki.hypr.land/Configuring/Variables/#animations
+animations {
+ enabled = yes, please :)
+
+ # Default animations, see https://wiki.hypr.land/Configuring/Animations/ for more
+
+ bezier = easeOutQuint,0.23,1,0.32,1
+ bezier = easeInOutCubic,0.65,0.05,0.36,1
+ bezier = linear,0,0,1,1
+ bezier = almostLinear,0.5,0.5,0.75,1.0
+ bezier = quick,0.15,0,0.1,1
+
+ animation = global, 1, 10, default
+ animation = border, 1, 5.39, easeOutQuint
+ animation = windows, 1, 4.79, easeOutQuint
+ animation = windowsIn, 1, 4.1, easeOutQuint, popin 87%
+ animation = windowsOut, 1, 1.49, linear, popin 87%
+ animation = fadeIn, 1, 1.73, almostLinear
+ animation = fadeOut, 1, 1.46, almostLinear
+ animation = fade, 1, 3.03, quick
+ animation = layers, 1, 3.81, easeOutQuint
+ animation = layersIn, 1, 4, easeOutQuint, fade
+ animation = layersOut, 1, 1.5, linear, fade
+ animation = fadeLayersIn, 1, 1.79, almostLinear
+ animation = fadeLayersOut, 1, 1.39, almostLinear
+ animation = workspaces, 1, 1.94, almostLinear, fade
+ animation = workspacesIn, 1, 1.21, almostLinear, fade
+ animation = workspacesOut, 1, 1.94, almostLinear, fade
+ animation = zoomFactor, 1, 7, quick
+}
+
+# Ref https://wiki.hypr.land/Configuring/Workspace-Rules/
+# "Smart gaps" / "No gaps when only"
+# uncomment all if you wish to use that.
+# workspace = w[tv1], gapsout:0, gapsin:0
+# workspace = f[1], gapsout:0, gapsin:0
+# windowrule = bordersize 0, floating:0, onworkspace:w[tv1]
+# windowrule = rounding 0, floating:0, onworkspace:w[tv1]
+# windowrule = bordersize 0, floating:0, onworkspace:f[1]
+# windowrule = rounding 0, floating:0, onworkspace:f[1]
+
+# See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more
+dwindle {
+ pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
+ preserve_split = true # You probably want this
+}
+
+# See https://wiki.hypr.land/Configuring/Master-Layout/ for more
+master {
+ new_status = master
+}
+
+# https://wiki.hypr.land/Configuring/Variables/#misc
+misc {
+ force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
+ disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :(
+}
+
+
+#############
+### INPUT ###
+#############
+
+# https://wiki.hypr.land/Configuring/Variables/#input
+input {
+ kb_layout = us
+ kb_variant =
+ kb_model =
+ kb_options =
+ kb_rules =
+
+ follow_mouse = 1
+
+ sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
+
+ touchpad {
+ natural_scroll = false
+ }
+}
+
+# https://wiki.hypr.land/Configuring/Variables/#gestures
+gestures {
+ workspace_swipe = false
+}
+
+# Example per-device config
+# See https://wiki.hypr.land/Configuring/Keywords/#per-device-input-configs for more
+device {
+ name = epic-mouse-v1
+ sensitivity = -0.5
+}
+
+
+###################
+### KEYBINDINGS ###
+###################
+
+# See https://wiki.hypr.land/Configuring/Keywords/
+$mainMod = SUPER # Sets "Windows" key as main modifier
+
+# Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more
+bind = $mainMod, Q, exec, $terminal
+bind = $mainMod, C, killactive,
+bind = $mainMod, M, exit,
+bind = $mainMod, E, exec, $fileManager
+bind = $mainMod, V, togglefloating,
+bind = $mainMod, R, exec, $menu
+bind = $mainMod, P, pseudo, # dwindle
+bind = $mainMod, J, togglesplit, # dwindle
+
+# Move focus with mainMod + arrow keys
+bind = $mainMod, left, movefocus, l
+bind = $mainMod, right, movefocus, r
+bind = $mainMod, up, movefocus, u
+bind = $mainMod, down, movefocus, d
+
+# Switch workspaces with mainMod + [0-9]
+bind = $mainMod, 1, workspace, 1
+bind = $mainMod, 2, workspace, 2
+bind = $mainMod, 3, workspace, 3
+bind = $mainMod, 4, workspace, 4
+bind = $mainMod, 5, workspace, 5
+bind = $mainMod, 6, workspace, 6
+bind = $mainMod, 7, workspace, 7
+bind = $mainMod, 8, workspace, 8
+bind = $mainMod, 9, workspace, 9
+bind = $mainMod, 0, workspace, 10
+
+# Move active window to a workspace with mainMod + SHIFT + [0-9]
+bind = $mainMod SHIFT, 1, movetoworkspace, 1
+bind = $mainMod SHIFT, 2, movetoworkspace, 2
+bind = $mainMod SHIFT, 3, movetoworkspace, 3
+bind = $mainMod SHIFT, 4, movetoworkspace, 4
+bind = $mainMod SHIFT, 5, movetoworkspace, 5
+bind = $mainMod SHIFT, 6, movetoworkspace, 6
+bind = $mainMod SHIFT, 7, movetoworkspace, 7
+bind = $mainMod SHIFT, 8, movetoworkspace, 8
+bind = $mainMod SHIFT, 9, movetoworkspace, 9
+bind = $mainMod SHIFT, 0, movetoworkspace, 10
+
+# Example special workspace (scratchpad)
+bind = $mainMod, S, togglespecialworkspace, magic
+bind = $mainMod SHIFT, S, movetoworkspace, special:magic
+
+# Scroll through existing workspaces with mainMod + scroll
+bind = $mainMod, mouse_down, workspace, e+1
+bind = $mainMod, mouse_up, workspace, e-1
+
+# Move/resize windows with mainMod + LMB/RMB and dragging
+bindm = $mainMod, mouse:272, movewindow
+bindm = $mainMod, mouse:273, resizewindow
+
+# Laptop multimedia keys for volume and LCD brightness
+bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
+bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
+bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
+bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
+bindel = ,XF86MonBrightnessUp, exec, brightnessctl -e4 -n2 set 5%+
+bindel = ,XF86MonBrightnessDown, exec, brightnessctl -e4 -n2 set 5%-
+
+# Requires playerctl
+bindl = , XF86AudioNext, exec, playerctl next
+bindl = , XF86AudioPause, exec, playerctl play-pause
+bindl = , XF86AudioPlay, exec, playerctl play-pause
+bindl = , XF86AudioPrev, exec, playerctl previous
+
+##############################
+### WINDOWS AND WORKSPACES ###
+##############################
+
+# See https://wiki.hypr.land/Configuring/Window-Rules/ for more
+# See https://wiki.hypr.land/Configuring/Workspace-Rules/ for workspace rules
+
+# Example windowrule
+# windowrule = float,class:^(kitty)$,title:^(kitty)$
+
+# Ignore maximize requests from apps. You'll probably like this.
+windowrule = suppressevent maximize, class:.*
+
+# Fix some dragging issues with XWayland
+windowrule = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0
diff --git a/quickshell b/quickshell
deleted file mode 120000
index 9a6bb70..0000000
--- a/quickshell
+++ /dev/null
@@ -1 +0,0 @@
-/home/doloro/.config/quickshell
\ No newline at end of file
diff --git a/quickshell_/Audio.qml b/quickshell_/Audio.qml
new file mode 100644
index 0000000..14783fe
--- /dev/null
+++ b/quickshell_/Audio.qml
@@ -0,0 +1,47 @@
+import Quickshell
+import Quickshell.Io
+import QtQuick
+import QtQuick.Layouts
+import Quickshell.Widgets
+import Quickshell.Services.Pipewire
+
+Item {
+ id: root
+ property PwNode speakerNode: Pipewire.defaultAudioSink
+ property PwNode microphoneNode: Pipewire.defaultAudioSource
+ PwObjectTracker { objects: [ root.microphoneNode, root.speakerNode ] }
+
+ width: row.width
+ height: row.height
+
+ // console.log("a");
+
+ RowLayout {
+ id: row
+
+ IconImage {
+ // anchors.centerIn: parent
+ implicitSize: 27
+ source: "root:Speaker.svg"
+ }
+
+
+ Text {
+ id: text
+ text: root.speakerNode.audio.muted ? "0%" : root.speakerNode.audio.volume * 100 + "%"
+ font.pointSize: 10.75
+ color: root.speakerNode.audio.muted ? "red" : "#FFFFFF"
+ }
+
+ IconImage {
+ implicitSize: 26
+ source: "root:Mic.svg"
+ }
+
+ Text {
+ text: parseInt(root.microphoneNode.audio.muted ? "0" : root.microphoneNode.audio.volume * 100) + "%"
+ font.pointSize: 10.75
+ color: root.microphoneNode.audio.muted ? "red" : "#FFFFFF"
+ }
+ }
+}
diff --git a/quickshell_/Bar.qml b/quickshell_/Bar.qml
new file mode 100644
index 0000000..6c904ce
--- /dev/null
+++ b/quickshell_/Bar.qml
@@ -0,0 +1,67 @@
+import Quickshell
+import Quickshell.Io
+import QtQuick
+import QtQuick.Layouts
+import Quickshell.Widgets
+
+ PanelWindow {
+ property var modelData
+ screen: modelData.values[0];
+
+ color: '#20ffffff'
+ anchors {
+ top: true
+ left: true
+ right: true
+ }
+
+ implicitHeight: 30
+ RowLayout {
+ anchors {
+ fill: parent
+ leftMargin: 10
+ rightMargin: 10
+ }
+ RowLayout { // Left
+ Layout.alignment: Qt.AlignLeft
+ }
+ RowLayout { // Center
+ // TODO: add icons of the active window per workspace in the workspace tab
+ anchors.centerIn: parent
+ Workspaces {}
+ }
+ RowLayout { // Right
+ Layout.alignment: Qt.AlignRight
+ VerticalSeprator {}
+ Loader {
+ sourceComponent: Audio {}
+ }
+ RowLayout {
+ id: aaaaa
+ // If player disapear the seprator disapears with it
+ visible: Player.activePlayer.isPlaying()
+ VerticalSeprator {}
+ PlayerText {
+ }
+ }
+ VerticalSeprator {}
+ WrapperItem {
+ Text { // Date & Time
+ text: Time.time
+ color: '#FFFFFF'
+ font.pointSize: 10.75
+ }
+ }
+ // VerticalSeprator {}
+ }
+ }
+ Rectangle {
+ anchors {
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ }
+ height: 2
+ color: "#8d8d8d"
+ }
+ }
diff --git a/quickshell_/BarButton.qml b/quickshell_/BarButton.qml
new file mode 100644
index 0000000..4d6a3cd
--- /dev/null
+++ b/quickshell_/BarButton.qml
@@ -0,0 +1,34 @@
+import QtQuick
+import QtQuick.Effects
+
+FullwidthMouseArea {
+ id: root
+ property bool showPressed: mouseArea.pressed;
+ property real baseMargin: 0;
+ property bool directScale: false;
+
+ readonly property Item contentItem: mContentItem;
+ default property alias contentItemData: mContentItem.data;
+
+ property real targetBrightness: root.showPressed ? -25 : root.mouseArea.containsMouse && root.enabled ? 75 : 0
+ Behavior on targetBrightness { SmoothedAnimation { velocity: 600 } }
+
+ property real targetMargins: root.showPressed ? 3 : 0;
+ Behavior on targetMargins { SmoothedAnimation { velocity: 25 } }
+
+ hoverEnabled: true
+
+ Item {
+ id: mContentItem
+ anchors.fill: parent;
+
+ anchors.margins: root.baseMargin + (root.directScale ? 0 : root.targetMargins);
+ scale: root.directScale ? (width - root.targetMargins * 2) / width : 1.0;
+
+ opacity: root.enabled ? 1.0 : 0.5;
+ Behavior on opacity { SmoothedAnimation { velocity: 5 } }
+
+ layer.enabled: root.targetBrightness != 0
+ layer.effect: MultiEffect { brightness: root.targetBrightness / 1000 }
+ }
+}
diff --git a/quickshell_/BarContain.qml b/quickshell_/BarContain.qml
new file mode 100644
index 0000000..146c0bb
--- /dev/null
+++ b/quickshell_/BarContain.qml
@@ -0,0 +1,79 @@
+import QtQuick
+import Quickshell
+import Quickshell.Hyprland
+import Quickshell.Wayland
+
+PanelWindow {
+ id: root
+
+ default property alias barItems: containment.data;
+
+ anchors {
+ left: true
+ top: true
+ bottom: true
+ }
+
+ property real baseWidth: 55
+ property real leftMargin: root.compactState * 10
+ width: baseWidth + 15
+ exclusiveZone: baseWidth + (isFullscreenWorkspace ? 0 : 15) - margins.left
+
+ mask: Region {
+ height: root.height
+ width: root.exclusiveZone
+ }
+
+ color: "transparent"
+
+ WlrLayershell.namespace: "shell:bar"
+
+ readonly property Tooltip tooltip: tooltip;
+ Tooltip {
+ id: tooltip
+ bar: root
+ }
+
+ readonly property real tooltipXOffset: root.baseWidth + root.leftMargin + 5;
+
+ function boundedY(targetY: real, height: real): real {
+ return Math.max(barRect.anchors.topMargin + height, Math.min(barRect.height + barRect.anchors.topMargin - height, targetY))
+ }
+
+ readonly property bool isFullscreenWorkspace: Hyprland.monitorFor(screen).activeWorkspace.hasFullscreen
+ property real compactState: isFullscreenWorkspace ? 0 : 1
+ Behavior on compactState {
+ NumberAnimation {
+ duration: 600
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: [0.0, 0.75, 0.15, 1.0, 1.0, 1.0]
+ }
+ }
+
+ Rectangle {
+ id: barRect
+
+ x: root.leftMargin - Lock.Controller.lockSlide * (barRect.width + root.leftMargin)
+ width: parent.width - 15
+
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ margins: root.compactState * 10
+ }
+
+ color: ShellGlobals.colors.bar
+ radius: root.compactState * 5
+ border.color: ShellGlobals.colors.barOutline
+ border.width: root.compactState
+
+ Item {
+ id: containment
+
+ anchors {
+ fill: parent
+ margins: 5
+ }
+ }
+ }
+}
diff --git a/quickshell_/FullwidthMouseArea.qml b/quickshell_/FullwidthMouseArea.qml
new file mode 100644
index 0000000..bb41790
--- /dev/null
+++ b/quickshell_/FullwidthMouseArea.qml
@@ -0,0 +1,53 @@
+import QtQuick
+
+Item {
+ id: root
+
+ property bool fillWindowWidth: false;
+ property real extraVerticalMargin: 0;
+
+ property alias mouseArea: mouseArea;
+ property alias hoverEnabled: mouseArea.hoverEnabled;
+ property alias acceptedButtons: mouseArea.acceptedButtons;
+ property alias pressedButtons: mouseArea.pressedButtons;
+ property alias containsMouse: mouseArea.containsMouse;
+ property alias isPressed: mouseArea.pressed;
+
+ signal clicked(event: MouseEvent);
+ signal entered();
+ signal exited();
+ signal pressed(event: MouseEvent);
+ signal released(event: MouseEvent);
+ signal wheel(event: WheelEvent);
+
+ MouseArea {
+ id: mouseArea
+
+ anchors {
+ fill: parent
+ // not much point in finding exact values
+ leftMargin: root.fillWindowWidth ? -50 : 0
+ rightMargin: root.fillWindowWidth ? -50 : 0
+ topMargin: -root.extraVerticalMargin
+ bottomMargin: -root.extraVerticalMargin
+ }
+
+ Component.onCompleted: {
+ this.clicked.connect(root.clicked);
+ //this.entered.connect(root.entered);
+ //this.exited.connect(root.exited);
+ //this.pressed.connect(root.pressed);
+ this.released.connect(root.released);
+ //this.wheel.connect(root.wheel);
+ }
+
+ // for some reason MouseArea.pressed is both a prop and signal so connect doesn't work
+ onPressed: event => root.pressed(event);
+
+ // connecting to onwheel seems to implicitly accept it. undo that.
+ onWheel: event => {
+ event.accepted = false;
+ root.wheel(event);
+ }
+ }
+}
diff --git a/quickshell_/Mic.svg b/quickshell_/Mic.svg
new file mode 100644
index 0000000..b11891e
--- /dev/null
+++ b/quickshell_/Mic.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/quickshell_/Player.qml b/quickshell_/Player.qml
new file mode 100644
index 0000000..59e9859
--- /dev/null
+++ b/quickshell_/Player.qml
@@ -0,0 +1,45 @@
+pragma Singleton
+
+import Quickshell
+import Quickshell.Io
+import QtQuick
+import Quickshell.Services.Mpris
+
+Singleton {
+ id: root
+ // property MprisPlayer trackedPlayer: null;
+ readonly property MprisPlayer activePlayer: PlayerController.activePlayer.identity === "Spotify"
+ ? PlayerController.activePlayer
+ : null
+
+ readonly property string time: {
+ root.activePlayer.trackTitle
+ }
+ readonly property string artist: {
+ root.activePlayer.trackArtist
+ }
+ readonly property string timetime: {
+ Math.floor(root.activePlayer.position / 60) + ":" +
+ formatTwoDigits(Math.floor(root.activePlayer.position % 60))
+ }
+
+ readonly property string fulltime: {
+ Math.floor(root.activePlayer.length / 60) + ":" +
+ formatTwoDigits(Math.floor(root.activePlayer.length % 60))
+ }
+
+ function formatTwoDigits(num) {
+ return (num < 10 ? "0" : "") + num;
+ }
+
+
+ Timer {
+ // only emit the signal when the position is actually changing.
+ running: root.activePlayer.playbackState == MprisPlaybackState.Playing
+ // Make sure the position updates at least once per second.
+ interval: 1000
+ repeat: true
+ // emit the positionChanged signal every second.
+ onTriggered: root.activePlayer.positionChanged()
+ }
+}
diff --git a/quickshell_/PlayerController.qml b/quickshell_/PlayerController.qml
new file mode 100644
index 0000000..f17f8a9
--- /dev/null
+++ b/quickshell_/PlayerController.qml
@@ -0,0 +1,173 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQml.Models
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import Quickshell.Services.Mpris
+// import qs
+
+Singleton {
+ id: root;
+ property MprisPlayer trackedPlayer: null;
+ property MprisPlayer activePlayer: findSpotify();
+
+
+ signal trackChanged(reverse: bool);
+
+ property bool __reverse: false;
+
+ property var activeTrack;
+
+ Instantiator {
+ model: Mpris.players;
+
+ Connections {
+ required property MprisPlayer modelData;
+ target: modelData;
+
+ Component.onCompleted: {
+ if (root.trackedPlayer == null || modelData.isPlaying) {
+ root.trackedPlayer = modelData;
+ }
+ }
+
+ Component.onDestruction: {
+ if (root.trackedPlayer == null || !root.trackedPlayer.isPlaying) {
+ for (const player of Mpris.players.values) {
+ if (player.playbackState.isPlaying) {
+ root.trackedPlayer = player;
+ break;
+ }
+ }
+
+ if (trackedPlayer == null && Mpris.players.values.length != 0) {
+ trackedPlayer = Mpris.players.values[0];
+ }
+ }
+ }
+
+ function onPlaybackStateChanged() {
+ if (root.trackedPlayer !== modelData) root.trackedPlayer = modelData;
+ }
+ }
+ }
+
+ Connections {
+ target: activePlayer
+
+ function onPostTrackChanged() {
+ root.updateTrack();
+ }
+
+ function onTrackArtUrlChanged() {
+ console.log("arturl:", activePlayer.trackArtUrl)
+ //root.updateTrack();
+ if (root.activePlayer.uniqueId == root.activeTrack.uniqueId && root.activePlayer.trackArtUrl != root.activeTrack.artUrl) {
+ // cantata likes to send cover updates *BEFORE* updating the track info.
+ // as such, art url changes shouldn't be able to break the reverse animation
+ const r = root.__reverse;
+ root.updateTrack();
+ root.__reverse = r;
+
+ }
+ }
+ }
+
+ onActivePlayerChanged: this.updateTrack();
+
+ function updateTrack() {
+ //console.log(`update: ${this.activePlayer?.trackTitle ?? ""} : ${this.activePlayer?.trackArtists}`)
+ this.activeTrack = {
+ uniqueId: this.activePlayer?.uniqueId ?? 0,
+ artUrl: this.activePlayer?.trackArtUrl ?? "",
+ title: this.activePlayer?.trackTitle || "Unknown Title",
+ artist: this.activePlayer?.trackArtist || "Unknown Artist",
+ album: this.activePlayer?.trackAlbum || "Unknown Album",
+ };
+
+ this.trackChanged(__reverse);
+ this.__reverse = false;
+ }
+
+ property bool isPlaying: this.activePlayer && this.activePlayer.isPlaying;
+ property bool canTogglePlaying: this.activePlayer?.canTogglePlaying ?? false;
+ function togglePlaying() {
+ if (this.canTogglePlaying) this.activePlayer.togglePlaying();
+ }
+
+ property bool canGoPrevious: this.activePlayer?.canGoPrevious ?? false;
+ function previous() {
+ if (this.canGoPrevious) {
+ this.__reverse = true;
+ this.activePlayer.previous();
+ }
+ }
+
+ property bool canGoNext: this.activePlayer?.canGoNext ?? false;
+ function next() {
+ if (this.canGoNext) {
+ this.__reverse = false;
+ this.activePlayer.next();
+ }
+ }
+
+ property bool canChangeVolume: this.activePlayer && this.activePlayer.volumeSupported && this.activePlayer.canControl;
+
+ property bool loopSupported: this.activePlayer && this.activePlayer.loopSupported && this.activePlayer.canControl;
+ property var loopState: this.activePlayer?.loopState ?? MprisLoopState.None;
+ function setLoopState(loopState: var) {
+ if (this.loopSupported) {
+ this.activePlayer.loopState = loopState;
+ }
+ }
+
+ property bool shuffleSupported: this.activePlayer && this.activePlayer.shuffleSupported && this.activePlayer.canControl;
+ property bool hasShuffle: this.activePlayer?.shuffle ?? false;
+ function setShuffle(shuffle: bool) {
+ if (this.shuffleSupported) {
+ this.activePlayer.shuffle = shuffle;
+ }
+ }
+
+ function setActivePlayer(player: MprisPlayer) {
+ const targetPlayer = player ?? Mpris.players[0];
+ console.log(`setactive: ${targetPlayer} from ${activePlayer}`)
+
+ if (targetPlayer && this.activePlayer) {
+ this.__reverse = Mpris.players.indexOf(targetPlayer) < Mpris.players.indexOf(this.activePlayer);
+ } else {
+ // always animate forward if going to null
+ this.__reverse = false;
+ }
+
+ this.trackedPlayer = targetPlayer;
+ }
+
+ function findSpotify(): MprisPlayer {
+ const players = Mpris.players
+ for (const player of players.values) {
+ if (player.identity == "Spotify") {
+ return player
+ }
+ }
+
+
+ console.log("");
+ }
+
+ IpcHandler {
+ target: "mpris"
+
+ function pauseAll(): void {
+ for (const player of Mpris.players.values) {
+ if (player.canPause) player.pause();
+ }
+ }
+
+ function playPause(): void { root.togglePlaying(); }
+ function previous(): void { root.previous(); }
+ function next(): void { root.next(); }
+ }
+}
diff --git a/quickshell_/PlayerControllerV2.qml b/quickshell_/PlayerControllerV2.qml
new file mode 100644
index 0000000..86e10dd
--- /dev/null
+++ b/quickshell_/PlayerControllerV2.qml
@@ -0,0 +1,176 @@
+// Reworked the PlayerController a bit
+
+
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQml.Models
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import Quickshell.Services.Mpris
+// import qs
+
+Singleton {
+ id: root;
+ property Mpris mpris: Mpris;
+ property MprisPlayer activePlayer: findSpotify();
+
+ signal trackChanged(reverse: bool);
+
+ property bool __reverse: false;
+
+ property var activeTrack;
+
+ PersistentProperties {
+ id: persist
+ reloadableId: "MusicWidget";
+ property string lastActivePlayerIdentify: "";
+
+ onReloaded: {
+ // rightclickMenu.snapOpacity(widgetOpen ? 1.0 : 0.0)
+ }
+ }
+
+ Instantiator {
+ model: Mpris.players;
+
+ Connections {
+ required property MprisPlayer modelData;
+ target: modelData;
+
+ Component.onCompleted: {
+ if (root.trackedPlayer == null || modelData.isPlaying) {
+ root.trackedPlayer = modelData;
+ }
+ }
+
+ Component.onDestruction: {
+ if (root.trackedPlayer == null || !root.trackedPlayer.isPlaying) {
+ for (const player of Mpris.players.values) {
+ if (player.playbackState.isPlaying) {
+ root.trackedPlayer = player;
+ break;
+ }
+ }
+
+ if (trackedPlayer == null && Mpris.players.values.length != 0) {
+ trackedPlayer = Mpris.players.values[0];
+ }
+ }
+ }
+
+ function onPlaybackStateChanged() {
+ if (root.trackedPlayer !== modelData) root.trackedPlayer = modelData;
+ }
+ }
+ }
+
+ Connections {
+ target: activePlayer
+
+ function onPostTrackChanged() {
+ root.updateTrack();
+ }
+
+ function onTrackArtUrlChanged() {
+ console.log("arturl:", activePlayer.trackArtUrl)
+ //root.updateTrack();
+ if (root.activePlayer.uniqueId == root.activeTrack.uniqueId && root.activePlayer.trackArtUrl != root.activeTrack.artUrl) {
+ // cantata likes to send cover updates *BEFORE* updating the track info.
+ // as such, art url changes shouldn't be able to break the reverse animation
+ const r = root.__reverse;
+ root.updateTrack();
+ root.__reverse = r;
+
+ }
+ }
+ }
+
+ onActivePlayerChanged: this.updateTrack();
+
+ function updateTrack() {
+ //console.log(`update: ${this.activePlayer?.trackTitle ?? ""} : ${this.activePlayer?.trackArtists}`)
+ this.activeTrack = {
+ uniqueId: this.activePlayer?.uniqueId ?? 0,
+ artUrl: this.activePlayer?.trackArtUrl ?? "",
+ title: this.activePlayer?.trackTitle || "Unknown Title",
+ artist: this.activePlayer?.trackArtist || "Unknown Artist",
+ album: this.activePlayer?.trackAlbum || "Unknown Album",
+ };
+
+ this.trackChanged(__reverse);
+ this.__reverse = false;
+ }
+
+ property bool isPlaying: this.activePlayer && this.activePlayer.isPlaying;
+ property bool canTogglePlaying: this.activePlayer?.canTogglePlaying ?? false;
+ function togglePlaying() {
+ if (this.canTogglePlaying) this.activePlayer.togglePlaying();
+ }
+
+ property bool canGoPrevious: this.activePlayer?.canGoPrevious ?? false;
+ function previous() {
+ if (this.canGoPrevious) {
+ this.__reverse = true;
+ this.activePlayer.previous();
+ }
+ }
+
+ property bool canGoNext: this.activePlayer?.canGoNext ?? false;
+ function next() {
+ if (this.canGoNext) {
+ this.__reverse = false;
+ this.activePlayer.next();
+ }
+ }
+
+ property bool canChangeVolume: this.activePlayer && this.activePlayer.volumeSupported && this.activePlayer.canControl;
+
+ property bool loopSupported: this.activePlayer && this.activePlayer.loopSupported && this.activePlayer.canControl;
+ property var loopState: this.activePlayer?.loopState ?? MprisLoopState.None;
+ function setLoopState(loopState: var) {
+ if (this.loopSupported) {
+ this.activePlayer.loopState = loopState;
+ }
+ }
+
+ property bool shuffleSupported: this.activePlayer && this.activePlayer.shuffleSupported && this.activePlayer.canControl;
+ property bool hasShuffle: this.activePlayer?.shuffle ?? false;
+ function setShuffle(shuffle: bool) {
+ if (this.shuffleSupported) {
+ this.activePlayer.shuffle = shuffle;
+ }
+ }
+
+ function setActivePlayer(player: MprisPlayer) {
+ const targetPlayer = player ?? Mpris.players[0];
+ console.log(`setactive: ${targetPlayer} from ${activePlayer}`)
+
+ this.activePlayer = player;
+ }
+
+ function findSpotify(): MprisPlayer {
+ const players = Mpris.players
+ for (const player of players.values) {
+ if (player.identity == "Spotify") {
+ return player
+ }
+ }
+ console.log("");
+ }
+
+ IpcHandler {
+ target: "mpris"
+
+ function pauseAll(): void {
+ for (const player of Mpris.players.values) {
+ if (player.canPause) player.pause();
+ }
+ }
+
+ function playPause(): void { root.togglePlaying(); }
+ function previous(): void { root.previous(); }
+ function next(): void { root.next(); }
+ }
+}
diff --git a/quickshell_/PlayerPopup.qml b/quickshell_/PlayerPopup.qml
new file mode 100644
index 0000000..4de9e5c
--- /dev/null
+++ b/quickshell_/PlayerPopup.qml
@@ -0,0 +1,48 @@
+import QtQml
+import QtQuick
+import Quickshell
+import QtQuick.Layouts
+
+PopupWindow {
+ property PlayerControllerV2 playerController: null;
+
+
+
+ id: audioPopup
+ anchor {
+ item: root
+ }
+ anchor.rect.x: root.width / 2 - width / 2
+ anchor.rect.y: root.height * 1.2
+ height: 350
+ width: 300
+ Rectangle {
+ anchors.fill: parent
+ color: "#919191"
+ ColumnLayout {
+ anchors.centerIn: parent
+ Item {
+ width: coverImg.width
+ height: coverImg.height
+ Image {
+ id: coverImg
+ anchors.fill: parent
+ source: Player.activePlayer.trackArtUrl;
+ width: 275
+ height: 275
+ fillMode: Image.PreserveAspectCrop
+ }
+ }
+ Text {
+ text: Player.activePlayer.trackTitle
+ color: "white"
+ font.pointSize: 10.75
+ }
+ Text {
+ text: Player.activePlayer.trackArtist
+ color: "white"
+ font.pointSize: 10.75
+ }
+ }
+ }
+}
diff --git a/quickshell_/PlayerText.qml b/quickshell_/PlayerText.qml
new file mode 100644
index 0000000..a782abf
--- /dev/null
+++ b/quickshell_/PlayerText.qml
@@ -0,0 +1,129 @@
+import Quickshell
+import Quickshell.Io
+import QtQuick
+import QtQuick.Layouts
+import Quickshell.Widgets
+import QtQuick.Effects
+// import playbutton.svg
+
+Item {
+ id: root
+ Layout.fillHeight: true
+ implicitWidth: hi.width
+
+ visible: Player.activePlayer.canPlay()
+
+
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ onEntered: event => {
+ audioPopup.visible = true;
+ }
+ onExited: event => {
+ audioPopup.visible = false;
+ }
+ }
+ Image {
+ source: Player.activePlayer.trackArtUrl;
+ anchors.fill: parent
+ fillMode: Image.PreserveAspectCrop
+
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ blur: 0.3
+ blurEnabled: true
+ }
+ }
+ // parent.aaaaa: false
+ RowLayout {
+ id: hi
+ Item {
+ implicitHeight: 30;
+ implicitWidth: 30;
+ IconImage {
+ implicitSize: 30;
+ source: "root:skip_next";
+ scale: -1
+ }
+ MouseArea {
+ anchors.fill: parent
+ onPressed: event => {
+ Player.activePlayer.previous();
+ }
+ }
+ }
+ Item {
+ implicitHeight: 30;
+ implicitWidth: 30;
+ IconImage {
+ implicitSize: 30;
+ source: !Player.activePlayer.isPlaying ? "root:playbutton.svg" : "root:pausebutton.svg";
+ }
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: false
+ onPressed: event => {
+ Player.activePlayer.togglePlaying();
+ // console.debug("" + Player.activePlayer.)
+ }
+ }
+ // PopupAnchor {}
+ }
+
+ Item {
+ implicitHeight: 30;
+ implicitWidth: 30;
+ IconImage {
+ implicitSize: 30;
+ source: "root:skip_next";
+ }
+ MouseArea {
+ anchors.fill: parent
+ onPressed: event => {
+ Player.activePlayer.next();
+ }
+ }
+ }
+ }
+ PopupWindow {
+ id: audioPopup
+ color: "transparent"
+ anchor {
+ item: root
+ }
+ anchor.rect.x: root.width / 2 - width / 2
+ anchor.rect.y: root.height * 1.2
+ height: 350
+ width: 300
+ Rectangle {
+ anchors.fill: parent
+ color: "#919191"
+ ColumnLayout {
+ anchors.centerIn: parent
+ Item {
+ width: coverImg.width
+ height: coverImg.height
+ Image {
+ id: coverImg
+ anchors.fill: parent
+ source: Player.activePlayer.trackArtUrl;
+ width: 275
+ height: 275
+ fillMode: Image.PreserveAspectCrop
+ }
+ }
+ Text {
+ text: Player.activePlayer.trackTitle
+ color: "white"
+ font.pointSize: 10.75
+ }
+ Text {
+ text: Player.activePlayer.trackArtist
+ color: "white"
+ font.pointSize: 10.75
+ }
+ }
+ }
+ }
+}
diff --git a/quickshell_/Speaker.svg b/quickshell_/Speaker.svg
new file mode 100644
index 0000000..b5c71be
--- /dev/null
+++ b/quickshell_/Speaker.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/quickshell_/Time.qml b/quickshell_/Time.qml
new file mode 100644
index 0000000..0492ac7
--- /dev/null
+++ b/quickshell_/Time.qml
@@ -0,0 +1,18 @@
+pragma Singleton
+
+import Quickshell
+import Quickshell.Io
+import QtQuick
+
+Singleton {
+ id: root
+ readonly property string time: {
+ Qt.formatDateTime(clock.date, "ddd d MMM [hh:mm]")
+ }
+
+
+ SystemClock {
+ id: clock
+ precision: SystemClock.Minutes
+ }
+}
diff --git a/quickshell_/VerticalSeprator.qml b/quickshell_/VerticalSeprator.qml
new file mode 100644
index 0000000..3287f01
--- /dev/null
+++ b/quickshell_/VerticalSeprator.qml
@@ -0,0 +1,13 @@
+import Quickshell
+import QtQml
+import QtQuick
+
+ Rectangle {
+ id: a
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+ width: 2
+ color: "#8d8d8d"
+ }
diff --git a/quickshell_/WindowInfo.qml b/quickshell_/WindowInfo.qml
new file mode 100644
index 0000000..5f21249
--- /dev/null
+++ b/quickshell_/WindowInfo.qml
@@ -0,0 +1,12 @@
+import Quickshell
+import Quickshell.Io
+import QtQuick
+import QtQuick.Layouts
+import Quickshell.Widgets
+import Quickshell.Hyprland
+
+Item {
+ RowLayout {
+
+ }
+}
diff --git a/quickshell_/Workspaces.qml b/quickshell_/Workspaces.qml
new file mode 100644
index 0000000..2c0c97b
--- /dev/null
+++ b/quickshell_/Workspaces.qml
@@ -0,0 +1,42 @@
+import Quickshell
+import Quickshell.Io
+import QtQuick
+import QtQuick.Layouts
+import Quickshell.Widgets
+import Quickshell.Hyprland
+
+RowLayout {
+ id: root
+ spacing: 3
+
+ Repeater {
+ model: Hyprland.workspaces
+ delegate: Item {
+ required property int index
+ property HyprlandWorkspace index_workspace: Hyprland.workspaces.values[index]
+ width: 25
+ height: 30
+ MouseArea {
+ Rectangle {
+ id: reg
+ anchors.fill: parent
+ color: index_workspace.focused ? "#3B3B3B" : "#808080"
+ }
+ Layout.fillWidth: true
+ Text {
+ anchors.centerIn: parent
+ text: index_workspace.id
+ color: "#FFFFFF"
+ font.pointSize: 10.75
+ }
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ onPressed: event => {
+ if (event.button === Qt.LeftButton) {
+ Hyprland.dispatch('workspace ' + index_workspace.id);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/quickshell_/pausebutton.svg b/quickshell_/pausebutton.svg
new file mode 100644
index 0000000..59231a6
--- /dev/null
+++ b/quickshell_/pausebutton.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/quickshell_/playbutton.svg b/quickshell_/playbutton.svg
new file mode 100644
index 0000000..4c8d13d
--- /dev/null
+++ b/quickshell_/playbutton.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/quickshell_/shell.qml b/quickshell_/shell.qml
new file mode 100644
index 0000000..252ec89
--- /dev/null
+++ b/quickshell_/shell.qml
@@ -0,0 +1,6 @@
+import Quickshell
+import QtQuick
+
+Scope {
+ Bar {}
+}
diff --git a/quickshell_/skip_next.svg b/quickshell_/skip_next.svg
new file mode 100644
index 0000000..82a9311
--- /dev/null
+++ b/quickshell_/skip_next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file