feat: update ags

This commit is contained in:
2023-12-30 20:38:47 +01:00
parent d2f9104fe4
commit 32d78e57a3
213 changed files with 8155 additions and 9843 deletions

View File

@@ -0,0 +1,73 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Header from "./widgets/Header.js";
import PopupWindow from "../misc/PopupWindow.js";
import { Volume, Microhone, SinkSelector, AppMixer } from "./widgets/Volume.js";
import { NetworkToggle, WifiSelection } from "./widgets/Network.js";
import { BluetoothToggle, BluetoothDevices } from "./widgets/Bluetooth.js";
import { ThemeToggle, ThemeSelector } from "./widgets/Theme.js";
import { ProfileToggle, ProfileSelector } from "./widgets/AsusProfile.js";
import Media from "./widgets/Media.js";
import Brightness from "./widgets/Brightness.js";
import DND from "./widgets/DND.js";
import MicMute from "./widgets/MicMute.js";
import options from "../options.js";
const Row = (toggles = [], menus = []) =>
Widget.Box({
vertical: true,
children: [
Widget.Box({
class_name: "row horizontal",
children: toggles,
}),
...menus,
],
});
const Homogeneous = (toggles) =>
Widget.Box({
homogeneous: true,
children: toggles,
});
export default () =>
PopupWindow({
name: "quicksettings",
connections: [
[
options.bar.position,
(self) => {
self.anchor = ["right", options.bar.position.value];
if (options.bar.position.value === "top")
self.transition = "slide_down";
if (options.bar.position.value === "bottom")
self.transition = "slide_up";
},
],
],
child: Widget.Box({
vertical: true,
children: [
Header(),
Widget.Box({
class_name: "sliders-box vertical",
vertical: true,
children: [
Row([Volume()], [SinkSelector(), AppMixer()]),
Microhone(),
Brightness(),
],
}),
Row(
[Homogeneous([NetworkToggle(), BluetoothToggle()]), DND()],
[WifiSelection(), BluetoothDevices()],
),
Row(
[Homogeneous([ProfileToggle(), ThemeToggle()]), MicMute()],
[ProfileSelector(), ThemeSelector()],
),
Media(),
],
}),
});

View File

@@ -0,0 +1,149 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import App from "resource:///com/github/Aylur/ags/app.js";
import Variable from "resource:///com/github/Aylur/ags/variable.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import icons from "../icons.js";
/** name of the currently opened menu */
export const opened = Variable("");
App.connect("window-toggled", (_, name, visible) => {
if (name === "quicksettings" && !visible)
Utils.timeout(500, () => (opened.value = ""));
});
/**
* @param {string} name - menu name
* @param {(() => void) | false=} activate
*/
export const Arrow = (name, activate) => {
let deg = 0;
let iconOpened = false;
return Widget.Button({
child: Widget.Icon({
icon: icons.ui.arrow.right,
connections: [
[
opened,
(icon) => {
if (
(opened.value === name && !iconOpened) ||
(opened.value !== name && iconOpened)
) {
const step = opened.value === name ? 10 : -10;
iconOpened = !iconOpened;
for (let i = 0; i < 9; ++i) {
Utils.timeout(15 * i, () => {
deg += step;
icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`);
});
}
}
},
],
],
}),
on_clicked: () => {
opened.value = opened.value === name ? "" : name;
if (typeof activate === "function") activate();
},
});
};
/**
* @param {Object} o
* @param {string} o.name - menu name
* @param {import('gi://Gtk').Gtk.Widget} o.icon
* @param {import('gi://Gtk').Gtk.Widget} o.label
* @param {() => void} o.activate
* @param {() => void} o.deactivate
* @param {boolean=} o.activateOnArrow
* @param {[import('gi://GObject').GObject.Object, () => boolean]} o.connection
*/
export const ArrowToggleButton = ({
name,
icon,
label,
activate,
deactivate,
activateOnArrow = true,
connection: [service, condition],
}) =>
Widget.Box({
class_name: "toggle-button",
connections: [
[
service,
(box) => {
box.toggleClassName("active", condition());
},
],
],
children: [
Widget.Button({
child: Widget.Box({
hexpand: true,
class_name: "label-box horizontal",
children: [icon, label],
}),
on_clicked: () => {
if (condition()) {
deactivate();
if (opened.value === name) opened.value = "";
} else {
activate();
}
},
}),
Arrow(name, activateOnArrow && activate),
],
});
/**
* @param {Object} o
* @param {string} o.name - menu name
* @param {import('gi://Gtk').Gtk.Widget} o.icon
* @param {import('gi://Gtk').Gtk.Widget} o.title
* @param {import('gi://Gtk').Gtk.Widget[]} o.content
*/
export const Menu = ({ name, icon, title, content }) =>
Widget.Revealer({
transition: "slide_down",
binds: [["reveal-child", opened, "value", (v) => v === name]],
child: Widget.Box({
class_names: ["menu", name],
vertical: true,
children: [
Widget.Box({
class_name: "title horizontal",
children: [icon, title],
}),
Widget.Separator(),
...content,
],
}),
});
/**
* @param {Object} o
* @param {import('gi://Gtk').Gtk.Widget} o.icon
* @param {() => void} o.toggle
* @param {[import('gi://GObject').GObject.Object, () => boolean]} o.connection
*/
export const SimpleToggleButton = ({
icon,
toggle,
connection: [service, condition],
}) =>
Widget.Button({
class_name: "simple-toggle",
connections: [
[
service,
(box) => {
box.toggleClassName("active", condition());
},
],
],
child: icon,
on_clicked: toggle,
});

View File

@@ -0,0 +1,61 @@
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../../icons.js";
import Asusctl from "../../services/asusctl.js";
import { ArrowToggleButton, Menu } from "../ToggleButton.js";
export const ProfileToggle = () =>
ArrowToggleButton({
name: "asusctl-profile",
icon: Widget.Icon({
binds: [["icon", Asusctl, "profile", (p) => icons.asusctl.profile[p]]],
}),
label: Widget.Label({
binds: [["label", Asusctl, "profile"]],
}),
connection: [Asusctl, () => Asusctl.profile !== "Balanced"],
activate: () => Asusctl.setProfile("Quiet"),
deactivate: () => Asusctl.setProfile("Balanced"),
activateOnArrow: false,
});
export const ProfileSelector = () =>
Menu({
name: "asusctl-profile",
icon: Widget.Icon({
binds: [["icon", Asusctl, "profile", (p) => icons.asusctl.profile[p]]],
}),
title: Widget.Label("Profile Selector"),
content: [
Widget.Box({
vertical: true,
hexpand: true,
children: [
Widget.Box({
vertical: true,
children: Asusctl.profiles.map((prof) =>
Widget.Button({
on_clicked: () => Asusctl.setProfile(prof),
child: Widget.Box({
children: [
Widget.Icon(icons.asusctl.profile[prof]),
Widget.Label(prof),
],
}),
}),
),
}),
],
}),
Widget.Separator(),
Widget.Button({
on_clicked: () => Utils.execAsync("rog-control-center"),
child: Widget.Box({
children: [
Widget.Icon(icons.ui.settings),
Widget.Label("Rog Control Center"),
],
}),
}),
],
});

View File

@@ -0,0 +1,95 @@
import Bluetooth from "resource:///com/github/Aylur/ags/service/bluetooth.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../../icons.js";
import { Menu, ArrowToggleButton } from "../ToggleButton.js";
export const BluetoothToggle = () =>
ArrowToggleButton({
name: "bluetooth",
icon: Widget.Icon({
connections: [
[
Bluetooth,
(icon) => {
icon.icon = Bluetooth.enabled
? icons.bluetooth.enabled
: icons.bluetooth.disabled;
},
],
],
}),
label: Widget.Label({
truncate: "end",
connections: [
[
Bluetooth,
(label) => {
if (!Bluetooth.enabled) return (label.label = "Disabled");
if (Bluetooth.connectedDevices.length === 0)
return (label.label = "Not Connected");
if (Bluetooth.connectedDevices.length === 1)
return (label.label = Bluetooth.connectedDevices[0].alias);
label.label = `${Bluetooth.connectedDevices.length} Connected`;
},
],
],
}),
connection: [Bluetooth, () => Bluetooth.enabled],
deactivate: () => (Bluetooth.enabled = false),
activate: () => (Bluetooth.enabled = true),
});
const DeviceItem = (device) =>
Widget.Box({
children: [
Widget.Icon(device.icon_name + "-symbolic"),
Widget.Label(device.name),
Widget.Label({
label: `${device.battery_percentage}%`,
binds: [["visible", device, "battery-percentage", (p) => p > 0]],
}),
Widget.Box({ hexpand: true }),
Widget.Spinner({
binds: [
["active", device, "connecting"],
["visible", device, "connecting"],
],
}),
Widget.Switch({
active: device.connected,
binds: [["visible", device, "connecting", (c) => !c]],
connections: [
[
"notify::active",
({ active }) => {
device.setConnection(active);
},
],
],
}),
],
});
export const BluetoothDevices = () =>
Menu({
name: "bluetooth",
icon: Widget.Icon(icons.bluetooth.disabled),
title: Widget.Label("Bluetooth"),
content: [
Widget.Box({
hexpand: true,
vertical: true,
binds: [
[
"children",
Bluetooth,
"devices",
(ds) => ds.filter((d) => d.name).map(DeviceItem),
],
],
}),
],
});

View File

@@ -0,0 +1,29 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../../icons.js";
import Brightness from "../../services/brightness.js";
const BrightnessSlider = () =>
Widget.Slider({
draw_value: false,
hexpand: true,
binds: [["value", Brightness, "screen"]],
on_change: ({ value }) => (Brightness.screen = value),
});
export default () =>
Widget.Box({
children: [
Widget.Button({
child: Widget.Icon(icons.brightness.indicator),
binds: [
[
"tooltip-text",
Brightness,
"screen",
(v) => `Screen Brightness: ${Math.floor(v * 100)}%`,
],
],
}),
BrightnessSlider(),
],
});

View File

@@ -0,0 +1,23 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Notifications from "resource:///com/github/Aylur/ags/service/notifications.js";
import icons from "../../icons.js";
import { SimpleToggleButton } from "../ToggleButton.js";
export default () =>
SimpleToggleButton({
icon: Widget.Icon({
connections: [
[
Notifications,
(icon) => {
icon.icon = Notifications.dnd
? icons.notifications.silent
: icons.notifications.noisy;
},
"notify::dnd",
],
],
}),
toggle: () => (Notifications.dnd = !Notifications.dnd),
connection: [Notifications, () => Notifications.dnd],
});

View File

@@ -0,0 +1,48 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Battery from "resource:///com/github/Aylur/ags/service/battery.js";
import PowerMenu from "../../services/powermenu.js";
import Lockscreen from "../../services/lockscreen.js";
import Avatar from "../../misc/Avatar.js";
import icons from "../../icons.js";
import { openSettings } from "../../settings/theme.js";
import { uptime } from "../../variables.js";
export default () =>
Widget.Box({
class_name: "header horizontal",
children: [
Avatar(),
Widget.Box({
hpack: "end",
vpack: "center",
hexpand: true,
children: [
Widget.Box({
class_name: "battery horizontal",
children: [
Widget.Icon({ binds: [["icon", Battery, "icon-name"]] }),
Widget.Label({
binds: [["label", Battery, "percent", (p) => `${p}%`]],
}),
],
}),
Widget.Label({
class_name: "uptime",
binds: [["label", uptime, "value", (v) => `up: ${v}`]],
}),
Widget.Button({
on_clicked: openSettings,
child: Widget.Icon(icons.ui.settings),
}),
Widget.Button({
on_clicked: () => Lockscreen.lockscreen(),
child: Widget.Icon(icons.lock),
}),
Widget.Button({
on_clicked: () => PowerMenu.action("shutdown"),
child: Widget.Icon(icons.powermenu.shutdown),
}),
],
}),
],
});

View File

@@ -0,0 +1,106 @@
import Mpris from "resource:///com/github/Aylur/ags/service/mpris.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as mpris from "../../misc/mpris.js";
import options from "../../options.js";
/** @param {import('types/service/mpris').MprisPlayer} player */
const Footer = (player) =>
Widget.CenterBox({
class_name: "footer-box",
children: [
Widget.Box({
class_name: "position",
children: [
mpris.PositionLabel(player),
mpris.Slash(player),
mpris.LengthLabel(player),
],
}),
Widget.Box({
class_name: "controls",
children: [
mpris.ShuffleButton(player),
mpris.PreviousButton(player),
mpris.PlayPauseButton(player),
mpris.NextButton(player),
mpris.LoopButton(player),
],
}),
mpris.PlayerIcon(player, {
symbolic: false,
hexpand: true,
hpack: "end",
}),
],
});
/** @param {import('types/service/mpris').MprisPlayer} player */
const TextBox = (player) =>
Widget.Box({
children: [
mpris.CoverArt(player, {
hpack: "end",
hexpand: false,
}),
Widget.Box({
hexpand: true,
vertical: true,
class_name: "labels",
children: [
mpris.TitleLabel(player, {
xalign: 0,
justification: "left",
wrap: true,
}),
mpris.ArtistLabel(player, {
xalign: 0,
justification: "left",
wrap: true,
}),
],
}),
],
});
/** @param {import('types/service/mpris').MprisPlayer} player */
const PlayerBox = (player) =>
Widget.Box({
class_name: `player ${player.name}`,
child: mpris.BlurredCoverArt(player, {
hexpand: true,
child: Widget.Box({
hexpand: true,
vertical: true,
children: [
TextBox(player),
mpris.PositionSlider(player),
Footer(player),
],
}),
}),
});
export default () =>
Widget.Box({
vertical: true,
class_name: "media vertical",
connections: [
[
"draw",
(self) => {
self.visible = Mpris.players.length > 0;
},
],
],
binds: [
[
"children",
Mpris,
"players",
(ps) =>
ps
.filter((p) => !options.mpris.black_list.value.includes(p.identity))
.map(PlayerBox),
],
],
});

View File

@@ -0,0 +1,23 @@
import Audio from "resource:///com/github/Aylur/ags/service/audio.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import icons from "../../icons.js";
import { SimpleToggleButton } from "../ToggleButton.js";
export default () =>
SimpleToggleButton({
icon: Widget.Icon({
connections: [
[
Audio,
(icon) => {
icon.icon = Audio.microphone?.is_muted
? icons.audio.mic.muted
: icons.audio.mic.high;
},
"microphone-changed",
],
],
}),
toggle: () => (Audio.microphone.is_muted = !Audio.microphone.is_muted),
connection: [Audio, () => Audio.microphone?.is_muted],
});

View File

@@ -0,0 +1,91 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import Network from "resource:///com/github/Aylur/ags/service/network.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import icons from "../../icons.js";
import { Menu, ArrowToggleButton } from "../ToggleButton.js";
import Applications from "resource:///com/github/Aylur/ags/service/applications.js";
export const NetworkToggle = () =>
ArrowToggleButton({
name: "network",
icon: Widget.Icon({
connections: [
[
Network,
(icon) => {
icon.icon = Network.wifi.icon_name || "";
},
],
],
}),
label: Widget.Label({
truncate: "end",
connections: [
[
Network,
(label) => {
label.label = Network.wifi.ssid || "Not Connected";
},
],
],
}),
connection: [Network, () => Network.wifi.enabled],
deactivate: () => (Network.wifi.enabled = false),
activate: () => {
Network.wifi.enabled = true;
Network.wifi.scan();
},
});
export const WifiSelection = () =>
Menu({
name: "network",
icon: Widget.Icon({
connections: [
[
Network,
(icon) => {
icon.icon = Network.wifi.icon_name;
},
],
],
}),
title: Widget.Label("Wifi Selection"),
content: [
Widget.Box({
vertical: true,
connections: [
[
Network,
(box) =>
(box.children = Network.wifi?.access_points.map((ap) =>
Widget.Button({
on_clicked: () =>
Utils.execAsync(`nmcli device wifi connect ${ap.bssid}`),
child: Widget.Box({
children: [
Widget.Icon(ap.iconName),
Widget.Label(ap.ssid || ""),
ap.active &&
Widget.Icon({
icon: icons.ui.tick,
hexpand: true,
hpack: "end",
}),
],
}),
}),
)),
],
],
}),
Widget.Separator(),
Widget.Button({
on_clicked: () =>
Applications.query("gnome-control-center")?.[0].launch(),
child: Widget.Box({
children: [Widget.Icon(icons.ui.settings), Widget.Label("Network")],
}),
}),
],
});

View File

@@ -0,0 +1,57 @@
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import { ArrowToggleButton, Menu, opened } from "../ToggleButton.js";
import themes from "../../themes.js";
import icons from "../../icons.js";
import options from "../../options.js";
import { setTheme, openSettings } from "../../settings/theme.js";
export const ThemeToggle = () =>
ArrowToggleButton({
name: "theme",
icon: Widget.Label({ binds: [["label", options.theme.icon]] }),
label: Widget.Label({ binds: [["label", options.theme.name]] }),
connection: [opened, () => opened.value === "theme"],
activate: () => opened.setValue("theme"),
activateOnArrow: false,
deactivate: () => {},
});
export const ThemeSelector = () =>
Menu({
name: "theme",
icon: Widget.Label({
binds: [["label", options.theme.icon]],
}),
title: Widget.Label("Theme Selector"),
content: [
...themes.map(({ name, icon }) =>
Widget.Button({
on_clicked: () => setTheme(name),
child: Widget.Box({
children: [
Widget.Label(icon),
Widget.Label(name),
Widget.Icon({
icon: icons.ui.tick,
hexpand: true,
hpack: "end",
binds: [
["visible", options.theme.name, "value", (v) => v === name],
],
}),
],
}),
}),
),
Widget.Separator(),
Widget.Button({
on_clicked: openSettings,
child: Widget.Box({
children: [
Widget.Icon(icons.ui.settings),
Widget.Label("Theme Settings"),
],
}),
}),
],
});

View File

@@ -0,0 +1,194 @@
import Audio from "resource:///com/github/Aylur/ags/service/audio.js";
import Widget from "resource:///com/github/Aylur/ags/widget.js";
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
import icons from "../../icons.js";
import FontIcon from "../../misc/FontIcon.js";
import { getAudioTypeIcon } from "../../utils.js";
import { Arrow } from "../ToggleButton.js";
import { Menu } from "../ToggleButton.js";
/** @param {'speaker' | 'microphone'=} type */
const VolumeIndicator = (type = "speaker") =>
Widget.Button({
on_clicked: () => (Audio[type].is_muted = !Audio[type].is_muted),
child: Widget.Icon({
connections: [
[
Audio,
(icon) => {
if (!Audio[type]) return;
icon.icon =
type === "speaker"
? getAudioTypeIcon(Audio[type].icon_name || "")
: icons.audio.mic.high;
icon.tooltip_text = `Volume ${Math.floor(
Audio[type].volume * 100,
)}%`;
},
`${type}-changed`,
],
],
}),
});
/** @param {'speaker' | 'microphone'=} type */
const VolumeSlider = (type = "speaker") =>
Widget.Slider({
hexpand: true,
draw_value: false,
on_change: ({ value }) => (Audio[type].volume = value),
connections: [
[
Audio,
(slider) => {
slider.value = Audio[type]?.volume;
},
`${type}-changed`,
],
],
});
export const Volume = () =>
Widget.Box({
children: [
VolumeIndicator("speaker"),
VolumeSlider("speaker"),
Widget.Box({
vpack: "center",
child: Arrow("sink-selector"),
}),
Widget.Box({
vpack: "center",
child: Arrow("app-mixer"),
connections: [
[
Audio,
(box) => {
box.visible = Audio.apps.length > 0;
},
],
],
}),
],
});
export const Microhone = () =>
Widget.Box({
class_name: "slider horizontal",
binds: [["visible", Audio, "recorders", (r) => r.length > 0]],
children: [VolumeIndicator("microphone"), VolumeSlider("microphone")],
});
/** @param {import('types/service/audio').Stream} stream */
const MixerItem = (stream) =>
Widget.Box({
hexpand: true,
class_name: "mixer-item horizontal",
children: [
Widget.Icon({
binds: [["tooltipText", stream, "name"]],
connections: [
[
stream,
(icon) => {
icon.icon = Utils.lookUpIcon(stream.name || "")
? stream.name || ""
: icons.mpris.fallback;
},
],
],
}),
Widget.Box({
vertical: true,
children: [
Widget.Label({
xalign: 0,
truncate: "end",
binds: [["label", stream, "description"]],
}),
Widget.Slider({
hexpand: true,
draw_value: false,
binds: [["value", stream, "volume"]],
on_change: ({ value }) => (stream.volume = value),
}),
],
}),
Widget.Label({
xalign: 1,
connections: [
[
stream,
(l) => {
l.label = `${Math.floor(stream.volume * 100)}%`;
},
],
],
}),
],
});
/** @param {import('types/service/audio').Stream} stream */
const SinkItem = (stream) =>
Widget.Button({
hexpand: true,
on_clicked: () => (Audio.speaker = stream),
child: Widget.Box({
children: [
Widget.Icon({
icon: getAudioTypeIcon(stream.icon_name || ""),
tooltip_text: stream.icon_name,
}),
Widget.Label(
(stream.description || "").split(" ").slice(0, 4).join(" "),
),
Widget.Icon({
icon: icons.ui.tick,
hexpand: true,
hpack: "end",
binds: [["visible", Audio, "speaker", (s) => s === stream]],
}),
],
}),
});
const SettingsButton = () =>
Widget.Button({
on_clicked: () => Utils.execAsync("pavucontrol"),
hexpand: true,
child: Widget.Box({
children: [Widget.Icon(icons.ui.settings), Widget.Label("Settings")],
}),
});
export const AppMixer = () =>
Menu({
name: "app-mixer",
icon: FontIcon(icons.audio.mixer),
title: Widget.Label("App Mixer"),
content: [
Widget.Box({
vertical: true,
binds: [["children", Audio, "apps", (a) => a.map(MixerItem)]],
}),
Widget.Separator(),
SettingsButton(),
],
});
export const SinkSelector = () =>
Menu({
name: "sink-selector",
icon: Widget.Icon(icons.audio.type.headset),
title: Widget.Label("Sink Selector"),
content: [
Widget.Box({
vertical: true,
binds: [["children", Audio, "speakers", (s) => s.map(SinkItem)]],
}),
Widget.Separator(),
SettingsButton(),
],
});