Moved bar to quickshell

This commit is contained in:
agryphus 2025-02-09 21:34:23 -05:00
parent ab0a71784e
commit 5a3a18f9f7
30 changed files with 971 additions and 206 deletions

View file

@ -0,0 +1,18 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: root
property var popupContext: PopupContext {};
property var date: new Date()
Timer {
interval: 1000
repeat: true
running: true
onTriggered: root.date = new Date()
}
}

View file

@ -0,0 +1,6 @@
import QtQuick
// Tracks which popup of a set is active.
QtObject {
property var popup: null;
}

View file

@ -0,0 +1,57 @@
import Quickshell
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import "blocks" as Blocks
Scope {
Variants {
model: Quickshell.screens
PanelWindow {
property var modelData
screen: modelData
color: "#cc000000"
height: 27
anchors {
top: true
left: true
right: true
}
RowLayout {
spacing: 0
width: parent.width
height: parent.height
// Left side
RowLayout {
spacing: 0
Layout.alignment: Qt.AlignLeft
Blocks.Icon {}
Blocks.Workspaces {}
Blocks.ActiveWorkspace {}
}
// Right side
RowLayout {
spacing: 0
Layout.alignment: Qt.AlignRight
Blocks.SystemTray {}
Blocks.Test {}
Blocks.Notifications {}
Blocks.Memory {}
Blocks.Sound {}
Blocks.Battery {}
Blocks.Date {}
Blocks.Time {}
}
}
}
}
}

View file

@ -0,0 +1,89 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
Rectangle {
id: root
Layout.preferredWidth: wsText.implicitWidth + 10
Layout.preferredHeight: 27
property string text
property bool dim: false
property bool underline
property var onClicked: function() {}
property int leftPadding
property int rightPadding
property string fgColor: "white"
property string dimFgColor: "#999999"
property string hoveredBgColor: "#444444"
// Background color
color: {
if (mouseArea.containsMouse)
return hoveredBgColor;
return "transparent";
}
states: [
State {
when: mouseArea.containsMouse
PropertyChanges {
target: root
}
}
]
Behavior on color {
ColorAnimation {
duration: 200
}
}
BarText {
id: wsText
text: root.text
anchors {
left: parent.left
right: parent.right
leftMargin: root.leftPadding
rightMargin: root.rightPadding
verticalCenter: parent.verticalCenter
}
color: {
if (mouseArea.containsMouse || !root.dim)
return fgColor
return dimFgColor
}
Behavior on color {
ColorAnimation {
duration: 100
}
}
}
MouseArea {
id: mouseArea
anchors.fill: root
hoverEnabled: true
acceptedButtons: Qt.LeftButton
onClicked: root.onClicked()
}
// While line underneath workspace
Rectangle {
id: wsLine
width: parent.width
height: 3
color: {
if (parent.underline)
return fgColor;
return "transparent";
}
anchors.bottom: parent.bottom
}
}

View file

@ -0,0 +1,39 @@
import Quickshell
import Quickshell.Io
import QtQuick
Item {
property string text
property string color: "white"
property string mainFont: "FiraCode"
property string symbolFont: "Symbols Nerd Font Mono"
property int pointSize: 11
implicitWidth: thetext.implicitWidth
implicitHeight: thetext.implicitHeight
function wrapSymbols(text) {
const isSymbol = (codePoint) =>
(codePoint >= 0xE000 && codePoint <= 0xF8FF) // Private Use Area
|| (codePoint >= 0xF0000 && codePoint <= 0xFFFFF) // Supplementary Private Use Area-A
|| (codePoint >= 0x100000 && codePoint <= 0x10FFFF); // Supplementary Private Use Area-B
return text.replace(/./gu, (c) => isSymbol(c.codePointAt(0))
? `<span style='font-family: ${symbolFont}; letter-spacing: -5px; font-size: ${pointSize + 4}px'>${c}</span>`
: c);
}
Text {
id: thetext
text: wrapSymbols(parent.text)
color: parent.color
anchors.centerIn: parent
font {
family: parent.mainFont
pointSize: parent.pointSize
}
textFormat: Text.RichText
}
}

View file

@ -0,0 +1,11 @@
import QtQuick
Text {
required property int id
required property string body
required property string summary
property int margin
text: `- ${summary}: ${body}`
}

View file

@ -0,0 +1,101 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Notifications
PanelWindow {
// required property font custom_font
required property color text_color
property list<QtObject> notification_objects
width: 500
height: 600
color: "#171a18"
WlrLayershell.layer: WlrLayer.Overlay
Rectangle {
border.width: 5
border.color: "#8ec07c"
anchors.fill: parent
color: "transparent"
ColumnLayout {
id: content
anchors {
left: parent.left
leftMargin: 10
right: parent.right
rightMargin: 10
top: parent.top
topMargin: 10
}
RowLayout {
Layout.fillWidth: true
Text {
Layout.fillWidth: true
text: "Notifications:"
// font: custom_font
color: text_color
}
Text {
text: "clear"
// font: custom_font
color: text_color
TapHandler {
id: tapHandler
gesturePolicy: TapHandler.ReleaseWithinBounds
onTapped: {
server.trackedNotifications.values.forEach((notification) => {
notification.tracked = false
})
notification_objects.forEach((object) => {
object.destroy();
})
notification_objects = [];
}
}
HoverHandler {
id: mouse
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
cursorShape: Qt.PointingHandCursor
}
}
}
}
}
NotificationServer {
id: server
onNotification: (notification) => {
notification.tracked = true
console.log(JSON.stringify(notification));
var notification_component = Qt.createComponent("Notification.qml");
var notification_object = notification_component
.createObject(content,
{
id: notification.id,
body: notification.body,
summary: notification.summary,
// font: custom_font,
color: text_color,
margin: 10
}
)
if (notification_object == null) {
console.log("Error creating notification")
} else {
notification_objects.push(notification_object);
}
}
}
}

View file

@ -0,0 +1,89 @@
import QtQuick
import Quickshell
import "root:/" // for Globals
LazyLoader {
id: root
// The item to display the tooltip at. If set to null the tooltip will be hidden.
property Item relativeItem: null
// Tracks the item after relativeItem is unset.
property Item displayItem: null
property PopupContext popupContext: Globals.popupContext
property bool hoverable: false;
readonly property bool hovered: item?.hovered ?? false
// The content to show in the tooltip.
required default property Component contentDelegate
active: displayItem != null && popupContext.popup == this
onRelativeItemChanged: {
if (relativeItem == null) {
if (item != null) item.hideTimer.start();
} else {
if (item != null) item.hideTimer.stop();
displayItem = relativeItem;
popupContext.popup = this;
}
}
PopupWindow {
anchor {
window: root.displayItem.QsWindow.window
rect.y: anchor.window.height + 3
rect.x: anchor.window.contentItem.mapFromItem(root.displayItem, root.displayItem.width / 2, 0).x
edges: Edges.Top
gravity: Edges.Bottom
}
visible: true
property alias hovered: body.containsMouse;
property Timer hideTimer: Timer {
interval: 250
// unloads the popup by causing active to become false
onTriggered: root.popupContext.popup = null;
}
color: "transparent"
// don't accept mouse input if !hoverable
Region { id: emptyRegion }
mask: root.hoverable ? null : emptyRegion
width: body.implicitWidth
height: body.implicitHeight
MouseArea {
id: body
anchors.fill: parent
implicitWidth: content.implicitWidth + 10
implicitHeight: content.implicitHeight + 10
hoverEnabled: root.hoverable
Rectangle {
anchors.fill: parent
radius: 5
border.width: 1
color: palette.active.toolTipBase
border.color: palette.active.light
Loader {
id: content
anchors.centerIn: parent
sourceComponent: contentDelegate
active: true
}
}
}
}
}

View file

@ -0,0 +1,33 @@
import QtQuick
import Quickshell.Io
import Quickshell.Hyprland
import "../"
BarText {
text: {
var str = activeWindowTitle
return str.length > chopLength ? str.slice(0, chopLength) + '...' : str;
}
property int chopLength: 70
property string activeWindowTitle
Process {
id: titleProc
command: ["sh", "-c", "hyprctl activewindow | grep title: | sed 's/^[^:]*: //'"]
running: true
stdout: SplitParser {
onRead: data => activeWindowTitle = data
}
}
Component.onCompleted: {
Hyprland.rawEvent.connect(hyprEvent)
}
function hyprEvent(e) {
titleProc.running = true
}
}

View file

@ -0,0 +1,26 @@
import QtQuick
import Quickshell.Io
import "../"
BarBlock {
property string battery
text: battery
Process {
id: batteryProc
command: ["block_battery"]
running: true
stdout: SplitParser {
onRead: data => battery = data
}
}
Timer {
interval: 1000
running: true
repeat: true
onTriggered: batteryProc.running = true
}
}

View file

@ -0,0 +1,37 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import "../"
Item {
implicitHeight: text.implicitHeight
implicitWidth: text.implicitWidth
property string percentUsed
Process {
id: cpuProc
command: ["sh", "-c", "top -bn 1 | grep \"%Cpu(s)\" | awk '{usage=100-$8; printf \"%02d%%\\n\", usage}'"]
running: true
stdout: SplitParser {
onRead: data => percentUsed = data
}
}
Timer {
interval: 2000
running: true
repeat: true
onTriggered: cpuProc.running = true
}
BarText {
id: text
text: "<div style = 'font-family: Symbols Nerd Font Mono'>%1</div>%2"
.arg(" ") // The rest of the string
.arg(percentUsed) // Symbol needs its own font
}
}

View file

@ -0,0 +1,8 @@
import QtQuick
import "../"
BarBlock {
id: text
text: ` ${Datetime.date}`
}

View file

@ -0,0 +1,31 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
Singleton {
property string time;
property string date;
Process {
id: dateProc
command: ["date", "+%a %e %b|%R"]
running: true
stdout: SplitParser {
onRead: data => {
date = data.split("|")[0]
time = data.split("|")[1]
}
}
}
Timer {
interval: 1000
running: true
repeat: true
onTriggered: dateProc.running = true
}
}

View file

@ -0,0 +1,11 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import "../"
BarBlock {
Layout.preferredWidth: 30
rightPadding: 5
text: ""
}

View file

@ -0,0 +1,29 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import "../"
BarBlock {
id: text
text: ` ${Math.floor(percentFree)}%`
property real percentFree
Process {
id: memProc
command: ["sh", "-c", "free | grep Mem | awk '{print $3/$2 * 100.0}'"]
running: true
stdout: SplitParser {
onRead: data => percentFree = data
}
}
Timer {
interval: 2000
running: true
repeat: true
onTriggered: memProc.running = true
}
}

View file

@ -0,0 +1,34 @@
import QtQuick
import Quickshell.Services.Notifications
import "../"
BarBlock {
id: root
property bool showNotification: false
text: " " + notifServer.trackedNotifications.values.length
onClicked: function() {
showNotification = !showNotification
}
NotificationServer {
id: notifServer
onNotification: (notification) => {
notification.tracked = true
}
}
NotificationPanel {
text_color: root.color
visible: showNotification
anchors {
top: parent.top
}
margins {
top: 10
}
}
}

View file

@ -0,0 +1,15 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Pipewire
import "../"
BarBlock {
id: text
text: ` ${Math.floor(sink?.audio.volume * 100)}%`
property PwNode sink: Pipewire.defaultAudioSink
PwObjectTracker { objects: [ sink ] }
}

View file

@ -0,0 +1,74 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.SystemTray
import "../" as Bar
RowLayout {
spacing: 5
Repeater {
model: SystemTray.items
MouseArea {
id: delegate
required property SystemTrayItem modelData
property alias item: delegate.modelData
Layout.fillHeight: true
implicitWidth: icon.implicitWidth + 5
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
hoverEnabled: true
onClicked: event => {
if (event.button == Qt.LeftButton) {
item.activate();
} else if (event.button == Qt.MiddleButton) {
item.secondaryActivate();
} else if (event.button == Qt.RightButton) {
menuAnchor.open();
}
}
onWheel: event => {
event.accepted = true;
const points = event.angleDelta.y / 120
item.scroll(points, false);
}
IconImage {
id: icon
anchors.centerIn: parent
source: item.icon
implicitSize: 16
}
QsMenuAnchor {
id: menuAnchor
menu: item.menu
anchor.window: delegate.QsWindow.window
anchor.adjustment: PopupAdjustment.Flip
anchor.onAnchoring: {
const window = delegate.QsWindow.window;
const widgetRect = window.contentItem.mapFromItem(delegate, 0, delegate.height, delegate.width, delegate.height);
menuAnchor.anchor.rect = widgetRect;
}
}
Bar.Tooltip {
relativeItem: delegate.containsMouse ? delegate : null
Label {
text: delegate.item.tooltipTitle || delegate.item.id
}
}
}
}
}

View file

@ -0,0 +1,77 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.SystemTray
import "../"
Repeater {
// model: SystemTray.items
model: ScriptModel {
values: SystemTray.items.values
.filter((item) => item.id == "nm-tray")
}
BarBlock {
id: block
Layout.preferredWidth: 30
leftPadding: 4
text: " "
required property SystemTrayItem modelData
property alias item: block.modelData
MouseArea {
id: delegate
anchors.fill: block
property alias item: block.item
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
hoverEnabled: true
onClicked: event => {
if (event.button == Qt.LeftButton) {
item.activate();
} else if (event.button == Qt.MiddleButton) {
item.secondaryActivate();
} else if (event.button == Qt.RightButton) {
menuAnchor.open();
}
}
onWheel: event => {
event.accepted = true;
const points = event.angleDelta.y / 120
item.scroll(points, false);
}
QsMenuAnchor {
id: menuAnchor
menu: item.menu
anchor.window: delegate.QsWindow.window
anchor.adjustment: PopupAdjustment.Flip
anchor.onAnchoring: {
console.log("here2")
const window = delegate.QsWindow.window;
const widgetRect = window.contentItem.mapFromItem(delegate, 0, delegate.height, delegate.width, delegate.height);
menuAnchor.anchor.rect = widgetRect;
}
}
Tooltip {
relativeItem: delegate.containsMouse ? delegate : null
Label {
text: delegate.item.tooltipTitle || delegate.item.id
}
}
}
}
}

View file

@ -0,0 +1,8 @@
import QtQuick
import "../"
BarBlock {
id: text
text: ` ${Datetime.time}`
}

View file

@ -0,0 +1,63 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import "root:/"
import "../"
RowLayout {
spacing: 0
property HyprlandMonitor monitor: Hyprland.monitorFor(screen)
Repeater {
model: ScriptModel {
values: {
var seenEmpty = false
return [...Hyprland.workspaces.values]
.filter((ws) => {
if (ws.monitor !== monitor || ws.name.includes("special"))
return false
// There is a flickering that can happen when switching from one
// empty workspace to another where both empty workspaces are shown
// on the bar at the same time. This ensures that only the first
// empty workspace is shown.
const isNumeric = /^\d+$/.test(ws.name);
if (!isNumeric)
return true;
if (!seenEmpty) {
seenEmpty = true
return true
}
return false;
})
// Sort workspaces by id
.sort((a, b) => a.id - b.id)
}
}
BarBlock {
property HyprlandWorkspace ws: modelData
property bool isActive: Hyprland.focusedMonitor?.activeWorkspace?.id === ws.id
property bool isOpen: monitor.activeWorkspace?.id === ws.id
property bool hasClients: ws.name.length > 2
dim: true
underline: isActive || isOpen
onClicked: function() {
Hyprland.dispatch(`workspace ${ws.id}`);
}
leftPadding: hasClients ? 2 : 0
text: {
if (isActive) {
if (!hasClients)
return `<span style='color:${fgColor};'>${ws.name}</span>`
var split_i = ws.id > 9 ? 3 : 2
return `<span style='color:${fgColor};'>${ws.name.slice(0, split_i)}</span>${ws.name.slice(split_i)}`
}
return ws.name
}
}
}
}

View file

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