sidebar: connectivity pane
Bluetooth module
This commit is contained in:
parent
0f8e6cf1c3
commit
8021e580a2
4 changed files with 198 additions and 2 deletions
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
src/modules/sidebar/connectivity.tsx
Normal file
7
src/modules/sidebar/connectivity.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Bluetooth from "./modules/bluetooth";
|
||||||
|
|
||||||
|
export default () => (
|
||||||
|
<box vertical className="pane connectivity" name="connectivity">
|
||||||
|
<Bluetooth />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
|
@ -2,12 +2,13 @@ import type { Monitor } from "@/services/monitors";
|
||||||
import { bind, register, Variable } from "astal";
|
import { bind, register, Variable } from "astal";
|
||||||
import { App, Astal, Gdk, Gtk, Widget } from "astal/gtk3";
|
import { App, Astal, Gdk, Gtk, Widget } from "astal/gtk3";
|
||||||
import { sidebar } from "config";
|
import { sidebar } from "config";
|
||||||
|
import Connectivity from "./connectivity";
|
||||||
import Dashboard from "./dashboard";
|
import Dashboard from "./dashboard";
|
||||||
import NotifPane from "./notifpane";
|
import NotifPane from "./notifpane";
|
||||||
|
|
||||||
@register()
|
@register()
|
||||||
export default class SideBar extends Widget.Window {
|
export default class SideBar extends Widget.Window {
|
||||||
readonly shown: Variable<string> = Variable("dashboard");
|
readonly shown: Variable<string>;
|
||||||
|
|
||||||
constructor({ monitor }: { monitor: Monitor }) {
|
constructor({ monitor }: { monitor: Monitor }) {
|
||||||
super({
|
super({
|
||||||
|
|
@ -20,7 +21,8 @@ export default class SideBar extends Widget.Window {
|
||||||
visible: sidebar.showOnStartup.get(),
|
visible: sidebar.showOnStartup.get(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const panes = [<Dashboard />, <NotifPane />];
|
const panes = [<Dashboard />, <Connectivity />, <NotifPane />];
|
||||||
|
this.shown = Variable(panes[0].name);
|
||||||
|
|
||||||
this.add(
|
this.add(
|
||||||
<eventbox
|
<eventbox
|
||||||
|
|
|
||||||
124
src/modules/sidebar/modules/bluetooth.tsx
Normal file
124
src/modules/sidebar/modules/bluetooth.tsx
Normal file
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue