2025-04-19 15:08:42 +02:00

887 lines
24 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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