summaryrefslogtreecommitdiff
path: root/widget/bar/Bar.tsx
blob: b057cdb19db3aab565ecf8828632c1d508f79cc9 (plain) (blame)
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { App, Astal, Gtk, Gdk } from "astal/gtk4"
import { bind, exec, GLib, Variable } from "astal"
import AstalTray from "gi://AstalTray?version=0.1";

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>
}


function Tray() {
  // BUG: personally I have one fantom icon being along other tray icons
  // For now I don't have any ideas why this is happening
  // TODO: rewrite this using more elements, as this is really restricted design
  const tray = AstalTray.get_default();

  return <box cssClasses={["Tray"]}>
    {bind(tray, "items").as(items =>
      items.map(item =>
        <menubutton
          setup={self => self.insert_action_group("dbusmenu", item.actionGroup)}
          tooltipText={bind(item, "tooltipMarkup")}
        >
          <image gicon={bind(item, "gicon")} />
          {Gtk.PopoverMenu.new_from_model(item.menuModel)}
        </menubutton>
      )
    )}
  </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}>
        <Tray />
        <Time format="%I:%M:%S %p %Z" />
      </box>
    </centerbox>
  </window>
}