diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-04-26 22:36:46 +1000 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-04-26 22:36:46 +1000 |
| commit | c2e78cc11c02db05a8d0a4680e715de8691b516a (patch) | |
| tree | 5571d22dc11d9f5cf2de4932c822d368eebc2fb1 | |
| parent | clean (diff) | |
| download | caelestia-shell-c2e78cc11c02db05a8d0a4680e715de8691b516a.tar.gz caelestia-shell-c2e78cc11c02db05a8d0a4680e715de8691b516a.tar.bz2 caelestia-shell-c2e78cc11c02db05a8d0a4680e715de8691b516a.zip | |
feat: basic quickshell bar
| -rw-r--r-- | config/Appearance.qml | 108 | ||||
| -rw-r--r-- | modules/bar/ActiveWindow.qml | 29 | ||||
| -rw-r--r-- | modules/bar/Bar.qml | 48 | ||||
| -rw-r--r-- | modules/bar/Clock.qml | 26 | ||||
| -rw-r--r-- | modules/bar/OsIcon.qml | 15 | ||||
| -rw-r--r-- | services/Hyprland.qml | 159 | ||||
| -rw-r--r-- | services/Time.qml | 20 | ||||
| -rw-r--r-- | shell.qml | 6 | ||||
| -rw-r--r-- | utils/Icons.qml | 181 | ||||
| -rw-r--r-- | widgets/Box.qml | 16 | ||||
| -rw-r--r-- | widgets/MaterialIcon.qml | 7 |
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 +} |