Compare commits

..

15 Commits

Author SHA1 Message Date
63e360d427 very broken keybinds lol 2026-06-30 15:55:32 +01:00
7a324e6119 fixed forceHide logic so that the bluetooth menu works 2026-06-30 12:03:18 +01:00
8abc848325 weird bluetooth hover thing 2026-06-29 18:57:03 +01:00
8c7ecc1b14 fixed some notification stuff lol 2026-06-27 18:09:24 +01:00
82d045d4df ignore quickshell file thing 2026-06-26 21:09:28 +01:00
1b60860354 Merge branch 'new-one' 2026-06-26 21:06:14 +01:00
33cfb4b539 fixed weird mpris and notification state issues 2026-06-26 21:05:35 +01:00
07ec781b68 working mpris and bluetooth menu 2026-06-26 17:34:25 +01:00
2b49211315 finished workspaces 2026-06-23 15:33:25 +01:00
26767ae588 wow numbers 2026-06-23 13:54:38 +01:00
2669f7e144 some items 2026-06-23 13:45:14 +01:00
175cea641b started on workspaces 2026-06-23 13:21:29 +01:00
10ff3c170e switched from flake to devenv (better trust) 2026-06-23 12:48:56 +01:00
be6647e6d6 removed console nonsense 2026-06-16 22:01:53 +01:00
603da98035 notifications working (totally) perfectly 2026-06-16 22:01:15 +01:00
30 changed files with 1934 additions and 194 deletions

12
.gitignore vendored
View File

@@ -1 +1,13 @@
.session .session
# Devenv
.devenv*
devenv.local.nix
devenv.local.yaml
# direnv
.direnv
# pre-commit
.pre-commit-config.yaml
/.qmlls.ini

View File

@@ -1 +0,0 @@
/run/user/1000/quickshell/vfs/feb6ddef68a55c1886b9c19683c71793/.qmlls.ini

48
Bar.qml
View File

@@ -1,8 +1,9 @@
import QtQuick import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
import Quickshell.Io
import Quickshell.Wayland
import "./Services/"
import "Bar" import "Bar"
import "Color.js" as Colors import "Color.js" as Colors
@@ -19,6 +20,15 @@ Scope {
required property var modelData required property var modelData
property HyprlandMonitor monitor: Hyprland.monitorFor(modelData) property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
function refocus() {
if (WlrLayershell == null)
return;
WlrLayershell.keyboardFocus = WlrKeyboardFocus.None;
Qt.callLater(() => {
WlrLayershell.keyboardFocus = WlrKeyboardFocus.Exclusive;
});
}
color: "transparent" color: "transparent"
exclusionMode: ExclusionMode.Normal exclusionMode: ExclusionMode.Normal
exclusiveZone: barExclusionZone exclusiveZone: barExclusionZone
@@ -39,6 +49,26 @@ Scope {
} }
} }
Component.onCompleted: {
if (this.WlrLayershell != null) {
this.WlrLayershell.keyboardFocus = WlrKeyboardFocus.OnDemand;
}
}
Connections {
function onRaiseCancelled() {
win.WlrLayershell.keyboardFocus = WlrKeyboardFocus.OnDemand;
}
function onRaiseRequested() {
// win.WlrLayershell.keyboardFocus = WlrKeyboardFocus.Exclusive;
win.refocus();
}
target: FocusManager
}
anchors { anchors {
left: true left: true
right: true right: true
@@ -57,7 +87,12 @@ Scope {
height: childrenRect.height height: childrenRect.height
anchors { anchors {
right: parent.right left: parent.left
leftMargin: 7
}
Left {
monitor: win.monitor
} }
} }
@@ -81,7 +116,12 @@ Scope {
height: childrenRect.height height: childrenRect.height
anchors { anchors {
left: parent.left right: parent.right
rightMargin: 7
}
Right {
monitor: win.monitor
} }
} }
} }

View File

@@ -1,39 +1,46 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
import Quickshell.Io
import Quickshell.Services.Mpris
import Quickshell.Services.Notifications
import "../Color.js" as Colors import "../Color.js" as Colors
import "../Components/" import "../Components/"
import "../Services/" import "../Services/"
import "./Center"
Container { Container {
id: centerPill id: root
property Notification latestNotification: NotificationManager.getLatestNotification() || null
required property HyprlandMonitor monitor required property HyprlandMonitor monitor
property NotificationServer notifServer: NotificationManager.notif
property string previousState property string previousState
boxColor: Colors.mauve boxColor: Colors.mauve
defaultItem: time defaultItem: time
exclusiveMonitor: centerPill.monitor exclusiveMonitor: root.monitor
exclusiveToScreen: true exclusiveToScreen: true
state: ""
states: [ states: [
State { State {
name: "" name: ""
PropertyChanges { PropertyChanges {
centerPill.boxHeight: 28 root.boxHeight: 28
centerPill.boxRadius: 9 root.boxRadius: 9
centerPill.boxWidth: 100 root.boxWidth: 100
root.visibleTopMargin: 0
} }
StateChangeScript { StateChangeScript {
script: { script: {
if (centerPill.previousState != "hovered") { if (root.previousState != "hovered") {
centerPill.stack.replace(time); root.stack.replace(time);
} }
centerPill.previousState = ""; root.previousState = "";
} }
} }
}, },
@@ -41,14 +48,32 @@ Container {
name: "hovered" name: "hovered"
PropertyChanges { PropertyChanges {
centerPill.boxHeight: 35 root.boxHeight: 35
centerPill.boxRadius: 15 root.boxRadius: 15
centerPill.boxWidth: 110 root.boxWidth: 110
root.visibleTopMargin: 0
} }
StateChangeScript { StateChangeScript {
script: { script: {
centerPill.previousState = "hovered"; root.previousState = "hovered";
}
}
},
State {
name: "mprisToast"
PropertyChanges {
root.boxHeight: 35
root.boxRadius: 15
root.boxWidth: 350
root.visibleTopMargin: 0
}
StateChangeScript {
script: {
root.previousState = "mpris";
root.stack.replace(mpris);
} }
} }
}, },
@@ -56,28 +81,157 @@ Container {
name: "expanded" name: "expanded"
PropertyChanges { PropertyChanges {
centerPill.boxHeight: 140 root.boxHeight: 140
centerPill.boxRadius: 30 root.boxRadius: 30
centerPill.boxWidth: 300 root.boxWidth: 300
root.visibleTopMargin: 0
} }
StateChangeScript { StateChangeScript {
script: { script: {
centerPill.stack.replace(fullTime); if (root.state != "notifications") {
centerPill.previousState = "expanded"; root.stack.replace(fullTime);
root.previousState = "expanded";
}
}
}
},
State {
extend: "expanded"
name: "notifications"
PropertyChanges {
root.boxHeight: 320
}
},
State {
name: "notified"
PropertyChanges {
root.boxHeight: 90
root.boxRadius: 30
root.boxWidth: 330
root.visibleTopMargin: 20
}
StateChangeScript {
script: {
root.stack.replace(notification);
root.previousState = "notified";
}
}
},
State {
name: "logout"
PropertyChanges {
root.boxHeight: 90
root.boxRadius: 30
root.boxWidth: 330
root.visibleTopMargin: 20
}
StateChangeScript {
script: {
root.stack.replace(logout);
root.previousState = "logout";
} }
} }
} }
] ]
mouse.onClicked: { hover.onHoveredChanged: {
centerPill.state == "expanded" ? centerPill.state = "" : centerPill.state = "expanded"; if (hover.hovered == true) {
if (root.state == "notified") {
root.state = "notified";
notificationViewTimer.stop();
} else if (root.state == "logout") {
root.state = "logout";
} else if (MprisManager.getPlaying() == true) {
root.state = "mprisToast";
} else {
root.state = "hovered";
}
} else {
root.state = "";
FocusManager.exitRaise();
}
} }
mouse.onEntered: { onLatestNotificationChanged: {
centerPill.state = "hovered"; if (latestNotification != null && Hyprland.focusedMonitor == monitor) {
if (root.latestNotification.lastGeneration == false) {
root.state = "notified";
notificationViewTimer.restart();
}
}
} }
mouse.onExited: { tap.onTapped: {
centerPill.state = ""; root.state == "hovered" ? root.state = "expanded" : undefined;
}
Connections {
function onactivated() {
console.log("some", root.state);
}
target: root.stack
}
Connections {
function onLogoutTriggered() {
if (Hyprland.focusedMonitor == root.exclusiveMonitor) {
root.state = "logout";
}
}
target: IpcManager
}
Connections {
function onRaiseCancelled() {
root.state = "";
}
target: FocusManager
}
Connections {
function onTrackChanged() {
if (Hyprland.focusedMonitor == root.monitor) {
musicToastTimer.restart();
if (root.state == "") {
root.state = "mprisToast";
}
}
}
target: MprisManager.defaultPlayer
}
Timer {
id: notificationViewTimer
interval: 3000
repeat: false
running: false
onTriggered: {
root.state = "";
}
}
Timer {
id: musicToastTimer
interval: 3000
repeat: false
running: false
onTriggered: {
if (!root.hover.hovered) {
root.state = "";
}
}
} }
Component { Component {
@@ -91,52 +245,72 @@ Container {
} }
Component { Component {
id: fullTime id: mpris
Item { Mpris {
Rectangle { TapHandler {
anchors.fill: parent onTapped: root.state = "expanded"
radius: 30
gradient: Gradient {
GradientStop {
color: "transparent"
position: 0.0
}
GradientStop {
color: "transparent"
position: 0.6
}
GradientStop {
color: Colors.red
position: 1.0
}
}
} }
Column { TapHandler {
anchors.centerIn: parent acceptedButtons: Qt.RightButton
spacing: 5
StyledText { onTapped: MprisManager.skip()
anchors.horizontalCenter: parent.horizontalCenter }
font.pointSize: 30
text: Time.time
}
StyledText { TapHandler {
anchors.horizontalCenter: parent.horizontalCenter acceptedButtons: Qt.MiddleButton
color: Colors.surface0
text: Time.date
font { onTapped: MprisManager.prev()
pointSize: 9 }
weight: 400 }
}
Component {
id: fullTime
Expanded {
NotificationList {
id: notifList
animEnabled: false
radius: root.boxRadius
rootState: root.state
server: root.notifServer
state: ""
TapHandler {
onTapped: {
notifList.animEnabled = true;
root.state = "notifications";
Qt.callLater(() => {
notifList.animEnabled = false;
});
} }
} }
} }
} }
} }
Component {
id: notification
NotificationDisplay {
body: root.latestNotification.body
summary: root.latestNotification.summary
TapHandler {
onTapped: {
root.state = "notifications";
root.stack.replace(fullTime);
}
}
}
}
Component {
id: logout
Logout {}
}
} }

55
Bar/Center/Expanded.qml Normal file
View File

@@ -0,0 +1,55 @@
import QtQuick
import Quickshell
import "../../Color.js" as Colors
import "../../Components/"
import "../../Services/"
Item {
id: fullTimeRoot
Rectangle {
anchors.fill: parent
radius: 30
gradient: MidpointGradient {
color: Colors.red
}
}
Item {
id: timeColumnContainer
anchors.fill: parent
Column {
id: timeColumn
spacing: 5
anchors {
centerIn: parent
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
color: Colors.surface0
text: Time.time
font {
pointSize: 30
}
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
color: Colors.surface1
text: Time.date
font {
pointSize: 9
weight: 400
}
}
}
}
}

18
Bar/Center/Logout.qml Normal file
View File

@@ -0,0 +1,18 @@
import QtQuick
import "../../Components/"
import "../../Services/"
Item {
CenteredText {
text: "some"
}
Shortcut {
sequence: "Escape"
onActivated: {
console.log("shortcut triggered");
FocusManager.exitRaise();
}
}
}

62
Bar/Center/Mpris.qml Normal file
View File

@@ -0,0 +1,62 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.Mpris
import "../../Color.js" as Colors
import "../../Components/"
import "../../Services/"
Item {
id: root
property MprisPlayer player: MprisManager.defaultPlayer
Rectangle {
color: Colors.red
radius: 5
anchors {
bottomMargin: 15
fill: parent
leftMargin: 10
rightMargin: parent.width - ((parent.width * MprisManager.pos) - 10)
topMargin: 15
}
}
RowLayout {
anchors {
bottomMargin: 2
fill: parent
leftMargin: 7
rightMargin: 7
topMargin: 2
}
Item {
Layout.fillHeight: true
Layout.preferredWidth: height
Image {
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: MprisManager.defaultPlayer.trackArtUrl
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
StyledText {
elide: Qt.ElideRight
horizontalAlignment: Qt.AlignHCenter
text: MprisManager.defaultPlayer.trackArtist + " - " + MprisManager.defaultPlayer.trackTitle
verticalAlignment: Qt.AlignVCenter
anchors {
fill: parent
}
}
}
}
}

View File

@@ -0,0 +1,49 @@
import QtQuick
import Quickshell
import Quickshell.Services.Notifications
import "../../Color.js" as Colors
import "../../Components/"
import "../../Services/"
Item {
id: root
required property string body
required property string summary
Rectangle {
anchors.fill: parent
radius: 30
gradient: MidpointGradient {
color: Colors.green
midpoint: 0.7
}
}
Column {
anchors.centerIn: parent
spacing: 5
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
font.pointSize: 11
text: root.summary
}
StyledText {
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
maximumLineCount: 2
text: root.body
verticalAlignment: Text.AlignVCenter
width: root.width - 20
wrapMode: Text.WordWrap
font {
pointSize: 10
weight: 500
}
}
}
}

View File

@@ -0,0 +1,307 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Services.Notifications
import "../../Color.js" as Colors
import "../../Components/"
import "../../Services/"
Item {
id: root
required property bool animEnabled
required property double radius
required property string rootState
required property NotificationServer server
Behavior on anchors.topMargin {
enabled: root.animEnabled
SpringAnimation {
damping: 0.35
spring: 4
}
}
states: [
State {
name: ""
when: root.rootState != "notifications"
PropertyChanges {
root.anchors.topMargin: root.parent.height - 21
}
},
State {
name: "expanded"
when: root.rootState == "notifications"
PropertyChanges {
root.anchors.topMargin: 5
}
}
]
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
top: parent.top
topMargin: parent.height - 21
}
Rectangle {
id: background
anchors.fill: parent
color: Colors.surface0
radius: root.radius - 3
states: [
State {
name: ""
when: root.rootState != "notifications"
PropertyChanges {}
},
State {
name: "expanded"
when: root.rootState == "notifications"
PropertyChanges {
background.anchors.bottomMargin: 5
background.anchors.leftMargin: 5
background.anchors.rightMargin: 5
}
}
]
anchors {
bottomMargin: 15
leftMargin: 20
rightMargin: 20
}
}
Item {
id: notifListContainer
anchors.fill: parent
clip: true
opacity: 0
anchors {
bottomMargin: 5
leftMargin: 5
rightMargin: 5
topMargin: 5
}
CenteredText {
color: Colors.overlay0
text: "No Notifications"
visible: root.server.trackedNotifications.values.length <= 0
}
ListView {
id: notifScrollingList
anchors.fill: parent
boundsBehavior: Flickable.StopAtBounds
height: Math.min(contentHeight, parent.height)
implicitHeight: background.height
model: root.server.trackedNotifications.values.length
spacing: 7
delegate: Item {
id: notifRoot
property Notification currentNotif: root.server.trackedNotifications.values[(notifRoot.notifLen
- 1) - index]
required property real index
property real notifLen: root.server.trackedNotifications.values.length
height: 50
width: ListView.view.width
states: [
State {
name: ""
when: notifHover.hovered == false
},
State {
name: "hovered"
when: notifHover.hovered == false
PropertyChanges {}
}
]
HoverHandler {
id: notifHover
}
Rectangle {
id: dismissButton
color: Colors.red
implicitWidth: 100
radius: 10
Behavior on anchors.rightMargin {
NumberAnimation {
duration: 100
}
}
states: [
State {
name: ""
when: notifHover.hovered == false
},
State {
name: "hovered"
when: notifHover.hovered == true
PropertyChanges {
dismissButton.anchors.rightMargin: 5
}
}
]
StyledText {
color: Colors.surface0
font.pointSize: 15
horizontalAlignment: Qt.AlignRight
text: "󰩹"
verticalAlignment: Qt.AlignVCenter
anchors {
fill: parent
rightMargin: 14
}
}
TapHandler {
onTapped: {
notifRoot.currentNotif.dismiss();
}
}
anchors {
bottom: parent.bottom
bottomMargin: 3
right: parent.right
rightMargin: 10
top: parent.top
topMargin: 3
}
}
Rectangle {
id: textContainer
anchors.fill: parent
clip: true
color: Colors.surface2
radius: 9
Behavior on anchors.rightMargin {
NumberAnimation {
duration: 100
}
}
states: [
State {
name: ""
when: notifHover.hovered == false
},
State {
name: "hovered"
when: notifHover.hovered == true
PropertyChanges {
textContainer.anchors.rightMargin: 40
}
}
]
Column {
id: textColumn
anchors {
centerIn: parent
left: parent.left
right: parent.right
}
StyledText {
color: Colors.text
horizontalAlignment: Text.AlignHCenter
text: notifRoot.currentNotif.summary
verticalAlignment: Text.AlignVCenter
width: notifRoot.width - 10
}
StyledText {
color: Colors.subtext1
horizontalAlignment: Text.AlignHCenter
text: notifRoot.currentNotif.body
verticalAlignment: Text.AlignVCenter
width: notifRoot.width - 10
}
}
}
}
anchors {
bottomMargin: 5
leftMargin: 10
rightMargin: 10
topMargin: 5
}
}
}
MultiEffect {
id: notifListClipper
anchors.fill: notifListContainer
maskEnabled: true
maskSource: clipsource
opacity: 0
source: notifListContainer
Behavior on opacity {
NumberAnimation {
duration: 100
}
}
states: [
State {
name: ""
when: root.rootState != "notifications"
PropertyChanges {
notifListClipper.opacity: 0
}
},
State {
name: "expanded"
when: root.rootState == "notifications"
PropertyChanges {
notifListClipper.opacity: 1
}
}
]
}
Rectangle {
id: clipsource
anchors.fill: background
color: "black"
layer.enabled: true
radius: background.radius
visible: false
}
}

70
Bar/Left.qml Normal file
View File

@@ -0,0 +1,70 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.Notifications
import "../Color.js" as Colors
import "../Components/"
import "../Services/"
import "./Center"
import "./Left/"
Container {
id: root
property double extraHoveredWidth: 0
required property HyprlandMonitor monitor
property double rowLeftMargin: 4
property double rowSpacing: 3
property double rowYmargin: 3
property double selectorFocusedExtra: 15
property double selectorHeight: 20
property double selectorWidth: 20
property double spacing: 3
property double workspaceNumber: WorkspaceManager.getNumberOfWorkspaces(monitor)
boxHeight: 25
boxWidth: (selectorWidth * workspaceNumber) + ((2 * rowLeftMargin) + (rowSpacing * (
workspaceNumber - 1))) + extraHoveredWidth + (hoverHandler.hovered == true
? selectorFocusedExtra : 0)
exclusiveMonitor: monitor
defaultItem: WorkspaceContainer {
extraHoveredWidth: root.extraHoveredWidth
monitor: root.monitor
rowLeftMargin: root.rowLeftMargin
rowSpacing: root.rowSpacing
rowYmargin: root.rowYmargin
selectorFocusedExtra: root.selectorFocusedExtra
selectorHeight: root.selectorHeight
selectorWidth: root.selectorWidth
}
Behavior on rowYmargin {
SpringAnimation {
damping: 0.3
spring: 4
}
}
states: [
State {
name: ""
when: !hoverHandler.hovered
},
State {
name: "hovered"
when: hoverHandler.hovered
PropertyChanges {
root.boxHeight: 40
root.extraHoveredWidth: 25
root.rowYmargin: 5
root.selectorWidth: 40
}
}
]
HoverHandler {
id: hoverHandler
}
}

View File

@@ -0,0 +1,86 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import "../../Color.js" as Colors
import "../../Components/"
import "../../Services/"
Item {
id: root
required property double extraHoveredWidth
required property HyprlandMonitor monitor
required property double rowLeftMargin
required property double rowSpacing
required property double rowYmargin
required property double selectorFocusedExtra
required property double selectorHeight
required property double selectorWidth
Item {
id: newWorkspaceButton
implicitWidth: 20
opacity: root.extraHoveredWidth > 0 ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: 100
}
}
TapHandler {
id: tapHandler
onTapped: WorkspaceManager.activateNextFreeWorkspace()
}
anchors {
bottom: parent.bottom
bottomMargin: 5 + root.rowYmargin
right: parent.right
rightMargin: 4
top: parent.top
topMargin: 5 + root.rowYmargin
}
Rectangle {
anchors.fill: parent
color: Colors.green
radius: 4
StyledText {
anchors.centerIn: parent
text: "+"
}
}
}
Row {
spacing: root.rowSpacing
anchors {
bottom: parent.bottom
bottomMargin: root.rowYmargin
left: parent.left
leftMargin: root.rowLeftMargin
top: parent.top
topMargin: root.rowYmargin
}
Repeater {
model: WorkspaceManager.getWorkspacesForMonitor(root.monitor)
WorkspaceSelector {
required property HyprlandWorkspace modelData
extraHoveredWidth: root.extraHoveredWidth
selectorFocusedExtra: root.selectorFocusedExtra
selectorHeight: root.selectorHeight
selectorWidth: root.selectorWidth
workspace: modelData
}
}
}
}

View File

@@ -0,0 +1,87 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Hyprland
import "../../Color.js" as Colors
import "../../Components/"
import "../../Services/"
Item {
id: root
required property double extraHoveredWidth
property bool hovered: extraHoveredWidth > 0 ? true : false
property color selectorColor: Colors.surface2
required property double selectorFocusedExtra
required property double selectorHeight
required property double selectorWidth
required property HyprlandWorkspace workspace
property string workspaceState: WorkspaceManager.getWorkspaceState(workspace)
implicitWidth: selectorWidth + (root.hovered == true ? (root.workspaceState == "focused"
? selectorFocusedExtra : 0) : 0)
Behavior on implicitWidth {
SpringAnimation {
damping: 0.3
spring: 4
}
}
states: [
State {
name: ""
when: root.workspaceState == "inactive"
PropertyChanges {
root.selectorColor: Colors.surface2
}
},
State {
name: "active"
when: root.workspaceState == "active"
PropertyChanges {
root.selectorColor: Colors.overlay2
}
},
State {
name: "focused"
when: root.workspaceState == "focused"
PropertyChanges {
root.selectorColor: Colors.lavender
}
}
]
TapHandler {
id: tapHandler
onTapped: root.workspace.activate()
}
anchors {
bottom: parent.bottom
top: parent.top
}
Rectangle {
color: root.selectorColor
radius: 6
Behavior on color {
ColorAnimation {
duration: 100
}
}
anchors {
fill: parent
}
CenteredText {
font.pointSize: 10.5
text: root.workspace.id
}
}
}

21
Bar/Right.qml Normal file
View File

@@ -0,0 +1,21 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.Notifications
import "../Color.js" as Colors
import "../Components/"
import "../Services/"
import "./Center"
import "./Right/"
Row {
id: root
required property HyprlandMonitor monitor
Bluetooth {
exclusiveMonitor: root.monitor
}
}

98
Bar/Right/Bluetooth.qml Normal file
View File

@@ -0,0 +1,98 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Bluetooth
import "../../Color.js" as Colors
import "../../Components/"
import "../../Services/"
import "Bluetooth"
Container {
id: root
property bool shown: false
animOffset: 200
boxColor: Colors.peach
boxHeight: 25
boxWidth: 30
defaultItem: icon
exclusiveToScreen: true
forceHidden: !(root.shown || BluetoothManager.getConnected())
hoverableWhenHidden: true
state: ""
states: [
State {
name: ""
PropertyChanges {
root.boxHeight: 25
root.boxWidth: 30
}
StateChangeScript {
script: {
root.stack.replace(icon);
}
}
},
State {
name: "expanded"
PropertyChanges {
root.boxHeight: 250
root.boxRadius: 20
root.boxWidth: 350
}
StateChangeScript {
script: {
root.stack.replace(expanded);
root.shown = true;
}
}
}
]
hover.onHoveredChanged: {
if (hover.hovered == false) {
root.state = "";
hoverIntervalTimer.start();
} else {
root.shown = true;
hoverIntervalTimer.start();
}
}
tap.onTapped: state = "expanded"
Timer {
id: hoverIntervalTimer
interval: 500
repeat: false
running: false
onTriggered: {
if (root.hover.hovered == false) {
root.shown = false;
}
}
}
Component {
id: icon
Item {
CenteredText {
text: BluetoothManager.getConnected() == true ? "󰂱" : "󰂲"
}
}
}
Component {
id: expanded
Expanded {}
}
}

View File

@@ -0,0 +1,384 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Bluetooth
import "../../../Color.js" as Colors
import "../../../Components/"
import "../../../Services/"
Item {
id: root
property double topBarHeight: 35
Rectangle {
color: Colors.surface0
radius: 17
anchors {
bottomMargin: 5
fill: parent
leftMargin: 5
rightMargin: 5
topMargin: 5
}
Rectangle {
id: topBar
property double topBarHeight: root.topBarHeight
color: Colors.peach
implicitHeight: topBarHeight
radius: 9
anchors {
left: parent.left
leftMargin: 5
right: parent.right
rightMargin: 5
top: parent.top
topMargin: 5
}
RowLayout {
id: topBarRow
property double topBarRowPadding: 4
spacing: 3
anchors {
left: parent.left
leftMargin: topBarRowPadding
right: parent.right
rightMargin: topBarRowPadding
top: parent.top
topMargin: topBarRowPadding
}
Rectangle {
Layout.fillWidth: true
// color: Qt.lighter(Colors.peach, 1.2)
color: Colors.surface1
implicitHeight: topBar.topBarHeight - (topBarRow.topBarRowPadding * 2)
radius: 6
StyledText {
anchors.verticalCenter: parent.verticalCenter
color: Colors.text
horizontalAlignment: Qt.AlignLeft
text: " " + BluetoothManager.defaultAdapterName
}
}
Rectangle {
id: scanButton
color: Qt.lighter(Colors.peach, 1.15)
implicitHeight: topBar.topBarHeight - (topBarRow.topBarRowPadding * 2)
implicitWidth: topBar.topBarHeight - (topBarRow.topBarRowPadding * 2)
radius: 6
Behavior on color {
ColorAnimation {
duration: 100
}
}
states: [
State {
name: "hovered"
PropertyChanges {
scanButton.color: Colors.green
}
}
]
border {
color: Colors.surface0
width: 2
}
HoverHandler {
onHoveredChanged: hovered == true ? scanButton.state = "hovered" : scanButton.state = ""
}
TapHandler {
onTapped: {
BluetoothManager.toggleDiscover();
}
}
CenteredText {
id: scanButtonText
text: ""
NumberAnimation {
id: animateRotation
duration: 1000
from: 0
loops: Animation.Infinite
properties: "rotation"
running: BluetoothManager.defaultAdapter.discovering
target: scanButtonText
to: 360
onStopped: {
scanButton.rotation = 0;
}
}
}
}
}
}
ListView {
boundsBehavior: Flickable.StopAtBounds
clip: true
model: BluetoothManager.getDevicesList()
spacing: 5
delegate: Item {
id: deviceRoot
required property real index
required property BluetoothDevice modelData
height: 55
width: ListView.view.width
Rectangle {
id: containerBox
anchors.fill: parent
clip: true
color: Colors.peach
radius: 7
Rectangle {
anchors.centerIn: parent
color: Colors.green
implicitHeight: parent.height - 10
implicitWidth: parent.width * (deviceRoot.modelData.batteryAvailable == true
? deviceRoot.modelData.battery : 0) - 10
opacity: 0.7
radius: 7
visible: deviceRoot.modelData.batteryAvailable == true
}
RowLayout {
anchors {
bottomMargin: 4
fill: parent
leftMargin: 4
rightMargin: 4
topMargin: 4
}
Rectangle {
Layout.fillHeight: true
Layout.preferredWidth: height
color: deviceRoot.modelData.connected ? Colors.green : Qt.lighter(Colors.peach, 1.25)
radius: 5
CenteredText {
color: Colors.surface0
font.pointSize: 30
text: BluetoothManager.getIcon(deviceRoot.modelData.icon)
}
}
ColumnLayout {
Layout.fillWidth: true
Rectangle {
Layout.fillHeight: true
Layout.fillWidth: true
color: Qt.lighter(Colors.peach, 1.15)
radius: 4
StyledText {
anchors.fill: parent
anchors.verticalCenter: parent.verticalCenter
elide: Qt.ElideRight
text: deviceRoot.modelData.name
}
}
Rectangle {
Layout.fillHeight: true
Layout.fillWidth: true
color: "transparent"
StyledText {
anchors.fill: parent
anchors.verticalCenter: parent.verticalCenter
color: Colors.surface2
font.weight: 300
text: deviceRoot.modelData.address
}
}
}
Item {
Layout.fillHeight: true
Layout.preferredWidth: height
RowLayout {
anchors.fill: parent
Rectangle {
id: itemEntry
Layout.fillHeight: true
Layout.fillWidth: true
color: Colors.red
radius: 3
Loader {
id: deleteButtonLoader
anchors.fill: parent
sourceComponent: forgetButton
states: [
State {
name: ""
when: deviceRoot.modelData.connected == false
PropertyChanges {
deleteButtonLoader.sourceComponent: forgetButton
}
},
State {
name: "connected"
when: deviceRoot.modelData.connected == true
PropertyChanges {
deleteButtonLoader.sourceComponent: disconnectButton
}
}
]
}
Component {
id: disconnectButton
Item {
anchors.fill: parent
TapHandler {
onTapped: deviceRoot.modelData.disconnect()
}
CenteredText {
text: ""
}
}
}
Component {
id: forgetButton
Item {
anchors.fill: parent
TapHandler {
onTapped: deviceRoot.modelData.forget()
}
CenteredText {
text: "󰩹"
}
}
}
}
Rectangle {
id: greenButton
property Component loadedItem: connectButton
Layout.fillHeight: true
Layout.fillWidth: true
color: Colors.green
radius: 3
states: [
State {
name: ""
when: deviceRoot.modelData.paired == true
PropertyChanges {
greenButton.color: Colors.green
greenButton.loadedItem: connectButton
}
},
State {
name: "unpaired"
when: deviceRoot.modelData.paired == false
PropertyChanges {
greenButton.color: Colors.blue
greenButton.loadedItem: pairButton
}
}
]
Loader {
anchors.fill: parent
sourceComponent: greenButton.loadedItem
}
Component {
id: connectButton
Item {
anchors.fill: parent
CenteredText {
text: "󱘖"
}
TapHandler {
onTapped: deviceRoot.modelData.connect()
}
}
}
Component {
id: pairButton
Item {
anchors.fill: parent
CenteredText {
text: ""
}
TapHandler {
onTapped: {
deviceRoot.modelData.pair();
}
}
}
}
}
}
}
}
}
}
anchors {
bottomMargin: 5
fill: parent
leftMargin: 5
rightMargin: 5
topMargin: 10 + root.topBarHeight
}
}
}
}

View File

@@ -1,37 +1,45 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
import "../Color.js" as Colors import "../Color.js" as Colors
Item { Item {
id: containerRoot id: root
property double animOffset: 0
property color boxColor: Colors.surface0 property color boxColor: Colors.surface0
property double boxHeight: 28 property double boxHeight: 28
property double boxRadius: 9 property double boxRadius: 9
property double boxWidth: 100 property double boxWidth: 100
readonly property double calculatedTopMargin: {
if (!root.exclusiveToScreen) {
return visibleTopMargin;
}
if (Hyprland.focusedMonitor == root.exclusiveMonitor) {
if (!root.forceHidden) {
return visibleTopMargin;
} else if (root.hoverableWhenHidden) {
return 7 - root.boxHeight - 5;
}
}
return -4 - root.boxHeight - 5;
}
property Component defaultItem property Component defaultItem
required property HyprlandMonitor exclusiveMonitor required property HyprlandMonitor exclusiveMonitor
property bool exclusiveToScreen: false property bool exclusiveToScreen: false
property alias mouse: mouseArea property bool forceHidden: false
property alias hover: hoverHandler
property bool hoverableWhenHidden: false
property alias rect: container property alias rect: container
property alias stack: containerContent property alias stack: containerContent
property alias tap: tapHandler
property double visibleTopMargin: 0
function getVisible() {
if (containerRoot.exclusiveToScreen) {
if (Hyprland.focusedMonitor != containerRoot.exclusiveMonitor) {
return -4 - containerRoot.boxHeight - 5;
} else {
return 0;
}
} else {
return 0;
}
}
clip: true
implicitHeight: boxHeight implicitHeight: boxHeight
implicitWidth: boxWidth implicitWidth: boxWidth
@@ -59,30 +67,41 @@ Item {
anchors { anchors {
top: parent.top top: parent.top
topMargin: getVisible() topMargin: calculatedTopMargin
} }
NumberAnimation { NumberAnimation {
id: defaultCurve id: defaultCurve
duration: 200 duration: 200 + root.animOffset
easing: Easing.InOutBack easing: Easing.InOutBack
} }
MouseArea { HoverHandler {
id: mouseArea id: hoverHandler
}
anchors.fill: parent TapHandler {
hoverEnabled: true id: tapHandler
z: 1 }
RectangularShadow {
anchors.fill: container
blur: 30
color: Colors.mantle
offset.x: 7
offset.y: 3
radius: container.radius
spread: 10
} }
Rectangle { Rectangle {
id: container id: container
anchors.fill: parent anchors.fill: parent
color: containerRoot.boxColor clip: true
radius: containerRoot.boxRadius color: root.boxColor
radius: root.boxRadius
StackView { StackView {
id: containerContent id: containerContent
@@ -113,7 +132,7 @@ Item {
} }
} }
Component.onCompleted: containerContent.push(containerRoot.defaultItem) Component.onCompleted: containerContent.push(root.defaultItem)
onCurrentItemChanged: { onCurrentItemChanged: {
if (currentItem) { if (currentItem) {
// Dynamically center the incoming child to the StackView // Dynamically center the incoming child to the StackView

View File

@@ -0,0 +1,23 @@
import QtQuick
Gradient {
id: root
required property color color
property double midpoint: 0.6
GradientStop {
color: "transparent"
position: 0.0
}
GradientStop {
color: "transparent"
position: root.midpoint
}
GradientStop {
color: root.color
position: 1.0
}
}

View File

@@ -3,6 +3,7 @@ import "../Color.js" as Colors
Text { Text {
color: Colors.base color: Colors.base
elide: Text.ElideRight
font { font {
family: "FiraMono Nerd Font" family: "FiraMono Nerd Font"

View File

@@ -0,0 +1,69 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Bluetooth
Singleton {
id: root
property BluetoothAdapter defaultAdapter: Bluetooth.defaultAdapter
property string defaultAdapterName: Bluetooth.defaultAdapter.adapterId
function getConnected() {
let deviceList = getDevicesList();
for (let device in deviceList) {
let currentDevice = deviceList[device];
if (currentDevice.connected == true) {
return true;
}
}
return false;
}
function getDevicesList() {
return root.defaultAdapter.devices.values;
}
function getIcon(name) {
const icons = {
"audio-card": "󰓃",
"audio-input-microphone": "",
"audio-headphones": "󰋋",
"audio-headset": "󰋋",
"battery": "󰂀",
"camera-photo": "󰻛",
"computer": "",
"input-keyboard": "󰌌",
"input-mouse": "󰍽",
"input-gaming": "󰊴",
"phone": "󰏲"
};
return icons[name] || "󰾰";
}
function pairTrustConnect(device) {
device.pair();
device.trusted = true;
device.connect();
}
function toggleDiscover() {
if (defaultAdapter.discovering == true) {
defaultAdapter.discovering = false;
} else {
defaultAdapter.discovering = true;
discoveringTimeout.running = true;
}
}
Timer {
id: discoveringTimeout
interval: 15000
repeat: false
running: false
onTriggered: defaultAdapter.discovering = false
}
}

19
Services/FocusManager.qml Normal file
View File

@@ -0,0 +1,19 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: root
signal raiseCancelled
signal raiseRequested
function exitRaise() {
console.log("exiting raise");
root.raiseCancelled();
}
function requestRaise() {
root.raiseRequested();
}
}

18
Services/IpcManager.qml Normal file
View File

@@ -0,0 +1,18 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
signal logoutTriggered()
IpcHandler {
function logout() {
root.logoutTriggered();
}
target: "main"
}
}

27
Services/MprisManager.qml Normal file
View File

@@ -0,0 +1,27 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Services.Mpris
Singleton {
id: root
property MprisPlayer defaultPlayer: Mpris.players.values[0]
property real pos: defaultPlayer.position / defaultPlayer.length
function getPlaying() {
if (!defaultPlayer) {
return false;
} else {
return defaultPlayer.playbackState == MprisPlaybackState.Playing;
}
}
function prev() {
defaultPlayer.previous();
}
function skip() {
defaultPlayer.next();
}
}

View File

@@ -0,0 +1,32 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Services.Notifications
Singleton {
id: root
property alias notif: server
function getLatestNotification() {
let notificationList = server.trackedNotifications.values;
let len = notificationList.length;
if (len <= 0) {
len = 1;
}
let latestNotification = notificationList[len - 1];
return latestNotification;
}
NotificationServer {
id: server
actionsSupported: true
bodyMarkupSupported: true
bodySupported: true
onNotification: notification => {
notification.tracked = true;
}
}
}

View File

@@ -0,0 +1,79 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Hyprland
Singleton {
id: root
function activateNextFreeWorkspace() {
let nextFree = getNextFreeWorkspace();
activateWorkspaceById(nextFree);
}
function activateWorkspaceById(id) {
Hyprland.dispatch(`hl.dsp.focus({workspace = ${id}})`);
}
function getAllNumberedWorkspaces() {
let allWorkspaces = Hyprland.workspaces.values;
let filteredWorkspaces = [];
for (let workspace in allWorkspaces) {
let currentWorkspace = allWorkspaces[workspace];
if (!currentWorkspace.name.includes("special")) {
if (currentWorkspace) {
filteredWorkspaces.push(currentWorkspace);
}
}
}
return filteredWorkspaces;
}
function getNextFreeWorkspace() {
let workspaceList = getAllNumberedWorkspaces();
if (workspaceList[workspaceList.length - 1].id == workspaceList.length) {
return workspaceList.length + 1;
} else {
let prevWorkspace = 0;
for (let workspace in workspaceList) {
let currentWorkspace = workspaceList[workspace];
if (currentWorkspace.id != (prevWorkspace + 1)) {
return currentWorkspace.id - 1;
} else {
prevWorkspace = currentWorkspace.id;
}
}
}
}
function getNumberOfWorkspaces(monitor) {
let workspaceList = getWorkspacesForMonitor(monitor);
return workspaceList.length;
}
function getWorkspaceState(workspace) {
if (workspace.focused == true) {
return "focused";
} else if (workspace.active == true) {
return "active";
} else {
return "inactive";
}
}
function getWorkspacesForMonitor(monitor) {
let allWorkspaces = getAllNumberedWorkspaces();
let monitorWorkspaces = [];
for (let workspace in allWorkspaces) {
let currentWorkspace = allWorkspaces[workspace];
if (currentWorkspace.monitor == monitor) {
if (currentWorkspace) {
monitorWorkspaces.push(currentWorkspace);
}
}
}
return monitorWorkspaces;
}
}

8
Services/qmldir Normal file
View File

@@ -0,0 +1,8 @@
module Services
singleton Time 1.0 Time.qml
singleton NotificationManager 1.0 NotificationManager.qml
singleton WorkspaceManager 1.0 WorkspaceManager.qml
singleton BluetoothManager 1.0 BluetoothManager.qml
singleton MprisManager 1.0 MprisManager.qml
singleton IpcManager 1.0 IpcManager.qml
singleton FocusManager 1.0 FocusManager.qml

65
devenv.lock Normal file
View File

@@ -0,0 +1,65 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1782153911,
"narHash": "sha256-Z7BG4cSJX1ybJeZztEMV8WlE6GrXJsSd34x9eHvpzU8=",
"owner": "cachix",
"repo": "devenv",
"rev": "d894e487bb23ed87e0555d2158710844bde65322",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"nixpkgs": {
"inputs": {
"nixpkgs-src": "nixpkgs-src"
},
"locked": {
"lastModified": 1782132010,
"narHash": "sha256-ZnAVHdVrotp80iIMm5CSR1fdxPlw7Uwmwxb+O/wsgZ8=",
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "12866ae2dddbc0ab8b329915f8072bb9c75bde89",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs-src": {
"flake": false,
"locked": {
"lastModified": 1781607440,
"narHash": "sha256-rxO+uc/KFbSJp+pgyXRuAX6QlG9hJdnt0BXpEQRXY+U=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3e41b24abd260e8f71dbe2f5737d24122f972158",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

17
devenv.nix Normal file
View File

@@ -0,0 +1,17 @@
{
pkgs,
...
}:
{
env.DEVSHELL_NAME = "󰏖 devenv/#fab387| quickshell/green";
packages = with pkgs; [
quickshell
kdePackages.qtdeclarative
];
processes = {
qs.exec = "quickshell -p .";
};
}

4
devenv.yaml Normal file
View File

@@ -0,0 +1,4 @@
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling

61
flake.lock generated
View File

@@ -1,61 +0,0 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1780243769,
"narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "331800de5053fcebacf6813adb5db9c9dca22a0c",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -1,42 +0,0 @@
{
description = "A simple Rust development environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
utils,
}:
utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { inherit system; };
in
{
devShells.default = pkgs.mkShell {
name = "flake";
buildInputs = with pkgs; [
quickshell
kdePackages.qtdeclarative
];
# Environment variables
shellHook = ''
export DEVSHELL_NAME="󱄅 flake/#89dceb| quickshell/green"
# Trigger zsh
if [[ -z "$ZSH_VERSION" ]]; then
exec zsh
fi
'';
};
}
);
}