added stuff
This commit is contained in:
parent
6d31f5b5a1
commit
7d4f626b7d
907 changed files with 70990 additions and 0 deletions
119
nyx/homes/notashelf/services/wayland/ags/js/icons.js
Normal file
119
nyx/homes/notashelf/services/wayland/ags/js/icons.js
Normal file
|
@ -0,0 +1,119 @@
|
|||
export const Icon = {
|
||||
settings: "org.gnome.Settings-symbolic",
|
||||
tick: "object-select-symbolic",
|
||||
audio: {
|
||||
mic: {
|
||||
muted: "microphone-disabled-symbolic",
|
||||
unmuted: "microphone-sensitivity-high-symbolic",
|
||||
},
|
||||
volume: {
|
||||
muted: "audio-volume-muted-symbolic",
|
||||
low: "audio-volume-low-symbolic",
|
||||
medium: "audio-volume-medium-symbolic",
|
||||
high: "audio-volume-high-symbolic",
|
||||
overamplified: "audio-volume-overamplified-symbolic",
|
||||
},
|
||||
type: {
|
||||
headset: "audio-headphones-symbolic",
|
||||
speaker: "audio-speakers-symbolic",
|
||||
card: "audio-card-symbolic",
|
||||
},
|
||||
mixer: "tool-symbolic",
|
||||
},
|
||||
apps: {
|
||||
apps: "view-app-grid-symbolic",
|
||||
search: "folder-saved-search-symbolic",
|
||||
},
|
||||
bluetooth: {
|
||||
enabled: "bluetooth-active-symbolic",
|
||||
disabled: "bluetooth-disabled-symbolic",
|
||||
},
|
||||
brightness: {
|
||||
indicator: "display-brightness-symbolic",
|
||||
keyboard: "keyboard-brightness-symbolic",
|
||||
screen: ["", "", "", "", "", "", "", "", "", "", ""],
|
||||
},
|
||||
powermenu: {
|
||||
sleep: "weather-clear-night-symbolic",
|
||||
reboot: "system-reboot-symbolic",
|
||||
logout: "system-log-out-symbolic",
|
||||
shutdown: "system-shutdown-symbolic",
|
||||
lock: "system-lock-screen-symbolic",
|
||||
close: "window-close-symbolic",
|
||||
},
|
||||
recorder: {
|
||||
recording: "media-record-symbolic",
|
||||
},
|
||||
notifications: {
|
||||
noisy: "preferences-system-notifications-symbolic",
|
||||
silent: "notifications-disabled-symbolic",
|
||||
critical: "messagebox_critical-symbolic",
|
||||
chat: "notification-symbolic",
|
||||
close: "window-close-symbolic",
|
||||
},
|
||||
header: {
|
||||
refresh: "view-refresh-symbolic",
|
||||
settings: "settings-symbolic",
|
||||
power: "system-shutdown-symbolic",
|
||||
},
|
||||
trash: {
|
||||
full: "user-trash-full-symbolic",
|
||||
empty: "user-trash-symbolic",
|
||||
},
|
||||
mpris: {
|
||||
fallback: "audio-x-generic-symbolic",
|
||||
shuffle: {
|
||||
enabled: "media-playlist-shuffle-symbolic",
|
||||
disabled: "media-playlist-no-shuffle-symbolic",
|
||||
},
|
||||
loop: {
|
||||
none: "media-playlist-no-repeat-symbolic",
|
||||
track: "media-playlist-repeat-song-symbolic",
|
||||
playlist: "media-playlist-repeat-symbolic",
|
||||
},
|
||||
playing: "media-playback-pause-symbolic",
|
||||
paused: "media-playback-start-symbolic",
|
||||
stopped: "media-playback-stop-symbolic",
|
||||
prev: "media-skip-backward-symbolic",
|
||||
next: "media-skip-forward-symbolic",
|
||||
},
|
||||
ui: {
|
||||
send: "mail-send-symbolic",
|
||||
arrow: {
|
||||
right: "pan-end-symbolic",
|
||||
left: "pan-start-symbolic",
|
||||
down: "pan-down-symbolic",
|
||||
up: "pan-up-symbolic",
|
||||
},
|
||||
},
|
||||
speaker: {
|
||||
overamplified: "\uf14b",
|
||||
high: "\ue050",
|
||||
medium: "\ue04d",
|
||||
low: "\ue04e",
|
||||
muted: "\ue04f",
|
||||
},
|
||||
microphone: {
|
||||
overamplified: "\ue029",
|
||||
high: "\ue029",
|
||||
medium: "\ue029",
|
||||
low: "\ue029",
|
||||
muted: "\ue02b",
|
||||
},
|
||||
wired: {
|
||||
power: "",
|
||||
poweroff: "",
|
||||
},
|
||||
wifi: {
|
||||
none: "",
|
||||
bad: "",
|
||||
low: "",
|
||||
normal: "",
|
||||
good: "",
|
||||
},
|
||||
system: {
|
||||
cpu: "org.gnome.SystemMonitor-symbolic",
|
||||
ram: "drive-harddisk-solidstate-symbolic",
|
||||
temp: "temperature-symbolic",
|
||||
},
|
||||
};
|
27
nyx/homes/notashelf/services/wayland/ags/js/imports.js
Normal file
27
nyx/homes/notashelf/services/wayland/ags/js/imports.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
export const require = async (file) => (await import(resource(file))).default;
|
||||
export const resource = (file) => `resource:///com/github/Aylur/ags/${file}.js`;
|
||||
export const fromService = async (file) => await require(`service/${file}`);
|
||||
export const requireCustom = async (/** @type {string} */ path) =>
|
||||
(await import(path)).default;
|
||||
|
||||
export const App = await require("app");
|
||||
export const GLib = await requireCustom("gi://GLib");
|
||||
export const Gtk = await requireCustom("gi://Gtk?version=3.0");
|
||||
export const Service = await require("service");
|
||||
export const Utils = await import(resource("utils"));
|
||||
export const Variable = await require("variable");
|
||||
export const Widget = await require("widget");
|
||||
|
||||
// Services
|
||||
export const Battery = await fromService("battery");
|
||||
export const Bluetooth = await fromService("bluetooth");
|
||||
export const Hyprland = await fromService("hyprland");
|
||||
export const Mpris = await fromService("mpris");
|
||||
export const Network = await fromService("network");
|
||||
export const Applications = await fromService("applications");
|
||||
export const Audio = await fromService("audio");
|
||||
export const Notifications = await fromService("notifications");
|
||||
export const SystemTray = await fromService("systemtray");
|
||||
|
||||
// Extras
|
||||
export const Icons = await requireCustom("./utils/icons.js");
|
|
@ -0,0 +1,50 @@
|
|||
import { Service, Utils } from "../imports.js";
|
||||
const { exec } = Utils;
|
||||
|
||||
class Brightness extends Service {
|
||||
static {
|
||||
Service.register(
|
||||
this,
|
||||
{},
|
||||
{
|
||||
screen: ["float", "rw"],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_screen = 0;
|
||||
|
||||
get screen() {
|
||||
return this._screen;
|
||||
}
|
||||
|
||||
set screen(percent) {
|
||||
if (percent < 0) percent = 0;
|
||||
|
||||
if (percent > 1) percent = 1;
|
||||
|
||||
Utils.execAsync(`brightnessctl s ${percent * 100}% -q`)
|
||||
.then(() => {
|
||||
this._screen = percent;
|
||||
this.changed("screen");
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
try {
|
||||
this._screen =
|
||||
Number(exec("brightnessctl g")) /
|
||||
Number(exec("brightnessctl m"));
|
||||
} catch (error) {
|
||||
console.error("missing dependancy: brightnessctl");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const service = new Brightness();
|
||||
|
||||
globalThis.brightness = service;
|
||||
|
||||
export default service;
|
|
@ -0,0 +1,49 @@
|
|||
import { Service } from "../imports.js";
|
||||
const { Gio } = imports.gi;
|
||||
|
||||
class DirectoryMonitorService extends Service {
|
||||
static {
|
||||
Service.register(this, {}, {});
|
||||
}
|
||||
|
||||
_monitors = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
recursiveDirectoryMonitor(directoryPath) {
|
||||
const directory = Gio.File.new_for_path(directoryPath);
|
||||
const monitor = directory.monitor_directory(
|
||||
Gio.FileMonitorFlags.NONE,
|
||||
null,
|
||||
);
|
||||
this._monitors.push(monitor);
|
||||
|
||||
monitor.connect(
|
||||
"changed",
|
||||
(fileMonitor, file, otherFile, eventType) => {
|
||||
if (eventType === Gio.FileMonitorEvent.CHANGES_DONE_HINT) {
|
||||
this.emit("changed");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const enumerator = directory.enumerate_children(
|
||||
"standard::*",
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
null,
|
||||
);
|
||||
|
||||
let fileInfo;
|
||||
while ((fileInfo = enumerator.next_file(null)) !== null) {
|
||||
const childPath = directoryPath + "/" + fileInfo.get_name();
|
||||
if (fileInfo.get_file_type() === Gio.FileType.DIRECTORY) {
|
||||
this.recursiveDirectoryMonitor(childPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const service = new DirectoryMonitorService();
|
||||
export default service;
|
|
@ -0,0 +1,56 @@
|
|||
import { Utils, Service } from "../imports.js";
|
||||
const { subprocess } = Utils;
|
||||
|
||||
class InputMonitorService extends Service {
|
||||
static {
|
||||
Service.register(
|
||||
this,
|
||||
{
|
||||
keypress: ["jsobject"],
|
||||
keyrelease: ["jsobject"],
|
||||
keyrepeat: ["jsobject"],
|
||||
event: ["jsobject"],
|
||||
},
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._evtest = subprocess("evtest /dev/input/event3", (str) =>
|
||||
this._handleEvent(str),
|
||||
);
|
||||
}
|
||||
|
||||
_handleEvent(event) {
|
||||
//ignore initial output
|
||||
if (!event.startsWith("Event")) return;
|
||||
//ignore SYN_REPORTS
|
||||
if (event.includes("SYN")) return;
|
||||
const eventData = event.substring(7).split(", ");
|
||||
const eventInfo = {};
|
||||
//evetnInfo structure:
|
||||
//{
|
||||
// time: unix timstamp
|
||||
// type: event type
|
||||
// code: keycode (this is the harware keycode)
|
||||
// value: depends on type, for EV_KEY 0->release, 1->press, 2->repeat(when holding)
|
||||
//}
|
||||
|
||||
eventData.forEach((data) => {
|
||||
const [key, value, value2] = data.split(" ");
|
||||
eventInfo[key] = isNaN(value) ? value : Number(value);
|
||||
if (key === "code") eventInfo["name"] = value2.slice(1, -1);
|
||||
});
|
||||
//only emit on EV_KEY
|
||||
if (eventInfo.type === 1) {
|
||||
if (eventInfo.value === 0) this.emit("keyrelease", eventInfo);
|
||||
if (eventInfo.value === 1) this.emit("keypress", eventInfo);
|
||||
if (eventInfo.value === 2) this.emit("keyrepeat", eventInfo);
|
||||
}
|
||||
//emit on every event, just in case, you need it
|
||||
this.emit("event", eventInfo);
|
||||
}
|
||||
}
|
||||
|
||||
export default new InputMonitorService();
|
21
nyx/homes/notashelf/services/wayland/ags/js/utils/appIcon.js
Normal file
21
nyx/homes/notashelf/services/wayland/ags/js/utils/appIcon.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { Widget } from "../imports.js";
|
||||
import { queryExact } from "./global.js";
|
||||
const { Button, Icon } = Widget;
|
||||
|
||||
export default ({
|
||||
appName,
|
||||
onClicked = () => queryExact(appName).launch(),
|
||||
icon = queryExact(appName).iconName,
|
||||
size = 36,
|
||||
...props
|
||||
}) => {
|
||||
const appIcon = Button({
|
||||
onClicked,
|
||||
child: Icon({
|
||||
icon,
|
||||
size,
|
||||
...props,
|
||||
}),
|
||||
});
|
||||
return appIcon;
|
||||
};
|
62
nyx/homes/notashelf/services/wayland/ags/js/utils/audio.js
Normal file
62
nyx/homes/notashelf/services/wayland/ags/js/utils/audio.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { Audio, Widget } from "../imports.js";
|
||||
const { Slider, Label } = Widget;
|
||||
const { speaker } = Audio;
|
||||
|
||||
const audio = {
|
||||
mixer: "",
|
||||
mic: {
|
||||
muted: "microphone-disabled-symbolic",
|
||||
low: "microphone-sensitivity-low-symbolic",
|
||||
medium: "microphone-sensitivity-medium-symbolic",
|
||||
high: "microphone-sensitivity-high-symbolic",
|
||||
},
|
||||
volume: {
|
||||
muted: "audio-volume-muted-symbolic",
|
||||
low: "audio-volume-low-symbolic",
|
||||
medium: "audio-volume-medium-symbolic",
|
||||
high: "audio-volume-high-symbolic",
|
||||
overamplified: "audio-volume-overamplified-symbolic",
|
||||
},
|
||||
type: {
|
||||
headset: "audio-headphones-symbolic",
|
||||
speaker: "audio-speakers-symbolic",
|
||||
card: "audio-card-symbolic",
|
||||
},
|
||||
};
|
||||
|
||||
export const getAudioIcon = (self) => {
|
||||
if (!Audio.speaker) return;
|
||||
|
||||
const { muted, low, medium, high, overamplified } = audio.volume;
|
||||
|
||||
if (Audio.speaker.is_muted) return (self.icon = muted);
|
||||
|
||||
/** @type {Array<[number, string]>} */
|
||||
const cons = [
|
||||
[101, overamplified],
|
||||
[67, high],
|
||||
[34, medium],
|
||||
[1, low],
|
||||
[0, muted],
|
||||
];
|
||||
|
||||
self.icon = cons.find(([n]) => n <= Audio.speaker.volume * 100)?.[1] || "";
|
||||
};
|
||||
|
||||
export const getSliderIcon = () =>
|
||||
Label({
|
||||
className: "volPopupIcon",
|
||||
label: speaker.bind("volume").as((/** @type {number} */ v) => {
|
||||
return ["", "", "", ""][
|
||||
speaker.stream?.isMuted ? 0 : Math.floor((v * 100) / 26)
|
||||
];
|
||||
}),
|
||||
});
|
||||
|
||||
export const volumePercentBar = () =>
|
||||
Slider({
|
||||
className: "volPopupBar",
|
||||
drawValue: false,
|
||||
value: speaker.bind("volume"),
|
||||
onChange: ({ value }) => (speaker.volume = value),
|
||||
});
|
57
nyx/homes/notashelf/services/wayland/ags/js/utils/battery.js
Normal file
57
nyx/homes/notashelf/services/wayland/ags/js/utils/battery.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { Battery } from "../imports.js";
|
||||
|
||||
/**
|
||||
* toTime converts a given value to a human-readable
|
||||
* format where the number of hours and minutes are
|
||||
* inferred from time, which is assumed to be in seconds.
|
||||
*
|
||||
* @param {number} time - time in seconds
|
||||
*/
|
||||
export const toTime = (time) => {
|
||||
const MINUTE = 60;
|
||||
const HOUR = MINUTE * 60;
|
||||
|
||||
if (time > 24 * HOUR) return "";
|
||||
|
||||
const hours = Math.round(time / HOUR);
|
||||
const minutes = Math.round((time - hours * HOUR) / MINUTE);
|
||||
|
||||
const hoursDisplay = hours > 0 ? `${hours}h ` : "";
|
||||
const minutesDisplay = minutes > 0 ? `${minutes}m ` : "";
|
||||
|
||||
return `${hoursDisplay}${minutesDisplay}`;
|
||||
};
|
||||
|
||||
export const getBatteryTime = () => {
|
||||
const timeRemaining = Battery.timeRemaining;
|
||||
return timeRemaining > 0 && toTime(timeRemaining) != ""
|
||||
? `${toTime(timeRemaining)}remaining`
|
||||
: "";
|
||||
};
|
||||
|
||||
export const getBatteryPercentage = () => {
|
||||
const percent = Battery.percent;
|
||||
return percent > 0 && percent < 100 ? `${percent}%` : "";
|
||||
};
|
||||
|
||||
export const getBatteryTooltip = () => {
|
||||
const time = getBatteryTime();
|
||||
const percent = Battery.percent;
|
||||
|
||||
return `${percent}% | ${time}`;
|
||||
};
|
||||
|
||||
export const getBatteryIcon = () => {
|
||||
// if Battery.percent is not between 0 and 100, handle the error
|
||||
if (Battery.percent < 0 || Battery.percent > 100)
|
||||
return "Battery percentage is not a valid value!";
|
||||
|
||||
const icons = [
|
||||
["", "", "", "", "", "", "", "", "", "", ""],
|
||||
["", "", "", "", "", "", "", "", "", "", ""],
|
||||
];
|
||||
|
||||
const chargingIndex = Battery.charging ? 1 : 0;
|
||||
const percentIndex = Math.floor(Battery.percent / 10);
|
||||
return icons[chargingIndex][percentIndex].toString();
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
import { Bluetooth, Icons } from "../imports.js";
|
||||
|
||||
export const getBluetoothDevice = (addr) =>
|
||||
Bluetooth.getDevice(addr).alias ?? Bluetooth.getDevice(addr).name;
|
||||
|
||||
export const getBluetoothIcon = (connected) => {
|
||||
if (!Bluetooth.enabled) return Icons.bluetooth.disabled;
|
||||
if (connected.length > 0) return Icons.bluetooth.active;
|
||||
return Icons.bluetooth.disconnected;
|
||||
};
|
||||
|
||||
export const getBluetoothTooltip = (connected) => {
|
||||
if (!Bluetooth.enabled) return "Bluetooth off";
|
||||
|
||||
if (connected.length > 0) {
|
||||
const dev = Bluetooth.getDevice(connected[0].address);
|
||||
let battery_str = "";
|
||||
|
||||
if (dev.battery_percentage > 0) {
|
||||
battery_str += ` ${dev.battery_percentage}%`;
|
||||
}
|
||||
|
||||
return dev.name + battery_str;
|
||||
}
|
||||
|
||||
return "Bluetooth on";
|
||||
};
|
||||
|
||||
export const getBluetoothClass = (connected) => {
|
||||
if (!Bluetooth.enabled) return "bluetooth-disabled";
|
||||
|
||||
if (connected.length > 0) {
|
||||
const dev = Bluetooth.getDevice(connected.at(0).address);
|
||||
|
||||
if (dev.battery_percentage <= 25) return "bluetooth-active-low-battery";
|
||||
|
||||
if (dev.battery_percentage > 25) return "bluetooth-paired";
|
||||
}
|
||||
|
||||
return "bluetooth-active";
|
||||
};
|
||||
|
||||
export const getBluetoothLabel = (connected) => {
|
||||
if (!Bluetooth.enabled) return "";
|
||||
|
||||
if (connected.length > 0) {
|
||||
const dev = Bluetooth.getDevice(connected.at(0).address);
|
||||
|
||||
if (dev.battery_percentage <= 25) return "";
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
36
nyx/homes/notashelf/services/wayland/ags/js/utils/desktop.js
Normal file
36
nyx/homes/notashelf/services/wayland/ags/js/utils/desktop.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Widget } from "../imports.js";
|
||||
import { queryExact } from "./global.js";
|
||||
const { Box, Icon, Label, Button } = Widget;
|
||||
|
||||
/**
|
||||
* Builds a desktop item with a specific name and label.
|
||||
* It uses the `queryExact` function to find the exact application based on its name.
|
||||
* Then, it creates a button widget with the application's icon and label.
|
||||
* When the button is clicked, it launches the application.
|
||||
*
|
||||
* @function buildDesktopItem
|
||||
* @param {string} name - The name of the application.
|
||||
* @param {string} label - The label of the desktop item.
|
||||
* @returns {Object} The desktop item widget.
|
||||
*/
|
||||
export const buildDesktopItem = (name, label) => {
|
||||
const app = queryExact(name);
|
||||
return Button({
|
||||
className: "desktopIcon",
|
||||
cursor: "pointer",
|
||||
onClicked: () => app.launch(),
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
Icon({
|
||||
icon: app.iconName,
|
||||
size: 48,
|
||||
}),
|
||||
Label({
|
||||
className: "desktopIconLabel",
|
||||
label,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
};
|
38
nyx/homes/notashelf/services/wayland/ags/js/utils/global.js
Normal file
38
nyx/homes/notashelf/services/wayland/ags/js/utils/global.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { Applications, Utils } from "../imports.js";
|
||||
const { execAsync } = Utils;
|
||||
const { list, query } = Applications;
|
||||
|
||||
/**
|
||||
* Queries the exact application based on its name.
|
||||
* First tries to find the application in the list of applications.
|
||||
* If it doesn't find it, then it queries the application by its name.
|
||||
*
|
||||
* @function queryExact
|
||||
* @param {string} appName - The name of the application to query.
|
||||
* @returns {Object} The queried application object. Returns null if the application is not found.
|
||||
*/
|
||||
export function queryExact(appName) {
|
||||
return (
|
||||
list.filter(
|
||||
(app) => app.name.toLowerCase() === appName.toLowerCase(),
|
||||
)[0] ?? query(appName)[0]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to launch an application based on its name.
|
||||
* First it tries to kill the application if it's already running.
|
||||
* Regardless of whether the killing has been successful or not, it
|
||||
* tries to launch the application.
|
||||
*
|
||||
* @function launchApp
|
||||
* @param {string} appName - The name of the application to launch.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function launchApp(appName) {
|
||||
if (queryExact(appName)) {
|
||||
execAsync(["sh", "-c", `killall ${appName}`]);
|
||||
}
|
||||
|
||||
execAsync(["sh", "-c", `${appName}`]);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { Hyprland } from "../imports.js";
|
||||
|
||||
export const getFocusedWorkspace = (self) =>
|
||||
self.children.forEach((btn) => {
|
||||
btn.className =
|
||||
btn.attribute.index === Hyprland.active.workspace.id
|
||||
? "focused"
|
||||
: "";
|
||||
btn.visible = Hyprland.workspaces.some(
|
||||
(ws) => ws.id === btn.attribute.index,
|
||||
);
|
||||
});
|
56
nyx/homes/notashelf/services/wayland/ags/js/utils/icons.js
Normal file
56
nyx/homes/notashelf/services/wayland/ags/js/utils/icons.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
export default {
|
||||
bluetooth: {
|
||||
active: "bluetooth-active-symbolic",
|
||||
disabled: "bluetooth-disabled-symbolic",
|
||||
disconnected: "bluetooth-disconnected-symbolic",
|
||||
},
|
||||
|
||||
brightness: "display-brightness-symbolic",
|
||||
|
||||
media: {
|
||||
play: "media-playback-start-symbolic",
|
||||
pause: "media-playback-pause-symbolic",
|
||||
next: "media-skip-forward-symbolic",
|
||||
previous: "media-skip-backward-symbolic",
|
||||
player: "multimedia-player-symbolic",
|
||||
},
|
||||
|
||||
volume: {
|
||||
muted: "audio-volume-muted-symbolic",
|
||||
low: "audio-volume-low-symbolic",
|
||||
medium: "audio-volume-medium-symbolic",
|
||||
high: "audio-volume-high-symbolic",
|
||||
overamplified: "audio-volume-overamplified-symbolic",
|
||||
},
|
||||
|
||||
speaker: {
|
||||
overamplified: "\uf14b",
|
||||
high: "\ue050",
|
||||
medium: "\ue04d",
|
||||
low: "\ue04e",
|
||||
muted: "\ue04f",
|
||||
},
|
||||
|
||||
microphone: {
|
||||
overamplified: "\ue029",
|
||||
high: "\ue029",
|
||||
medium: "\ue029",
|
||||
low: "\ue029",
|
||||
muted: "\ue02b",
|
||||
},
|
||||
|
||||
wired: {
|
||||
power: "",
|
||||
poweroff: "",
|
||||
},
|
||||
|
||||
wifi: {
|
||||
none: "",
|
||||
bad: "",
|
||||
low: "",
|
||||
normal: "",
|
||||
good: "",
|
||||
},
|
||||
|
||||
powerButton: "system-shutdown-symbolic",
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
export const getLauncherIcon = (self, windowName, visible) => {
|
||||
windowName === "launcher" && (self.child.label = visible ? "" : "");
|
||||
};
|
46
nyx/homes/notashelf/services/wayland/ags/js/utils/mpris.js
Normal file
46
nyx/homes/notashelf/services/wayland/ags/js/utils/mpris.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { Icons, Utils } from "../imports.js";
|
||||
import GLib from "gi://GLib";
|
||||
|
||||
export const findPlayer = (players) => {
|
||||
// try to get the first active player
|
||||
const activePlayer = players.find((p) => p.playBackStatus == "Playing");
|
||||
if (activePlayer != null) return activePlayer;
|
||||
|
||||
// otherwise get the first "working" player
|
||||
for (const p of players) {
|
||||
if (p.title != "undefined") return p;
|
||||
}
|
||||
};
|
||||
|
||||
export const mprisStateIcon = (status) => {
|
||||
const state = status == "Playing" ? "pause" : "play";
|
||||
return Icons.media[state];
|
||||
};
|
||||
|
||||
export const MEDIA_CACHE_PATH = Utils.CACHE_DIR + "/media";
|
||||
export const blurredPath = MEDIA_CACHE_PATH + "/blurred";
|
||||
|
||||
export const generateBackground = (cover_path) => {
|
||||
const url = cover_path;
|
||||
if (!url) return "";
|
||||
|
||||
const makeBg = (bg) => `background: center/cover url('${bg}')`;
|
||||
|
||||
const blurred = blurredPath + url.substring(MEDIA_CACHE_PATH.length);
|
||||
|
||||
if (GLib.file_test(blurred, GLib.FileTest.EXISTS)) {
|
||||
return makeBg(blurred);
|
||||
}
|
||||
|
||||
Utils.ensureDirectory(blurredPath);
|
||||
Utils.exec(`convert ${url} -blur 0x22 ${blurred}`);
|
||||
|
||||
return makeBg(blurred);
|
||||
};
|
||||
|
||||
export function lengthStr(length) {
|
||||
const min = Math.floor(length / 60);
|
||||
const sec = Math.floor(length % 60);
|
||||
const sec0 = sec < 10 ? "0" : "";
|
||||
return `${min}:${sec0}${sec}`;
|
||||
}
|
40
nyx/homes/notashelf/services/wayland/ags/js/utils/network.js
Normal file
40
nyx/homes/notashelf/services/wayland/ags/js/utils/network.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { Network } from "../imports.js";
|
||||
|
||||
import { Icon } from "../icons.js";
|
||||
const { wifi, wired } = Icon;
|
||||
|
||||
export const getWifiIcon = (strength) => {
|
||||
if (strength < 0.1) return wifi.none;
|
||||
if (strength < 0.26) return wifi.bad;
|
||||
if (strength < 0.51) return wifi.low;
|
||||
if (strength < 0.76) return wifi.normal;
|
||||
if (strength > 0.76) return wifi.good;
|
||||
else return wifi.none;
|
||||
};
|
||||
|
||||
export const getWifiTooltip = (strength, ssid) => {
|
||||
const wifi = Network.wifi;
|
||||
const wifiStrength = `Strength: ${strength * 100}`;
|
||||
|
||||
switch (wifi.internet) {
|
||||
case "connected":
|
||||
return `Connected to ${ssid} | Strength: ${wifiStrength}`;
|
||||
case "connecting":
|
||||
return `Connecting to ${ssid} | Strength: ${wifiStrength}`;
|
||||
case "disconnected":
|
||||
return `Disconnected from ${ssid} | Strength: ${wifiStrength}`;
|
||||
default:
|
||||
return `No connection | Strength: ${wifiStrength}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const getWiredIcon = (internet) => {
|
||||
if (internet === "connected") return wired.power;
|
||||
if (internet === "connecting") return wired.poweroff;
|
||||
if (internet === "disconnected") return wired.poweroff;
|
||||
return wired.poweroff;
|
||||
};
|
||||
|
||||
export const getWiredTooltip = (internet) => {
|
||||
return `Status: ${internet}`;
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
import { App, Widget, Utils } from "../imports.js";
|
||||
const { Box, Revealer, Window } = Widget;
|
||||
|
||||
export default ({
|
||||
onOpen = () => {},
|
||||
onClose = () => {},
|
||||
|
||||
name,
|
||||
child,
|
||||
transition = "slide_up",
|
||||
transitionDuration = 250,
|
||||
...props
|
||||
}) => {
|
||||
const window = Window({
|
||||
name,
|
||||
visible: false,
|
||||
...props,
|
||||
|
||||
child: Box({
|
||||
css: `
|
||||
min-height: 2px;
|
||||
min-width: 2px;
|
||||
`,
|
||||
child: Revealer({
|
||||
transition,
|
||||
transitionDuration,
|
||||
child: child || Box(),
|
||||
setup: (self) => {
|
||||
self.hook(App, (rev, currentName, isOpen) => {
|
||||
if (currentName === name) {
|
||||
rev.revealChild = isOpen;
|
||||
|
||||
if (isOpen) {
|
||||
onOpen(window);
|
||||
} else {
|
||||
Utils.timeout(transitionDuration, () => {
|
||||
onClose(window);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
}),
|
||||
}),
|
||||
});
|
||||
window.getChild = () => window.child.children[0].child;
|
||||
window.setChild = (newChild) => {
|
||||
window.child.children[0].child = newChild;
|
||||
window.child.children[0].show_all();
|
||||
};
|
||||
|
||||
return window;
|
||||
};
|
25
nyx/homes/notashelf/services/wayland/ags/js/utils/swallow.js
Normal file
25
nyx/homes/notashelf/services/wayland/ags/js/utils/swallow.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { App, Utils } from "../imports.js";
|
||||
const { exec, execAsync } = Utils;
|
||||
|
||||
function genCommand(arg) {
|
||||
return ["sh", "-c", `${App.configDir}/bin/hyprctl_swallow ${arg}`];
|
||||
}
|
||||
|
||||
const swallowQuery = genCommand("query");
|
||||
const swallowToggle = genCommand("toggle");
|
||||
|
||||
export const getSwallowStatus = () => {
|
||||
execAsync(swallowQuery);
|
||||
|
||||
let result = exec("hyprctl -j getoption misc:enable_swallow");
|
||||
return JSON.parse(result).set;
|
||||
};
|
||||
|
||||
export const status = Variable(getSwallowStatus());
|
||||
|
||||
export const toggleSwallowStatus = () => {
|
||||
execAsync(swallowToggle);
|
||||
|
||||
// toggle swallow status
|
||||
status.value = !status.value;
|
||||
};
|
17
nyx/homes/notashelf/services/wayland/ags/js/utils/tray.js
Normal file
17
nyx/homes/notashelf/services/wayland/ags/js/utils/tray.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Widget, SystemTray } from "../imports.js";
|
||||
const { Button, Icon } = Widget;
|
||||
|
||||
export const getTrayItems = (self) => {
|
||||
self.children = SystemTray.items.map((item) =>
|
||||
Button({
|
||||
className: "trayIcon",
|
||||
child: Icon({
|
||||
setup: (self) => self.bind("icon", item, "icon"),
|
||||
}),
|
||||
setup: (self) =>
|
||||
self.bind("tooltip-markup", item, "tooltip-markup"),
|
||||
onPrimaryClick: (_, event) => item.activate(event),
|
||||
onSecondaryClick: (_, event) => item.openMenu(event),
|
||||
}),
|
||||
);
|
||||
};
|
15
nyx/homes/notashelf/services/wayland/ags/js/utils/weather.js
Normal file
15
nyx/homes/notashelf/services/wayland/ags/js/utils/weather.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { Variable, App } from "../imports.js";
|
||||
|
||||
export const WeatherValue = Variable(
|
||||
{},
|
||||
{
|
||||
poll: [
|
||||
36000,
|
||||
["sh", "-c", `python ${App.configDir}/bin/weather`],
|
||||
(out) => JSON.parse(out),
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
export const getWeatherIcon = (value) => value.text || "...";
|
||||
export const getWeatherTooltip = (value) => value.tooltip || "...";
|
|
@ -0,0 +1,71 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
const { Window, Box, CenterBox } = Widget;
|
||||
|
||||
// Widgets
|
||||
import { LauncherIcon } from "./modules/launcher.js";
|
||||
import { Workspaces } from "./modules/workspaces.js";
|
||||
import { Tray } from "./modules/tray.js";
|
||||
import { BatteryWidget } from "./modules/battery.js";
|
||||
import { Clock } from "./modules/clock.js";
|
||||
import { PowerMenu } from "./modules/power.js";
|
||||
import { Swallow } from "./modules/swallow.js";
|
||||
import { BluetoothWidget } from "./modules/bluetooth.js";
|
||||
import { AudioWidget } from "./modules/audio.js";
|
||||
import { NetworkWidget } from "./modules/network.js";
|
||||
import { SystemUsage } from "./modules/system.js";
|
||||
import { Weather } from "./modules/weather.js";
|
||||
|
||||
const Top = () =>
|
||||
Box({
|
||||
className: "barTop",
|
||||
vertical: true,
|
||||
vpack: "start",
|
||||
children: [LauncherIcon(), SystemUsage(), Weather()],
|
||||
});
|
||||
|
||||
const Center = () =>
|
||||
Box({
|
||||
className: "barCenter",
|
||||
vertical: true,
|
||||
children: [Workspaces()],
|
||||
});
|
||||
|
||||
const Bottom = () =>
|
||||
Box({
|
||||
className: "barBottom",
|
||||
vertical: true,
|
||||
vpack: "end",
|
||||
children: [
|
||||
Tray(),
|
||||
Box({
|
||||
className: "utilsBox",
|
||||
vertical: true,
|
||||
children: [
|
||||
BluetoothWidget(),
|
||||
AudioWidget(),
|
||||
Swallow(),
|
||||
BatteryWidget(),
|
||||
NetworkWidget(),
|
||||
],
|
||||
}),
|
||||
Clock(),
|
||||
PowerMenu(),
|
||||
],
|
||||
});
|
||||
|
||||
export const Bar = ({ monitor } = {}) =>
|
||||
Window({
|
||||
name: "bar",
|
||||
anchor: ["top", "bottom", "left"],
|
||||
exclusivity: "exclusive",
|
||||
layer: "top",
|
||||
margins: [8, 0, 8, 8],
|
||||
monitor,
|
||||
child: CenterBox({
|
||||
className: "bar",
|
||||
vertical: true,
|
||||
startWidget: Top(),
|
||||
centerWidget: Center(),
|
||||
endWidget: Bottom(),
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
import { Audio, Widget } from "../../../imports.js";
|
||||
import { getAudioIcon } from "../../../utils/audio.js";
|
||||
import { launchApp } from "../../../utils/global.js";
|
||||
|
||||
const { Button, Icon } = Widget;
|
||||
|
||||
const AudioIcon = () =>
|
||||
Icon({
|
||||
setup: (self) => {
|
||||
self.hook(Audio, getAudioIcon, "speaker-changed");
|
||||
},
|
||||
});
|
||||
|
||||
export const AudioWidget = () => {
|
||||
return Button({
|
||||
className: "audio",
|
||||
cursor: "pointer",
|
||||
visible: true,
|
||||
child: AudioIcon(),
|
||||
onClicked: () => launchApp("pavucontrol"),
|
||||
});
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
import { Widget, Battery } from "../../../imports.js";
|
||||
import {
|
||||
getBatteryPercentage,
|
||||
getBatteryTooltip,
|
||||
getBatteryIcon,
|
||||
} from "../../../utils/battery.js";
|
||||
const { Button, Box, Label, Revealer } = Widget;
|
||||
|
||||
const BatIcon = () =>
|
||||
Label({ className: "batIcon" })
|
||||
// NOTE: label needs to be used instead of icon here
|
||||
.bind("label", Battery, "percent", getBatteryIcon)
|
||||
.bind("tooltip-text", Battery, "percent", getBatteryTooltip);
|
||||
|
||||
const BatStatus = () =>
|
||||
Revealer({
|
||||
transition: "slide_down",
|
||||
transition_duration: 200,
|
||||
child: Label().bind("label", Battery, "percent", getBatteryPercentage),
|
||||
});
|
||||
|
||||
export const BatteryWidget = () =>
|
||||
Button({
|
||||
onPrimaryClick: (self) => {
|
||||
self.child.children[1].revealChild =
|
||||
!self.child.children[1].revealChild;
|
||||
},
|
||||
child: Box({
|
||||
className: "battery",
|
||||
cursor: "pointer",
|
||||
vertical: true,
|
||||
children: [BatIcon(), BatStatus()],
|
||||
visible: Battery.bind("available"),
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
import { Bluetooth, Widget, Utils } from "../../../imports.js";
|
||||
import {
|
||||
getBluetoothIcon,
|
||||
getBluetoothLabel,
|
||||
getBluetoothClass,
|
||||
getBluetoothTooltip,
|
||||
} from "../../../utils/bluetooth.js";
|
||||
const { Button, Label } = Widget;
|
||||
|
||||
const BluetoothModule = () =>
|
||||
Label({ className: "bluetoothIcon" })
|
||||
.bind("label", Bluetooth, "connected-devices", getBluetoothIcon)
|
||||
.bind("class", Bluetooth, "connected-devices", getBluetoothClass)
|
||||
.bind("label", Bluetooth, "connected-devices", getBluetoothLabel)
|
||||
.bind(
|
||||
"tooltip-text",
|
||||
Bluetooth,
|
||||
"connected-devices",
|
||||
getBluetoothTooltip,
|
||||
);
|
||||
|
||||
export const BluetoothWidget = () =>
|
||||
Button({
|
||||
className: "bluetooth",
|
||||
cursor: "pointer",
|
||||
child: BluetoothModule(),
|
||||
visible: Bluetooth.connectedDevices.length > 0,
|
||||
onClicked: () => Utils.exec("blueman-applet"),
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
import { Widget, Utils } from "../../../imports.js";
|
||||
const { exec, execAsync } = Utils;
|
||||
const { Label, Box } = Widget;
|
||||
|
||||
const Time = () =>
|
||||
Label({
|
||||
className: "timeLabel",
|
||||
setup: (self) => {
|
||||
// the current quote syntax is the only one that works
|
||||
// eslint-disable-next-line quotes
|
||||
self.poll(1000, (self) => (self.label = exec('date "+%H%n%M"')));
|
||||
self.poll(1000, (self) =>
|
||||
execAsync(["date", "+%H%n%M"])
|
||||
.then((time) => (self.label = time))
|
||||
// eslint-disable-next-line no-undef
|
||||
.catch(print.error),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const Clock = () =>
|
||||
Box({
|
||||
className: "clock",
|
||||
vertical: true,
|
||||
children: [Time()],
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import { Widget, App } from "../../../imports.js";
|
||||
import { getLauncherIcon } from "../../../utils/launcher.js";
|
||||
const { Button, Label } = Widget;
|
||||
|
||||
export const LauncherIcon = () =>
|
||||
Button({
|
||||
vexpand: false,
|
||||
className: "launcherIcon",
|
||||
cursor: "pointer",
|
||||
child: Label(""),
|
||||
onClicked: () => App.toggleWindow("launcher"),
|
||||
setup: (self) => {
|
||||
self.hook(App, getLauncherIcon, "window-toggled");
|
||||
},
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
import { Widget, Utils } from "../../../imports.js";
|
||||
const { Button, Label } = Widget;
|
||||
|
||||
export const Lock = () =>
|
||||
Button({
|
||||
className: "lock",
|
||||
cursor: "pointer",
|
||||
child: Label(""),
|
||||
onClicked: () => Utils.exec("swaylock"),
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
import { Network, Widget, Utils } from "../../../imports.js";
|
||||
import {
|
||||
getWifiIcon,
|
||||
getWifiTooltip,
|
||||
getWiredIcon,
|
||||
getWiredTooltip,
|
||||
} from "../../../utils/network.js";
|
||||
const { Stack, Button, Label } = Widget;
|
||||
|
||||
const WifiIndicator = () =>
|
||||
Label({ has_tooltip: true })
|
||||
.bind("label", Network.wifi, "strength", getWifiIcon)
|
||||
.bind("tooltip-text", Network.wifi, "strength", getWifiTooltip);
|
||||
|
||||
const WiredIndicator = () =>
|
||||
Label({ cursor: "pointer" })
|
||||
.bind("label", Network.wired, "internet", getWiredIcon)
|
||||
.bind("tooltip-text", Network.wired, "internet", getWiredTooltip);
|
||||
|
||||
export const NetworkWidget = () =>
|
||||
Button({
|
||||
className: "network",
|
||||
cursor: "pointer",
|
||||
onClicked: () => Utils.exec("nm-connection-editor"),
|
||||
child: Stack({
|
||||
shown: Network.bind("primary").as(
|
||||
(/** @type {any} */ p) => p || "wifi",
|
||||
),
|
||||
children: {
|
||||
wifi: WifiIndicator(),
|
||||
wired: WiredIndicator(),
|
||||
},
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
import { Widget } from "../../../imports.js";
|
||||
const { Button, Label } = Widget;
|
||||
|
||||
export const PowerMenu = () =>
|
||||
Button({
|
||||
vexpand: false,
|
||||
className: "power",
|
||||
cursor: "pointer",
|
||||
child: Label(""),
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import { Widget } from "../../../imports.js";
|
||||
const { Label, Button } = Widget;
|
||||
|
||||
import { toggleSwallowStatus, status } from "../../../utils/swallow.js";
|
||||
|
||||
export const Swallow = () =>
|
||||
Button({
|
||||
className: "swallow",
|
||||
cursor: "pointer",
|
||||
tooltipText: `Swallow: ${status.value}`,
|
||||
onPrimaryClick: toggleSwallowStatus,
|
||||
child: Label({
|
||||
label: "",
|
||||
}),
|
||||
}).hook(status, (self) => (self.tooltipText = `${status.value}`));
|
|
@ -0,0 +1,134 @@
|
|||
import { Variable, Widget } from "../../../imports.js";
|
||||
const { Button, Revealer, Box, Label, CircularProgress } = Widget;
|
||||
|
||||
const getMemClass = (v) => {
|
||||
const val = v * 100;
|
||||
const className = [
|
||||
[100, "memCritical"],
|
||||
[75, "memHigh"],
|
||||
[35, "memMod"],
|
||||
[5, "memLow"],
|
||||
[0, "memIdle"],
|
||||
[-1, "memRevealer"],
|
||||
].find(([threshold]) => threshold <= val)[1];
|
||||
|
||||
return className;
|
||||
};
|
||||
|
||||
const getCpuClass = (v) => {
|
||||
const val = v * 100;
|
||||
|
||||
const className = [
|
||||
[100, "cpuCritical"],
|
||||
[75, "cpuHigh"],
|
||||
[35, "cpuMod"],
|
||||
[5, "cpuLow"],
|
||||
[0, "cpuIdle"],
|
||||
[-1, "cpuRevealer"],
|
||||
].find(([threshold]) => threshold <= val)[1];
|
||||
|
||||
return className;
|
||||
};
|
||||
|
||||
const divide = ([total, free]) => free / total;
|
||||
|
||||
const cpu = Variable(0, {
|
||||
poll: [
|
||||
2000,
|
||||
"top -b -n 1",
|
||||
(out) =>
|
||||
divide([
|
||||
100,
|
||||
out
|
||||
.split("\n")
|
||||
.find((line) => line.includes("Cpu(s)"))
|
||||
.split(/\s+/)[1]
|
||||
.replace(",", "."),
|
||||
]),
|
||||
],
|
||||
});
|
||||
|
||||
const mem = Variable(0, {
|
||||
poll: [
|
||||
2000,
|
||||
"free",
|
||||
(out) =>
|
||||
divide(
|
||||
out
|
||||
.split("\n")
|
||||
.find((line) => line.includes("Mem:"))
|
||||
.split(/\s+/)
|
||||
.splice(1, 2),
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {typeof cpu | typeof ram} process
|
||||
* @param {Array<any>} extraChildren
|
||||
* @param {() => void} onPrimary
|
||||
*/
|
||||
const systemWidget = (name, process, extraChildren = [], onPrimary) =>
|
||||
Button({
|
||||
className: name + "Button",
|
||||
onPrimaryClick: onPrimary,
|
||||
child: Box({
|
||||
className: name,
|
||||
vertical: true,
|
||||
children: [
|
||||
CircularProgress({
|
||||
className: name + "Progress",
|
||||
// binds: [["value", process]],
|
||||
rounded: true,
|
||||
inverted: false,
|
||||
startAt: 0.27,
|
||||
}).bind("value", process),
|
||||
...extraChildren,
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const CPU = systemWidget(
|
||||
"cpu",
|
||||
cpu,
|
||||
[
|
||||
Revealer({
|
||||
transition: "slide_down",
|
||||
child: Label()
|
||||
.bind("label", cpu, "value", (v) => `${Math.floor(v * 100)}%`)
|
||||
.bind("className", cpu, "value", getCpuClass),
|
||||
transition_duration: 250,
|
||||
}),
|
||||
],
|
||||
(self) => {
|
||||
self.child.children[1].revealChild =
|
||||
!self.child.children[1].revealChild;
|
||||
},
|
||||
);
|
||||
|
||||
const MEM = systemWidget(
|
||||
"mem",
|
||||
mem,
|
||||
[
|
||||
Revealer({
|
||||
transition: "slide_down",
|
||||
child: Label()
|
||||
.bind("label", mem, "value", (v) => `${Math.floor(v * 100)}%`)
|
||||
.bind("className", cpu, "value", getMemClass),
|
||||
transition_duration: 250,
|
||||
}),
|
||||
],
|
||||
(self) => {
|
||||
self.child.children[1].revealChild =
|
||||
!self.child.children[1].revealChild;
|
||||
},
|
||||
);
|
||||
|
||||
export const SystemUsage = () =>
|
||||
Box({
|
||||
className: "systemUsage",
|
||||
vertical: true,
|
||||
cursor: "pointer",
|
||||
children: [CPU, MEM],
|
||||
});
|
|
@ -0,0 +1,40 @@
|
|||
import { Widget, SystemTray } from "../../../imports.js";
|
||||
import { getTrayItems } from "../../../utils/tray.js";
|
||||
const { Box, EventBox, Label, Revealer } = Widget;
|
||||
|
||||
const RevIcon = () =>
|
||||
Label({
|
||||
className: "trayChevron",
|
||||
label: "",
|
||||
});
|
||||
|
||||
const TrayItems = () =>
|
||||
Box({
|
||||
className: "trayIcons",
|
||||
vertical: true,
|
||||
setup: (self) => {
|
||||
self.hook(SystemTray, getTrayItems);
|
||||
},
|
||||
});
|
||||
|
||||
export const Tray = () =>
|
||||
EventBox({
|
||||
onPrimaryClick: (self) => {
|
||||
self.child.children[0].label = self.child.children[1].revealChild
|
||||
? ""
|
||||
: "";
|
||||
self.child.children[1].revealChild =
|
||||
!self.child.children[1].revealChild;
|
||||
},
|
||||
child: Box({
|
||||
className: "tray",
|
||||
vertical: true,
|
||||
children: [
|
||||
RevIcon(),
|
||||
Revealer({
|
||||
transition: "slide_up",
|
||||
child: TrayItems(),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
import { Widget } from "../../../imports.js";
|
||||
import {
|
||||
WeatherValue,
|
||||
getWeatherIcon,
|
||||
getWeatherTooltip,
|
||||
} from "../../../utils/weather.js";
|
||||
const { Label } = Widget;
|
||||
|
||||
const weatherWidget = () =>
|
||||
Label({
|
||||
hexpand: false,
|
||||
vexpand: false,
|
||||
class_name: "weather",
|
||||
setup: (self) => {
|
||||
self.bind("label", WeatherValue, "value", getWeatherIcon);
|
||||
self.bind("tooltip-text", WeatherValue, "value", getWeatherTooltip);
|
||||
},
|
||||
});
|
||||
|
||||
export const Weather = () =>
|
||||
Widget.CenterBox({
|
||||
vertical: true,
|
||||
centerWidget: weatherWidget(),
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
import { Widget, Hyprland } from "../../../imports.js";
|
||||
import { getFocusedWorkspace } from "../../../utils/hyprland.js";
|
||||
const { Box, Button } = Widget;
|
||||
const { messageAsync } = Hyprland;
|
||||
|
||||
export const Workspaces = () =>
|
||||
Box({
|
||||
className: "workspaces",
|
||||
child: Box({
|
||||
vertical: true,
|
||||
children: Array.from({ length: 10 }, (_, i) => i + 1).map((i) =>
|
||||
Button({
|
||||
cursor: "pointer",
|
||||
attribute: { index: i },
|
||||
onClicked: () => messageAsync(`dispatch workspace ${i}`),
|
||||
onSecondaryClick: () =>
|
||||
messageAsync(`dispatch movetoworkspacesilent ${i}`),
|
||||
}),
|
||||
),
|
||||
|
||||
setup: (self) => {
|
||||
self.hook(Hyprland, getFocusedWorkspace);
|
||||
},
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
const { Box } = Widget;
|
||||
|
||||
export const DesktopIcons = () =>
|
||||
Box({
|
||||
className: "desktopIcons",
|
||||
vertical: true,
|
||||
hpack: "start",
|
||||
vpack: "start",
|
||||
children: [],
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
import { Widget, Utils } from "../../imports.js";
|
||||
const { Box, EventBox, Label, MenuItem, Menu } = Widget;
|
||||
const { exec, execAsync } = Utils;
|
||||
|
||||
/**
|
||||
* Creates a menu item with an icon.
|
||||
* @param {string} icon - The icon to display for the menu item.
|
||||
* @param {string} itemLabel - The label for the menu item.
|
||||
* @param {Function} onClick - The function to be executed when the menu item is activated.
|
||||
* @returns {Object} A menu item object with the specified icon, label, and click action.
|
||||
*/
|
||||
function ItemWithIcon(icon, itemLabel, onClick) {
|
||||
return MenuItem({
|
||||
className: "desktopMenuItem",
|
||||
child: Box({
|
||||
children: [
|
||||
Label({
|
||||
className: "desktopMenuItemIcon",
|
||||
label: icon,
|
||||
}),
|
||||
Label(itemLabel),
|
||||
],
|
||||
}),
|
||||
onActivate: onClick,
|
||||
});
|
||||
}
|
||||
|
||||
const Separator = () =>
|
||||
MenuItem({
|
||||
child: Box({
|
||||
className: "separator",
|
||||
css: `
|
||||
min-height: 1px;
|
||||
margin: 3px 6px;
|
||||
`,
|
||||
}),
|
||||
});
|
||||
|
||||
const rioMenu = () => {
|
||||
return [
|
||||
ItemWithIcon("", "Terminal", () =>
|
||||
exec(
|
||||
'sh -c "$HOME/.config/ags/bin/open_window `slurp -d -c 999999 -w 2` foot"',
|
||||
),
|
||||
),
|
||||
ItemWithIcon("", "Resize", () =>
|
||||
exec(
|
||||
'sh -c "$HOME/.config/ags/bin/move_window `slurp -d -c 999999 -w 2`"',
|
||||
),
|
||||
),
|
||||
ItemWithIcon("", "Move", () => exec("hyprctl dispatch submap move")),
|
||||
ItemWithIcon("", "Delete", () => exec("hyprctl kill")),
|
||||
Separator(),
|
||||
];
|
||||
};
|
||||
|
||||
const Powermenu = () => {
|
||||
return MenuItem({
|
||||
className: "desktopMenuItem",
|
||||
child: Box({
|
||||
children: [
|
||||
Label({
|
||||
className: "desktopMenuItemIcon",
|
||||
label: "",
|
||||
}),
|
||||
Label("Powermenu"),
|
||||
],
|
||||
}),
|
||||
submenu: Menu({
|
||||
className: "desktopMenu",
|
||||
children: [
|
||||
ItemWithIcon("", "Lock", () => Utils.exec("gtklock")),
|
||||
ItemWithIcon("", "Log Out", () =>
|
||||
exec("hyprctl dispatch exit"),
|
||||
),
|
||||
ItemWithIcon("", "Suspend", () => exec("systemctl suspend")),
|
||||
ItemWithIcon("", "Reboot", () => exec("systemctl reboot")),
|
||||
ItemWithIcon("", "Shutdown", () => exec("systemctl poweroff")),
|
||||
],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export const DesktopMenu = () =>
|
||||
EventBox({
|
||||
onSecondaryClick: (_, event) =>
|
||||
Menu({
|
||||
className: "desktopMenu",
|
||||
children: [
|
||||
...rioMenu(),
|
||||
ItemWithIcon("", "Colorpicker", () =>
|
||||
execAsync(["hyprpicker", "-a", "wl-copy"]),
|
||||
),
|
||||
Separator(),
|
||||
Powermenu(),
|
||||
],
|
||||
}).popup_at_pointer(event),
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
const { Window } = Widget;
|
||||
|
||||
import { DesktopMenu } from "./desktopMenu.js";
|
||||
import { DesktopIcons } from "./desktopIcons.js";
|
||||
|
||||
export const Desktop = ({ monitor } = {}) =>
|
||||
Window({
|
||||
name: "desktop",
|
||||
anchor: ["top", "bottom", "left", "right"],
|
||||
layer: "bottom",
|
||||
monitor,
|
||||
child: Widget.Overlay({
|
||||
child: DesktopMenu(),
|
||||
overlays: [DesktopIcons()],
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,113 @@
|
|||
import { Widget, App, Applications, Utils, Hyprland } from "../../imports.js";
|
||||
import PopupWindow from "../../utils/popupWindow.js";
|
||||
const { Box, Button, Icon, Label, Scrollable, Entry } = Widget;
|
||||
|
||||
const WINDOW_NAME = "launcher";
|
||||
|
||||
const truncateString = (str, maxLength) =>
|
||||
str.length > maxLength ? `${str.slice(0, maxLength)}...` : str;
|
||||
|
||||
const AppItem = (app) =>
|
||||
Button({
|
||||
className: "launcherApp",
|
||||
onClicked: () => {
|
||||
App.closeWindow(WINDOW_NAME);
|
||||
Hyprland.messageAsync(`dispatch exec gtk-launch ${app.desktop}`);
|
||||
++app.frequency;
|
||||
},
|
||||
setup: (self) => (self.app = app),
|
||||
child: Box({
|
||||
children: [
|
||||
Icon({
|
||||
className: "launcherItemIcon",
|
||||
icon: app.iconName || "",
|
||||
size: 24,
|
||||
}),
|
||||
Box({
|
||||
className: "launcherItem",
|
||||
vertical: true,
|
||||
vpack: "center",
|
||||
children: [
|
||||
Label({
|
||||
className: "launcherItemTitle",
|
||||
label: app.name,
|
||||
xalign: 0,
|
||||
vpack: "center",
|
||||
truncate: "end",
|
||||
}),
|
||||
!!app.description &&
|
||||
Widget.Label({
|
||||
className: "launcherItemDescription",
|
||||
label:
|
||||
truncateString(app.description, 75) || "",
|
||||
wrap: true,
|
||||
xalign: 0,
|
||||
justification: "left",
|
||||
vpack: "center",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const Launcher = () => {
|
||||
const list = Box({ vertical: true });
|
||||
|
||||
const entry = Entry({
|
||||
className: "launcherEntry",
|
||||
hexpand: true,
|
||||
text: "-",
|
||||
onAccept: ({ text }) => {
|
||||
const isCommand = text.startsWith(">");
|
||||
const appList = Applications.query(text || "");
|
||||
if (isCommand === true) {
|
||||
App.toggleWindow(WINDOW_NAME);
|
||||
Utils.execAsync(text.slice(1));
|
||||
} else if (appList[0]) {
|
||||
App.toggleWindow(WINDOW_NAME);
|
||||
appList[0].launch();
|
||||
}
|
||||
},
|
||||
onChange: ({ text }) =>
|
||||
list.children.map((item) => {
|
||||
item.visible = item.app.match(text);
|
||||
}),
|
||||
});
|
||||
|
||||
return Widget.Box({
|
||||
className: "launcher",
|
||||
vertical: true,
|
||||
setup: (self) => {
|
||||
self.hook(App, (_, name, visible) => {
|
||||
if (name !== WINDOW_NAME) return;
|
||||
|
||||
list.children = Applications.list.map(AppItem);
|
||||
|
||||
entry.text = "";
|
||||
if (visible) entry.grab_focus();
|
||||
});
|
||||
},
|
||||
children: [
|
||||
entry,
|
||||
Scrollable({
|
||||
hscroll: "never",
|
||||
css: "min-width: 250px; min-height: 360px;",
|
||||
child: list,
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
export const AppLauncher = () =>
|
||||
PopupWindow({
|
||||
name: WINDOW_NAME,
|
||||
anchor: ["top", "bottom", "right"],
|
||||
margins: [13, 13, 0, 13],
|
||||
layer: "overlay",
|
||||
transition: "slide_down",
|
||||
transitionDuration: 150,
|
||||
popup: true,
|
||||
keymode: "on-demand",
|
||||
child: Launcher(),
|
||||
});
|
29
nyx/homes/notashelf/services/wayland/ags/js/windows/music/controls.js
vendored
Normal file
29
nyx/homes/notashelf/services/wayland/ags/js/windows/music/controls.js
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Icons, Widget } from "../../imports.js";
|
||||
import { mprisStateIcon } from "../../utils/mpris.js";
|
||||
|
||||
export default (player) =>
|
||||
Widget.CenterBox({
|
||||
className: "controls",
|
||||
hpack: "center",
|
||||
|
||||
startWidget: Widget.Button({
|
||||
onClicked: () => player.previous(),
|
||||
child: Widget.Icon(Icons.media.previous),
|
||||
}),
|
||||
|
||||
centerWidget: Widget.Button({
|
||||
onClicked: () => player.playPause(),
|
||||
|
||||
child: Widget.Icon().bind(
|
||||
"icon",
|
||||
player,
|
||||
"play-back-status",
|
||||
mprisStateIcon,
|
||||
),
|
||||
}),
|
||||
|
||||
endWidget: Widget.Button({
|
||||
onClicked: () => player.next(),
|
||||
child: Widget.Icon(Icons.media.next),
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
|
||||
export default (player) =>
|
||||
Widget.Box({ className: "cover" }).bind(
|
||||
"css",
|
||||
player,
|
||||
"cover-path",
|
||||
(cover) => `background-image: url('${cover ?? ""}')`,
|
||||
);
|
|
@ -0,0 +1,45 @@
|
|||
import { Mpris, Widget } from "../../imports.js";
|
||||
import { findPlayer, generateBackground } from "../../utils/mpris.js";
|
||||
import PopupWindow from "./popup_window.js";
|
||||
|
||||
import Cover from "./cover.js";
|
||||
import { Artists, Title } from "./title_artists.js";
|
||||
import TimeInfo from "./time_info.js";
|
||||
import Controls from "./controls.js";
|
||||
import PlayerInfo from "./player_info.js";
|
||||
|
||||
const Info = (player) =>
|
||||
Widget.Box({
|
||||
className: "info",
|
||||
vertical: true,
|
||||
vexpand: false,
|
||||
hexpand: false,
|
||||
homogeneous: true,
|
||||
|
||||
children: [
|
||||
PlayerInfo(player),
|
||||
Title(player),
|
||||
Artists(player),
|
||||
Controls(player),
|
||||
TimeInfo(player),
|
||||
],
|
||||
});
|
||||
|
||||
const MusicBox = (player) =>
|
||||
Widget.Box({
|
||||
className: "music window",
|
||||
children: [Cover(player), Info(player)],
|
||||
}).bind("css", player, "cover-path", generateBackground);
|
||||
|
||||
export const Media = () =>
|
||||
PopupWindow({
|
||||
monitor: 0,
|
||||
anchor: ["top"],
|
||||
layer: "top",
|
||||
margins: [8, 0, 0, 0],
|
||||
name: "music",
|
||||
child: Widget.Box(),
|
||||
}).bind("child", Mpris, "players", (players) => {
|
||||
if (players.length == 0) return Widget.Box();
|
||||
return MusicBox(findPlayer(players));
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
import { Icons, Utils, Widget } from "../../imports.js";
|
||||
|
||||
export default (player) =>
|
||||
Widget.Box({
|
||||
className: "player-info",
|
||||
vexpand: true,
|
||||
vpack: "start",
|
||||
|
||||
children: [
|
||||
Widget.Icon({
|
||||
hexpand: true,
|
||||
hpack: "end",
|
||||
className: "player-icon",
|
||||
tooltipText: player.identity ?? "",
|
||||
}).bind("icon", player, "entry", (entry) => {
|
||||
// the Spotify icon is called spotify-client
|
||||
if (entry == "spotify") entry = "spotify-client";
|
||||
return Utils.lookUpIcon(entry ?? "")
|
||||
? entry
|
||||
: Icons.media.player;
|
||||
}),
|
||||
],
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
import App from "resource:///com/github/Aylur/ags/app.js";
|
||||
import { Widget } from "../../imports.js";
|
||||
const { Box, Revealer, Window } = Widget;
|
||||
|
||||
export default ({
|
||||
name,
|
||||
child,
|
||||
revealerSetup = null,
|
||||
transition = "crossfade",
|
||||
transitionDuration = 200,
|
||||
...props
|
||||
}) => {
|
||||
const window = Window({
|
||||
name,
|
||||
popup: false,
|
||||
focusable: false,
|
||||
visible: false,
|
||||
...props,
|
||||
|
||||
setup: (self) => (self.getChild = () => child),
|
||||
|
||||
child: Box({
|
||||
css: `
|
||||
min-height: 1px;
|
||||
min-width: 1px;
|
||||
padding: 1px;
|
||||
`,
|
||||
child: Revealer({
|
||||
transition,
|
||||
transitionDuration,
|
||||
child: child,
|
||||
|
||||
setup:
|
||||
revealerSetup ??
|
||||
((self) =>
|
||||
self.hook(App, (self, currentName, visible) => {
|
||||
if (currentName === name) {
|
||||
self.reveal_child = visible;
|
||||
}
|
||||
})),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
return window;
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
import { lengthStr } from "../../utils/mpris.js";
|
||||
|
||||
export const PositionLabel = (player) =>
|
||||
Widget.Label({
|
||||
className: "position",
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
|
||||
setup: (self) => {
|
||||
const update = (_, time) => {
|
||||
player.length > 0
|
||||
? (self.label = lengthStr(time || player.position))
|
||||
: (self.visible = !!player);
|
||||
};
|
||||
|
||||
self.hook(player, update, "position").poll(1000, update);
|
||||
},
|
||||
});
|
||||
|
||||
export const LengthLabel = (player) =>
|
||||
Widget.Label({
|
||||
className: "length",
|
||||
hexpand: true,
|
||||
xalign: 1,
|
||||
})
|
||||
.bind("visible", player, "length", (length) => length > 0)
|
||||
.bind("label", player, "length", (length) => lengthStr(length));
|
||||
|
||||
export const Position = (player) =>
|
||||
Widget.Slider({
|
||||
className: "position",
|
||||
draw_value: false,
|
||||
|
||||
onChange: ({ value }) => (player.position = player.length * value),
|
||||
|
||||
setup: (self) => {
|
||||
const update = () => {
|
||||
if (self.dragging) return;
|
||||
|
||||
self.visible = player.length > 0;
|
||||
|
||||
if (player.length > 0) {
|
||||
self.value = player.position / player.length;
|
||||
}
|
||||
};
|
||||
|
||||
self.hook(player, update)
|
||||
.hook(player, update, "position")
|
||||
.poll(1000, update);
|
||||
},
|
||||
});
|
||||
|
||||
export default (player) =>
|
||||
Widget.Box({
|
||||
vertical: true,
|
||||
vexpand: true,
|
||||
vpack: "end",
|
||||
|
||||
children: [
|
||||
Widget.Box({
|
||||
hexpand: true,
|
||||
children: [PositionLabel(player), LengthLabel(player)],
|
||||
}),
|
||||
Position(player),
|
||||
],
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
|
||||
export const Title = (player) =>
|
||||
Widget.Scrollable({
|
||||
className: "title",
|
||||
vscroll: "never",
|
||||
hscroll: "automatic",
|
||||
|
||||
child: Widget.Label({
|
||||
className: "title",
|
||||
label: "Nothing playing",
|
||||
}).bind(
|
||||
"label",
|
||||
player,
|
||||
"track-title",
|
||||
(title) => title ?? "Nothing playing",
|
||||
),
|
||||
});
|
||||
|
||||
export const Artists = (player) =>
|
||||
Widget.Scrollable({
|
||||
className: "artists",
|
||||
vscroll: "never",
|
||||
hscroll: "automatic",
|
||||
|
||||
child: Widget.Label({ className: "artists" }).bind(
|
||||
"label",
|
||||
player,
|
||||
"track-artists",
|
||||
(artists) => artists.join(", ") ?? "",
|
||||
),
|
||||
});
|
|
@ -0,0 +1,134 @@
|
|||
import { Hyprland, Notifications, Utils, Widget } from "../../imports.js";
|
||||
const { Box, Icon, Label, Button, EventBox, Window } = Widget;
|
||||
const { lookUpIcon } = Utils;
|
||||
|
||||
const closeAll = () => {
|
||||
Notifications.popups.map((n) => n.dismiss());
|
||||
};
|
||||
|
||||
const NotificationIcon = ({ app_entry, app_icon, image }) => {
|
||||
if (image) {
|
||||
return Box({
|
||||
css: `
|
||||
background-image: url("${image}");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
`,
|
||||
});
|
||||
}
|
||||
|
||||
if (lookUpIcon(app_icon)) {
|
||||
return Icon(app_icon);
|
||||
}
|
||||
|
||||
if (app_entry && lookUpIcon(app_entry)) {
|
||||
return Icon(app_entry);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Notification = (notif) => {
|
||||
const icon = Box({
|
||||
vpack: "start",
|
||||
class_name: "icon",
|
||||
// @ts-ignore
|
||||
setup: (/** @type {{ child: any; }} */ self) => {
|
||||
const icon = NotificationIcon(notif);
|
||||
if (icon !== null) self.child = icon;
|
||||
},
|
||||
});
|
||||
|
||||
const title = Label({
|
||||
class_name: "title",
|
||||
xalign: 0,
|
||||
justification: "left",
|
||||
hexpand: true,
|
||||
max_width_chars: 24,
|
||||
truncate: "end",
|
||||
wrap: true,
|
||||
label: notif.summary,
|
||||
use_markup: true,
|
||||
});
|
||||
|
||||
const body = Label({
|
||||
class_name: "body",
|
||||
hexpand: true,
|
||||
use_markup: true,
|
||||
xalign: 0,
|
||||
justification: "left",
|
||||
max_width_chars: 100,
|
||||
wrap: true,
|
||||
label: notif.body,
|
||||
});
|
||||
|
||||
const actions = Box({
|
||||
class_name: "actions",
|
||||
children: notif.actions
|
||||
.filter(({ id }) => id != "default")
|
||||
.map(({ id, label }) =>
|
||||
Button({
|
||||
class_name: "action-button",
|
||||
on_clicked: () => notif.invoke(id),
|
||||
hexpand: true,
|
||||
child: Label(label),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
return EventBox({
|
||||
on_primary_click: () => {
|
||||
if (notif.actions.length > 0) notif.invoke(notif.actions[0].id);
|
||||
},
|
||||
on_middle_click: closeAll,
|
||||
on_secondary_click: () => notif.dismiss(),
|
||||
child: Box({
|
||||
class_name: `notification ${notif.urgency}`,
|
||||
vertical: true,
|
||||
|
||||
children: [
|
||||
Box({
|
||||
class_name: "info",
|
||||
children: [
|
||||
icon,
|
||||
Box({
|
||||
vertical: true,
|
||||
class_name: "text",
|
||||
vpack: "center",
|
||||
|
||||
setup: (self) => {
|
||||
if (notif.body.length > 0)
|
||||
self.children = [title, body];
|
||||
else self.children = [title];
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
actions,
|
||||
],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
let lastMonitor;
|
||||
export const Notifs = () =>
|
||||
Window({
|
||||
name: "notifications",
|
||||
anchor: ["top", "right"],
|
||||
margins: [8, 8, 8, 0],
|
||||
child: Box({
|
||||
css: "padding: 1px;",
|
||||
class_name: "notifications",
|
||||
vertical: true,
|
||||
// @ts-ignore
|
||||
children: Notifications.bind("popups").transform((popups) => {
|
||||
return popups.map(Notification);
|
||||
}),
|
||||
}),
|
||||
}).hook(Hyprland.active, (self) => {
|
||||
// prevent useless resets
|
||||
if (lastMonitor === Hyprland.active.monitor) return;
|
||||
|
||||
self.monitor = Hyprland.active.monitor.id;
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
import { Widget, Utils } from "../../imports.js";
|
||||
import Brightness from "../../services/brightness.js";
|
||||
const { Box, Slider, Label, Revealer } = Widget;
|
||||
|
||||
const BrightnessIcon = () =>
|
||||
Label({
|
||||
className: "brtPopupIcon",
|
||||
setup: (self) => {
|
||||
self.hook(Brightness, (self) => {
|
||||
const icons = ["", "", "", "", "", "", "", "", ""];
|
||||
|
||||
let index = Math.floor((Brightness.screen * 100) / 11);
|
||||
index = Math.max(0, Math.min(index, icons.length - 1));
|
||||
|
||||
if (index >= 0 && index < icons.length) {
|
||||
self.label = icons[index].toString();
|
||||
} else {
|
||||
log("Index out of bounds:", index);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const PercentBar = () =>
|
||||
Slider({
|
||||
className: "brtPopupBar",
|
||||
drawValue: false,
|
||||
onChange: ({ value }) => (Brightness.screen = value),
|
||||
setup: (self) => {
|
||||
self.hook(Brightness, (self) => (self.value = Brightness.screen));
|
||||
},
|
||||
});
|
||||
|
||||
export const BrightnessPopup = () =>
|
||||
Box({
|
||||
css: `min-height: 1px;
|
||||
min-width: 1px;`,
|
||||
child: Revealer({
|
||||
transition: "slide_up",
|
||||
child: Box({
|
||||
className: "brightnessPopup",
|
||||
children: [BrightnessIcon(), PercentBar()],
|
||||
}),
|
||||
attribute: { count: 0 },
|
||||
setup: (self) => {
|
||||
self.hook(Brightness, (self) => {
|
||||
self.revealChild = true;
|
||||
self.attribute.count++;
|
||||
Utils.timeout(1500, () => {
|
||||
self.attribute.count--;
|
||||
|
||||
if (self.attribute.count === 0)
|
||||
self.revealChild = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import { Widget } from "../../imports.js";
|
||||
|
||||
// Widgets
|
||||
import { BrightnessPopup } from "./brightnessPopup.js";
|
||||
import { VolumePopup } from "./volumePopup.js";
|
||||
|
||||
export const Popups = () =>
|
||||
Widget.Window({
|
||||
name: "popups",
|
||||
className: "popups",
|
||||
anchor: ["bottom", "right"],
|
||||
layer: "overlay",
|
||||
margins: [0, 12, 8, 0],
|
||||
child: Widget.Box({
|
||||
vertical: true,
|
||||
children: [BrightnessPopup(), VolumePopup()],
|
||||
}),
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
import { Widget, Utils, Audio } from "../../imports.js";
|
||||
import { getSliderIcon, volumePercentBar } from "../../utils/audio.js";
|
||||
const { Box, Revealer } = Widget;
|
||||
const { speaker } = Audio;
|
||||
const { timeout } = Utils;
|
||||
|
||||
export const VolumePopup = () =>
|
||||
Box({
|
||||
css: `
|
||||
min-height: 2px;
|
||||
min-width: 2px;
|
||||
`,
|
||||
child: Revealer({
|
||||
transition: "slide_up",
|
||||
child: Box({
|
||||
className: "volumePopup",
|
||||
children: [getSliderIcon(), volumePercentBar()],
|
||||
}),
|
||||
attribute: { count: 0 },
|
||||
setup: (self) =>
|
||||
self.hook(
|
||||
speaker,
|
||||
() => {
|
||||
self.reveal_child = true;
|
||||
self.attribute.count++;
|
||||
timeout(1500, () => {
|
||||
self.attribute.count--;
|
||||
|
||||
if (self.attribute.count === 0)
|
||||
self.reveal_child = false;
|
||||
});
|
||||
},
|
||||
"notify::volume",
|
||||
),
|
||||
}),
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue