[AGS] Prepare bar

This commit is contained in:
Janis Hutz 2025-04-19 15:07:22 +02:00
parent 196d553627
commit a9c7b7d7ee
25 changed files with 1172 additions and 107 deletions

886
config/ags/bar/dump Normal file
View File

@ -0,0 +1,886 @@
import GLib from "gi://GLib";
import Astal from "gi://Astal";
import AstalHyprland from "gi://AstalHyprland";
import Gio from "gi://Gio";
import Gtk from "gi://Gtk";
import Gdk from "gi://Gdk";
import { exec } from "gi://GLib";
function getSystemStats() {
const cpu = exec("sh -c 'top -bn1 | grep \"Cpu(s)\" | sed \"s/.*, *\\([0-9.]*\\)%* id.*/\\1/\" | awk \"{print 100 - $1}\"'");
const memory = exec("free | grep Mem | awk '{print $3/$2 * 100.0}'");
const brightness = exec("brightnessctl g");
const battery = exec("cat /sys/class/power_supply/BAT0/capacity");
return { cpu, memory, brightness, battery };
}
function getWorkspaceNumber() {
return AstalHyprland.get_default().get_active_workspace().get_id();
}
function getWindowTitle() {
return exec("xdotool getactivewindow getwindowname");
}
function getDateTime() {
return GLib.DateTime.new_now_local().format("%a %b %d, %Y %H:%M:%S");
}
function createQuickActionMenu() {
const menu = new Gtk.Menu();
const volumeItem = new Gtk.MenuItem({ label: "🔊 Volume" });
const micItem = new Gtk.MenuItem({ label: "🎙️ Microphone" });
const wifiItem = new Gtk.MenuItem({ label: "📶 WiFi" });
const powerItem = new Gtk.MenuItem({ label: "🔌 Power" });
menu.append(volumeItem);
menu.append(micItem);
menu.append(wifiItem);
menu.append(powerItem);
volumeItem.show();
micItem.show();
wifiItem.show();
powerItem.show();
return menu;
}
function Bar(gdkmonitor) {
const { cpu, memory, brightness, battery } = getSystemStats();
const workspaceNumber = getWorkspaceNumber();
const windowTitle = getWindowTitle();
const dateTime = getDateTime();
const quickActionMenu = createQuickActionMenu();
return (
<window gdkmonitor={gdkmonitor}>
<box orientation="horizontal" spacing={10}>
<box>
<label>{dateTime}</label>
<label>{`CPU: ${cpu}%`}</label>
<label>{`Mem: ${memory}%`}</label>
<label>{`Brightness: ${brightness}%`}</label>
<label>{`Battery: ${battery}%`}</label>
<label>{`Workspace: ${workspaceNumber}`}</label>
</box>
<label>{windowTitle}</label>
<box>
<tray />
<button icon="quickaction" menu={quickActionMenu} />
</box>
</box>
</window>
);
}
function main() {
for (const m of AstalHyprland.get_default().get_monitors()) {
Bar(m.gdk_monitor);
}
}
App.start({ main });
import GLib from "gi://GLib";
import Astal from "gi://Astal";
import AstalHyprland from "gi://AstalHyprland";
import Gtk from "gi://Gtk";
import { exec } from "gi://GLib";
import { createQuickActionsMenu } from "./quickactions.js";
function getSystemStats() {
const cpu = exec("sh -c 'top -bn1 | grep \"Cpu(s)\" | sed \"s/.*, *\\([0-9.]*\\)%* id.*/\\1/\" | awk \"{print 100 - $1}\"'");
const memory = exec("free | grep Mem | awk '{print $3/$2 * 100.0}'");
const brightness = exec("brightnessctl g");
const battery = exec("cat /sys/class/power_supply/BAT0/capacity");
return { cpu, memory, brightness, battery };
}
function getWorkspaceNumber() {
return AstalHyprland.get_default().get_active_workspace().get_id();
}
function getWindowTitle() {
return exec("xdotool getactivewindow getwindowname");
}
function getDateTime() {
return GLib.DateTime.new_now_local().format("%a %b %d, %Y %H:%M:%S");
}
function Bar(gdkmonitor) {
const { cpu, memory, brightness, battery } = getSystemStats();
const workspaceNumber = getWorkspaceNumber();
const windowTitle = getWindowTitle();
const dateTime = getDateTime();
const quickActionMenu = createQuickActionsMenu();
return (
<window gdkmonitor={gdkmonitor}>
<box orientation="horizontal" spacing={10}>
<box>
<label>{dateTime}</label>
<label>{`CPU: ${cpu}%`}</label>
<label>{`Mem: ${memory}%`}</label>
<label>{`Brightness: ${brightness}%`}</label>
<label>{`Battery: ${battery}%`}</label>
<label>{`Workspace: ${workspaceNumber}`}</label>
</box>
<label>{windowTitle}</label>
<box>
<tray />
<button icon="quickaction" menu={quickActionMenu} />
</box>
</box>
</window>
);
}
function main() {
for (const m of AstalHyprland.get_default().get_monitors()) {
Bar(m.gdk_monitor);
}
}
App.start({ main });
import AstalNetwork from "gi://AstalNetwork";
import AstalBluetooth from "gi://AstalBluetooth";
import Gtk from "gi://Gtk";
function createQuickActionsMenu() {
const menu = new Gtk.Menu();
const wifiItem = new Gtk.MenuItem({ label: "📶 WiFi" });
const bluetoothItem = new Gtk.MenuItem({ label: "🔵 Bluetooth" });
const volumeItem = new Gtk.MenuItem({ label: "🔊 Volume" });
const micItem = new Gtk.MenuItem({ label: "🎙️ Microphone" });
const powerItem = new Gtk.MenuItem({ label: "🔌 Power" });
menu.append(wifiItem);
menu.append(bluetoothItem);
menu.append(volumeItem);
menu.append(micItem);
menu.append(powerItem);
wifiItem.connect("activate", () => toggleWiFi());
bluetoothItem.connect("activate", () => toggleBluetooth());
volumeItem.connect("activate", () => adjustVolume());
micItem.connect("activate", () => adjustMic());
powerItem.connect("activate", () => showPowerMenu());
wifiItem.show();
bluetoothItem.show();
volumeItem.show();
micItem.show();
powerItem.show();
return menu;
}
function toggleWiFi() {
const network = AstalNetwork.get_default();
const wifi = network.get_wifi();
const state = wifi.get_state();
if (state === AstalNetwork.WifiState.DISCONNECTED) {
wifi.connect();
} else {
wifi.disconnect();
}
}
function toggleBluetooth() {
const bluetooth = AstalBluetooth.get_default();
const adapter = bluetooth.get_adapter();
const powered = adapter.get_powered();
adapter.set_powered(!powered);
}
function adjustVolume() {
exec("pavucontrol");
}
function adjustMic() {
exec("pavucontrol");
}
function showPowerMenu() {
exec("power-menu");
}
export { createQuickActionsMenu };
// colors.scss
$accent: #4a90e2; // Primary accent color (change this)
$bg: #1e1e2e; // Background
$fg: #f8f8f2; // Foreground/text
$hover: lighten($accent, 10%);
$border: rgba(255, 255, 255, 0.1);
@import "colors";
* {
all: unset;
font-family: "JetBrainsMono Nerd Font", monospace;
font-size: 14px;
color: $fg;
}
window {
background-color: transparent;
}
box {
background-color: $bg;
border-radius: 16px;
padding: 6px 12px;
border: 1px solid $border;
margin: 8px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.3);
}
label {
padding: 0 8px;
}
// Clickable buttons/icons (e.g. QuickAction, tray items)
button {
background-color: transparent;
border-radius: 12px;
padding: 6px;
transition: background-color 0.2s ease-in-out;
&:hover {
background-color: $hover;
}
}
// System Tray area
tray {
padding: 0 8px;
}
// Layout specifics
.left-section,
.center-section,
.right-section {
padding: 0 12px;
}
.left-section {
border-right: 1px solid $border;
}
.right-section {
border-left: 1px solid $border;
}
// Icons (you'll replace them with fluentui icons)
.icon {
font-family: "FluentUI System Icons"; // Ensure this font is loaded
font-size: 16px;
}
import Gtk from "gi://Gtk";
import AstalNetwork from "gi://AstalNetwork";
import AstalBluetooth from "gi://AstalBluetooth";
import { exec } from "gi://GLib";
let quickActionsMenu;
// Toggle WiFi connection
function toggleWiFi() {
const network = AstalNetwork.get_default();
const wifi = network.get_wifi();
const state = wifi.get_state();
if (state === AstalNetwork.WifiState.DISCONNECTED) {
wifi.connect();
} else {
wifi.disconnect();
}
}
// Toggle Bluetooth power state
function toggleBluetooth() {
const bluetooth = AstalBluetooth.get_default();
const adapter = bluetooth.get_adapter();
const powered = adapter.get_powered();
adapter.set_powered(!powered);
}
// Adjust volume or microphone (opens pavucontrol)
function adjustVolume() {
exec("pavucontrol");
}
function adjustMic() {
exec("pavucontrol");
}
// Power menu
function showPowerMenu() {
exec("power-menu");
}
// Show WiFi network picker
function pickWiFi() {
const wifi = AstalNetwork.get_default().get_wifi();
wifi.show_picker();
}
// Show Bluetooth device picker
function pickBluetooth() {
const bluetooth = AstalBluetooth.get_default();
bluetooth.show_picker();
}
// Create menu using JSX
function createQuickActionsMenu() {
quickActionsMenu = (
<menu>
<item label="📶 WiFi" onActivate={toggleWiFi} />
<item label="→ Pick Network" onActivate={pickWiFi} />
<item label="🔵 Bluetooth" onActivate={toggleBluetooth} />
<item label="→ Pick Device" onActivate={pickBluetooth} />
<item label="🔊 Volume" onActivate={adjustVolume} />
<item label="🎙️ Microphone" onActivate={adjustMic} />
<item label="🔌 Power" onActivate={showPowerMenu} />
</menu>
);
return quickActionsMenu;
}
// Export the function
export { createQuickActionsMenu };
import Gtk from "gi://Gtk";
import AstalNetwork from "gi://AstalNetwork";
import AstalBluetooth from "gi://AstalBluetooth";
import { exec } from "gi://GLib";
function toggleWiFi() {
const network = AstalNetwork.get_default();
const wifi = network.get_wifi();
wifi.get_state() === AstalNetwork.WifiState.DISCONNECTED
? wifi.connect()
: wifi.disconnect();
}
function toggleBluetooth() {
const adapter = AstalBluetooth.get_default().get_adapter();
adapter.set_powered(!adapter.get_powered());
}
function adjustVolume() {
exec("pavucontrol");
}
function adjustMic() {
exec("pavucontrol");
}
function showPowerMenu() {
exec("power-menu");
}
// Build submenu for WiFi networks
function buildWifiMenu() {
const wifi = AstalNetwork.get_default().get_wifi();
const menu = new Gtk.Menu();
for (const ap of wifi.get_access_points()) {
const ssid = ap.get_ssid();
const strength = ap.get_strength();
const item = new Gtk.MenuItem({ label: `${ssid} (${strength}%)` });
item.connect("activate", () => {
wifi.connect_access_point(ap);
});
item.show();
menu.append(item);
}
return menu;
}
// Build submenu for Bluetooth devices
function buildBluetoothMenu() {
const bluetooth = AstalBluetooth.get_default();
const menu = new Gtk.Menu();
for (const device of bluetooth.get_devices()) {
const name = device.get_name();
const connected = device.get_connected();
const label = connected ? `✔ ${name}` : name;
const item = new Gtk.MenuItem({ label });
item.connect("activate", () => {
if (!connected) {
device.connect_device();
} else {
device.disconnect_device();
}
});
item.show();
menu.append(item);
}
return menu;
}
function createQuickActionsMenu() {
const wifiPicker = new Gtk.MenuItem({ label: "→ Pick Network ▸" });
const btPicker = new Gtk.MenuItem({ label: "→ Pick Device ▸" });
const wifiSubMenu = new Gtk.Menu();
const btSubMenu = new Gtk.Menu();
// Populate submenus on open
wifiPicker.connect("activate", () => {
wifiSubMenu.foreach(child => wifiSubMenu.remove(child));
const newMenu = buildWifiMenu();
newMenu.foreach(child => wifiSubMenu.append(child));
wifiSubMenu.show_all();
});
btPicker.connect("activate", () => {
btSubMenu.foreach(child => btSubMenu.remove(child));
const newMenu = buildBluetoothMenu();
newMenu.foreach(child => btSubMenu.append(child));
btSubMenu.show_all();
});
wifiPicker.set_submenu(wifiSubMenu);
btPicker.set_submenu(btSubMenu);
return (
<menu>
<item label="📶 WiFi" onActivate={toggleWiFi} />
{wifiPicker}
<item label="🔵 Bluetooth" onActivate={toggleBluetooth} />
{btPicker}
<item label="🔊 Volume" onActivate={adjustVolume} />
<item label="🎙️ Microphone" onActivate={adjustMic} />
<item label="🔌 Power" onActivate={showPowerMenu} />
</menu>
);
}
export { createQuickActionsMenu };
import Gtk from "gi://Gtk";
import AstalNetwork from "gi://AstalNetwork";
import AstalBluetooth from "gi://AstalBluetooth";
import { exec } from "gi://GLib";
function createToggleRow({ label, active, onToggle, onExpand }) {
return (
<box className="toggle-row" orientation="horizontal">
<button className={active ? "toggle active" : "toggle"} onClicked={onToggle}>
{label}
</button>
<button className="arrow" onClicked={onExpand}>
&gt;
</button>
</box>
);
}
function createQuickActionsMenu() {
const wifi = AstalNetwork.get_default().get_wifi();
const bt = AstalBluetooth.get_default().get_adapter();
const wifiSub = new Gtk.Menu();
const btSub = new Gtk.Menu();
function refreshWifiMenu() {
wifiSub.foreach(c => wifiSub.remove(c));
for (const ap of wifi.get_access_points()) {
const item = new Gtk.MenuItem({ label: `${ap.get_ssid()} (${ap.get_strength()}%)` });
item.connect("activate", () => wifi.connect_access_point(ap));
item.show();
wifiSub.append(item);
}
}
function refreshBtMenu() {
btSub.foreach(c => btSub.remove(c));
for (const dev of AstalBluetooth.get_default().get_devices()) {
const name = dev.get_name();
const label = dev.get_connected() ? `✔ ${name}` : name;
const item = new Gtk.MenuItem({ label });
item.connect("activate", () => {
dev.get_connected() ? dev.disconnect_device() : dev.connect_device();
});
item.show();
btSub.append(item);
}
}
return (
<menu className="quickactions-menu">
<submenu label="">
{createToggleRow({
label: "📶 WiFi",
active: wifi.get_state() !== AstalNetwork.WifiState.DISCONNECTED,
onToggle: () => {
wifi.get_state() === AstalNetwork.WifiState.DISCONNECTED ? wifi.connect() : wifi.disconnect();
},
onExpand: () => {
refreshWifiMenu();
wifiSub.popup();
}
})}
</submenu>
<submenu label="">
{createToggleRow({
label: "🔵 Bluetooth",
active: bt.get_powered(),
onToggle: () => bt.set_powered(!bt.get_powered()),
onExpand: () => {
refreshBtMenu();
btSub.popup();
}
})}
</submenu>
<item label="🔊 Volume" onActivate={() => exec("pavucontrol")} />
<item label="🎙️ Microphone" onActivate={() => exec("pavucontrol")} />
<item label="🔌 Power" onActivate={() => exec("power-menu")} />
</menu>
);
}
export { createQuickActionsMenu };
@import "colors";
.toggle-row {
background-color: $bg;
border-radius: 12px;
margin: 6px 0;
overflow: hidden;
border: 1px solid $border;
button {
padding: 10px 16px;
font-size: 14px;
transition: background 0.2s ease;
border: none;
background: transparent;
&:hover {
background-color: $hover;
}
}
.toggle {
flex: 1;
background-color: transparent;
text-align: left;
&.active {
background-color: $accent;
color: white;
}
}
.arrow {
width: 40px;
background-color: transparent;
text-align: center;
}
}
.audio-control {
display: flex;
align-items: center;
padding: 8px 12px;
gap: 8px;
label {
min-width: 60px;
color: $fg;
}
scale {
flex: 1;
}
button {
background-color: transparent;
padding: 6px;
border-radius: 6px;
&:hover {
background-color: $hover;
}
}
}
.media-control {
padding: 12px;
text-align: center;
label {
display: block;
margin-bottom: 6px;
}
button {
margin: 0 4px;
padding: 6px 10px;
border-radius: 6px;
&:hover {
background-color: $hover;
}
}
}
import Gtk from "gi://Gtk";
import AstalNetwork from "gi://AstalNetwork";
import AstalBluetooth from "gi://AstalBluetooth";
import AstalAudio from "gi://AstalAudio";
import AstalMedia from "gi://AstalMedia";
import { exec } from "gi://GLib";
// Helpers
function createToggleRow({ label, active, onToggle, onExpand }) {
return (
<box className="toggle-row" orientation="horizontal">
<button className={active ? "toggle active" : "toggle"} onClicked={onToggle}>
{label}
</button>
<button className="arrow" onClicked={onExpand}>
&gt;
</button>
</box>
);
}
function createPowerRow({ onExpand }) {
return (
<box className="toggle-row" orientation="horizontal">
<button className="toggle" onClicked={() => exec("systemctl suspend")}>
🔋 Power
</button>
<button className="arrow" onClicked={onExpand}>
&gt;
</button>
</box>
);
}
function createQuickActionsMenu() {
const wifi = AstalNetwork.get_default().get_wifi();
const bt = AstalBluetooth.get_default().get_adapter();
const audio = AstalAudio.get_default();
const media = AstalMedia.get_default();
// Submenus
const wifiSub = new Gtk.Menu();
const btSub = new Gtk.Menu();
const powerSub = new Gtk.Menu();
const outputSub = new Gtk.Menu();
const inputSub = new Gtk.Menu();
function refreshWifiMenu() {
wifiSub.foreach(c => wifiSub.remove(c));
for (const ap of wifi.get_access_points()) {
const item = new Gtk.MenuItem({ label: `${ap.get_ssid()} (${ap.get_strength()}%)` });
item.connect("activate", () => wifi.connect_access_point(ap));
item.show();
wifiSub.append(item);
}
}
function refreshBtMenu() {
btSub.foreach(c => btSub.remove(c));
for (const dev of AstalBluetooth.get_default().get_devices()) {
const label = dev.get_connected() ? `✔ ${dev.get_name()}` : dev.get_name();
const item = new Gtk.MenuItem({ label });
item.connect("activate", () =>
dev.get_connected() ? dev.disconnect_device() : dev.connect_device()
);
item.show();
btSub.append(item);
}
}
function refreshPowerMenu() {
const entries = [
{ label: "⏻ Shutdown", cmd: "systemctl poweroff" },
{ label: "🔁 Reboot", cmd: "systemctl reboot" },
{ label: "🔒 Lock", cmd: "loginctl lock-session" },
{ label: "🚪 Logout", cmd: "hyprctl dispatch exit" },
];
powerSub.foreach(c => powerSub.remove(c));
for (const entry of entries) {
const item = new Gtk.MenuItem({ label: entry.label });
item.connect("activate", () => exec(entry.cmd));
item.show();
powerSub.append(item);
}
}
function refreshOutputDevices() {
outputSub.foreach(c => outputSub.remove(c));
for (const device of audio.get_outputs()) {
const label = device.is_default() ? `✔ ${device.get_description()}` : device.get_description();
const item = new Gtk.MenuItem({ label });
item.connect("activate", () => audio.set_default_output(device));
item.show();
outputSub.append(item);
}
}
function refreshInputDevices() {
inputSub.foreach(c => inputSub.remove(c));
for (const device of audio.get_inputs()) {
const label = device.is_default() ? `✔ ${device.get_description()}` : device.get_description();
const item = new Gtk.MenuItem({ label });
item.connect("activate", () => audio.set_default_input(device));
item.show();
inputSub.append(item);
}
}
return (
<menu className="quickactions-menu">
{/* WiFi */}
<submenu label="">
{createToggleRow({
label: "📶 WiFi",
active: wifi.get_state() !== AstalNetwork.WifiState.DISCONNECTED,
onToggle: () =>
wifi.get_state() === AstalNetwork.WifiState.DISCONNECTED
? wifi.connect()
: wifi.disconnect(),
onExpand: () => {
refreshWifiMenu();
wifiSub.popup();
},
})}
</submenu>
{/* Bluetooth */}
<submenu label="">
{createToggleRow({
label: "🔵 Bluetooth",
active: bt.get_powered(),
onToggle: () => bt.set_powered(!bt.get_powered()),
onExpand: () => {
refreshBtMenu();
btSub.popup();
},
})}
</submenu>
{/* Output Volume */}
<box className="audio-control">
<label>🔊 Output</label>
<scale
value={audio.get_output_volume()}
onChange={v => audio.set_output_volume(v)}
drawValue={false}
min={0}
max={100}
/>
<button onClicked={() => {
refreshOutputDevices();
outputSub.popup();
}}>⋯</button>
</box>
{/* Input Volume */}
<box className="audio-control">
<label>🎙️ Mic</label>
<scale
value={audio.get_input_volume()}
onChange={v => audio.set_input_volume(v)}
drawValue={false}
min={0}
max={100}
/>
<button onClicked={() => {
refreshInputDevices();
inputSub.popup();
}}>⋯</button>
</box>
{/* Media Controls */}
{media && (
<box className="media-control">
<label>{media.get_artist()} {media.get_title()}</label>
<box>
<button onClicked={() => media.prev()}>⏮</button>
<button onClicked={() => media.play_pause()}>⏯</button>
<button onClicked={() => media.next()}>⏭</button>
</box>
</box>
)}
{/* Power */}
<submenu label="">
{createPowerRow({
onExpand: () => {
refreshPowerMenu();
powerSub.popup();
},
})}
</submenu>
</menu>
);
}
export { createQuickActionsMenu };

View File

@ -9,6 +9,6 @@
// "checkJs": true, // "checkJs": true,
// "allowJs": true, // "allowJs": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "astal/gtk4", "jsxImportSource": "astal/gtk3",
} }
} }

View File

@ -1,36 +1,23 @@
import { App, Astal, Gtk, Gdk } from "astal/gtk4" import { createQuickActionsMenu } from "./QuickActions";
import { Variable } from "astal" import { GLib } from "astal";
import { Astal, Gdk, Gtk } from "astal/gtk3";
const time = Variable("").poll(1000, "date") const Bar = (gdkmonitor: Gdk.Monitor) => {
export default function Bar(gdkmonitor: Gdk.Monitor) { return (
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor <window gdkmonitor={gdkmonitor}
cssClasses={["Bar"]}>
return <window <box orientation={Gtk.Orientation.HORIZONTAL} spacing={10}>
visible <box>
cssClasses={["Bar"]} </box>
gdkmonitor={gdkmonitor} <label>{windowTitle}</label>
exclusivity={Astal.Exclusivity.EXCLUSIVE} <box>
anchor={TOP | LEFT | RIGHT} <tray />
application={App}> <button icon="quickaction" menu={quickActionMenu} />
<centerbox cssName="centerbox"> </box>
<button </box>
onClicked="echo hello"
hexpand
halign={Gtk.Align.CENTER}
>
Welcome to AGS!
</button>
<box />
<menubutton
hexpand
halign={Gtk.Align.CENTER}
>
<label label={time()} />
<popover>
<Gtk.Calendar />
</popover>
</menubutton>
</centerbox>
</window> </window>
);
} }
export default Bar;

View File

@ -0,0 +1,76 @@
import Gtk from "gi://Gtk";
import AstalNetwork from "gi://AstalNetwork";
import AstalBluetooth from "gi://AstalBluetooth";
import { exec } from "gi://GLib";
let quickActionsMenu;
// Toggle WiFi connection
function toggleWiFi() {
const network = AstalNetwork.get_default();
const wifi = network.get_wifi()!;
const state = wifi.get_state();
if (state === AstalNetwork.WifiState.DISCONNECTED) {
wifi.connect();
} else {
wifi.disconnect();
}
}
// Toggle Bluetooth power state
function toggleBluetooth() {
const bluetooth = AstalBluetooth.get_default();
const adapter = bluetooth.get_adapter();
const powered = adapter?.get_powered();
adapter?.set_powered(!powered);
}
// Adjust volume or microphone (opens pavucontrol)
function adjustVolume() {
exec("pavucontrol");
}
function adjustMic() {
exec("pavucontrol");
}
// Power menu
function showPowerMenu() {
exec("power-menu");
}
// Show WiFi network picker
function pickWiFi() {
const wifi = AstalNetwork.get_default().get_wifi();
wifi.show_picker();
}
// Show Bluetooth device picker
function pickBluetooth() {
const bluetooth = AstalBluetooth.get_default();
bluetooth.show_picker();
}
// Create menu using JSX
function createQuickActionsMenu() {
quickActionsMenu = (
<menu>
<item label="📶 WiFi" onActivate={toggleWiFi} />
<item label="→ Pick Network" onActivate={pickWiFi} />
<item label="🔵 Bluetooth" onActivate={toggleBluetooth} />
<item label="→ Pick Device" onActivate={pickBluetooth} />
<item label="🔊 Volume" onActivate={adjustVolume} />
<item label="🎙️ Microphone" onActivate={adjustMic} />
<item label="🔌 Power" onActivate={showPowerMenu} />
</menu>
);
return quickActionsMenu;
}
// Export the function
export { createQuickActionsMenu };

View File

@ -0,0 +1,57 @@
import AstalTray from "gi://AstalTray";
import { bind } from "astal";
import AstalHyprland from "gi://AstalHyprland";
const SysTray = () => {
const tray = AstalTray.get_default();
return <box className="SysTray">
{bind(tray, "items").as( items => items.map( item => (
<button
tooltipMarkup={bind(item, "tooltipMarkup")}
usePopover={false}
actionGroup={bind(item, "actionGroup").as(ag => ["dbusmenu", ag])}
menuModel={bind(item, "menuModel")}>
<icon gicon={bind(item, "gicon")} />
</button>
) ) ) }
</box>
}
const HyprlandWorkspace = () => {
const hypr = AstalHyprland.get_default()
return <box className={"HyprlandWorkspaces"}>
{bind(hypr, "workspaces").as(wss => wss
.filter(ws => !(ws.id >= -99 && ws.id <= -2)) // filter out special workspaces
.sort((a, b) => a.id - b.id)
.map(ws => (
<button
className={bind(hypr, "focusedWorkspace").as(fw =>
ws === fw ? "HyprlandFocusedWorkspace" : "")}
onClicked={() => ws.focus()}>
{ws.id}
</button>
))
)}
</box>
}
const HyprlandActiveWindow = () => {
const hypr = AstalHyprland.get_default();
const focused = bind( hypr, "focusedClient" );
return <box className={"HyprlandFocusedClients"} visible={focused.as(Boolean)}>
{focused.as( client => (
client && <label label={bind( client, "title" ).as( String )} />
))}
</box>
}
export default {
HyprlandWorkspace,
HyprlandActiveWindow,
SysTray
}

View File

@ -0,0 +1,37 @@
@import "colors";
.toggle-row {
background-color: $bg;
border-radius: 12px;
margin: 6px 0;
overflow: hidden;
border: 1px solid $border;
button {
padding: 10px 16px;
font-size: 14px;
transition: background 0.2s ease;
border: none;
background: transparent;
&:hover {
background-color: $hover;
}
}
.toggle {
flex: 1;
background-color: transparent;
text-align: left;
&.active {
background-color: $accent;
color: white;
}
}
.arrow {
width: 40px;
background-color: transparent;
text-align: center;
}
}

View File

@ -0,0 +1,68 @@
import AstalHyprland from "gi://AstalHyprland";
const getAvailableWorkspaces = (): number[] => {
const workspaces: number[] = [];
AstalHyprland.get_default().get_workspaces().forEach( val => {
workspaces.push( val.get_id() );
} )
return workspaces;
}
const getCurrentWorkspaceID = (): number => {
return AstalHyprland.get_default().get_focused_workspace().get_id();
}
const getCurrentWindowTitle = (): string => {
return AstalHyprland.get_default().get_focused_client().get_title();
}
/**
* Get the workspace ID of a window by its address
* @param address - The address of the window
* @returns The workspace ID
*/
const getWorkspaceIDOfWindowByAddress = ( address: string ): number => {
AstalHyprland.get_default().get_clients().forEach( client => {
if ( client.get_address() === address ) {
return client.get_workspace().get_id();
}
} );
return -1;
}
interface HyprlandSubscriptions {
[key: string]: ( data: string ) => void;
}
const hooks: HyprlandSubscriptions = {};
/**
* Add an event listener for Hyprland events.
* @param event - A hyprland IPC event. See https://wiki.hyprland.org/IPC/. Useful events include: urgent, windowtitlev2, workspace, createworkspacev2, destroyworkspacev2, activewindowv2
* @param cb - The callback function
*/
const subscribeToUpdates = ( event: string, cb: ( data: string ) => void ): void => {
hooks[ event ] = cb;
}
/**
* Listen to events. Must be called at some point if events are to be listened for
*/
const listen = () => {
AstalHyprland.get_default().connect( "event", ( name: string, data: string ) => {
if ( hooks[ name ] ) {
hooks[ name ]( data );
}
} );
}
export default {
getAvailableWorkspaces,
getCurrentWorkspaceID,
getCurrentWindowTitle,
getWorkspaceIDOfWindowByAddress,
subscribeToUpdates,
listen
}

View File

@ -1,2 +0,0 @@
node_modules/
@girs/

View File

@ -1,10 +0,0 @@
import { App } from "astal/gtk4"
import style from "./style.scss"
import Bar from "./widget/Bar"
App.start({
css: style,
main() {
App.get_monitors().map(Bar)
},
})

View File

@ -1,21 +0,0 @@
declare const SRC: string
declare module "inline:*" {
const content: string
export default content
}
declare module "*.scss" {
const content: string
export default content
}
declare module "*.blp" {
const content: string
export default content
}
declare module "*.css" {
const content: string
export default content
}

View File

@ -1,6 +0,0 @@
{
"name": "astal-shell",
"dependencies": {
"astal": "/usr/share/astal/gjs"
}
}

View File

@ -1,20 +0,0 @@
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
$fg-color: #{"@theme_fg_color"};
$bg-color: #{"@theme_bg_color"};
window.Bar {
background: transparent;
color: $fg-color;
font-weight: bold;
>centerbox {
background: $bg-color;
border-radius: 10px;
margin: 8px;
}
button {
border-radius: 8px;
margin: 2px;
}
}

View File

@ -1,14 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"experimentalDecorators": true,
"strict": true,
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Bundler",
// "checkJs": true,
// "allowJs": true,
"jsx": "react-jsx",
"jsxImportSource": "astal/gtk4",
}
}

27
prompts.md Normal file
View File

@ -0,0 +1,27 @@
# Bar
## Attempt 1
Use Astal (https://aylur.github.io/astal) to write me a new status bar for hyprland. You may use JSX (be aware, it's not react, but gjs based and has therefore some limitations), but I prefer using the other syntax that is similar to what it was for AGS (the old version of astal), which more closely resembles GTK syntax. The bar should include the following, from left to right:
Left aligned
- Date & Time (with seconds, preferably)
- System stats (i.e. CPU, Memory util, Screen Brightness & Battery status, if available)
- Workspace number
Centered
- The window name
Right aligned (still left to right)
- System tray
- QuickAction menu in GNOME QuickAction menu style, but only the closed version showing icons for volume, mic, WiFi / Ethernet and a Power icon. For the icons (all throughout the bar) use the fluentui-icons (so the Windows 11 icons). If you can't provide them here, tell me what icon should go there and I will put it there manually
The actual QuickAction Menu (which you could also write in a separate file) will provide options where I can pick WiFi, Bluetooth (also turn it on and off), change volume of mic and output, pick the mic and output, have media controls and have a power menu).
For the QuickAction menu, provide a function that is exposed from the file to open and close it, as for all the features (like enabling BT, WiFi, etc).
### Followups
To the WiFi and Bluetooth menus, add the option to pick a WiFi Network / Bluetooth device. Please also fully extract the QuickActions menu to a separate file.
Now, can you also provide scss for the bar, such that:
- I have an easy way to customize colours (which I will be doing using one of my scripts, so having a separate colour config file will be a great option)
- it is very rounded (the corners)
- it has a very sleek, but modern design, with only a single accent colour
- Hovering over something clickable uses a hover colour