feat: add functionalities to bar
This is the base of the bar I want to work with. It includes workspaces for Hyprland, a time/calendar widget, and quick settings including: - Toggles for wifi, bluetooth and idle inhibition (using matcha as a dependency) - Sliders for audio and brightness
This commit is contained in:
parent
6f90989dcc
commit
5a918d507a
5 changed files with 165 additions and 101 deletions
21
flake.lock
generated
21
flake.lock
generated
|
@ -42,6 +42,26 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"matcha": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1738448307,
|
||||||
|
"narHash": "sha256-jRmfa28gVEDyS7We881BJDpr6L1kla0AgpEWjZ04tZo=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "90721110060e6b870839df847601d79f9a7a6461",
|
||||||
|
"revCount": 28,
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://codeberg.org/QuincePie/matcha"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://codeberg.org/QuincePie/matcha"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1739020877,
|
"lastModified": 1739020877,
|
||||||
|
@ -61,6 +81,7 @@
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"ags": "ags",
|
"ags": "ags",
|
||||||
|
"matcha": "matcha",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,24 @@
|
||||||
url = "github:aylur/ags";
|
url = "github:aylur/ags";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
matcha = {
|
||||||
|
url = "git+https://codeberg.org/QuincePie/matcha";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = {
|
outputs = {
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
ags,
|
ags,
|
||||||
|
matcha,
|
||||||
}: let
|
}: let
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
astalPkgs = with ags.packages.${system}; [
|
astalPkgs = with ags.packages.${system}; [
|
||||||
battery
|
battery
|
||||||
|
bluetooth
|
||||||
hyprland
|
hyprland
|
||||||
mpris
|
mpris
|
||||||
network
|
network
|
||||||
|
@ -47,6 +54,7 @@
|
||||||
extraPackages = astalPkgs;
|
extraPackages = astalPkgs;
|
||||||
})
|
})
|
||||||
pkgs.brightnessctl
|
pkgs.brightnessctl
|
||||||
|
matcha.packages.${pkgs.system}.default
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
35
style.scss
35
style.scss
|
@ -34,6 +34,13 @@ $accent: #{"@accent_color"};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Time {
|
||||||
|
calendar {
|
||||||
|
label:selected {
|
||||||
|
background-color: $accent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.Battery label {
|
.Battery label {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
@ -42,11 +49,29 @@ $accent: #{"@accent_color"};
|
||||||
image {
|
image {
|
||||||
-gtk-icon-size: 1.2em;
|
-gtk-icon-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
.Audio,
|
||||||
menubutton box {
|
.Wifi {
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
&:last-child {
|
}
|
||||||
margin-right: 0;
|
|
||||||
|
popover {
|
||||||
|
min-width: 350px;
|
||||||
|
|
||||||
|
.Toggles {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
button image {
|
||||||
|
-gtk-icon-size: 3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trough highlight {
|
||||||
|
background-color: $accent;
|
||||||
|
border-radius: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
slider {
|
||||||
|
min-height: 1.7em;
|
||||||
|
min-width: 1.7em;
|
||||||
|
margin: -0.2em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +84,6 @@ $accent: #{"@accent_color"};
|
||||||
}
|
}
|
||||||
|
|
||||||
popover {
|
popover {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.8em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
110
widget/Bar.tsx
110
widget/Bar.tsx
|
@ -1,14 +1,18 @@
|
||||||
import { Astal, Gtk, Gdk } from "astal/gtk4";
|
import { Astal, Gtk, Gdk } from "astal/gtk4";
|
||||||
import { Variable, GLib, bind } from "astal";
|
import { Variable, GLib, bind, execAsync, exec } from "astal";
|
||||||
import Battery from "gi://AstalBattery";
|
import Battery from "gi://AstalBattery";
|
||||||
|
import Bluetooth from "gi://AstalBluetooth";
|
||||||
import Wp from "gi://AstalWp";
|
import Wp from "gi://AstalWp";
|
||||||
import Network from "gi://AstalNetwork";
|
import Network from "gi://AstalNetwork";
|
||||||
import Hyprland from "gi://AstalHyprland";
|
import Hyprland from "gi://AstalHyprland";
|
||||||
|
import Brightness from "../service/brightness";
|
||||||
|
|
||||||
function Wifi() {
|
|
||||||
const network = Network.get_default();
|
const network = Network.get_default();
|
||||||
const wifi = bind(network, "wifi");
|
const wifi = bind(network, "wifi");
|
||||||
|
const hypr = Hyprland.get_default();
|
||||||
|
const bluetooth = Bluetooth.get_default();
|
||||||
|
|
||||||
|
function Wifi() {
|
||||||
return (
|
return (
|
||||||
<box visible={wifi.as(Boolean)}>
|
<box visible={wifi.as(Boolean)}>
|
||||||
{wifi.as(
|
{wifi.as(
|
||||||
|
@ -31,6 +35,42 @@ function Audio() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AudioSlider() {
|
||||||
|
const speaker = Wp.get_default()?.audio.defaultSpeaker!;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box cssClasses={["AudioSlider"]}>
|
||||||
|
<image iconName={bind(speaker, "volumeIcon")} />
|
||||||
|
<slider
|
||||||
|
hexpand
|
||||||
|
widthRequest={100}
|
||||||
|
onChangeValue={({ value }) => {
|
||||||
|
speaker.volume = value;
|
||||||
|
}}
|
||||||
|
value={bind(speaker, "volume")}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BrightnessSlider() {
|
||||||
|
const brightness = Brightness.get_default();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box cssClasses={["BrightnessSlider"]}>
|
||||||
|
<image iconName={"display-brightness-symbolic"} />
|
||||||
|
<slider
|
||||||
|
hexpand
|
||||||
|
widthRequest={100}
|
||||||
|
onChangeValue={({ value }) => {
|
||||||
|
brightness.screen = value;
|
||||||
|
}}
|
||||||
|
value={bind(brightness, "screen")}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function BatteryLevel() {
|
function BatteryLevel() {
|
||||||
const bat = Battery.get_default();
|
const bat = Battery.get_default();
|
||||||
|
|
||||||
|
@ -62,6 +102,29 @@ function Time({ format = "%H:%M - %A %e." }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function IdleInhibitor() {
|
||||||
|
const icon = Variable(getIcon());
|
||||||
|
|
||||||
|
function getIcon() {
|
||||||
|
const state = exec("matcha --status");
|
||||||
|
const enabled = state.match(/on/g);
|
||||||
|
return enabled ? "my-caffeine-on-symbolic" : "my-caffeine-off-symbolic";
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
exec("matcha --toggle");
|
||||||
|
icon.set(getIcon());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box cssName="IdleInhibitor">
|
||||||
|
<button
|
||||||
|
onClicked={() => toggle()}
|
||||||
|
iconName={bind(icon).as((iconName) => iconName)}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
function QuickSettings() {
|
function QuickSettings() {
|
||||||
return (
|
return (
|
||||||
<box cssClasses={["QuickSettings"]}>
|
<box cssClasses={["QuickSettings"]}>
|
||||||
|
@ -71,15 +134,50 @@ function QuickSettings() {
|
||||||
<Wifi />
|
<Wifi />
|
||||||
<BatteryLevel />
|
<BatteryLevel />
|
||||||
</box>
|
</box>
|
||||||
<popover hasArrow={false}></popover>
|
<popover hasArrow={false}>
|
||||||
|
<box vertical>
|
||||||
|
<box
|
||||||
|
cssClasses={["Toggles"]}
|
||||||
|
spacing={30}
|
||||||
|
halign={Gtk.Align.CENTER}
|
||||||
|
hexpand
|
||||||
|
>
|
||||||
|
<box visible={wifi.as(Boolean)} cssClasses={["WifiButton"]}>
|
||||||
|
{wifi.as(
|
||||||
|
(wifi) =>
|
||||||
|
wifi && (
|
||||||
|
<button
|
||||||
|
onClicked={() => (wifi.enabled = !wifi.enabled)}
|
||||||
|
iconName={bind(wifi, "iconName")}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
<box
|
||||||
|
visible={bluetooth.adapter != null}
|
||||||
|
halign={Gtk.Align.CENTER}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
onClicked={() => bluetooth.toggle()}
|
||||||
|
iconName={bind(bluetooth, "is_powered").as((p) =>
|
||||||
|
p ? "bluetooth-symbolic" : "bluetooth-disabled-symbolic",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
<IdleInhibitor />
|
||||||
|
</box>
|
||||||
|
<box vertical>
|
||||||
|
<AudioSlider />
|
||||||
|
<BrightnessSlider />
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
</popover>
|
||||||
</menubutton>
|
</menubutton>
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Workspaces() {
|
function Workspaces() {
|
||||||
const hypr = Hyprland.get_default();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box cssClasses={["Workspaces"]}>
|
<box cssClasses={["Workspaces"]}>
|
||||||
{bind(hypr, "workspaces").as((wss) =>
|
{bind(hypr, "workspaces").as((wss) =>
|
||||||
|
@ -118,7 +216,7 @@ export default function Bar(monitor: Gdk.Monitor) {
|
||||||
<box halign={Gtk.Align.CENTER}>
|
<box halign={Gtk.Align.CENTER}>
|
||||||
<Time />
|
<Time />
|
||||||
</box>
|
</box>
|
||||||
<box hexpand halign={Gtk.Align.END}>
|
<box halign={Gtk.Align.END}>
|
||||||
<QuickSettings />
|
<QuickSettings />
|
||||||
</box>
|
</box>
|
||||||
</centerbox>
|
</centerbox>
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
import { Astal, Gdk, Gtk, Widget } from "astal/gtk4";
|
|
||||||
|
|
||||||
const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor;
|
|
||||||
|
|
||||||
type PopoverProps = Pick<
|
|
||||||
Widget.WindowProps,
|
|
||||||
| "name"
|
|
||||||
| "namespace"
|
|
||||||
| "className"
|
|
||||||
| "visible"
|
|
||||||
| "child"
|
|
||||||
| "marginBottom"
|
|
||||||
| "marginTop"
|
|
||||||
| "marginLeft"
|
|
||||||
| "marginRight"
|
|
||||||
| "halign"
|
|
||||||
| "valign"
|
|
||||||
> & {
|
|
||||||
onClose?(self): void;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Full screen window widget where you can space the child widget
|
|
||||||
* using margins and alignment properties.
|
|
||||||
*
|
|
||||||
* NOTE: Child widgets will assume they can span across the full window width
|
|
||||||
* this means that setting `wrap` or `ellipsize` on labels for example will not work
|
|
||||||
* without explicitly setting its `max_width_chars` property.
|
|
||||||
* For a workaround see Popover2.tsx
|
|
||||||
*/
|
|
||||||
export default function Popover({
|
|
||||||
child,
|
|
||||||
marginBottom,
|
|
||||||
marginTop,
|
|
||||||
marginLeft,
|
|
||||||
marginRight,
|
|
||||||
halign = Gtk.Align.CENTER,
|
|
||||||
valign = Gtk.Align.CENTER,
|
|
||||||
onClose,
|
|
||||||
...props
|
|
||||||
}: PopoverProps) {
|
|
||||||
return (
|
|
||||||
<window
|
|
||||||
{...props}
|
|
||||||
css="background-color: transparent"
|
|
||||||
keymode={Astal.Keymode.EXCLUSIVE}
|
|
||||||
anchor={TOP | BOTTOM | LEFT | RIGHT}
|
|
||||||
exclusivity={Astal.Exclusivity.IGNORE}
|
|
||||||
onNotifyVisible={(self) => {
|
|
||||||
if (!self.visible) onClose?.(self);
|
|
||||||
}}
|
|
||||||
// close when click occurs otside of child
|
|
||||||
onButtonPressEvent={(self, event) => {
|
|
||||||
const [, _x, _y] = event.get_coords();
|
|
||||||
const { x, y, width, height } = self.get_child()!.get_allocation();
|
|
||||||
|
|
||||||
const xOut = _x < x || _x > x + width;
|
|
||||||
const yOut = _y < y || _y > y + height;
|
|
||||||
|
|
||||||
// clicked outside
|
|
||||||
if (xOut || yOut) {
|
|
||||||
self.visible = false;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
// close when hitting Escape
|
|
||||||
onKeyPressEvent={(self, event: Gdk.Event) => {
|
|
||||||
if (event.get_keyval()[1] === Gdk.KEY_Escape) {
|
|
||||||
self.visible = false;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<box
|
|
||||||
// make sure click event does not bubble up
|
|
||||||
onButtonPressEvent={() => true}
|
|
||||||
// child can be positioned with `halign` `valign` and margins
|
|
||||||
expand
|
|
||||||
halign={halign}
|
|
||||||
valign={valign}
|
|
||||||
marginBottom={marginBottom}
|
|
||||||
marginTop={marginTop}
|
|
||||||
marginStart={marginLeft}
|
|
||||||
marginEnd={marginRight}
|
|
||||||
>
|
|
||||||
{child}
|
|
||||||
</box>
|
|
||||||
</window>
|
|
||||||
);
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue