1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
import { App, Astal, Gtk, Gdk } from "astal/gtk4"
import { exec, GLib, Variable } from "astal"
type NiriWorkspace = {
id: number,
idx: number,
name: string | null,
output: string,
is_active: boolean,
is_focused: boolean,
active_window_id: number | null,
};
function getWorkspaces(): NiriWorkspace[] {
// NOTE: this works only in non-systemd environment on NixOS
// TODO: try to use Niri socket if it is documented
return JSON.parse(exec("niri msg -j workspaces"));
}
function getWorkspacesByOutput(output: string): NiriWorkspace[] {
return getWorkspaces().filter(workspace => workspace.output == output).sort((a, b) => a.idx - b.idx);
}
function focusWorkspace(idx: number) {
// NOTE: this works only in non-systemd environment on NixOS
// TODO: try to use Niri socket if it is documented
exec(`niri msg action focus-workspace ${idx}`);
}
type WorkspaceButtonArguments = {
idx: number,
isActive: boolean,
isFocused: boolean,
};
function WorkspaceButton(args: WorkspaceButtonArguments) {
const classes: string[] = [];
args.isActive && classes.push("active");
args.isFocused && classes.push("focused");
return <button cssClasses={classes} onClicked={() => focusWorkspace(args.idx)} />
}
type WorkspacesArguments = {
connector: string,
};
function Workspaces(args: WorkspacesArguments) {
// NOTE: it is pretty inefficient and not so much responsive
// TODO: it would be better to use Niri socket in the future
const workspaces: Variable<NiriWorkspace[]> = Variable(getWorkspacesByOutput(args.connector))
.poll(1000, () => getWorkspacesByOutput(args.connector));
// BUG: on workspace change there's no CSS transition
// I guess it is due to rerender here
return <box cssClasses={["Workspaces"]}>
{workspaces(v => v.map(workspace => <WorkspaceButton idx={workspace.idx} isActive={workspace.is_active} isFocused={workspace.is_focused} />))}
</box>
}
type TimeArguments = {
format: string,
};
function Time(args: TimeArguments) {
const time = Variable<GLib.DateTime>(GLib.DateTime.new_now_local()).poll(1000, () => GLib.DateTime.new_now_local())
return <box>
{time(v => v.format(args.format))}
</box>
}
export default function Bar(gdkmonitor: Gdk.Monitor) {
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor
return <window
visible
name={"Bar"}
cssClasses={["Bar"]}
gdkmonitor={gdkmonitor}
exclusivity={Astal.Exclusivity.EXCLUSIVE}
anchor={TOP | LEFT | RIGHT}
application={App}>
<centerbox cssClasses={["bar-container"]}>
<box halign={Gtk.Align.START}>
<Workspaces connector={gdkmonitor.get_connector()!} />
</box>
<box halign={Gtk.Align.CENTER}>
</box>
<box halign={Gtk.Align.END}>
<Time format="%I:%M:%S %p %Z" />
</box>
</centerbox>
</window>
}
|