feat: use gtk icons instead of nerdfonts across the shell

also removed hour count from recording widgets
This commit is contained in:
retrozinndev
2025-06-04 17:29:36 -03:00
parent a17fc3c127
commit b1db748351
32 changed files with 375 additions and 241 deletions
+1
View File
@@ -38,6 +38,7 @@ const runnerPlugins: Array<Runner.Plugin> = [
App.start({
instanceName: "astal",
icons: "icons/",
requestHandler: (request: string, response: (result: any) => void): void => {
response(handleArguments(request));
},
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 7.40625 1 c -0.613281 0.007812 -1.234375 0.089844 -1.847656 0.253906 c -3.273438 0.878906 -5.558594 3.855469 -5.558594 7.246094 s 2.285156 6.367188 5.558594 7.242188 c 3.273437 0.878906 6.742187 -0.558594 8.4375 -3.492188 c 0.273437 -0.480469 0.109375 -1.089844 -0.367188 -1.367188 c -0.476562 -0.273437 -1.089844 -0.109374 -1.367187 0.367188 c -1.246094 2.160156 -3.777344 3.207031 -6.1875 2.5625 c -2.40625 -0.644531 -4.074219 -2.820312 -4.074219 -5.3125 c 0 -2.496094 1.667969 -4.667969 4.074219 -5.3125 c 2.410156 -0.644531 4.941406 0.402344 6.1875 2.5625 c 0.058593 0.085938 0.125 0.164062 0.203125 0.226562 l -0.019532 0.015626 l -0.007812 0.007812 h -1.4375 c -0.550781 0 -1 0.449219 -1 1 c 0 0 0 1 1 1 h 5 v -5 s 0.003906 -1 -1 -1 c -0.550781 0 -1 0.449219 -1 1 v 1.6875 l -0.015625 0.011719 l -0.011719 0.011719 c -1.277344 -2.179688 -3.53125 -3.519532 -5.953125 -3.691407 c -0.203125 -0.015625 -0.40625 -0.019531 -0.613281 -0.019531 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 8 1 c -3.855469 0 -7 3.144531 -7 7 s 3.144531 7 7 7 s 7 -3.144531 7 -7 s -3.144531 -7 -7 -7 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 261 B

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 5.019531 -0.00390625 c -0.96875 0 -2 1.05078125 -2 2.00000025 v 2.988281 c 0 0.429687 0.222657 0.675781 0.554688 1.007813 l 2.023437 2.003906 l -2.007812 1.992187 c -0.367188 0.363281 -0.570313 0.6875 -0.570313 1 v 3.007813 c 0 1.011718 0.988281 2 2 2 h 6 c 1.007813 0 2 -1.011719 2 -2.003906 v -3.003907 c 0 -0.3125 -0.222656 -0.628906 -0.570312 -0.976562 l -2.015625 -2.015625 l 1.988281 -1.988282 c 0.261719 -0.261718 0.585937 -0.6875 0.597656 -1.015624 v -2.996094 c 0 -1.003906 -1.007812 -2.00000025 -2 -2.00000025 z m 6 5.00000025 h -6 v -3 h 6 m -3.589843 7 h 1.175781 l 2.414062 2.414062 v 1.585938 l -3 -2 l -3 2 v -1.613282 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 803 B

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 4 3 c -2.199219 0 -4 1.800781 -4 4 v 2 c 0 2.199219 1.800781 4 4 4 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 c -1.125 0 -2 -0.875 -2 -2 v -2 c 0 -1.125 0.875 -2 2 -2 h 8 c 1.125 0 2 0.875 2 2 v 2 c 0 1.125 -0.875 2 -2 2 h -4 c -0.550781 0 -1 0.449219 -1 1 s 0.449219 1 1 1 h 4 c 2.199219 0 4 -1.800781 4 -4 v -2 c 0 -2.199219 -1.800781 -4 -4 -4 z m 0 0"/><path d="m 10 10.996094 v -2.003906 h -1 v 0.007812 c -0.265625 0 -0.519531 0.105469 -0.707031 0.289062 l -2 2 c -0.390625 0.390626 -0.390625 1.023438 0 1.414063 l 2 2 c 0.1875 0.183594 0.441406 0.289063 0.707031 0.285156 v 0.011719 h 1 v -1.992188"/></g></svg>

After

Width:  |  Height:  |  Size: 781 B

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 8 1.03125 c -3.871094 0 -7 3.128906 -7 7 s 3.128906 7 7 7 s 7 -3.128906 7 -7 s -3.128906 -7 -7 -7 z m -4 6 h 8 v 2 h -8 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 289 B

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 0 2.316406 v 5.507813 c 0 2.214843 1.1875 4.257812 3.109375 5.355469 l 4.890625 2.796874 l 4.890625 -2.796874 c 1.921875 -1.097657 3.109375 -3.140626 3.109375 -5.355469 v -5.507813 l -8 -2.285156 z m 14.726562 1.71875 l -0.726562 -0.964844 v 4.753907 c 0 1.496093 -0.800781 2.875 -2.101562 3.617187 l -4.394532 2.511719 h 0.992188 l -4.394532 -2.511719 c -1.300781 -0.742187 -2.101562 -2.121094 -2.101562 -3.617187 v -4.753907 l -0.726562 0.964844 l 7 -2 h -0.546876 z m 0 0"/><path d="m 5.941406 6.957031 l 3.058594 3.058594 c 0.292969 0.292969 0.765625 0.292969 1.058594 0 c 0.292968 -0.292969 0.292968 -0.765625 0 -1.058594 l -3.058594 -3.058593 c -0.292969 -0.292969 -0.765625 -0.292969 -1.058594 0 c -0.292968 0.292968 -0.292968 0.765624 0 1.058593 z m 0 0"/><path d="m 9 5.898438 l -3.058594 3.058593 c -0.292968 0.292969 -0.292968 0.765625 0 1.058594 c 0.292969 0.292969 0.765625 0.292969 1.058594 0 l 3.058594 -3.058594 c 0.292968 -0.292969 0.292968 -0.765625 0 -1.058593 c -0.292969 -0.292969 -0.765625 -0.292969 -1.058594 0 z m 0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 0 2.316406 v 5.507813 c 0 2.214843 1.1875 4.257812 3.109375 5.355469 l 4.890625 2.796874 l 4.890625 -2.796874 c 1.921875 -1.097657 3.109375 -3.140626 3.109375 -5.355469 v -5.507813 l -8 -2.285156 z m 14.726562 1.71875 l -0.726562 -0.964844 v 4.753907 c 0 1.496093 -0.800781 2.875 -2.101562 3.617187 l -4.394532 2.511719 h 0.992188 l -4.394532 -2.511719 c -1.300781 -0.742187 -2.101562 -2.121094 -2.101562 -3.617187 v -4.753907 l -0.726562 0.964844 l 7 -2 h -0.546876 z m 0 0"/><path d="m 5.46875 7.78125 l 2 2 c 0.292969 0.292969 0.769531 0.292969 1.0625 0 l 3 -3 c 0.292969 -0.292969 0.292969 -0.769531 0 -1.0625 s -0.769531 -0.292969 -1.0625 0 l -3 3 h 1.0625 l -2 -2 c -0.292969 -0.292969 -0.769531 -0.292969 -1.0625 0 s -0.292969 0.769531 0 1.0625 z m 0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 928 B

+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
<g fill="#2e3436">
<path d="m 1 3 h 14 c 0.550781 0 1 0.449219 1 1 s -0.449219 1 -1 1 h -14 c -0.550781 0 -1 -0.449219 -1 -1 s 0.449219 -1 1 -1 z m 0 0"/>
<path d="m 4 4 v -1.5 c 0 -1.386719 1.113281 -2.5 2.5 -2.5 h 2.980469 c 1.382812 0 2.5 1.113281 2.5 2.5 v 1.5 h -2 v -1.5 c 0 -0.269531 -0.230469 -0.5 -0.5 -0.5 h -2.980469 c -0.269531 0 -0.5 0.230469 -0.5 0.5 v 1.5 z m 0 0"/>
<path d="m 4 4 v 9 c 0 0.546875 0.453125 1 1 1 h 6 c 0.546875 0 1 -0.453125 1 -1 v -9 h 2 v 9 c 0 1.660156 -1.339844 3 -3 3 h -6 c -1.660156 0 -3 -1.339844 -3 -3 v -9 z m 0 0"/>
<path d="m 7 7 v 5 c 0 0.277344 -0.222656 0.5 -0.5 0.5 s -0.5 -0.222656 -0.5 -0.5 v -5 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 s 0.5 0.222656 0.5 0.5 z m 0 0"/>
<path d="m 10 7 v 5 c 0 0.277344 -0.222656 0.5 -0.5 0.5 s -0.5 -0.222656 -0.5 -0.5 v -5 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 s 0.5 0.222656 0.5 0.5 z m 0 0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

-9
View File
@@ -242,15 +242,6 @@ selection {
background: colors.$bg-tertiary;
}
label.nf,
button.nf label {
font-size: 12px;
font-family: "Symbols Nerd Font Mono", "Noto Sans Nerd Font Mono",
"0xProto Nerd Font Mono", "Fira Code Nerd Font Mono",
"Symbols Nerd Font", "Noto Sans Nerd Font", "Fira Code Nerd Font",
"Font Awesome";
}
trough {
background: functions.toRGB(color.adjust($color: wal.$color1, $lightness: -20%));
border-radius: 8px;
+15 -15
View File
@@ -149,7 +149,7 @@
box-shadow: inset 0 0 0 300px rgba(colors.$fg-primary, .2);
}
& .icon {
& icon {
font-size: 14px;
}
@@ -162,8 +162,8 @@
& > button {
margin: 4px 1px;
& label {
font-size: 8px;
& icon {
font-size: 10px;
}
}
}
@@ -211,23 +211,23 @@
& > box {
padding: 0 8px;
& .nf {
margin: {
right: 3px;
left: 2px;
};
font-size: 12px;
& icon {
font-size: 14px;
}
& .status-icons {
padding-left: 4px;
& .notification-count {
font-size: 5px;
margin-left: -3px;
margin-top: 1px;
& revealer > eventbox > box {
background: rgba($color: colors.$bg-tertiary, $alpha: .7);
border-radius: 12px;
margin: 4px 0;
padding: 2px 6px;
}
& icon.notification-count {
font-size: 6px;
margin-top: -14px;
}
}
}
+2 -2
View File
@@ -51,11 +51,11 @@
& .bottom {
& .controls {
margin-top: 5px;
margin-top: 9px;
& button {
padding: 7px;
& label {
font-size: 10px;
font-size: 9px;
}
}
}
+29 -22
View File
@@ -23,11 +23,19 @@
}
& .quickactions {
margin-bottom: .8em;
& .hostname {
font-size: 15px;
font-weight: 600;
}
& > box:not(.button-row) icon {
font-size: 12px;
color: colors.$fg-disabled;
margin-right: 3px;
}
& .uptime {
font-size: 10.1px;
font-family: "Symbols Nerd Font Mono";
@@ -46,31 +54,19 @@
}
& .sliders {
padding: 0;
& > box {
margin: 8px 0;
&:first-child {
margin-top: 0;
icon {
font-size: 16px;
}
&:last-child {
margin-bottom: 0;
}
}
label.nf,
button.nf label {
margin-right: 8px;
font-size: 15px;
}
button.more {
@include mixins.hover-shadow;
button {
@include mixins.hover-shadow2;
padding: 4px;
border-radius: 16px;
margin-left: 6px;
icon {
font-size: 14px;
}
}
& .page .content {
@@ -133,6 +129,16 @@
& label {
font-size: 14px;
}
& label.description {
font-size: 10px;
font-weight: 400;
color: colors.$fg-disabled;
}
& icon {
font-size: 16px;
}
}
& .bottom-buttons button {
@@ -176,14 +182,15 @@ box.history {
& > .button-row {
margin-top: 12px;
& button {
padding: 6px;
& label.nf {
& icon {
font-size: 16px;
}
& label:not(.nf) {
& label {
font-size: 12px;
font-weight: 600;
}
+1 -1
View File
@@ -2,7 +2,7 @@
/**
* GTK3 only supports sRGB color space, unfortunatly
* GTK3 only supports sRGB color space, unfortunately
*/
@function toRGB($color) {
@return rgba(
+2 -2
View File
@@ -17,8 +17,8 @@
margin: 0 150px;
& > button {
& label {
font-size: 96px;
& icon {
font-size: 128px;
}
margin: {
+1 -1
View File
@@ -92,7 +92,7 @@ export function NotificationWidget(notification: AstalNotifd.Notification|number
label: GLib.DateTime.new_from_unix_local(notification.time).format("%H:%M"),
} as Widget.LabelProps),
new Widget.Button({
className: "close nf",
className: "close",
onClick: () => onClose && onClose(notification),
image: new Widget.Icon({
className: "close icon",
+45 -31
View File
@@ -1,16 +1,15 @@
import { bind, execAsync, GLib } from "astal";
import { bind } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import AstalMpris from "gi://AstalMpris";
import { Separator, SeparatorProps } from "../Separator";
import { Windows } from "../../windows";
import { Clipboard } from "../../scripts/clipboard";
const playerIcons = {
spotify: '󰓇',
clapper: '󰿎',
mpv: '',
spotube: '󰋋',
firefox: '󰈹'
spotify: "spotify-symbolic",
mpv: "mpv-symbolic",
Clapper: "com.github.rafostar.Clapper-symbolic"
}
export function Media(): Gtk.Widget {
@@ -28,35 +27,46 @@ export function Media(): Gtk.Widget {
children: bind(AstalMpris.get_default(), "players").as((players: Array<AstalMpris.Player>) =>
players[0] ? [
new Widget.Button({
className: "link nf",
label: "󰌹",
className: "link",
image: new Widget.Icon({
icon: "edit-paste-symbolic"
} as Widget.IconProps),
tooltipText: "Copy link to Clipboard",
visible: bind(players[0], "metadata").as((_metadata: GLib.HashTable) =>
players[0].get_meta("xesam:url") === null),
onClick: () => execAsync(`sh -c "wl-copy \\"$(playerctl metadata 'xesam:url')\\""`)
visible: bind(players[0], "metadata").as((meta) =>
Boolean(meta["xesam:url"]?.get_string()[0])),
onClick: () => Clipboard.getDefault().copyAsync(
players[0].metadata["xesam:url"].get_string()[0]
)
} as Widget.ButtonProps),
new Widget.Button({
className: "previous nf",
label: "󰒮",
className: "previous",
image: new Widget.Icon({
icon: "media-skip-backward-symbolic"
} as Widget.IconProps),
tooltipText: "Previous",
onClick: () => players[0].canGoPrevious && players[0].previous()
} as Widget.ButtonProps),
new Widget.Button({
className: "pause nf",
tooltipText: bind(players[0], "playback_status").as((status: AstalMpris.PlaybackStatus) =>
status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play"),
label: bind(players[0], "playbackStatus").as((status: AstalMpris.PlaybackStatus) =>
status === AstalMpris.PlaybackStatus.PLAYING ? "󰏤" : "󰐊"),
onClick: () => {
players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ?
className: "play-pause",
tooltipText: bind(players[0], "playback_status").as((status) =>
status === AstalMpris.PlaybackStatus.PLAYING ?
"Pause"
: "Play"),
image: new Widget.Icon({
icon: bind(players[0], "playbackStatus").as((status: AstalMpris.PlaybackStatus) =>
status === AstalMpris.PlaybackStatus.PLAYING ?
"media-playback-pause-symbolic"
: "media-playback-start-symbolic")
} as Widget.IconProps),
onClick: () => players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ?
players[0].play()
:
players[0].pause()
}
: players[0].pause()
} as Widget.ButtonProps),
new Widget.Button({
className: "next nf",
label: "󰒭",
className: "next",
image: new Widget.Icon({
icon: "media-skip-forward-symbolic"
} as Widget.IconProps),
tooltipText: "Next",
onClick: () => players[0].canGoNext && players[0].next()
} as Widget.ButtonProps)
@@ -80,13 +90,17 @@ export function Media(): Gtk.Widget {
spacing: 4,
children: bind(AstalMpris.get_default(), "players").as((players: Array<AstalMpris.Player>) =>
players[0] ? [
new Widget.Label({
className: "icon nf",
label: bind(players[0], "busName").as((busName: string) => {
const playerName: string = busName.split('.')[busName.split('.').length-1];
return playerIcons[playerName.toLowerCase() as keyof typeof playerIcons] || "󰎇";
new Widget.Icon({
icon: bind(players[0], "busName").as((busName: string) => {
const splitName = busName.split('.').filter(str => str !== "");
return playerIcons[
Object.keys(playerIcons).filter(icon =>
splitName[splitName.length - 1].includes(icon))?.[0] as keyof typeof playerIcons
]
?? "folder-music-symbolic";
})
} as Widget.LabelProps),
} as Widget.IconProps),
new Widget.Label({
className: "title",
label: bind(players[0], "title").as((title: string) => title || "No Title"),
+38 -38
View File
@@ -24,13 +24,15 @@ export function Status(): Gtk.Widget {
className: "sink",
endpoint: Wireplumber.getDefault().getDefaultSink(),
icon: bind(Wireplumber.getDefault().getDefaultSink(), "volumeIcon").as(icon =>
!Wireplumber.getDefault().isMutedSink() && Wireplumber.getDefault().getSinkVolume() > 0 ? icon : "audio-volume-muted-symbolic"),
!Wireplumber.getDefault().isMutedSink() && Wireplumber.getDefault().getSinkVolume() > 0 ?
icon : "audio-volume-muted-symbolic"),
}),
volumeStatus({
className: "source",
endpoint: Wireplumber.getDefault().getDefaultSource(),
icon: bind(Wireplumber.getDefault().getDefaultSource(), "volumeIcon").as(icon =>
!Wireplumber.getDefault().isMutedSource() && Wireplumber.getDefault().getSourceVolume() > 0 ? icon : "microphone-sensitivity-muted-symbolic"),
!Wireplumber.getDefault().isMutedSource() && Wireplumber.getDefault().getSourceVolume() > 0 ?
icon : "microphone-sensitivity-muted-symbolic"),
}),
StatusIcons()
]
@@ -69,9 +71,10 @@ function StatusIcons(): Gtk.Widget {
bind(AstalBluetooth.get_default(), "isConnected")
], (powered, connected) => {
return powered ? (
connected ? "󰂱"
: "󰂯"
) : "󰂲"
connected ?
"bluetooth-active-symbolic"
: "bluetooth-symbolic"
) : "bluetooth-disabled-symbolic"
});
const networkIcon: Variable<string> = Variable.derive([
@@ -82,15 +85,15 @@ function StatusIcons(): Gtk.Widget {
(primary, wired, wifi) => {
switch(primary) {
case AstalNetwork.Primary.WIRED: return wired ?
"󰛳"
: "󰛵";
"network-wired-symbolic"
: "network-wired-no-route-symbolic";
case AstalNetwork.Primary.WIFI: return wifi ?
"󰤨"
: "󰤭";
"network-wireless-signal-excellent-symbolic"
: "network-wireless-offline-symbolic";
}
return "󰲊";
return "network-no-route-symbolic";
});
const recordingTimer: Variable<string> = Variable.derive([
@@ -103,18 +106,15 @@ function StatusIcons(): Gtk.Widget {
const startedAtSeconds = dateTime.to_unix() - Recording.getDefault().startedAt!.to_unix();
if(startedAtSeconds <= 0) return "00:00";
const hours = Math.floor(startedAtSeconds / 120);
const minutes = Math.floor(startedAtSeconds / 60);
const seconds = Math.floor(startedAtSeconds % 60);
return `${ hours > 0 ? `${hours < 10 ? `0${hours}` : hours }:` : ""
}${ minutes < 10 ? `0${minutes}` : minutes
}:${ seconds < 10 ? `0${seconds}` : seconds }`;
return `${ minutes < 10 ? `0${minutes}` : minutes }:${ seconds < 10 ? `0${seconds}` : seconds }`;
});
return new Widget.Box({
className: "status-icons",
spacing: 3,
spacing: 8,
children: [
new Widget.Revealer({
revealChild: bind(Recording.getDefault(), "recording"),
@@ -127,10 +127,11 @@ function StatusIcons(): Gtk.Widget {
tooltipText: tr("control_center.tiles.recording.enabled_desc"),
child: new Widget.Box({
children: [
new Widget.Label({
className: "recording nf state",
label: '󰻃'
} as Widget.LabelProps),
new Widget.Icon({
className: "recording state",
icon: "media-record-symbolic",
css: "margin-right: 4px;"
} as Widget.IconProps),
new Widget.Label({
className: "rec-time",
label: recordingTimer()
@@ -139,32 +140,31 @@ function StatusIcons(): Gtk.Widget {
} as Widget.BoxProps)
} as Widget.EventBoxProps)
} as Widget.RevealerProps),
new Widget.Label({
className: "bluetooth nf state",
new Widget.Icon({
className: "bluetooth state",
visible: bind(AstalBluetooth.get_default(), "adapter").as(Boolean),
label: bluetoothIcon(),
icon: bluetoothIcon(),
onDestroy: () => bluetoothIcon.drop()
} as Widget.LabelProps),
new Widget.Label({
className: "network nf state",
label: networkIcon(),
} as Widget.IconProps),
new Widget.Icon({
className: "network state",
icon: networkIcon(),
onDestroy: () => networkIcon.drop()
} as Widget.LabelProps),
} as Widget.IconProps),
new Widget.Box({
children: [
new Widget.Label({
className: "bell nf state",
label: bind(Notifications.getDefault().getNotifd(), "dontDisturb").as((dnd: boolean) =>
dnd ? "󰂠" : "󰂚")
} as Widget.LabelProps),
new Widget.Label({
className: "notification-count nf",
xalign: 0,
yalign: 0.25,
new Widget.Icon({
className: "bell state",
icon: bind(Notifications.getDefault().getNotifd(), "dontDisturb").as((dnd) =>
dnd ? "minus-circle-filled-symbolic"
: "preferences-system-notifications-symbolic")
} as Widget.IconProps),
new Widget.Icon({
className: "notification-count",
visible: bind(Notifications.getDefault(), "history").as(history =>
history.length > 0),
label: '󰧞'
} as Widget.LabelProps)
icon: "circle-filled-symbolic"
} as Widget.IconProps)
]
} as Widget.BoxProps)
]
+57 -27
View File
@@ -96,59 +96,89 @@ export function BigMedia(): Gtk.Widget {
className: "controls button-row",
children: [
new Widget.Button({
className: "link nf",
label: "󰌹",
className: "link",
image: new Widget.Icon({
icon: "edit-paste-symbolic"
} as Widget.IconProps),
tooltipText: "Copy link to Clipboard",
visible: bind(players[0], "metadata").as((_meta: GLib.HashTable) =>
players[0].get_meta("xesam:url") === null),
onClick: () => execAsync(`sh -c "wl-copy \\"$(playerctl metadata 'xesam:url')\\""`)
} as Widget.ButtonProps),
new Widget.Button({
className: "shuffle nf",
visible: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) =>
className: "shuffle",
visible: bind(players[0], "shuffleStatus").as((shuffleStatus) =>
shuffleStatus !== AstalMpris.Shuffle.UNSUPPORTED),
label: bind(players[0], "shuffleStatus").as((shuffleStatus: AstalMpris.Shuffle) =>
shuffleStatus === AstalMpris.Shuffle.ON ? "󰒝" : "󰒞"),
tooltipText: "Toggle Shuffle",
image: new Widget.Icon({
icon: bind(players[0], "shuffleStatus").as((shuffleStatus) =>
shuffleStatus === AstalMpris.Shuffle.ON ?
"media-playlist-shuffle-symbolic"
: "media-playlist-consecutive-symbolic")
} as Widget.IconProps),
tooltipText: bind(players[0], "shuffleStatus").as((shuffleStatus) =>
shuffleStatus === AstalMpris.Shuffle.ON ?
"Shuffle"
: "No shuffle"),
onClick: () => players[0].shuffle()
} as Widget.ButtonProps),
new Widget.Button({
className: "previous nf",
label: "󰒮",
className: "previous",
image: new Widget.Icon({
icon: "media-skip-backward-symbolic"
} as Widget.IconProps),
tooltipText: "Previous",
onClick: () => players[0].canGoPrevious && players[0].previous()
} as Widget.ButtonProps),
new Widget.Button({
className: "pause nf",
tooltipText: bind(players[0], "playback_status").as((status: AstalMpris.PlaybackStatus) =>
className: "pause",
tooltipText: bind(players[0], "playback_status").as((status) =>
status === AstalMpris.PlaybackStatus.PLAYING ? "Pause" : "Play"),
label: bind(players[0], "playbackStatus").as((status: AstalMpris.PlaybackStatus) =>
status === AstalMpris.PlaybackStatus.PLAYING ? "󰏤" : "󰐊"),
onClick: () => {
players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ?
image: new Widget.Icon({
icon: bind(players[0], "playbackStatus").as((status) =>
status === AstalMpris.PlaybackStatus.PLAYING ?
"media-playback-pause-symbolic"
: "media-playback-start-symbolic"),
} as Widget.IconProps),
onClick: () => players[0].playbackStatus === AstalMpris.PlaybackStatus.PAUSED ?
players[0].play()
:
players[0].pause()
}
: players[0].pause()
} as Widget.ButtonProps),
new Widget.Button({
className: "next nf",
label: "󰒭",
className: "next",
image: new Widget.Icon({
icon: "media-skip-forward-symbolic"
} as Widget.IconProps),
tooltipText: "Next",
onClick: () => players[0].canGoNext && players[0].next()
} as Widget.ButtonProps),
new Widget.Button({
className: "repeat nf",
visible: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) =>
className: "repeat",
visible: bind(players[0], "loopStatus").as((loopStatus) =>
loopStatus !== AstalMpris.Loop.UNSUPPORTED),
label: bind(players[0], "loopStatus").as((loopStatus: AstalMpris.Loop) => {
image: new Widget.Icon({
icon: bind(players[0], "loopStatus").as((loopStatus) => {
switch(loopStatus) {
case AstalMpris.Loop.TRACK: return "󰑘";
case AstalMpris.Loop.PLAYLIST: return "󰑖";
default: return "󰑗";
case AstalMpris.Loop.TRACK:
return "media-playlist-repeat-song-symbolic";
case AstalMpris.Loop.PLAYLIST:
return "media-playlist-repeat-symbolic";
}
return "loop-arrow-symbolic";
})
} as Widget.IconProps),
tooltipText: bind(players[0], "loopStatus").as((loopStatus) => {
switch(loopStatus) {
case AstalMpris.Loop.TRACK:
return "Loop song";
case AstalMpris.Loop.PLAYLIST:
return "Loop playlist";
}
return "No loop";
}),
tooltipText: "Toggle Loop",
onClick: () => players[0].loop()
} as Widget.ButtonProps)
]
+4 -5
View File
@@ -47,11 +47,10 @@ export const NotifHistory = () => {
className: "clear-all",
child: new Widget.Box({
children: [
new Widget.Label({
className: "nf",
css: "margin-right: 6px",
label: "󰎟"
} as Widget.LabelProps),
new Widget.Icon({
css: "margin-right: 6px;",
icon: "edit-clear-all-symbolic"
} as Widget.IconProps),
new Widget.Label({
label: tr("clear")
} as Widget.LabelProps)
+23 -11
View File
@@ -7,8 +7,9 @@ import { Wallpaper } from "../../scripts/wallpaper";
function LockButton(): Widget.Button {
return new Widget.Button({
className: "nf",
label: "󰌾",
image: new Widget.Icon({
icon: "system-lock-screen-symbolic"
} as Widget.IconProps),
onClick: () => {
Windows.close("control-center");
AstalHyprland.get_default().dispatch("exec", "hyprlock");
@@ -18,8 +19,9 @@ function LockButton(): Widget.Button {
function ColorPickerButton(): Widget.Button {
return new Widget.Button({
className: "nf",
label: "󰴱",
image: new Widget.Icon({
icon: "color-select-symbolic"
} as Widget.IconProps),
onClick: () => AstalHyprland.get_default().dispatch(
"exec",
"sh $HOME/.config/hypr/scripts/color-picker.sh"
@@ -29,8 +31,9 @@ function ColorPickerButton(): Widget.Button {
function ScreenshotButton(): Widget.Button {
return new Widget.Button({
className: "nf",
label: "󰹑",
image: new Widget.Icon({
icon: "applets-screenshooter-symbolic"
} as Widget.IconProps),
onClick: () => {
Windows.close("control-center");
execAsync(`sh ${GLib.get_user_config_dir()}/hypr/scripts/screenshot.sh`);
@@ -40,8 +43,9 @@ function ScreenshotButton(): Widget.Button {
function SelectWallpaperButton(): Widget.Button {
return new Widget.Button({
className: "nf",
label: "󰸉",
image: new Widget.Icon({
icon: "preferences-desktop-wallpaper-symbolic"
} as Widget.IconProps),
onClick: () => {
Windows.close("control-center");
Wallpaper.getDefault().pickWallpaper();
@@ -51,8 +55,9 @@ function SelectWallpaperButton(): Widget.Button {
function LogoutButton(): Widget.Button {
return new Widget.Button({
className: "nf",
label: "󰗽",
image: new Widget.Icon({
icon: "system-shutdown-symbolic"
} as Widget.IconProps),
onClick: () => Windows.open("logout-menu")
} as Widget.ButtonProps);
}
@@ -76,14 +81,21 @@ export const QuickActions = () => {
tooltipText: "Host name",
label: GLib.get_host_name()
} as Widget.LabelProps),
new Widget.Box({
children: [
new Widget.Icon({
icon: "hourglass-symbolic"
} as Widget.IconProps),
new Widget.Label({
className: "uptime",
xalign: 0,
tooltipText: "Uptime",
onDestroy: () => uptime.drop(),
label: uptime().as((uptime: string) => `󰥔 ${uptime}`)
label: uptime()
} as Widget.LabelProps)
]
} as Widget.BoxProps)
]
} as Widget.BoxProps),
new Widget.Box({
orientation: Gtk.Orientation.HORIZONTAL,
+5 -10
View File
@@ -12,20 +12,18 @@ export function Sliders() {
className: "sliders",
orientation: Gtk.Orientation.VERTICAL,
expand: true,
spacing: 10,
children: [
new Widget.Box({
className: "sink speaker",
spacing: 3,
children: bind(Wireplumber.getWireplumber(), "defaultSpeaker").as((sink) => [
new Widget.Button({
className: "nf",
onClick: () => Wireplumber.getDefault().toggleMuteSink(),
children: [
new Widget.Icon ({
image: new Widget.Icon ({
icon: bind(sink, "volumeIcon").as((icon) =>
!Wireplumber.getDefault().isMutedSink() && Wireplumber.getDefault().getSinkVolume() > 0 ? icon : "audio-volume-muted-symbolic"),
css: "margin-right: 10px;"
} as Widget.IconProps),
]
} as Widget.ButtonProps),
new Widget.Slider({
drawValue: false,
@@ -46,17 +44,14 @@ export function Sliders() {
} as Widget.BoxProps),
new Widget.Box({
className: "source microphone",
spacing: 3,
children: bind(Wireplumber.getWireplumber(), "defaultMicrophone").as((source) => [
new Widget.Button({
className: "nf",
onClick: () => Wireplumber.getDefault().toggleMuteSource(),
children: [
new Widget.Icon ({
image: new Widget.Icon ({
icon: bind(source, "volumeIcon").as((icon) =>
!Wireplumber.getDefault().isMutedSource() && Wireplumber.getDefault().getSourceVolume() > 0 ? icon : "microphone-sensitivity-muted-symbolic"),
css: "margin-right: 10px;"
} as Widget.IconProps),
]
} as Widget.ButtonProps),
new Widget.Slider({
drawValue: false,
+48 -14
View File
@@ -1,10 +1,12 @@
import { bind, Variable } from "astal";
import { bind, Gio, Variable } from "astal";
import { Gtk, Widget } from "astal/gtk3";
import AstalBluetooth from "gi://AstalBluetooth";
import { Page, PageButton } from "./Page";
import { tr } from "../../../i18n/intl";
import AstalHyprland from "gi://AstalHyprland";
import { Windows } from "../../../windows";
import { Notifications } from "../../../scripts/notifications";
import AstalNotifd from "gi://AstalNotifd";
export const BluetoothPage: (() => Page) = () => new Page({
id: "bluetooth",
@@ -13,9 +15,13 @@ export const BluetoothPage: (() => Page) = () => new Page({
className: "bluetooth",
headerButtons: [
new Widget.Button({
className: "discover nf",
label: bind(AstalBluetooth.get_default().adapter, "discovering").as((discovering) =>
!discovering ? '󰑓' : '󰙦'),
className: "discover",
image: new Widget.Icon({
icon: bind(AstalBluetooth.get_default().adapter, "discovering").as((discovering) =>
!discovering ?
"arrow-circular-top-right-symbolic"
: "media-playback-stop-symbolic")
} as Widget.IconProps),
tooltipText: bind(AstalBluetooth.get_default().adapter, "discovering").as((discovering) =>
!discovering ?
tr("control_center.pages.bluetooth.start_discovering")
@@ -117,8 +123,11 @@ function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget {
bind(dev, "trusted")
], (connected, paired, trusted) => paired ? [
new Widget.Button({
className: "nf",
label: connected ? '󰅖' : "󰢃",
image: new Widget.Icon({
icon: connected ?
"list-remove-symbolic"
: "user-trash-symbolic"
} as Widget.IconProps),
tooltipText: tr(connected ? "disconnect" : "control_center.pages.bluetooth.unpair_device"),
onClick: () => {
if(!connected) {
@@ -130,8 +139,11 @@ function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget {
},
} as Widget.ButtonProps),
new Widget.Button({
className: "nf",
label: trusted ? "󰫜" : "󰫚",
image: new Widget.Icon({
icon: trusted ?
"shield-safe-symbolic"
: "shield-danger-symbolic"
} as Widget.IconProps),
tooltipText: tr(`control_center.pages.bluetooth.${trusted ? "un": ""}trust_device`),
onClick: () => dev.set_trusted(!trusted)
} as Widget.ButtonProps)
@@ -141,15 +153,36 @@ function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget {
className: bind(dev, "connected").as((connected) => connected ? "connected" : ""),
title: bind(dev, "alias").as(alias => alias ?? "Unknown Device"),
icon: dev.icon ?? "bluetooth-active-symbolic",
description: bind(dev, "connecting").as(connecting =>
connecting ? `${tr("connecting")}...` : ""),
tooltipText: bind(dev, "connected").as(connected => !connected ?
tr("connect")
: ""),
onDestroy: () => devActions.drop(),
onClick: () => {
if(dev.connected) return;
if(!dev.paired) dev.pair();
dev.connect_device(null);
let skipConnection: boolean = false;
if(!dev.paired)
(async () => dev.pair())().catch((err: Gio.IOErrorEnum) => {
skipConnection = true;
Notifications.getDefault().sendNotification({
appName: "bluetooth",
summary: "Device pairing error",
body: `Couldn't connect to ${dev.alias ?? dev.name}, an error occurred: ${err.message || err.stack}`,
urgency: AstalNotifd.Urgency.NORMAL
})
});
if(!skipConnection)
(async () => dev.connect_device(null))().catch((err: Gio.IOErrorEnum) =>
Notifications.getDefault().sendNotification({
appName: "bluetooth",
summary: "Device connection error",
body: `Couldn't connect to ${dev.alias ?? dev.name}, an error occurred: ${err.message || err.stack}`,
urgency: AstalNotifd.Urgency.NORMAL
})
);
},
endWidget: new Widget.Box({
visible: bind(dev, "batteryPercentage").as((batt: number) =>
@@ -160,12 +193,13 @@ function DeviceWidget(dev: AstalBluetooth.Device): Gtk.Widget {
children: [
new Widget.Label({
halign: Gtk.Align.END,
label: bind(dev, "batteryPercentage").as((bat: number) =>
`${Math.floor(bat * 100)}%`)
label: bind(dev, "batteryPercentage").as((batt: number) =>
`${Math.floor(batt * 100)}%`)
} as Widget.LabelProps),
new Widget.Icon({
icon: "battery-symbolic",
css: "font-size: 18px; margin-left: 6px;"
icon: bind(dev, "batteryPercentage").as(batt =>
`battery-level-${Math.floor(batt * 100)}-symbolic`),
css: "font-size: 16px; margin-left: 6px;"
} as Widget.IconProps)
]
} as Widget.BoxProps)
+6 -5
View File
@@ -13,11 +13,12 @@ export const PageNetwork: (() => Page) = () => new Page({
className: "network",
headerButtons: [
new Widget.Button({
className: "reload nf",
label: "󰑓",
visible: bind(AstalNetwork.get_default(), "primary").as(
(primary: AstalNetwork.Primary) => primary === AstalNetwork.Primary.WIFI
),
className: "reload",
image: new Widget.Icon({
icon: "arrow-circular-top-right-symbolic"
} as Widget.IconProps),
visible: bind(AstalNetwork.get_default(), "primary").as((primary) =>
primary === AstalNetwork.Primary.WIFI),
tooltipText: "Re-scan connections",
onClick: () => AstalNetwork.get_default().wifi.scan()
} as Widget.ButtonProps)
+18 -2
View File
@@ -177,6 +177,7 @@ export function PageButton(props: {
icon?: string | Binding<string>;
title: string | Binding<string>;
endWidget?: Gtk.Widget | Binding<Gtk.Widget>;
description?: string | Binding<string>;
extraButtons?: Array<Widget.Button> | Binding<Array<Gtk.Widget>>;
onDestroy?: (self: Widget.Box) => void;
onClick?: (self: Widget.Button) => void;
@@ -203,13 +204,28 @@ export function PageButton(props: {
visible: props.icon,
css: "font-size: 20px; margin-right: 6px;"
} as Widget.IconProps),
new Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
expand: true,
children: [
new Widget.Label({
className: "title",
halign: Gtk.Align.START,
hexpand: true,
xalign: 0,
truncate: true,
label: props.title
} as Widget.LabelProps),
new Widget.Label({
className: "description",
xalign: 0,
visible: (props.description instanceof Binding) ?
props.description.as(Boolean)
: Boolean(props.description),
label: props.description,
truncate: true,
tooltipText: props.description
} as Widget.LabelProps)
]
} as Widget.BoxProps),
new Widget.Box({
visible: (props.endWidget instanceof Binding) ?
props.endWidget.as(Boolean)
+4 -1
View File
@@ -11,7 +11,10 @@ export const TileBluetooth = () => {
bind(AstalBluetooth.get_default(), "isConnected")
],
(powered: boolean, isConnected: boolean) =>
powered ? ( isConnected ? "󰂱" : "󰂯" ) : "󰂲"
powered ? ( isConnected ?
"bluetooth-active-symbolic"
: "bluetooth-symbolic"
) : "bluetooth-disabled-symbolic"
);
return Tile({
title: "Bluetooth",
@@ -9,7 +9,7 @@ export const TileDND = Tile({
(dnd: boolean) => dnd ? tr("control_center.tiles.enabled") : tr("control_center.tiles.disabled")),
onToggledOff: () => Notifications.getDefault().getNotifd().dontDisturb = false,
onToggledOn: () => Notifications.getDefault().getNotifd().dontDisturb = true,
icon: "󰍶",
icon: "minus-circle-filled-symbolic",
iconSize: 16,
toggleState: Notifications.getDefault().getNotifd().dontDisturb
});
+5 -6
View File
@@ -33,8 +33,7 @@ export const TileNetwork = () => new Widget.Box({
onToggledOn: () => wifi.set_enabled(true),
onToggledOff: () => wifi.set_enabled(false),
onClickMore: () => TilesPages?.toggle(PageNetwork()),
icon: "󰤨",
iconSize: 16,
icon: "network-wireless-signal-excellent-symbolic",
toggleState: bind(wifi, "enabled")
} as TileProps)();
@@ -57,12 +56,12 @@ export const TileNetwork = () => new Widget.Box({
icon: bind(wired, "internet").as((internet: AstalNetwork.Internet) => {
switch(internet) {
case AstalNetwork.Internet.CONNECTED:
return '󰛳';
return "network-wired-symbolic";
case AstalNetwork.Internet.DISCONNECTED:
return '󰲛';
return "network-wired-disconnected-symbolic";
}
return "󰛵";
return "network-wired-no-route-symbolic";
}),
iconSize: 16,
toggleState: bind(wired, "internet").as((internet: AstalNetwork.Internet) =>
@@ -78,7 +77,7 @@ export const TileNetwork = () => new Widget.Box({
onToggledOn: () => execAsync("nmcli n on"),
onToggledOff: () => execAsync("nmcli n off"),
onClickMore: () => TilesPages?.toggle(PageNetwork()),
icon: "󰲛",
icon: "network-wired-disconnected-symbolic",
iconSize: 16,
toggleState: bind(wired, "internet").as((internet: AstalNetwork.Internet) =>
internet === AstalNetwork.Internet.CONNECTING || internet === AstalNetwork.Internet.CONNECTED)
@@ -10,7 +10,7 @@ import { Widget } from "astal/gtk3";
export const TileNightLight = () => isInstalled("hyprsunset") ?
Tile({
title: tr("control_center.tiles.night_light.title"),
icon: "󰖔",
icon: "weather-clear-night-symbolic",
description: Variable.derive([
bind(NightLight.getDefault(), "temperature"),
bind(NightLight.getDefault(), "gamma")
+2 -5
View File
@@ -18,19 +18,16 @@ export const TileRecording = () => {
const startedAtSeconds = dateTime.to_unix() - Recording.getDefault().startedAt!.to_unix();
if(startedAtSeconds <= 0) return "00:00";
const hours = Math.floor(startedAtSeconds / 120);
const minutes = Math.floor(startedAtSeconds / 60);
const seconds = Math.floor(startedAtSeconds % 60);
return `${ hours > 0 ? `${hours < 10 ? `0${hours}` : hours }:` : ""
}${ minutes < 10 ? `0${minutes}` : minutes
}:${ seconds < 10 ? `0${seconds}` : seconds }`;
return `${ minutes < 10 ? `0${minutes}` : minutes }:${ seconds < 10 ? `0${seconds}` : seconds }`;
});
return Tile({
title: tr("control_center.tiles.recording.title") || "Screen Recording",
description: description(),
icon: "󰻂",
icon: "media-record-symbolic",
visible: wfRecorderInstalled,
onDestroy: () => description.drop(),
onToggledOff: () => Recording.getDefault().stopRecording(),
+8 -5
View File
@@ -65,11 +65,14 @@ export function Tile(props: TileProps): (() => Gtk.Widget) {
expand: true,
hexpand: true,
children: [
new Widget.Label({
className: "icon nf",
label: props.icon || "icon",
css: `label { font-size: ${props.iconSize || 12}px; }`
} as Widget.LabelProps),
new Widget.Icon({
className: "icon",
icon: props.icon,
visible: (props.icon instanceof Binding) ?
props.icon.as(Boolean)
: Boolean(props.icon),
css: `font-size: ${props.iconSize ?? 16}px;`
} as Widget.IconProps),
new Widget.Box({
className: "text",
orientation: Gtk.Orientation.VERTICAL,
+16 -8
View File
@@ -52,8 +52,10 @@ export const LogoutMenu = (mon: number) => new Widget.Window({
height_request: 360,
children: [
new Widget.Button({
className: "poweroff nf",
label: "󰐥",
className: "poweroff",
image: new Widget.Icon({
icon: "system-shutdown-symbolic"
} as Widget.IconProps),
onClick: () => AskPopup({
title: "Power Off",
text: "Are you sure you want to power off? Unsaved work will be lost.",
@@ -64,8 +66,10 @@ export const LogoutMenu = (mon: number) => new Widget.Window({
})
} as Widget.ButtonProps),
new Widget.Button({
className: "reboot nf",
label: "󰜉",
className: "reboot",
image: new Widget.Icon({
icon: "arrow-circular-top-right-symbolic"
} as Widget.IconProps),
onClick: () => AskPopup({
title: "Reboot",
text: "Are you sure you want to Reboot? Unsaved work will be lost.",
@@ -76,8 +80,10 @@ export const LogoutMenu = (mon: number) => new Widget.Window({
})
} as Widget.ButtonProps),
new Widget.Button({
className: "suspend nf",
label: "󰤄",
className: "suspend",
image: new Widget.Icon({
icon: "weather-clear-night-symbolic"
} as Widget.IconProps),
onClick: () => AskPopup({
title: "Suspend",
text: "Are you sure you want to Suspend?",
@@ -85,8 +91,10 @@ export const LogoutMenu = (mon: number) => new Widget.Window({
})
} as Widget.ButtonProps),
new Widget.Button({
className: "logout nf",
label: "󰗽",
className: "logout",
image: new Widget.Icon({
icon: "system-log-out-symbolic"
} as Widget.IconProps),
onClick: () => AskPopup({
title: "Log out",
text: "Are you sure you want to log out? Your session will be ended.",