!qol(restructure): heavy restructure

This commit is contained in:
2025-12-10 21:37:23 +00:00
parent b5c2089694
commit f1337d3edd
114 changed files with 2 additions and 360 deletions

View File

@@ -0,0 +1,4 @@
_: {
home = ./home.nix;
# nixos: ./nixos.nix;
}

View File

@@ -0,0 +1,35 @@
{
inputs,
config,
lib,
pkgs,
system,
...
}:
let
pkg = inputs.quickshell.packages.${system}.quickshell;
cfg = config.modules.quickshell;
in
{
options.modules.quickshell = {
enable = lib.mkEnableOption "quickshell configuration module";
};
config.xdg.configFile."quickshell" = lib.mkIf cfg.enable {
recursive = true;
source = config.lib.file.mkOutOfStoreSymlink "./quickshell";
};
config.systemd.user.services.quickshell = lib.mkIf cfg.enable {
Unit = {
Description = "Quickshell daemon";
After = [ "hyprland-session.target" ];
};
Install = {
WantedBy = [ "default.target" ];
};
Service = {
ExecStart = "${pkg}/bin/quickshell";
Restart = "on-failure";
RestartSec = "5s";
};
};
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M480-420q-41.92 0-70.96-29.04Q380-478.08 380-520v-240q0-41.92 29.04-70.96Q438.08-860 480-860q41.92 0 70.96 29.04Q580-801.92 580-760v240q0 41.92-29.04 70.96Q521.92-420 480-420Zm-30 290v-131.85q-99-11.31-164.5-84.92Q220-420.39 220-520h60q0 83 58.5 141.5T480-320q83 0 141.5-58.5T680-520h60q0 99.61-65.5 173.23Q609-273.16 510-261.85V-130h-60Z"/></svg>

After

Width:  |  Height:  |  Size: 463 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#EA3323"><path d="m710-362-58-58q14-23 21-48t7-52h80q0 44-13 83.5T710-362ZM592-482 360-714v-46q0-50 35-85t85-35q50 0 85 35t35 85v240q0 11-2.5 20t-5.5 18ZM440-120v-124q-104-14-172-92.5T200-520h80q0 83 58.5 141.5T480-320q34 0 64.5-10.5T600-360l57 57q-29 23-63.5 38.5T520-244v124h-80Zm352 64L56-792l56-56 736 736-56 56Z"/></svg>

After

Width:  |  Height:  |  Size: 424 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#EA3323"><path d="M792-56 671-177q-25 16-53 27.5T560-131v-82q14-5 27.5-10t25.5-12L480-368v208L280-360H120v-240h128L56-792l56-56 736 736-56 56Zm-8-232-58-58q17-31 25.5-65t8.5-70q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 53-14.5 102T784-288ZM650-422l-90-90v-130q47 22 73.5 66t26.5 96q0 15-2.5 29.5T650-422ZM480-592 376-696l104-104v208Z"/></svg>

After

Width:  |  Height:  |  Size: 450 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M561.54-155.62v-62q86.54-27.53 139.42-100 52.89-72.46 52.89-163.38t-52.89-163.38q-52.88-72.47-139.42-100v-62q111.69 29.92 182 119.92 70.3 90 70.3 205.46 0 115.46-70.3 205.46-70.31 90-182 119.92ZM146.16-380v-200h148.46l171.53-171.53v543.06L294.62-380H146.16Zm415.38 46.15v-294.3q40.46 22 62.54 61.96 22.07 39.96 22.07 86.19 0 45.61-22.27 84.88-22.27 39.27-62.34 61.27Z"/></svg>

After

Width:  |  Height:  |  Size: 492 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M560-200v-560h160v560H560Zm-320 0v-560h160v560H240Z"/></svg>

After

Width:  |  Height:  |  Size: 176 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M320-200v-560l440 280-440 280Z"/></svg>

After

Width:  |  Height:  |  Size: 155 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M660-240v-480h80v480h-80Zm-440 0v-480l360 240-360 240Z"/></svg>

After

Width:  |  Height:  |  Size: 179 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M220-240v-480h80v480h-80Zm520 0L380-480l360-240v480Z"/></svg>

After

Width:  |  Height:  |  Size: 177 B

View File

@@ -0,0 +1,76 @@
//@ pragma UseQApplication
import Quickshell
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Quickshell.Hyprland
import "widgets" as Widgets
import "widgets/player" as Player
import "widgets/common" as Common
import "widgets/clock" as Clock
import "widgets/workspace" as Workspace
// Tako kindly threatened you to sort the naming schema and to put all the svg's in an asset folder, so please do that
PanelWindow {
property var modelData
screen: modelData.values[0];
color: '#20ffffff'
anchors {
top: true
left: true
right: true
}
implicitHeight: 32
RowLayout {
height: 28
anchors {
top: parent.top
left: parent.left
right: parent.right
// bottomMargin: 2
leftMargin: 10
rightMargin: 10
}
RowLayout { // Left
Layout.alignment: Qt.AlignLeft
Clock.Date {}
Clock.Clock {}
Workspace.WorkspaceWidget {}
}
RowLayout { // Center
// TODO: add icons of the active window per workspace in the workspace tab
anchors.centerIn: parent
}
RowLayout { // Right
Layout.alignment: Qt.AlignRight
Loader {
sourceComponent: Widgets.Audio {}
}
RowLayout {
Text {
text: HyprlandWindowTracker.HyprlandWindowTracker.aaaa
}
visible: Player.activePlayer.isPlaying()
Player.PlayerWidgetV2 {
}
}
Widgets.SystemTray {
id: systemTray
}
}
}
Rectangle {
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
height: 1
color: "#8d8d8d"
}
}

View File

@@ -0,0 +1,60 @@
import Quickshell
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Quickshell.Services.Pipewire
import "common" as Common
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
Item {
implicitWidth: 28
implicitHeight: 28
Rectangle {
width: parent.width
height: parent.height
radius: 7
color: "black"
}
Text {
anchors.centerIn: parent
// implicitSize: 25
font.pixelSize: 28
color: root.speakerNode.audio.muted ? "#FF474C" : "white"
font.family: "CaskaydiaCove Nerd Font Mono"
text: root.speakerNode.audio.muted ? Common.Icons.audioIcons.speakerMuted : Common.Icons.audioIcons.speaker
}
}
Item {
implicitWidth: 28
implicitHeight: 28
Rectangle {
width: parent.width
height: parent.height
radius: 7
color: "black"
}
Text {
anchors.centerIn: parent
// implicitSize: 25
font.pixelSize: 34
color: root.microphoneNode.audio.muted ? "#FF474C" : "white"
font.family: "CaskaydiaCove Nerd Font Mono"
text: root.microphoneNode.audio.muted ? Common.Icons.audioIcons.microphoneMuted : Common.Icons.audioIcons.microphone
}
}
}
}

View File

@@ -0,0 +1,63 @@
import Quickshell
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import QtQuick.Effects
import Quickshell.Services.SystemTray
Item {
id: root
Layout.preferredHeight: 30
Layout.preferredWidth: layout.implicitWidth
RowLayout {
spacing: 0
anchors {
top: parent.top
bottom: parent.bottom
}
id: layout
Repeater {
model: SystemTray.items
delegate: Item {
required property SystemTrayItem modelData
id: item
Layout.preferredHeight: 30
Layout.preferredWidth: 30
MouseArea {
anchors.fill: parent
anchors.centerIn: parent
onPressed: event => {
item.modelData.display(trayItemPopupWindow, mouseX, mouseY)
}
}
IconImage {
anchors.centerIn: parent
implicitSize: 20
source: modelData.icon
}
PopupWindow {
// hacky ass way to get a tray item popup window
implicitHeight: 1
implicitWidth: 1
color: "transparent"
id: trayItemPopupWindow
anchor.item: item
visible: true
}
}
}
}
Rectangle {
z: -1
anchors {
left: root.left
right: root.right
verticalCenter: root.verticalCenter
}
height: root.height - 5
radius: 6
color: "black"
}
}

View File

@@ -0,0 +1,45 @@
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);
}
for (x in index_workspace.toplevels.values) {
console.log(index_workspace.toplevels.values[x].title)
}
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
import Quickshell
import Quickshell.Io
import QtQuick
import "../common" as Common
Item {
id: root
width: text.width + 15
height: text.height
Common.Container {}
Text {
anchors.centerIn: parent
id: text
text: ClockData.time
font.pointSize: 10.25
color: "white"
}
}

View File

@@ -0,0 +1,22 @@
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]")
Qt.formatDateTime(clock.date, "hh:mm")
}
readonly property string date: {
Qt.formatDateTime(clock.date, "d MMM")
// Qt.formatDateTime(clock.date, "hh:mm")
}
SystemClock {
id: clock
precision: SystemClock.Minutes
}
}

View File

@@ -0,0 +1,19 @@
import Quickshell
import Quickshell.Io
import QtQuick
import "../common" as Common
Item {
id: root
width: text.width + 15
height: text.height
Common.Container {}
Text {
anchors.centerIn: parent
id: text
text: ClockData.date
font.pointSize: 10.25
color: "white"
}
}

View File

@@ -0,0 +1,12 @@
pragma Singleton
import Quickshell
import Quickshell.Io
Singleton {
id: root
readonly property var colors: ({
"primary": "",
})
}

View File

@@ -0,0 +1,13 @@
import Quickshell
import QtQuick
Rectangle {
z: -1
color: "black"
anchors {
verticalCenter: parent.verticalCenter
}
height: 25
width: parent.width
radius: 7
}

View File

@@ -0,0 +1,8 @@
import Quickshell
import QtQuick
Text {
text: "•"
color: "white"
font.pointSize: 12.5
}

View File

@@ -0,0 +1,30 @@
pragma Singleton
import Quickshell
import Quickshell.Io
Singleton {
id: root
readonly property var audioIcons: ({
microphone: "",
microphoneMuted: "",
speaker: "󰕾",
speakerMuted: "󰖁",
})
// {class}: {icon}
readonly property var appIcons: ({
"vesktop": "",
"steam": "",
"org.telegram.desktop": "",
"chromium": "",
"dev.zed.Zed": "",
"foot": "",
"gamescope": "󰺵",
"blender": "󰂫",
"Spotify": "",
"Unity": "",
"fallback": "󰏩"
})
}

View File

@@ -0,0 +1,17 @@
// import qs.services
// import qs.config
import "text" as Text
Text.StyledText {
property real fill
property int grade: Colours.light ? 0 : -25
font.family: Appearance.font.family.material
font.pointSize: Appearance.font.size.larger
font.variableAxes: ({
FILL: fill.toFixed(1),
GRAD: grade,
opsz: fontInfo.pixelSize,
wght: fontInfo.weight
})
}

View File

@@ -0,0 +1,9 @@
import Quickshell
import QtQuick
Item {
function iconFromDesktopfile(location): String { // testing // serves no purpose atm
return "/usr/share/pixmaps/spotify-client.png"
return "/usr/share/pixmaps/" + DesktopEntries.byId(location).icon + ".png"
}
}

View File

@@ -0,0 +1,13 @@
import Quickshell
import QtQml
import QtQuick
Rectangle {
id: a
anchors {
top: parent.top
bottom: parent.bottom
}
width: 2
color: "#8d8d8d"
}

View File

@@ -0,0 +1,54 @@
pragma ComponentBehavior: Bound
// https://github.com/caelestia-dots/shell/blob/a23bb48c243348096b27f5f4ac94f663271ece10/widgets/StyledText.qml
// import qs.services
// import qs.config
import QtQuick
Text {
id: root
property bool animate: false
property string animateProp: "scale"
property real animateFrom: 0
property real animateTo: 1
property int animateDuration: Appearance.anim.durations.normal
renderType: Text.NativeRendering
textFormat: Text.PlainText
color: Colours.palette.m3onSurface
font.family: Appearance.font.family.sans
font.pointSize: Appearance.font.size.smaller
Behavior on color {
ColorAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
Behavior on text {
enabled: root.animate
SequentialAnimation {
Anim {
to: root.animateFrom
easing.bezierCurve: Appearance.anim.curves.standardAccel
}
PropertyAction {}
Anim {
to: root.animateTo
easing.bezierCurve: Appearance.anim.curves.standardDecel
}
}
}
component Anim: NumberAnimation {
target: root
property: root.animateProp
duration: root.animateDuration / 2
easing.type: Easing.BezierSpline
}
}

View File

@@ -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(); }
}
}

View File

@@ -0,0 +1,227 @@
// 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;
property string activeTrackPositionFormated: "0:00";
property string activeTrackLengthFormated: "0:00";
PersistentProperties {
id: persist
reloadableId: "MusicWidget";
property string lastActivePlayerIdentify: "";
onReloaded: {
root.setActivePlayerFromIdentity(lastActivePlayerIdentify)
root.updateActiveTrackPosition()
}
onLoaded: {
root.setActivePlayerFromIdentity(lastActivePlayerIdentify)
root.updateActiveTrackPosition()
}
}
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() {
console.log("Playback state has been changed")
// 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 setPlayerByIdentity(id: string) {
for (var i = 0; i < 9; i++) {
console.log(i)
}
mpris.players
}
function updateTrack() {
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;
console.log(`setactive: ${targetPlayer} from ${activePlayer}`)
persist.lastActivePlayerIdentify = targetPlayer.identity
this.activePlayer = player;
}
function setActivePlayerFromIdentity(identify: string) {
for (x in root.mpris.players) {
if (x.identify == identify) {
this.activePlayer = x
return
}
}
}
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.updateActiveTrackPosition()
}
function updateActiveTrackPosition() {
activeTrackPositionFormated = Math.floor(root.activePlayer.position / 60) + ":" + ("0" + Math.floor(root.activePlayer.position % 60)).slice(-2)
activeTrackLengthFormated = Math.floor(root.activePlayer.length / 60) + ":" + ("0" + Math.floor(root.activePlayer.length % 60)).slice(-2)
}
function update() {
updateActiveTrackPosition()
}
function trackPositionFormatter(position: double): string {
return Math.floor(position / 60) + ":" + ("0" + Math.floor(position % 60)).slice(-2)
}
function findSpotify(): MprisPlayer {
const players = Mpris.players
for (const player of players.values) {
if (player.identity == "Spotify") {
return player
}
}
return undefined
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(); }
}
}

View File

@@ -0,0 +1,46 @@
import QtQml
import QtQuick
import Quickshell
import QtQuick.Layouts
PopupWindow {
property PlayerControllerV2 player: PlayerControllerV2;
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
}
}
}
}

View File

@@ -0,0 +1,254 @@
import QtQml
import QtQuick
import Quickshell
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell.Widgets
import Quickshell.Services.Mpris
PopupWindow {
property PlayerControllerV2 player: PlayerControllerV2;
readonly property string elementBackgroundColor : "#323232"
readonly property string elementButtonBaseColor : "#515151"
readonly property string elementButtonHoverColor : "#737373"
id: audioPopup
anchor {
item: root
}
anchor.rect.x: root.width / 2 - width / 2
anchor.rect.y: root.height * 1.2
height: columnContainer.height + 5
width: 425
Rectangle {
anchors.fill: parent
color: "#919191"
ColumnLayout {
id: columnContainer
anchors.centerIn: parent
Repeater {
model: player.mpris.players
Item {
id: root
width: 375
height: 110
required property MprisPlayer modelData
property string trackPosition: PlayerControllerV2.trackPositionFormatter(root.modelData.position)
property string trackLength: "0:00"
Rectangle {
id: background
anchors.fill: parent
color: audioPopup.elementBackgroundColor
radius: 7
}
MouseArea {
anchors.fill: parent
onClicked: event => {
audioPopup.player.setActivePlayer(root.modelData);
audioPopup.player.update();
}
}
ColumnLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
Layout.alignment: Qt.AlignHCenter
width:root.width - 15
RowLayout {
implicitWidth: root.width - 15
ClippingWrapperRectangle {
implicitWidth: 75
implicitHeight: 75
radius: 7
Image {
anchors.fill: parent
mipmap: true
source: root.modelData.trackArtUrl
}
}
ColumnLayout {
spacing: 2
ScrollView {
implicitWidth: 180
height: parent.height
Text {
text: root.modelData.trackTitle
color: "white"
font.pointSize: 11
antialiasing: true
}
}
Text {
text: root.modelData.trackArtist
color: "white"
font.pointSize: 8
antialiasing: true
}
}
Rectangle { // Spacer
color: "transparent"
Layout.fillWidth: true
}
RowLayout {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
// Layout.
spacing: 0
// buttons
Item {
implicitWidth: 30
implicitHeight: 25
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Item {
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Rectangle {
id: gobackButton
width: 30
height: 25
topLeftRadius: 7
bottomLeftRadius: 7
color: audioPopup.elementButtonBaseColor
}
IconImage {
Layout.alignment: Qt.AlignCenter
implicitSize: 25
source: "root:/assets/media_player/skip_previous.svg"
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: event => {
gobackButton.color = audioPopup.elementButtonHoverColor
}
onExited: event => {
gobackButton.color = audioPopup.elementButtonBaseColor
}
onClicked: event => {
root.modelData.previous();
}
}
}
Item {
width: 30
height: 25
Item {
Layout.alignment: Qt.AlignCenter
width: 30
height: 25
Rectangle {
id: pauseButton
width: 30
height: 25
// topLeftRadius: 7
// bottomLeftRadius: 7
color: audioPopup.elementButtonBaseColor
}
IconImage {
Layout.alignment: Qt.AlignCenter
anchors.centerIn: parent
implicitSize: 25
source: root.modelData.isPlaying ? "root:/assets/media_player/pausebutton.svg" : "root:/assets/media_player/playbutton.svg"
}
}
MouseArea {
// id: pauseButton
anchors.fill: parent
hoverEnabled: true
onEntered: event => {
pauseButton.color = audioPopup.elementButtonHoverColor
}
onExited: event => {
pauseButton.color = audioPopup.elementButtonBaseColor
}
onClicked: event => {
root.modelData.togglePlaying();
}
}
}
Item {
width: 30
height: 25
Item {
Layout.alignment: Qt.AlignCenter
Rectangle {
id: gonextButton
width: 30
height: 25
topRightRadius: 7
bottomRightRadius: 7
color: audioPopup.elementButtonBaseColor
}
IconImage {
Layout.alignment: Qt.AlignCenter
implicitSize: 25
source: "root:/assets/media_player/skip_next.svg"
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: event => {
gonextButton.color = audioPopup.elementButtonHoverColor
}
onExited: event => {
gonextButton.color = audioPopup.elementButtonBaseColor
}
onClicked: event => {
root.modelData.next();
}
}
}
}
}
RowLayout {
Slider {
id: slider
Layout.fillWidth: true
from: 0
value: root.modelData.position
to: root.modelData.length
property double lastPosition;
property bool debounceValue: false;
onMoved: event => {
debounceValue = true; // there is insane lag if we put the move logic here, so its better to trigger a time with the logic instead
}
Timer {
running: slider.debounceValue
interval: 250
repeat: true
onTriggered: {
root.modelData.position = slider.value
slider.debounceValue = false;
}
}
}
Text {
id: positionText
text: trackPosition + "/" + trackLength // returns float of seconds of length
color: "white"
font.pointSize: 10
}
}
}
Timer {
id: updateTimer
property bool tempOneshot: false;
running: root.modelData.isPlaying || (root.modelData.position != slider.lastPosition)
interval: 750
repeat: true
onTriggered: {
trackPosition = PlayerControllerV2.trackPositionFormatter(root.modelData.position)
trackLength = PlayerControllerV2.trackPositionFormatter(root.modelData.length)
updateTimer.tempOneshot = false;
slider.lastPosition = slider.value
slider.value = root.modelData.position
// root.audioPopup.player.update();
}
}
}
}
}
}
}

View File

@@ -0,0 +1,88 @@
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
property PlayerControllerV2 player: PlayerControllerV2;
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:assets/media_player/skip_next.svg";
scale: -1
}
MouseArea {
anchors.fill: parent
onPressed: event => {
player.activePlayer.previous();
}
}
}
Item {
implicitHeight: 30;
implicitWidth: 30;
IconImage {
implicitSize: 30;
source: !player.activePlayer.isPlaying ? "root:/assets/media_player/playbutton.svg" : "root:/assets/media_player/pausebutton.svg";
}
MouseArea {
anchors.fill: parent
hoverEnabled: false
onPressed: event => {
player.activePlayer.togglePlaying();
}
}
}
Item {
implicitHeight: 30;
implicitWidth: 30;
IconImage {
implicitSize: 30;
source: "root:assets/media_player/skip_next.svg";
}
MouseArea {
anchors.fill: parent
onPressed: event => {
player.activePlayer.next();
}
}
}
}
PlayerPopup {
id: audioPopup
}
}

View File

@@ -0,0 +1,151 @@
import Quickshell
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import QtQuick.Effects
import "../common" as Common
// import playbutton.svg
Item {
property PlayerControllerV2 player: PlayerControllerV2;
id: root
Layout.fillHeight: true
Layout.preferredWidth: mainLayout.width + 10
Loader {
id: audioPopupLoader
active: true
sourceComponent: PlayerPopupV2 {
id: audioPopup
}
}
RowLayout {
id: mainLayout
Layout.fillHeight: true
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
}
Common.Meow {
id: commons
}
Item {
implicitWidth: text.width
implicitHeight: text.height
Rectangle {
id: hoverColor
anchors.centerIn: parent
visible: false
radius: 7
width: parent.width + 5
height: parent.height + 2.5
color: "grey"
}
Text {
id: text
text: (root.player.activePlayer == undefined) ? "Nothing" : player.activePlayer.trackTitle
color: "white"
font.pointSize: 10
font.italic: (root.player.activePlayer == undefined) ? true : false
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: event => {
hoverColor.visible = true
}
onExited: event => {
hoverColor.visible = false
}
onClicked: event => {
audioPopupLoader.item.visible = !audioPopupLoader.item.visible;
}
}
}
Common.Dot {}
Item {
id: mediaPositionControls
implicitWidth: positionText.width
implicitHeight: positionText.height
Text {
id: positionText
text: root.player.activeTrackPositionFormated + "/" + player.activeTrackLengthFormated // returns float of seconds of length
color: "white"
font.pointSize: 10
}
RowLayout {
visible: !positionText.visible
// enable: !positionText.visible
anchors {
left: parent.left
right: parent.right
}
anchors.verticalCenter: parent.verticalCenter
Item {
width: 20
height: 20
IconImage {
anchors.fill: parent
implicitSize: 20
source: "root:/assets/media_player/skip_previous.svg"
}
}
Item {
width: 20
height: 20
MouseArea {
anchors.fill: parent
width: 20
height: 20
hoverEnabled: true
onClicked: event => {
root.player.activePlayer.togglePlaying()
}
}
IconImage {
anchors.fill: parent
implicitSize: 20
source: (root.player.activePlayer.isPlaying) ? "root:/assets/media_player/pausebutton.svg" : "root:/assets/media_player/playbutton.svg"
}
}
Item {
width: 20
height: 20
IconImage {
anchors.fill: parent
implicitSize: 20
source: "root:/assets/media_player/skip_next.svg"
}
}
}
MouseArea {
z: 1
id: mouseAreaMediaButtons
height: parent.height + 15
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true // allows events to transparently go through to an overlapping mouseArea
onEntered: event => {
positionText.visible = false;
}
onExited: event => {
positionText.visible = true;
}
}
}
}
Rectangle {
z: -1
color: "black"
anchors {
verticalCenter: parent.verticalCenter
}
height: 25
width: parent.width
radius: 7
}
}

View File

@@ -0,0 +1,7 @@
import Quickshell
import QtQuick
Item {
property string initialClass: "";
property string initialTitle: "";
}

View File

@@ -0,0 +1,29 @@
pragma Singleton
import Quickshell
import QtQuick
import Quickshell.Io
import Quickshell.Hyprland
Singleton {
id: root
// property string aaaa: "";
property list<HyprlandClient> meow;
property Hyprland hyprland: Hyprland;
Process {
id: dateProc
command: ["hyprctl", "clients"]
running: true
stdout: StdioCollector {
onStreamFinished: parseClients(this.text)
}
}
function parseClients(text) {
hyprland.refreshToplevels();
var meow = hyprland.activeToplevel.lastIpcObject;
console.log(meow.class);
}
}

View File

@@ -0,0 +1,91 @@
import Quickshell
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import Quickshell.Widgets
import Quickshell.Hyprland
import "root:/bar/widgets/common" as Common
RowLayout {
id: root
spacing: 10
Layout.fillHeight: true
Repeater {
model: Hyprland.workspaces
delegate: Item {
id: root2
required property HyprlandWorkspace modelData
implicitWidth: 35 + appIconContainer.implicitWidth
Layout.fillHeight: true
Item {
visible: root2.modelData.toplevels.values[0] != undefined
id: appIconContainer
implicitWidth: row.implicitWidth
implicitHeight: 25
x: 30
anchors.verticalCenter: root2.verticalCenter
Rectangle {
x: -10
radius: 7
color: "black"
implicitWidth: row.width + 20
implicitHeight: 25
}
RowLayout {
x: 5
anchors.verticalCenter: parent.verticalCenter
id: row
spacing: 15
Repeater {
model: root2.modelData.toplevels
delegate: Item {
required property HyprlandToplevel modelData
width: icon.implicitWidth
height: icon.implicitHeight
Text {
id: icon
color: "white"
font.family: "CaskaydiaCove Nerd Font Mono" // https://www.reddit.com/r/Polybar/comments/sh8krs/comment/hv3lykm/
font.pointSize: 13
// long-ass oneliner for "if there is no class icon, go to fallback"
text: (Common.Icons.appIcons[(modelData.lastIpcObject.class)] != undefined ) ? Common.Icons.appIcons[(modelData.lastIpcObject.class)] : Common.Icons.appIcons["fallback"]
}
}
}
}
}
Item {
width: 30
height: 30
MouseArea {
width: 30
height: 30
onPressed: event => {
modelData.activate();
}
}
Rectangle {
width: 30
height: 30
radius: 10
color: modelData.focused ? "grey" : "black"
}
Text {
anchors.centerIn: parent
text: modelData.id
color: "white"
}
}
}
}
Timer {
id: updateTimer
running: true
interval: 1000 // 10secs
repeat: true
onTriggered: {
Hyprland.refreshToplevels()
}
}
}

View File

@@ -0,0 +1,8 @@
//@ pragma UseQApplication
import Quickshell
import QtQuick
import "bar" as Bar
Scope {
Bar.Bar {}
}