summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/Appearance.qml108
-rw-r--r--modules/bar/ActiveWindow.qml29
-rw-r--r--modules/bar/Bar.qml48
-rw-r--r--modules/bar/Clock.qml26
-rw-r--r--modules/bar/OsIcon.qml15
-rw-r--r--services/Hyprland.qml159
-rw-r--r--services/Time.qml20
-rw-r--r--shell.qml6
-rw-r--r--utils/Icons.qml181
-rw-r--r--widgets/Box.qml16
-rw-r--r--widgets/MaterialIcon.qml7
11 files changed, 615 insertions, 0 deletions
diff --git a/config/Appearance.qml b/config/Appearance.qml
new file mode 100644
index 0000000..8c441b1
--- /dev/null
+++ b/config/Appearance.qml
@@ -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()}`;
+ }
+ }
+ }
+}
diff --git a/modules/bar/ActiveWindow.qml b/modules/bar/ActiveWindow.qml
new file mode 100644
index 0000000..f65a83f
--- /dev/null
+++ b/modules/bar/ActiveWindow.qml
@@ -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
+ }
+}
diff --git a/modules/bar/Bar.qml b/modules/bar/Bar.qml
new file mode 100644
index 0000000..5aa18f7
--- /dev/null
+++ b/modules/bar/Bar.qml
@@ -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
+ }
+ }
+ }
+}
diff --git a/modules/bar/Clock.qml b/modules/bar/Clock.qml
new file mode 100644
index 0000000..4c743a6
--- /dev/null
+++ b/modules/bar/Clock.qml
@@ -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
+ }
+}
diff --git a/modules/bar/OsIcon.qml b/modules/bar/OsIcon.qml
new file mode 100644
index 0000000..1737814
--- /dev/null
+++ b/modules/bar/OsIcon.qml
@@ -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
+}
diff --git a/services/Hyprland.qml b/services/Hyprland.qml
new file mode 100644
index 0000000..9ca1d0e
--- /dev/null
+++ b/services/Hyprland.qml
@@ -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);
+ }
+ }
+}
diff --git a/services/Time.qml b/services/Time.qml
new file mode 100644
index 0000000..c4b3913
--- /dev/null
+++ b/services/Time.qml
@@ -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
+ }
+}
diff --git a/shell.qml b/shell.qml
new file mode 100644
index 0000000..9f91e59
--- /dev/null
+++ b/shell.qml
@@ -0,0 +1,6 @@
+import "modules/bar"
+import Quickshell
+
+ShellRoot {
+ Bar {}
+}
diff --git a/utils/Icons.qml b/utils/Icons.qml
new file mode 100644
index 0000000..1f0a947
--- /dev/null
+++ b/utils/Icons.qml
@@ -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];
+ }
+ }
+}
diff --git a/widgets/Box.qml b/widgets/Box.qml
new file mode 100644
index 0000000..c0d7f06
--- /dev/null
+++ b/widgets/Box.qml
@@ -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
+}
diff --git a/widgets/MaterialIcon.qml b/widgets/MaterialIcon.qml
new file mode 100644
index 0000000..0115abf
--- /dev/null
+++ b/widgets/MaterialIcon.qml
@@ -0,0 +1,7 @@
+import "root:/config"
+import QtQuick
+
+Text {
+ font.family: Appearance.font.family.material
+ font.pointSize: Appearance.font.size.larger
+}