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