summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-03-26 16:54:08 +1100
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-03-26 16:54:08 +1100
commit8021e580a24fa4703238af27da3bbdee7ff4b144 (patch)
treea72d5c76e6948f9d3632ed0b95ff8af11a081b88
parentnotification: body tooltip for dashboard (diff)
downloadcaelestia-shell-8021e580a24fa4703238af27da3bbdee7ff4b144.tar.gz
caelestia-shell-8021e580a24fa4703238af27da3bbdee7ff4b144.tar.bz2
caelestia-shell-8021e580a24fa4703238af27da3bbdee7ff4b144.zip
sidebar: connectivity pane
Bluetooth module
-rw-r--r--scss/sidebar.scss63
-rw-r--r--src/modules/sidebar/connectivity.tsx7
-rw-r--r--src/modules/sidebar/index.tsx6
-rw-r--r--src/modules/sidebar/modules/bluetooth.tsx124
4 files changed, 198 insertions, 2 deletions
diff --git a/scss/sidebar.scss b/scss/sidebar.scss
index 8f4d6d7..8611ed9 100644
--- a/scss/sidebar.scss
+++ b/scss/sidebar.scss
@@ -283,4 +283,67 @@
}
}
}
+
+
+ .bluetooth {
+ .list {
+ @include lib.spacing(10, true);
+ }
+
+ .device {
+ @include lib.rounded(20);
+
+ background-color: color.change(scheme.$surface1, $alpha: 0.4);
+ padding: lib.s(10) lib.s(15);
+
+ @include lib.spacing(5);
+
+ &.connected {
+ background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 50%), $alpha: 0.4);
+
+ & > button {
+ background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 50%), $alpha: 0.5);
+
+ &:hover,
+ &:focus {
+ background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 30%), $alpha: 0.5);
+ }
+
+ &:active {
+ background-color: color.change(color.mix(scheme.$surface1, scheme.$primary, 20%), $alpha: 0.5);
+ }
+ }
+ }
+
+ .icon {
+ font-size: lib.s(28);
+ margin-right: lib.s(12);
+ }
+
+ .sublabel {
+ font-size: lib.s(14);
+ color: scheme.$subtext0;
+ }
+
+ & > button {
+ @include lib.rounded(1000);
+ @include lib.element-decel;
+ @include font.icon;
+
+ font-size: lib.s(18);
+ min-width: lib.s(30);
+ min-height: lib.s(30);
+ background-color: color.change(scheme.$surface1, $alpha: 0.5);
+
+ &:hover,
+ &:focus {
+ background-color: color.change(scheme.$surface2, $alpha: 0.5);
+ }
+
+ &:active {
+ background-color: color.change(scheme.$overlay0, $alpha: 0.5);
+ }
+ }
+ }
+ }
}
diff --git a/src/modules/sidebar/connectivity.tsx b/src/modules/sidebar/connectivity.tsx
new file mode 100644
index 0000000..437e513
--- /dev/null
+++ b/src/modules/sidebar/connectivity.tsx
@@ -0,0 +1,7 @@
+import Bluetooth from "./modules/bluetooth";
+
+export default () => (
+ <box vertical className="pane connectivity" name="connectivity">
+ <Bluetooth />
+ </box>
+);
diff --git a/src/modules/sidebar/index.tsx b/src/modules/sidebar/index.tsx
index 49a118b..2a13f5c 100644
--- a/src/modules/sidebar/index.tsx
+++ b/src/modules/sidebar/index.tsx
@@ -2,12 +2,13 @@ import type { Monitor } from "@/services/monitors";
import { bind, register, Variable } from "astal";
import { App, Astal, Gdk, Gtk, Widget } from "astal/gtk3";
import { sidebar } from "config";
+import Connectivity from "./connectivity";
import Dashboard from "./dashboard";
import NotifPane from "./notifpane";
@register()
export default class SideBar extends Widget.Window {
- readonly shown: Variable<string> = Variable("dashboard");
+ readonly shown: Variable<string>;
constructor({ monitor }: { monitor: Monitor }) {
super({
@@ -20,7 +21,8 @@ export default class SideBar extends Widget.Window {
visible: sidebar.showOnStartup.get(),
});
- const panes = [<Dashboard />, <NotifPane />];
+ const panes = [<Dashboard />, <Connectivity />, <NotifPane />];
+ this.shown = Variable(panes[0].name);
this.add(
<eventbox
diff --git a/src/modules/sidebar/modules/bluetooth.tsx b/src/modules/sidebar/modules/bluetooth.tsx
new file mode 100644
index 0000000..c74a708
--- /dev/null
+++ b/src/modules/sidebar/modules/bluetooth.tsx
@@ -0,0 +1,124 @@
+import { bind, Variable } from "astal";
+import { Astal, Gtk } from "astal/gtk3";
+import AstalBluetooth from "gi://AstalBluetooth";
+
+const sortDevices = (a: AstalBluetooth.Device, b: AstalBluetooth.Device) => {
+ if (a.connected || b.connected) return a.connected ? -1 : 1;
+ if (a.paired || b.paired) return a.paired ? -1 : 1;
+ return 0;
+};
+
+const BluetoothDevice = (device: AstalBluetooth.Device) => (
+ <box className={bind(device, "connected").as(c => `device ${c ? "connected" : ""}`)}>
+ <icon
+ className="icon"
+ icon={bind(device, "icon").as(i =>
+ Astal.Icon.lookup_icon(`${i}-symbolic`) ? `${i}-symbolic` : "bluetooth-symbolic"
+ )}
+ />
+ <box vertical hexpand>
+ <label truncate xalign={0} label={bind(device, "alias")} />
+ <label
+ truncate
+ className="sublabel"
+ xalign={0}
+ setup={self => {
+ const update = () => {
+ self.label = device.connected ? "Connected" : "Paired";
+ self.visible = device.connected || device.paired;
+ };
+ self.hook(device, "notify::connected", update);
+ self.hook(device, "notify::paired", update);
+ update();
+ }}
+ />
+ </box>
+ <button
+ valign={Gtk.Align.CENTER}
+ visible={bind(device, "paired")}
+ cursor="pointer"
+ onClicked={() => AstalBluetooth.get_default().adapter.remove_device(device)}
+ label="delete"
+ />
+ <button
+ valign={Gtk.Align.CENTER}
+ cursor="pointer"
+ onClicked={self => {
+ if (device.connected)
+ device.disconnect_device((_, res) => {
+ self.sensitive = true;
+ device.disconnect_device_finish(res);
+ });
+ else
+ device.connect_device((_, res) => {
+ self.sensitive = true;
+ device.connect_device_finish(res);
+ });
+ self.sensitive = false;
+ }}
+ label={bind(device, "connected").as(c => (c ? "bluetooth_disabled" : "bluetooth_searching"))}
+ />
+ </box>
+);
+
+const List = ({ devNotify }: { devNotify: Variable<boolean> }) => (
+ <box vertical valign={Gtk.Align.START} className="list">
+ {bind(devNotify).as(() => AstalBluetooth.get_default().devices.sort(sortDevices).map(BluetoothDevice))}
+ </box>
+);
+
+const NoDevices = () => (
+ <box homogeneous name="empty">
+ <box vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} className="empty">
+ <label className="icon" label="bluetooth_searching" />
+ <label label="No Bluetooth devices" />
+ </box>
+ </box>
+);
+
+export default () => {
+ const bluetooth = AstalBluetooth.get_default();
+ const devNotify = Variable(false); // Aggregator for device state changes (connected/paired)
+
+ const update = () => devNotify.set(!devNotify.get());
+ const connectSignals = (device: AstalBluetooth.Device) => {
+ device.connect("notify::connected", update);
+ device.connect("notify::paired", update);
+ };
+ bluetooth.get_devices().forEach(connectSignals);
+ bluetooth.connect("device-added", (_, device) => connectSignals(device));
+ bluetooth.connect("notify::devices", update);
+
+ return (
+ <box vertical className="bluetooth">
+ <box className="header-bar">
+ <label
+ label={bind(devNotify).as(() => {
+ const nConnected = bluetooth.get_devices().filter(d => d.connected).length;
+ return `${nConnected} connected device${nConnected === 1 ? "" : "s"}`;
+ })}
+ />
+ <box hexpand />
+ <button
+ className={bind(bluetooth.adapter, "discovering").as(d => (d ? "enabled" : ""))}
+ cursor="pointer"
+ onClicked={() => {
+ if (bluetooth.adapter.discovering) bluetooth.adapter.start_discovery();
+ else bluetooth.adapter.stop_discovery();
+ }}
+ label="󰀂 Discovery"
+ />
+ </box>
+ <stack
+ transitionType={Gtk.StackTransitionType.CROSSFADE}
+ transitionDuration={200}
+ shown={bind(bluetooth, "devices").as(d => (d.length > 0 ? "list" : "empty"))}
+ >
+ <NoDevices />
+ <scrollable expand hscroll={Gtk.PolicyType.NEVER} name="list">
+ <List devNotify={devNotify} />
+ </scrollable>
+ </stack>
+ </box>
+ );
+};