parent
5d27b3d975
commit
f3e06554e4
105 changed files with 245 additions and 254 deletions
nixosModules/ags/config/widgets/bar
77
nixosModules/ags/config/widgets/bar/_index.scss
Normal file
77
nixosModules/ags/config/widgets/bar/_index.scss
Normal file
|
@ -0,0 +1,77 @@
|
|||
@use 'sass:color';
|
||||
@use '../../style/colors';
|
||||
|
||||
.bar {
|
||||
margin-left: 5px;
|
||||
margin-right: 15px;
|
||||
margin-bottom: 13px;
|
||||
|
||||
.bar-item {
|
||||
padding: 5px 10px 5px 10px;
|
||||
border-radius: 7px;
|
||||
background-color: color.adjust(colors.$window_bg_color, $lightness: -3%);
|
||||
font-size: 20px;
|
||||
min-height: 35px;
|
||||
|
||||
transition: background-color 300ms;
|
||||
|
||||
&:hover {
|
||||
background-color: color.adjust(colors.$window_bg_color, $lightness: 3%);
|
||||
}
|
||||
|
||||
&.network icon {
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
&.battery icon {
|
||||
&.charging {
|
||||
color: green;
|
||||
}
|
||||
|
||||
&.low {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
.workspaces {
|
||||
.button {
|
||||
margin: 0 2.5px;
|
||||
min-height: 22px;
|
||||
min-width: 22px;
|
||||
border-radius: 100%;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.occupied {
|
||||
border: 2px solid colors.$window_bg_color;
|
||||
background: colors.$accent_color;
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.urgent {
|
||||
border: 2px solid colors.$window_bg_color;
|
||||
background: red;
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.active {
|
||||
border: 2px solid #50fa7b;
|
||||
transition: margin-left 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&.system-tray {
|
||||
.tray-item {
|
||||
all: unset;
|
||||
font-size: 30px;
|
||||
min-width: 36px;
|
||||
border-radius: 100%;
|
||||
transition: background-color 300ms;
|
||||
|
||||
&:hover {
|
||||
background: colors.$window_bg_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
nixosModules/ags/config/widgets/bar/binto.tsx
Normal file
61
nixosModules/ags/config/widgets/bar/binto.tsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { Astal, Gtk } from 'astal/gtk3';
|
||||
|
||||
import Audio from './items/audio';
|
||||
import Clock from './items/clock';
|
||||
import CurrentClient from './items/current-client';
|
||||
import Network from './items/network';
|
||||
import NotifButton from './items/notif-button';
|
||||
import SysTray from './items/tray';
|
||||
import Workspaces from './items/workspaces';
|
||||
|
||||
import BarRevealer from './fullscreen';
|
||||
import Separator from '../misc/separator';
|
||||
import { get_gdkmonitor_from_desc } from '../../lib';
|
||||
|
||||
|
||||
export default () => (
|
||||
<BarRevealer
|
||||
gdkmonitor={get_gdkmonitor_from_desc('desc:Acer Technologies Acer K212HQL T3EAA0014201')}
|
||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||
anchor={
|
||||
Astal.WindowAnchor.BOTTOM |
|
||||
Astal.WindowAnchor.LEFT |
|
||||
Astal.WindowAnchor.RIGHT
|
||||
}
|
||||
>
|
||||
<centerbox className="bar widget">
|
||||
<box hexpand halign={Gtk.Align.START}>
|
||||
<Workspaces />
|
||||
|
||||
<Separator size={8} />
|
||||
|
||||
<CurrentClient />
|
||||
|
||||
<Separator size={8} />
|
||||
</box>
|
||||
|
||||
<box>
|
||||
<Clock />
|
||||
</box>
|
||||
|
||||
<box hexpand halign={Gtk.Align.END}>
|
||||
<SysTray />
|
||||
|
||||
<Separator size={8} />
|
||||
|
||||
<Network />
|
||||
|
||||
<Separator size={8} />
|
||||
|
||||
<NotifButton />
|
||||
|
||||
<Separator size={8} />
|
||||
|
||||
<Audio />
|
||||
|
||||
<Separator size={2} />
|
||||
</box>
|
||||
</centerbox>
|
||||
|
||||
</BarRevealer>
|
||||
);
|
187
nixosModules/ags/config/widgets/bar/fullscreen.tsx
Normal file
187
nixosModules/ags/config/widgets/bar/fullscreen.tsx
Normal file
|
@ -0,0 +1,187 @@
|
|||
import { App, Astal, Gdk, Gtk, Widget } from 'astal/gtk3';
|
||||
import { bind, Variable } from 'astal';
|
||||
|
||||
import AstalHyprland from 'gi://AstalHyprland';
|
||||
const Hyprland = AstalHyprland.get_default();
|
||||
|
||||
import { get_hyprland_monitor_desc, get_monitor_desc, hyprMessage } from '../../lib';
|
||||
|
||||
|
||||
const FullscreenState = Variable({
|
||||
monitors: [] as string[],
|
||||
clientAddrs: new Map() as Map<string, string>,
|
||||
});
|
||||
|
||||
Hyprland.connect('event', async() => {
|
||||
const arrayEquals = (a1: unknown[], a2: unknown[]) =>
|
||||
a1.sort().toString() === a2.sort().toString();
|
||||
|
||||
const mapEquals = (m1: Map<string, string>, m2: Map<string, string>) =>
|
||||
m1.size === m2.size &&
|
||||
Array.from(m1.keys()).every((key) => m1.get(key) === m2.get(key));
|
||||
|
||||
try {
|
||||
const newMonitors = JSON.parse(await hyprMessage('j/monitors')) as AstalHyprland.Monitor[];
|
||||
|
||||
const fs = FullscreenState.get();
|
||||
const fsClients = Hyprland.get_clients().filter((c) => {
|
||||
const mon = newMonitors.find((monitor) => monitor.id === c.get_monitor()?.id);
|
||||
|
||||
return c.fullscreenClient !== 0 &&
|
||||
c.workspace.id === mon?.activeWorkspace.id;
|
||||
});
|
||||
|
||||
const monitors = fsClients.map((c) =>
|
||||
get_monitor_desc(c.monitor));
|
||||
|
||||
const clientAddrs = new Map(fsClients.map((c) => [
|
||||
get_monitor_desc(c.monitor),
|
||||
c.address ?? '',
|
||||
]));
|
||||
|
||||
const hasChanged =
|
||||
!arrayEquals(monitors, fs.monitors) ||
|
||||
!mapEquals(clientAddrs, fs.clientAddrs);
|
||||
|
||||
if (hasChanged) {
|
||||
FullscreenState.set({
|
||||
monitors,
|
||||
clientAddrs,
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
export default ({
|
||||
anchor,
|
||||
gdkmonitor = Gdk.Display.get_default()?.get_monitor(0) as Gdk.Monitor,
|
||||
child,
|
||||
...rest
|
||||
}: {
|
||||
anchor: Astal.WindowAnchor
|
||||
gdkmonitor?: Gdk.Monitor
|
||||
} & Widget.WindowProps) => {
|
||||
const monitor = get_hyprland_monitor_desc(gdkmonitor);
|
||||
const BarVisible = Variable(true);
|
||||
|
||||
FullscreenState.subscribe((v) => {
|
||||
BarVisible.set(!v.monitors.includes(monitor));
|
||||
});
|
||||
|
||||
const barCloser = (
|
||||
<window
|
||||
name={`bar-${monitor}-closer`}
|
||||
css="all: unset;"
|
||||
visible={false}
|
||||
gdkmonitor={gdkmonitor}
|
||||
layer={Astal.Layer.OVERLAY}
|
||||
anchor={
|
||||
Astal.WindowAnchor.TOP |
|
||||
Astal.WindowAnchor.BOTTOM |
|
||||
Astal.WindowAnchor.LEFT |
|
||||
Astal.WindowAnchor.RIGHT
|
||||
}
|
||||
>
|
||||
<eventbox
|
||||
on_hover={() => {
|
||||
barCloser.visible = false;
|
||||
BarVisible.set(false);
|
||||
}}
|
||||
>
|
||||
<box css="padding: 1px;" />
|
||||
</eventbox>
|
||||
</window>
|
||||
);
|
||||
|
||||
// Hide bar instantly when out of focus
|
||||
Hyprland.connect('notify::focused-workspace', () => {
|
||||
const addr = FullscreenState.get().clientAddrs.get(monitor);
|
||||
|
||||
if (addr) {
|
||||
const client = Hyprland.get_client(addr);
|
||||
|
||||
if (client?.workspace.id !== Hyprland.get_focused_workspace().get_id()) {
|
||||
BarVisible.set(true);
|
||||
barCloser.visible = false;
|
||||
}
|
||||
else {
|
||||
BarVisible.set(false);
|
||||
barCloser.visible = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const buffer = (
|
||||
<box
|
||||
css="min-height: 10px;"
|
||||
visible={bind(BarVisible).as((v) => !v)}
|
||||
/>
|
||||
);
|
||||
|
||||
const vertical = anchor >= (Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT);
|
||||
const isBottomOrLeft = (
|
||||
anchor === (Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT | Astal.WindowAnchor.BOTTOM)
|
||||
) || (
|
||||
anchor === (Astal.WindowAnchor.LEFT | Astal.WindowAnchor.TOP | Astal.WindowAnchor.BOTTOM)
|
||||
);
|
||||
|
||||
let transition: Gtk.RevealerTransitionType;
|
||||
|
||||
if (vertical) {
|
||||
transition = isBottomOrLeft ?
|
||||
Gtk.RevealerTransitionType.SLIDE_UP :
|
||||
Gtk.RevealerTransitionType.SLIDE_DOWN;
|
||||
}
|
||||
else {
|
||||
transition = isBottomOrLeft ?
|
||||
Gtk.RevealerTransitionType.SLIDE_RIGHT :
|
||||
Gtk.RevealerTransitionType.SLIDE_LEFT;
|
||||
}
|
||||
|
||||
const barWrap = (
|
||||
<revealer
|
||||
reveal_child={bind(BarVisible)}
|
||||
transitionType={transition}
|
||||
>
|
||||
{child}
|
||||
</revealer>
|
||||
);
|
||||
|
||||
const win = (
|
||||
<window
|
||||
name={`bar-${monitor}`}
|
||||
namespace={`bar-${monitor}`}
|
||||
layer={Astal.Layer.OVERLAY}
|
||||
gdkmonitor={gdkmonitor}
|
||||
anchor={anchor}
|
||||
{...rest}
|
||||
>
|
||||
<eventbox
|
||||
onHover={() => {
|
||||
if (!BarVisible.get()) {
|
||||
barCloser.visible = true;
|
||||
BarVisible.set(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<box
|
||||
css="min-height: 1px; padding: 1px;"
|
||||
hexpand
|
||||
halign={Gtk.Align.FILL}
|
||||
vertical={vertical}
|
||||
>
|
||||
{isBottomOrLeft ?
|
||||
[buffer, barWrap] :
|
||||
[barWrap, buffer]}
|
||||
</box>
|
||||
</eventbox>
|
||||
</window>
|
||||
) as Widget.Window;
|
||||
|
||||
App.add_window(win);
|
||||
|
||||
return win;
|
||||
};
|
28
nixosModules/ags/config/widgets/bar/items/audio.tsx
Normal file
28
nixosModules/ags/config/widgets/bar/items/audio.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { bind } from 'astal';
|
||||
|
||||
import AstalWp from 'gi://AstalWp';
|
||||
|
||||
export default () => {
|
||||
const speaker = AstalWp.get_default()?.audio.default_speaker;
|
||||
|
||||
if (!speaker) {
|
||||
throw new Error('Could not find default audio devices.');
|
||||
}
|
||||
|
||||
return (
|
||||
<box className="bar-item audio">
|
||||
<overlay>
|
||||
<circularprogress
|
||||
startAt={0.75}
|
||||
endAt={0.75}
|
||||
value={bind(speaker, 'volume')}
|
||||
|
||||
rounded
|
||||
className={bind(speaker, 'mute').as((muted) => muted ? 'disabled' : '')}
|
||||
/>
|
||||
|
||||
<icon icon={bind(speaker, 'volumeIcon')} />
|
||||
</overlay>
|
||||
</box>
|
||||
);
|
||||
};
|
45
nixosModules/ags/config/widgets/bar/items/battery.tsx
Normal file
45
nixosModules/ags/config/widgets/bar/items/battery.tsx
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { bind } from 'astal';
|
||||
|
||||
import AstalBattery from 'gi://AstalBattery';
|
||||
const Battery = AstalBattery.get_default();
|
||||
|
||||
import Separator from '../../misc/separator';
|
||||
|
||||
|
||||
const LOW_BATT = 20;
|
||||
|
||||
export default () => (
|
||||
<box className="bar-item battery">
|
||||
<icon
|
||||
setup={(self) => {
|
||||
const update = () => {
|
||||
const percent = Math.round(Battery.get_percentage() * 100);
|
||||
const level = Math.floor(percent / 10) * 10;
|
||||
const isCharging = Battery.get_charging();
|
||||
const charged = percent === 100 && isCharging;
|
||||
const iconName = charged ?
|
||||
'battery-level-100-charged-symbolic' :
|
||||
`battery-level-${level}${isCharging ?
|
||||
'-charging' :
|
||||
''}-symbolic`;
|
||||
|
||||
self.set_icon(iconName);
|
||||
|
||||
self.toggleClassName('charging', isCharging);
|
||||
self.toggleClassName('charged', charged);
|
||||
self.toggleClassName('low', percent < LOW_BATT);
|
||||
};
|
||||
|
||||
update();
|
||||
|
||||
Battery.connect('notify::percentage', () => update());
|
||||
Battery.connect('notify::icon-name', () => update());
|
||||
Battery.connect('notify::battery-icon-name', () => update());
|
||||
}}
|
||||
/>
|
||||
|
||||
<Separator size={8} />
|
||||
|
||||
<label label={bind(Battery, 'percentage').as((v) => `${Math.round(v * 100)}%`)} />
|
||||
</box>
|
||||
);
|
20
nixosModules/ags/config/widgets/bar/items/brightness.tsx
Normal file
20
nixosModules/ags/config/widgets/bar/items/brightness.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { bind } from 'astal';
|
||||
|
||||
import Brightness from '../../../services/brightness';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<box className="bar-item brightness">
|
||||
<overlay>
|
||||
<circularprogress
|
||||
startAt={0.75}
|
||||
endAt={0.75}
|
||||
value={bind(Brightness, 'screen')}
|
||||
rounded
|
||||
/>
|
||||
|
||||
<icon icon={bind(Brightness, 'screenIcon')} />
|
||||
</overlay>
|
||||
</box>
|
||||
);
|
||||
};
|
55
nixosModules/ags/config/widgets/bar/items/clock.tsx
Normal file
55
nixosModules/ags/config/widgets/bar/items/clock.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { bind, Variable } from 'astal';
|
||||
import { App } from 'astal/gtk3';
|
||||
|
||||
import GLib from 'gi://GLib';
|
||||
import PopupWindow from '../../misc/popup-window';
|
||||
|
||||
|
||||
export default () => {
|
||||
const timeVar = Variable<string>('').poll(1000, (prev) => {
|
||||
const time = GLib.DateTime.new_now_local();
|
||||
|
||||
const dayName = time.format('%a. ');
|
||||
const dayNum = time.get_day_of_month();
|
||||
const date = time.format(' %b. ');
|
||||
const hour = (new Date().toLocaleString([], {
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hour12: true,
|
||||
}) ?? '')
|
||||
.replace('a.m.', 'AM')
|
||||
.replace('p.m.', 'PM');
|
||||
|
||||
return (dayNum && dayName && date) ?
|
||||
(dayName + dayNum + date + hour) :
|
||||
prev;
|
||||
});
|
||||
|
||||
return (
|
||||
<button
|
||||
className="bar-item"
|
||||
cursor="pointer"
|
||||
|
||||
onButtonReleaseEvent={(self) => {
|
||||
const win = App.get_window('win-calendar') as PopupWindow;
|
||||
|
||||
win.set_x_pos(
|
||||
self.get_allocation(),
|
||||
'right',
|
||||
);
|
||||
|
||||
win.visible = !win.visible;
|
||||
}}
|
||||
|
||||
setup={(self) => {
|
||||
App.connect('window-toggled', (_, win) => {
|
||||
if (win.name === 'win-notif-center') {
|
||||
self.toggleClassName('toggle-on', win.visible);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<label label={bind(timeVar)} />
|
||||
</button>
|
||||
);
|
||||
};
|
78
nixosModules/ags/config/widgets/bar/items/current-client.tsx
Normal file
78
nixosModules/ags/config/widgets/bar/items/current-client.tsx
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { bind, Variable } from 'astal';
|
||||
|
||||
import AstalApps from 'gi://AstalApps';
|
||||
const Applications = AstalApps.Apps.new();
|
||||
|
||||
import AstalHyprland from 'gi://AstalHyprland';
|
||||
const Hyprland = AstalHyprland.get_default();
|
||||
|
||||
import Separator from '../../misc/separator';
|
||||
import { hyprMessage } from '../../../lib';
|
||||
|
||||
|
||||
export default () => {
|
||||
const visibleIcon = Variable<boolean>(false);
|
||||
const focusedIcon = Variable<string>('');
|
||||
const focusedTitle = Variable<string>('');
|
||||
|
||||
let lastFocused: string | undefined;
|
||||
|
||||
const updateVars = (
|
||||
client: AstalHyprland.Client | null = Hyprland.get_focused_client(),
|
||||
) => {
|
||||
lastFocused = client?.get_address();
|
||||
const app = Applications.fuzzy_query(
|
||||
client?.get_class() ?? '',
|
||||
)[0];
|
||||
|
||||
const icon = app?.iconName;
|
||||
|
||||
if (icon) {
|
||||
visibleIcon.set(true);
|
||||
focusedIcon.set(icon);
|
||||
}
|
||||
else {
|
||||
visibleIcon.set(false);
|
||||
}
|
||||
|
||||
focusedTitle.set(client?.get_title() ?? '');
|
||||
const id = client?.connect('notify::title', (c) => {
|
||||
if (c.get_address() !== lastFocused) {
|
||||
c.disconnect(id);
|
||||
}
|
||||
focusedTitle.set(c.get_title());
|
||||
});
|
||||
};
|
||||
|
||||
updateVars();
|
||||
Hyprland.connect('notify::focused-client', () => updateVars());
|
||||
Hyprland.connect('client-removed', () => updateVars());
|
||||
Hyprland.connect('client-added', async() => {
|
||||
try {
|
||||
updateVars(Hyprland.get_client(JSON.parse(await hyprMessage('j/activewindow')).address));
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<box
|
||||
className="bar-item current-window"
|
||||
visible={bind(focusedTitle).as((title) => title !== '')}
|
||||
>
|
||||
<icon
|
||||
css="font-size: 32px;"
|
||||
icon={bind(focusedIcon)}
|
||||
visible={bind(visibleIcon)}
|
||||
/>
|
||||
|
||||
<Separator size={8} />
|
||||
|
||||
<label
|
||||
label={bind(focusedTitle)}
|
||||
truncate
|
||||
/>
|
||||
</box>
|
||||
);
|
||||
};
|
53
nixosModules/ags/config/widgets/bar/items/network.tsx
Normal file
53
nixosModules/ags/config/widgets/bar/items/network.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { bind, Variable } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
|
||||
import AstalNetwork from 'gi://AstalNetwork';
|
||||
const Network = AstalNetwork.get_default();
|
||||
|
||||
|
||||
export default () => {
|
||||
const Hovered = Variable(false);
|
||||
|
||||
return (
|
||||
<button
|
||||
className="bar-item network"
|
||||
cursor="pointer"
|
||||
|
||||
onHover={() => Hovered.set(true)}
|
||||
onHoverLost={() => Hovered.set(false)}
|
||||
>
|
||||
{bind(Network, 'primary').as((primary) => {
|
||||
if (primary === AstalNetwork.Primary.UNKNOWN) {
|
||||
return (<icon icon="network-wireless-signal-none-symbolic" />);
|
||||
}
|
||||
else if (primary === AstalNetwork.Primary.WIFI) {
|
||||
const Wifi = Network.get_wifi();
|
||||
|
||||
if (!Wifi) { return; }
|
||||
|
||||
return (
|
||||
<box>
|
||||
<icon icon={bind(Wifi, 'iconName')} />
|
||||
|
||||
<revealer
|
||||
revealChild={bind(Hovered)}
|
||||
transitionType={Gtk.RevealerTransitionType.SLIDE_LEFT}
|
||||
>
|
||||
{bind(Wifi, 'activeAccessPoint').as((ap) => (
|
||||
<label label={bind(ap, 'ssid')} />
|
||||
))}
|
||||
</revealer>
|
||||
</box>
|
||||
);
|
||||
}
|
||||
else {
|
||||
const Wired = Network.get_wired();
|
||||
|
||||
if (!Wired) { return; }
|
||||
|
||||
return (<icon icon={bind(Wired, 'iconName')} />);
|
||||
}
|
||||
})}
|
||||
</button>
|
||||
);
|
||||
};
|
59
nixosModules/ags/config/widgets/bar/items/notif-button.tsx
Normal file
59
nixosModules/ags/config/widgets/bar/items/notif-button.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { bind } from 'astal';
|
||||
import { App } from 'astal/gtk3';
|
||||
|
||||
import AstalNotifd from 'gi://AstalNotifd';
|
||||
const Notifications = AstalNotifd.get_default();
|
||||
|
||||
import Separator from '../../misc/separator';
|
||||
|
||||
const SPACING = 4;
|
||||
|
||||
// Types
|
||||
import PopupWindow from '../../misc/popup-window';
|
||||
|
||||
|
||||
export default () => (
|
||||
<button
|
||||
className="bar-item"
|
||||
cursor="pointer"
|
||||
|
||||
onButtonReleaseEvent={(self) => {
|
||||
const win = App.get_window('win-notif-center') as PopupWindow;
|
||||
|
||||
win.set_x_pos(
|
||||
self.get_allocation(),
|
||||
'right',
|
||||
);
|
||||
|
||||
win.visible = !win.visible;
|
||||
}}
|
||||
|
||||
setup={(self) => {
|
||||
App.connect('window-toggled', (_, win) => {
|
||||
if (win.name === 'win-notif-center') {
|
||||
self.toggleClassName('toggle-on', win.visible);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<box>
|
||||
<icon
|
||||
icon={bind(Notifications, 'notifications').as((notifs) => {
|
||||
if (Notifications.dontDisturb) {
|
||||
return 'notification-disabled-symbolic';
|
||||
}
|
||||
else if (notifs.length > 0) {
|
||||
return 'notification-new-symbolic';
|
||||
}
|
||||
else {
|
||||
return 'notification-symbolic';
|
||||
}
|
||||
})}
|
||||
/>
|
||||
|
||||
<Separator size={SPACING} />
|
||||
|
||||
<label label={bind(Notifications, 'notifications').as((n) => String(n.length))} />
|
||||
</box>
|
||||
</button>
|
||||
);
|
78
nixosModules/ags/config/widgets/bar/items/tray.tsx
Normal file
78
nixosModules/ags/config/widgets/bar/items/tray.tsx
Normal file
|
@ -0,0 +1,78 @@
|
|||
import { App, Gdk, Gtk, Widget } from 'astal/gtk3';
|
||||
import { bind, idle } from 'astal';
|
||||
|
||||
import AstalTray from 'gi://AstalTray';
|
||||
const Tray = AstalTray.get_default();
|
||||
|
||||
|
||||
const SKIP_ITEMS = ['.spotify-wrapped'];
|
||||
|
||||
const TrayItem = (item: AstalTray.TrayItem) => {
|
||||
if (item.iconThemePath) {
|
||||
App.add_icons(item.iconThemePath);
|
||||
}
|
||||
|
||||
const menu = item.create_menu();
|
||||
|
||||
return (
|
||||
<revealer
|
||||
transitionType={Gtk.RevealerTransitionType.SLIDE_RIGHT}
|
||||
revealChild={false}
|
||||
>
|
||||
<button
|
||||
className="tray-item"
|
||||
cursor="pointer"
|
||||
tooltipMarkup={bind(item, 'tooltipMarkup')}
|
||||
onDestroy={() => menu?.destroy()}
|
||||
onClickRelease={(self) => {
|
||||
menu?.popup_at_widget(self, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null);
|
||||
}}
|
||||
>
|
||||
<icon gIcon={bind(item, 'gicon')} />
|
||||
</button>
|
||||
</revealer>
|
||||
);
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const itemMap = new Map<string, Widget.Revealer>();
|
||||
|
||||
return (
|
||||
<box
|
||||
className="bar-item system-tray"
|
||||
visible={bind(Tray, 'items').as((items) => items.length !== 0)}
|
||||
setup={(self) => {
|
||||
self
|
||||
.hook(Tray, 'item-added', (_, item: string) => {
|
||||
if (itemMap.has(item) || SKIP_ITEMS.includes(Tray.get_item(item).title)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const widget = TrayItem(Tray.get_item(item)) as Widget.Revealer;
|
||||
|
||||
itemMap.set(item, widget);
|
||||
|
||||
self.add(widget);
|
||||
|
||||
idle(() => {
|
||||
widget.set_reveal_child(true);
|
||||
});
|
||||
})
|
||||
|
||||
.hook(Tray, 'item-removed', (_, item: string) => {
|
||||
if (!itemMap.has(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const widget = itemMap.get(item);
|
||||
|
||||
widget?.set_reveal_child(false);
|
||||
|
||||
setTimeout(() => {
|
||||
widget?.destroy();
|
||||
}, 1000);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
172
nixosModules/ags/config/widgets/bar/items/workspaces.tsx
Normal file
172
nixosModules/ags/config/widgets/bar/items/workspaces.tsx
Normal file
|
@ -0,0 +1,172 @@
|
|||
import { Gtk, Widget } from 'astal/gtk3';
|
||||
import { timeout } from 'astal';
|
||||
|
||||
import AstalHyprland from 'gi://AstalHyprland';
|
||||
const Hyprland = AstalHyprland.get_default();
|
||||
|
||||
import { hyprMessage } from '../../../lib';
|
||||
|
||||
|
||||
const URGENT_DURATION = 1000;
|
||||
|
||||
const Workspace = ({ id = 0 }) => (
|
||||
<revealer
|
||||
name={id.toString()}
|
||||
transitionType={Gtk.RevealerTransitionType.SLIDE_RIGHT}
|
||||
>
|
||||
<eventbox
|
||||
cursor="pointer"
|
||||
tooltip_text={id.toString()}
|
||||
|
||||
onClickRelease={() => {
|
||||
hyprMessage(`dispatch workspace ${id}`).catch(console.log);
|
||||
}}
|
||||
>
|
||||
<box
|
||||
valign={Gtk.Align.CENTER}
|
||||
className="button"
|
||||
|
||||
setup={(self) => {
|
||||
const update = (
|
||||
_: Widget.Box,
|
||||
client?: AstalHyprland.Client,
|
||||
) => {
|
||||
const workspace = Hyprland.get_workspace(id);
|
||||
const occupied = workspace && workspace.get_clients().length > 0;
|
||||
|
||||
self.toggleClassName('occupied', occupied);
|
||||
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isUrgent = client &&
|
||||
client.get_workspace().get_id() === id;
|
||||
|
||||
if (isUrgent) {
|
||||
self.toggleClassName('urgent', true);
|
||||
|
||||
// Only show for a sec when urgent is current workspace
|
||||
if (Hyprland.get_focused_workspace().get_id() === id) {
|
||||
timeout(URGENT_DURATION, () => {
|
||||
self.toggleClassName('urgent', false);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
update(self);
|
||||
self
|
||||
.hook(Hyprland, 'event', () => update(self))
|
||||
|
||||
// Deal with urgent windows
|
||||
.hook(Hyprland, 'urgent', update)
|
||||
|
||||
.hook(Hyprland, 'notify::focused-workspace', () => {
|
||||
if (Hyprland.get_focused_workspace().get_id() === id) {
|
||||
self.toggleClassName('urgent', false);
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</eventbox>
|
||||
</revealer>
|
||||
);
|
||||
|
||||
export default () => {
|
||||
const L_PADDING = 2;
|
||||
const WS_WIDTH = 30;
|
||||
|
||||
const updateHighlight = (self: Widget.Box) => {
|
||||
const currentId = Hyprland.get_focused_workspace().get_id().toString();
|
||||
|
||||
const indicators = ((self.get_parent() as Widget.Overlay)
|
||||
.child as Widget.Box)
|
||||
.children as Widget.Revealer[];
|
||||
|
||||
const currentIndex = indicators.findIndex((w) => w.name === currentId);
|
||||
|
||||
if (currentIndex >= 0) {
|
||||
self.css = `margin-left: ${L_PADDING + (currentIndex * WS_WIDTH)}px`;
|
||||
}
|
||||
};
|
||||
|
||||
const highlight = (
|
||||
<box
|
||||
className="button active"
|
||||
|
||||
valign={Gtk.Align.CENTER}
|
||||
halign={Gtk.Align.START}
|
||||
|
||||
setup={(self) => {
|
||||
self.hook(Hyprland, 'notify::focused-workspace', updateHighlight);
|
||||
}}
|
||||
/>
|
||||
) as Widget.Box;
|
||||
|
||||
let workspaces: Widget.Revealer[] = [];
|
||||
|
||||
return (
|
||||
<box
|
||||
className="bar-item"
|
||||
>
|
||||
<overlay
|
||||
className="workspaces"
|
||||
passThrough
|
||||
overlay={highlight}
|
||||
>
|
||||
<box
|
||||
setup={(self) => {
|
||||
const refresh = () => {
|
||||
(self.children as Widget.Revealer[]).forEach((rev) => {
|
||||
rev.reveal_child = false;
|
||||
});
|
||||
|
||||
workspaces.forEach((ws) => {
|
||||
ws.reveal_child = true;
|
||||
});
|
||||
};
|
||||
|
||||
const updateWorkspaces = () => {
|
||||
Hyprland.get_workspaces().forEach((ws) => {
|
||||
const currentWs = (self.children as Widget.Revealer[])
|
||||
.find((ch) => ch.name === ws.id.toString());
|
||||
|
||||
if (!currentWs && ws.id > 0) {
|
||||
self.add(Workspace({ id: ws.id }));
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure the order is correct
|
||||
workspaces.forEach((workspace, i) => {
|
||||
(workspace.get_parent() as Widget.Box)
|
||||
.reorder_child(workspace, i);
|
||||
});
|
||||
};
|
||||
|
||||
const updateAll = () => {
|
||||
workspaces = (self.children as Widget.Revealer[])
|
||||
.filter((ch) => {
|
||||
return Hyprland.get_workspaces().find((ws) => {
|
||||
return ws.id.toString() === ch.name;
|
||||
});
|
||||
})
|
||||
.sort((a, b) => parseInt(a.name ?? '0') - parseInt(b.name ?? '0'));
|
||||
|
||||
updateWorkspaces();
|
||||
refresh();
|
||||
|
||||
// Make sure the highlight doesn't go too far
|
||||
const TEMP_TIMEOUT = 100;
|
||||
|
||||
timeout(TEMP_TIMEOUT, () => updateHighlight(highlight));
|
||||
};
|
||||
|
||||
updateAll();
|
||||
self.hook(Hyprland, 'event', updateAll);
|
||||
}}
|
||||
/>
|
||||
</overlay>
|
||||
</box>
|
||||
);
|
||||
};
|
70
nixosModules/ags/config/widgets/bar/wim.tsx
Normal file
70
nixosModules/ags/config/widgets/bar/wim.tsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { Astal, Gtk } from 'astal/gtk3';
|
||||
|
||||
import Audio from './items/audio';
|
||||
import Battery from './items/battery';
|
||||
import Brightness from './items/brightness';
|
||||
import Clock from './items/clock';
|
||||
import CurrentClient from './items/current-client';
|
||||
import Network from './items/network';
|
||||
import NotifButton from './items/notif-button';
|
||||
import SysTray from './items/tray';
|
||||
import Workspaces from './items/workspaces';
|
||||
|
||||
import BarRevealer from './fullscreen';
|
||||
import Separator from '../misc/separator';
|
||||
|
||||
|
||||
// TODO: add Bluetooth
|
||||
export default () => (
|
||||
<BarRevealer
|
||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||
anchor={
|
||||
Astal.WindowAnchor.TOP |
|
||||
Astal.WindowAnchor.LEFT |
|
||||
Astal.WindowAnchor.RIGHT
|
||||
}
|
||||
>
|
||||
<centerbox className="bar widget">
|
||||
<box hexpand halign={Gtk.Align.START}>
|
||||
<Workspaces />
|
||||
|
||||
<Separator size={8} />
|
||||
|
||||
<SysTray />
|
||||
|
||||
<Separator size={8} />
|
||||
|
||||
<CurrentClient />
|
||||
|
||||
<Separator size={8} />
|
||||
</box>
|
||||
|
||||
<box>
|
||||
<Clock />
|
||||
</box>
|
||||
|
||||
<box hexpand halign={Gtk.Align.END}>
|
||||
<Network />
|
||||
|
||||
<Separator size={8} />
|
||||
|
||||
<NotifButton />
|
||||
|
||||
<Separator size={8} />
|
||||
|
||||
<Audio />
|
||||
|
||||
<Separator size={8} />
|
||||
|
||||
<Brightness />
|
||||
|
||||
<Separator size={8} />
|
||||
|
||||
<Battery />
|
||||
|
||||
<Separator size={2} />
|
||||
</box>
|
||||
</centerbox>
|
||||
|
||||
</BarRevealer>
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue