feat: basic quickshell bar

This commit is contained in:
2 * r + 2 * t 2025-04-26 22:36:46 +10:00
parent 3c579d0e27
commit c2e78cc11c
11 changed files with 615 additions and 0 deletions

108
config/Appearance.qml Normal file
View file

@ -0,0 +1,108 @@
pragma Singleton
import Quickshell
import Quickshell.Io
import QtQuick
import Qt.labs.platform
Singleton {
id: root
property bool borders: true
readonly property QtObject transparency: QtObject {
property real base: 0.58
property real layers: 0.3
}
readonly property QtObject rounding: QtObject {
readonly property int small: 12
readonly property int normal: 17
readonly property int large: 25
readonly property int full: 1000
}
readonly property QtObject spacing: QtObject {
readonly property int small: 7
readonly property int smaller: 10
readonly property int normal: 12
readonly property int larger: 15
readonly property int large: 20
}
readonly property QtObject padding: QtObject {
readonly property int small: 5
readonly property int smaller: 7
readonly property int normal: 10
readonly property int larger: 12
readonly property int large: 15
}
readonly property QtObject font: QtObject {
readonly property QtObject family: QtObject {
readonly property string sans: "IBM Plex Sans"
readonly property string mono: "JetBrains Mono NF"
readonly property string material: "Material Symbols Rounded"
}
readonly property QtObject size: QtObject {
readonly property int small: 11
readonly property int smaller: 12
readonly property int normal: 13
readonly property int larger: 15
readonly property int large: 18
}
}
readonly property QtObject colours: QtObject {
property color primary: "#85D2E7"
property color secondary: "#B2CBD3"
property color tertiary: "#BFC4EB"
property color text: "#DEE3E6"
property color subtext1: "#BFC8CB"
property color subtext0: "#899295"
property color overlay2: "#788083"
property color overlay1: "#666D70"
property color overlay0: "#555C5E"
property color surface2: "#434A4D"
property color surface1: "#32393B"
property color surface0: "#202628"
property color base: "#0F1416"
property color mantle: "#090C0D"
property color crust: "#050607"
property color success: "#93E5B6"
property color error: "#EA8DC1"
property color rosewater: "#9BD4A1"
property color flamingo: "#84D5C3"
property color pink: "#8CD0F1"
property color mauve: "#91CEF5"
property color red: "#80D4DC"
property color maroon: "#85D2E7"
property color peach: "#80D5D0"
property color yellow: "#93D5A9"
property color green: "#8DD5B3"
property color teal: "#81D3E0"
property color sky: "#83D2E4"
property color sapphire: "#8AD1EE"
property color blue: "#9CCBFA"
property color lavender: "#86D1EB"
}
function alpha(c: color, layer: bool): color {
return Qt.rgba(c.r, c.g, c.b, layer ? transparency.layers : transparency.base);
}
FileView {
path: `${StandardPaths.standardLocations(StandardPaths.GenericStateLocation)[0]}/caelestia/scheme/current.txt`
watchChanges: true
onFileChanged: this.reload()
onLoaded: {
const contents = this.text();
for (const line of contents.split("\n")) {
const [name, colour] = line.split(" ");
root.colours[name.trim()] = `#${colour.trim()}`;
}
}
}
}

View file

@ -0,0 +1,29 @@
import "root:/widgets"
import "root:/services"
import "root:/utils"
import "root:/config"
import QtQuick
import QtQuick.Layouts
Box {
id: root
property color colour: Appearance.colours.pink
MaterialIcon {
Layout.alignment: Qt.AlignCenter
text: Icons.getAppCategoryIcon(Hyprland.activeClient?.wmClass) ?? "desktop_windows"
color: root.colour
}
Text {
Layout.alignment: Qt.AlignCenter
text: Hyprland.activeClient?.title ?? "Desktop"
font.pointSize: Appearance.font.size.smaller
font.family: Appearance.font.family.mono
color: root.colour
rotation: root.vertical ? 90 : 0
Layout.maximumWidth: root.vertical ? this.implicitHeight : this.implicitWidth
}
}

48
modules/bar/Bar.qml Normal file
View file

@ -0,0 +1,48 @@
import "root:/widgets"
import "root:/config"
import Quickshell
import Quickshell.Wayland
Variants {
model: Quickshell.screens
WlrLayershell {
id: win
property var modelData
property bool vertical: false
screen: modelData
namespace: "caelestia-bar"
color: Appearance.alpha(Appearance.colours.base, false)
anchors {
top: true
left: true
right: !vertical
bottom: vertical
}
width: contents.implicitWidth + (vertical ? Appearance.padding.normal * 2 : 0)
height: contents.implicitHeight + (vertical ? 0 : Appearance.padding.smaller * 2)
Box {
id: contents
vertical: win.vertical
spacing: Appearance.spacing.larger
x: Appearance.padding.normal
y: vertical ? Appearance.padding.normal : Appearance.padding.smaller
OsIcon {}
Clock {
vertical: win.vertical
}
ActiveWindow {
vertical: win.vertical
}
}
}
}

26
modules/bar/Clock.qml Normal file
View file

@ -0,0 +1,26 @@
import "root:/widgets"
import "root:/services"
import "root:/config"
import QtQuick
import QtQuick.Layouts
Box {
id: root
property color colour: Appearance.colours.peach
MaterialIcon {
Layout.alignment: Qt.AlignCenter
text: "calendar_month"
color: root.colour
}
Text {
Layout.alignment: Qt.AlignCenter
horizontalAlignment: Text.AlignJustify
text: root.vertical ? Time.format("hh\nmm") : Time.format("dd/MM/yy hh:mm")
font.pointSize: Appearance.font.size.smaller
font.family: Appearance.font.family.mono
color: root.colour
}
}

15
modules/bar/OsIcon.qml Normal file
View file

@ -0,0 +1,15 @@
import "root:/widgets"
import "root:/services"
import "root:/utils"
import "root:/config"
import QtQuick
import QtQuick.Layouts
Text {
Layout.alignment: Qt.AlignCenter
text: Icons.osIcon
font.pointSize: Appearance.font.size.smaller
font.family: Appearance.font.family.mono
color: Appearance.colours.yellow
}

159
services/Hyprland.qml Normal file
View file

@ -0,0 +1,159 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import QtQuick
Singleton {
id: root
property list<var> clients: []
property list<var> monitors: []
property list<var> workspaces: []
property var activeClient: null
property var activeWorkspace: null
property var focusedMonitor: null
function reload() {
getClients.running = true;
getMonitors.running = true;
getWorkspaces.running = true;
getActiveClient.running = true;
getActiveWorkspace.running = true;
}
Component.onCompleted: reload()
Connections {
target: Hyprland
function onRawEvent(event: string): void {
if (!event.endsWith("v2"))
root.reload();
}
}
component HyprData: Process {
id: proc
required property string type
property string prop: type
property bool array: true
function transform(data) {
return data;
}
command: ["bash", "-c", `hyprctl -j ${type} | jq -c`]
stdout: SplitParser {
onRead: data => {
root[proc.prop] = proc.array ? JSON.parse(data).map(proc.transform) : proc.transform(JSON.parse(data));
if (proc.type === "monitors")
root.focusedMonitor = root.monitors.find(m => m.focused);
}
}
function transformClient(client) {
if (!client.address)
return null;
return {
address: client.address,
x: client.at[0],
y: client.at[1],
width: client.size[0],
height: client.size[1],
workspace: transformWorkspace(client.workspace),
floating: client.floating,
monitor: client.monitor,
wmClass: client.class,
title: client.title,
initialClass: client.initialClass,
initialTitle: client.initialTitle,
pid: client.pid,
xwayland: client.xwayland,
pinned: client.pinned,
fullscreen: client.fullscreen,
focusHistoryId: client.focusHistoryID,
inhibitingIdle: client.inhibitingIdle
};
}
function transformMonitor(monitor) {
return {
id: monitor.id,
name: monitor.name,
description: monitor.description,
make: monitor.make,
model: monitor.model,
serial: monitor.serial,
x: monitor.x,
y: monitor.y,
width: monitor.width,
height: monitor.height,
refreshRate: monitor.refreshRate,
activeWorkspace: transformWorkspace(monitor.activeWorkspace),
specialWorkspace: transformWorkspace(monitor.specialWorkspace),
reserved: monitor.reserved,
scale: monitor.scale,
transform: monitor.transform,
focused: monitor.focused,
dpms: monitor.dpms,
vrr: monitor.vrr,
disabled: monitor.disabled
};
}
function transformWorkspace(workspace) {
return {
id: workspace.id,
name: workspace.name,
special: workspace.name.startsWith("special:")
};
}
}
HyprData {
id: getClients
type: "clients"
function transform(client) {
return transformClient(client);
}
}
HyprData {
id: getMonitors
type: "monitors"
function transform(monitor) {
return transformMonitor(monitor);
}
}
HyprData {
id: getWorkspaces
type: "workspaces"
function transform(workspace) {
return transformWorkspace(workspace);
}
}
HyprData {
id: getActiveClient
type: "activewindow"
prop: "activeClient"
array: false
function transform(client) {
return transformClient(client);
}
}
HyprData {
id: getActiveWorkspace
type: "activeworkspace"
prop: "activeWorkspace"
array: false
function transform(workspace) {
return transformWorkspace(workspace);
}
}
}

20
services/Time.qml Normal file
View file

@ -0,0 +1,20 @@
pragma Singleton
import Quickshell
Singleton {
property alias enabled: clock.enabled
readonly property date date: clock.date
readonly property int hours: clock.hours
readonly property int minutes: clock.minutes
readonly property int seconds: clock.seconds
function format(fmt: string): string {
return Qt.formatDateTime(clock.date, fmt);
}
SystemClock {
id: clock
precision: SystemClock.Seconds
}
}

6
shell.qml Normal file
View file

@ -0,0 +1,6 @@
import "modules/bar"
import Quickshell
ShellRoot {
Bar {}
}

181
utils/Icons.qml Normal file
View file

@ -0,0 +1,181 @@
pragma Singleton
import Quickshell
import Quickshell.Io
Singleton {
id: root
readonly property var osIcons: ({
almalinux: "",
alpine: "",
arch: "",
archcraft: "",
arcolinux: "",
artix: "",
centos: "",
debian: "",
devuan: "",
elementary: "",
endeavouros: "",
fedora: "",
freebsd: "",
garuda: "",
gentoo: "",
hyperbola: "",
kali: "",
linuxmint: "󰣭",
mageia: "",
openmandriva: "",
manjaro: "",
neon: "",
nixos: "",
opensuse: "",
suse: "",
sles: "",
sles_sap: "",
"opensuse-tumbleweed": "",
parrot: "",
pop: "",
raspbian: "",
rhel: "",
rocky: "",
slackware: "",
solus: "",
steamos: "",
tails: "",
trisquel: "",
ubuntu: "",
vanilla: "",
void: "",
zorin: ""
})
readonly property var weatherIcons: ({
warning: "󰼯",
sunny: "󰖙",
clear: "󰖔",
partly_cloudy: "󰖕",
partly_cloudy_night: "󰼱",
cloudy: "󰖐",
overcast: "󰖕",
mist: "󰖑",
patchy_rain_nearby: "󰼳",
patchy_rain_possible: "󰼳",
patchy_snow_possible: "󰼴",
patchy_sleet_possible: "󰙿",
patchy_freezing_drizzle_possible: "󰙿",
thundery_outbreaks_possible: "󰙾",
blowing_snow: "󰼶",
blizzard: "󰼶",
fog: "󰖑",
freezing_fog: "󰖑",
patchy_light_drizzle: "󰼳",
light_drizzle: "󰼳",
freezing_drizzle: "󰙿",
heavy_freezing_drizzle: "󰙿",
patchy_light_rain: "󰼳",
light_rain: "󰼳",
moderate_rain_at_times: "󰖗",
moderate_rain: "󰼳",
heavy_rain_at_times: "󰖖",
heavy_rain: "󰖖",
light_freezing_rain: "󰙿",
moderate_or_heavy_freezing_rain: "󰙿",
light_sleet: "󰙿",
moderate_or_heavy_sleet: "󰙿",
patchy_light_snow: "󰼴",
light_snow: "󰼴",
patchy_moderate_snow: "󰼴",
moderate_snow: "󰼶",
patchy_heavy_snow: "󰼶",
heavy_snow: "󰼶",
ice_pellets: "󰖒",
light_rain_shower: "󰖖",
moderate_or_heavy_rain_shower: "󰖖",
torrential_rain_shower: "󰖖",
light_sleet_showers: "󰼵",
moderate_or_heavy_sleet_showers: "󰼵",
light_snow_showers: "󰼵",
moderate_or_heavy_snow_showers: "󰼵",
light_showers_of_ice_pellets: "󰖒",
moderate_or_heavy_showers_of_ice_pellets: "󰖒",
patchy_light_rain_with_thunder: "󰙾",
moderate_or_heavy_rain_with_thunder: "󰙾",
moderate_or_heavy_rain_in_area_with_thunder: "󰙾",
patchy_light_snow_with_thunder: "󰼶",
moderate_or_heavy_snow_with_thunder: "󰼶"
})
readonly property var desktopEntrySubs: ({
Firefox: "firefox"
})
readonly property var categoryIcons: ({
WebBrowser: "web",
Printing: "print",
Security: "security",
Network: "chat",
Archiving: "archive",
Compression: "archive",
Development: "code",
IDE: "code",
TextEditor: "edit_note",
Audio: "music_note",
Music: "music_note",
Player: "music_note",
Recorder: "mic",
Game: "sports_esports",
FileTools: "files",
FileManager: "files",
Filesystem: "files",
FileTransfer: "files",
Settings: "settings",
DesktopSettings: "settings",
HardwareSettings: "settings",
TerminalEmulator: "terminal",
ConsoleOnly: "terminal",
Utility: "build",
Monitor: "monitor_heart",
Midi: "graphic_eq",
Mixer: "graphic_eq",
AudioVideoEditing: "video_settings",
AudioVideo: "music_video",
Video: "videocam",
Building: "construction",
Graphics: "photo_library",
"2DGraphics": "photo_library",
RasterGraphics: "photo_library",
TV: "tv",
System: "host"
})
property string osIcon: ""
function getAppCategoryIcon(name) {
if (!name)
return null;
const categories = DesktopEntries.applications.values.find(app => app.id === name)?.categories;
if (categories)
for (const [key, value] of Object.entries(this.categoryIcons))
if (categories.includes(key))
return value;
return "terminal";
}
FileView {
path: "/etc/os-release"
onLoaded: {
const osId = this.text().split("\n").find(l => l.startsWith("ID="))?.split("=")[1];
if (root.osIcons.hasOwnProperty(osId))
return root.osIcon = root.osIcons[osId];
const osIdLike = this.text().split("\n").find(l => l.startsWith("ID_LIKE="))?.split("=")[1];
if (osIdLike)
for (const id of osIdLike.split(" "))
if (root.osIcons.hasOwnProperty(id))
return root.osIcon = root.osIcons[id];
}
}
}

16
widgets/Box.qml Normal file
View file

@ -0,0 +1,16 @@
import "root:/config"
import QtQuick.Layouts
GridLayout {
property bool vertical: false
property real spacing: Appearance.spacing.small
property bool homogenous: false
flow: vertical ? GridLayout.TopToBottom : GridLayout.LeftToRight
uniformCellWidths: homogenous && vertical
uniformCellHeights: homogenous && !vertical
rows: vertical ? -1 : 1
columns: vertical ? 1 : -1
rowSpacing: spacing
columnSpacing: spacing
}

7
widgets/MaterialIcon.qml Normal file
View file

@ -0,0 +1,7 @@
import "root:/config"
import QtQuick
Text {
font.family: Appearance.font.family.material
font.pointSize: Appearance.font.size.larger
}