working mpris and bluetooth menu

This commit is contained in:
2026-06-26 17:34:25 +01:00
parent 2b49211315
commit 07ec781b68
12 changed files with 708 additions and 13 deletions

View File

@@ -89,6 +89,10 @@ Scope {
right: parent.right
rightMargin: 7
}
Right {
monitor: win.monitor
}
}
}
}

View File

@@ -3,6 +3,7 @@ import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.Mpris
import Quickshell.Services.Notifications
import "../Color.js" as Colors
import "../Components/"
@@ -49,13 +50,18 @@ Container {
PropertyChanges {
root.boxHeight: 35
root.boxRadius: 15
root.boxWidth: 110
root.boxWidth: MprisManager.getPlaying() == true ? 350 : 110
root.visibleTopMargin: 0
}
StateChangeScript {
script: {
root.previousState = "hovered";
if (MprisManager.getPlaying()) {
root.previousState = "mpris";
root.stack.replace(mpris);
} else {
root.previousState = "hovered";
}
}
}
},
@@ -121,6 +127,17 @@ Container {
root.state == "hovered" ? root.state = "expanded" : undefined;
}
Connections {
function onTrackChanged() {
if (Hyprland.focusedMonitor == root.monitor) {
musicToastTimer.start();
root.state = "hovered";
}
}
target: MprisManager.defaultPlayer
}
Timer {
id: notificationViewTimer
@@ -133,6 +150,18 @@ Container {
}
}
Timer {
id: musicToastTimer
interval: 2000
repeat: false
running: false
onTriggered: {
root.state = "";
}
}
Component {
id: time
@@ -143,6 +172,28 @@ Container {
}
}
Component {
id: mpris
Mpris {
TapHandler {
onTapped: root.state = "expanded"
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: MprisManager.skip()
}
TapHandler {
acceptedButtons: Qt.MiddleButton
onTapped: MprisManager.prev()
}
}
}
Component {
id: fullTime

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

@@ -19,6 +19,8 @@ Item {
required property double selectorWidth
Item {
id: newWorkspaceButton
implicitWidth: 20
opacity: root.extraHoveredWidth > 0 ? 1 : 0
@@ -45,7 +47,7 @@ Item {
Rectangle {
anchors.fill: parent
color: Colors.red
color: Colors.green
radius: 4
StyledText {

View File

@@ -79,8 +79,7 @@ Item {
fill: parent
}
StyledText {
anchors.centerIn: 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
}
}

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

@@ -0,0 +1,72 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Bluetooth
import "../../Color.js" as Colors
import "../../Components/"
import "../../Services/"
import "Bluetooth"
Container {
id: root
animOffset: 200
boxColor: Colors.peach
boxHeight: 25
boxWidth: 30
defaultItem: icon
exclusiveToScreen: true
forceHidden: BluetoothManager.getConnected()
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);
}
}
}
]
hover.onHoveredChanged: hover.hovered == false ? state = "" : undefined
tap.onTapped: state = "expanded"
Component {
id: icon
Item {
CenteredText {
text: "󰂱"
}
}
}
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

@@ -7,16 +7,17 @@ import Quickshell.Hyprland
import "../Color.js" as Colors
Item {
id: containerRoot
id: root
property double animOffset: 0
property color boxColor: Colors.surface0
property double boxHeight: 28
property double boxRadius: 9
property double boxWidth: 100
readonly property double calculatedTopMargin: {
if (containerRoot.exclusiveToScreen) {
if (Hyprland.focusedMonitor != containerRoot.exclusiveMonitor) {
return -4 - containerRoot.boxHeight - 5;
if (root.exclusiveToScreen) {
if (Hyprland.focusedMonitor != root.exclusiveMonitor || forceHidden == true) {
return -4 - root.boxHeight - 5;
} else {
return visibleTopMargin;
}
@@ -27,6 +28,7 @@ Item {
property Component defaultItem
required property HyprlandMonitor exclusiveMonitor
property bool exclusiveToScreen: false
property bool forceHidden: false
property alias hover: hoverHandler
property alias rect: container
property alias stack: containerContent
@@ -66,7 +68,7 @@ Item {
NumberAnimation {
id: defaultCurve
duration: 200
duration: 200 + root.animOffset
easing: Easing.InOutBack
}
@@ -93,8 +95,8 @@ Item {
anchors.fill: parent
clip: true
color: containerRoot.boxColor
radius: containerRoot.boxRadius
color: root.boxColor
radius: root.boxRadius
StackView {
id: containerContent
@@ -125,7 +127,7 @@ Item {
}
}
Component.onCompleted: containerContent.push(containerRoot.defaultItem)
Component.onCompleted: containerContent.push(root.defaultItem)
onCurrentItemChanged: {
if (currentItem) {
// Dynamically center the incoming child to the StackView

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 false;
}
}
return true;
}
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
}
}

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

@@ -2,3 +2,5 @@ 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