refactor(ags): switch to TypeScript
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2024-01-13 11:15:08 -05:00
parent a65a59e3db
commit 3e0b416a33
74 changed files with 1078 additions and 1327 deletions

View file

@ -3,12 +3,13 @@
"es2021": true "es2021": true
}, },
"extends": "eslint:recommended", "extends": "eslint:recommended",
"parser": "@typescript-eslint/parser",
"overrides": [], "overrides": [],
"parserOptions": { "parserOptions": {
"ecmaVersion": "latest", "ecmaVersion": "latest",
"sourceType": "module" "sourceType": "module"
}, },
"plugins": ["@stylistic"], "plugins": ["@stylistic", "@typescript-eslint"],
"rules": { "rules": {
"array-callback-return": ["error", { "array-callback-return": ["error", {
"allowImplicit": true, "allowImplicit": true,
@ -76,6 +77,8 @@
"prefer-regex-literals": ["error"], "prefer-regex-literals": ["error"],
"prefer-template": ["warn"], "prefer-template": ["warn"],
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "warn",
"@stylistic/array-bracket-newline": ["warn", "consistent"], "@stylistic/array-bracket-newline": ["warn", "consistent"],
"@stylistic/array-bracket-spacing": ["warn", "never"], "@stylistic/array-bracket-spacing": ["warn", "never"],

View file

@ -1,25 +1,57 @@
import { watchAndCompileSass } from './js/utils.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import windows from './js/main.js'; import { execAsync, monitorFile } from 'resource:///com/github/Aylur/ags/utils.js';
const watchAndCompileSass = () => {
const reloadCss = () => {
const scss = `${App.configDir}/scss/main.scss`;
const css = `${App.configDir}/style.css`;
execAsync(`sassc ${scss} ${css}`).then(() => {
App.resetCss();
App.applyCss(css);
});
};
monitorFile(
`${App.configDir}/scss`,
reloadCss,
'directory',
);
reloadCss();
};
const transpileTypeScript = async() => {
const dir = '/tmp/ags';
const promises = [];
const files = (await execAsync([
'find', `${App.configDir}/`,
'-wholename', '*services/*.ts',
'-o',
'-wholename', '*/ts/*.ts',
])).split('\n');
/** @param {string} p */
const getDirectoryPath = (p) => p.substring(0, p.lastIndexOf('/'));
files.forEach((file) => {
const outDir = getDirectoryPath(dir + file
.replace(`${App.configDir}/ts`, '/js')
.replace(`${App.configDir}/services`, '/services'));
promises.push(
execAsync([
'bun', 'build', file,
'--outdir', outDir,
'--external', '*',
]).catch(print),
);
});
await Promise.all(promises);
return await import(`file://${dir}/js/main.js`);
};
watchAndCompileSass(); watchAndCompileSass();
export default (await transpileTypeScript()).default;
const closeWinDelay = 800;
export default {
notificationPopupTimeout: 5000,
cacheNotificationActions: true,
closeWindowDelay: {
'applauncher': closeWinDelay,
'calendar': closeWinDelay,
'notification-center': closeWinDelay,
'osd': 300,
'osk': closeWinDelay,
'overview': closeWinDelay,
'powermenu': closeWinDelay,
'quick-settings': closeWinDelay,
},
windows: [
...windows,
],
};

View file

@ -1,33 +0,0 @@
import Setup from './setup.js';
import AppLauncher from './applauncher/main.js';
import Bar from './bar/main.js';
import BgFade from './misc/background-fade.js';
import Calendar from './date.js';
import Corners from './corners/main.js';
import { NotifPopups, NotifCenter } from './notifications/main.js';
import OSD from './osd/main.js';
import OSK from './on-screen-keyboard/main.js';
import Overview from './overview/main.js';
import Powermenu from './powermenu.js';
import QSettings from './quick-settings/main.js';
Setup();
export default [
// Put the corners first so they
// don't block the cursor on the bar
...Corners(),
AppLauncher(),
Calendar(),
NotifCenter(),
OSD(),
OSK(),
Overview(),
Powermenu(),
QSettings(),
Bar(),
BgFade(),
NotifPopups(),
];

View file

@ -1,230 +0,0 @@
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import { Box, CenterBox } from 'resource:///com/github/Aylur/ags/widget.js';
import * as mpris from './mpris.js';
import PlayerGesture from './gesture.js';
import Separator from '../misc/separator.js';
const FAVE_PLAYER = 'org.mpris.MediaPlayer2.spotify';
const SPACING = 8;
/**
* @typedef {import('types/service/mpris').MprisPlayer} Player
* @typedef {import('types/widgets/overlay').default} Overlay
* @typedef {import('types/variable').Variable} Variable
*/
/**
* @param {Player} player
* @param {Overlay} overlay
*/
const Top = (player, overlay) => Box({
class_name: 'top',
hpack: 'start',
vpack: 'start',
children: [
mpris.PlayerIcon(player, overlay),
],
});
/**
* @param {Player} player
* @param {Variable} colors
*/
const Center = (player, colors) => Box({
class_name: 'center',
children: [
CenterBox({
// @ts-expect-error
vertical: true,
children: [
Box({
class_name: 'metadata',
vertical: true,
hpack: 'start',
vpack: 'center',
hexpand: true,
children: [
mpris.TitleLabel(player),
mpris.ArtistLabel(player),
],
}),
null,
null,
],
}),
CenterBox({
// @ts-expect-error
vertical: true,
children: [
null,
mpris.PlayPauseButton(player, colors),
null,
],
}),
],
});
/**
* @param {Player} player
* @param {Variable} colors
*/
const Bottom = (player, colors) => Box({
class_name: 'bottom',
children: [
mpris.PreviousButton(player, colors),
Separator(SPACING),
mpris.PositionSlider(player, colors),
Separator(SPACING),
mpris.NextButton(player, colors),
Separator(SPACING),
mpris.ShuffleButton(player, colors),
Separator(SPACING),
mpris.LoopButton(player, colors),
],
});
/**
* @param {Player} player
* @param {Variable} colors
* @param {Overlay} overlay
*/
const PlayerBox = (player, colors, overlay) => {
const widget = mpris.CoverArt(player, colors, {
class_name: `player ${player.name}`,
hexpand: true,
start_widget: Top(player, overlay),
center_widget: Center(player, colors),
end_widget: Bottom(player, colors),
});
widget.visible = false;
return widget;
};
export default () => {
const content = PlayerGesture({
attribute: {
players: new Map(),
setup: false,
},
setup: /** @param {any} self */ (self) => {
self
.hook(Mpris,
/**
* @param {Overlay} overlay
* @param {string} bus_name
*/
(overlay, bus_name) => {
const players = overlay.attribute.players;
if (players.has(bus_name)) {
return;
}
// Sometimes the signal doesn't give the bus_name
if (!bus_name) {
const player = Mpris.players.find((p) => {
return !players.has(p.bus_name);
});
if (player) {
bus_name = player.bus_name;
}
else {
return;
}
}
// Get the one on top so we can move it up later
const previousFirst = overlay.attribute.list().at(-1);
// Make the new player
const player = Mpris.getPlayer(bus_name);
const Colors = Variable(null);
if (!player) {
return;
}
players.set(
bus_name,
// @ts-expect-error
PlayerBox(player, Colors, content.child),
);
overlay.overlays = Array.from(players.values())
.map((widget) => widget);
const includes = overlay.attribute
.includesWidget(previousFirst);
// Select favorite player at startup
const attrs = overlay.attribute;
if (!attrs.setup && players.has(FAVE_PLAYER)) {
attrs.moveToTop(players.get(FAVE_PLAYER));
attrs.setup = true;
}
// Move previousFirst on top again
else if (includes) {
attrs.moveToTop(previousFirst);
}
},
'player-added')
.hook(Mpris,
/**
* @param {Overlay} overlay
* @param {string} bus_name
*/
(overlay, bus_name) => {
const players = overlay.attribute.players;
if (!bus_name || !players.has(bus_name)) {
return;
}
// Get the one on top so we can move it up later
const previousFirst = overlay.attribute.list().at(-1);
// Remake overlays without deleted one
players.delete(bus_name);
overlay.overlays = Array.from(players.values())
.map((widget) => widget);
// Move previousFirst on top again
const includes = overlay.attribute
.includesWidget(previousFirst);
if (includes) {
overlay.attribute.moveToTop(previousFirst);
}
},
'player-closed');
},
});
return Box({
class_name: 'media',
child: content,
});
};

View file

@ -1,112 +0,0 @@
/**
* This is the old version of my popup windows.
* I don't use it anymore but I will keep it in
* case my new one breaks or simply as an example.
*/
import App from 'resource:///com/github/Aylur/ags/app.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { Revealer, Box, Window } from 'resource:///com/github/Aylur/ags/widget.js';
import { timeout } from 'resource:///com/github/Aylur/ags/utils.js';
/**
* @typedef {import('types/widgets/revealer').RevealerProps} RevProps
* @typedef {import('types/widgets/window').WindowProps} WinProps
* @typedef {import('gi://Gtk').Gtk.Widget} Widget
*/
/**
* @param {WinProps & {
* transition?: RevProps['transition']
* transition_duration?: RevProps['transition_duration']
* on_open?: function
* on_close?: function
* blur?: boolean
* }} o
*/
export default ({
transition = 'slide_down',
transition_duration = 800,
on_open = () => {/**/},
on_close = () => {/**/},
// Window props
name,
child = Box(),
visible = false,
layer = 'overlay',
blur = false,
...props
}) => {
const window = Window({
name,
layer,
visible: false,
...props,
attribute: {
// @ts-expect-error
get_child: () => window.child.children[0].child,
/** @param {Widget} new_child */
set_child: (new_child) => {
// @ts-expect-error
window.child.children[0].child = new_child;
// @ts-expect-error
window.child.children[0].show_all();
},
},
setup: () => {
// Add way to make window open on startup
const id = App.connect('config-parsed', () => {
if (visible) {
App.openWindow(String(name));
}
App.disconnect(id);
});
if (blur) {
Hyprland.sendMessage('[[BATCH]] ' +
`keyword layerrule ignorealpha[0.97],${name}; ` +
`keyword layerrule blur,${name}`);
}
},
// Wrapping the revealer inside a box is needed
// to allocate some space for it even when not revealed
child: Box({
css: `
min-height:1px;
min-width:1px;
padding: 1px;
`,
child: Revealer({
transition,
transition_duration,
child,
setup: (self) => {
self.hook(App, (_, currentName, isOpen) => {
if (currentName === name) {
self.reveal_child = isOpen;
if (isOpen) {
on_open(window);
}
else {
timeout(Number(transition_duration), () => {
on_close(window);
});
}
}
});
},
}),
}),
});
return window;
};

View file

@ -1,58 +0,0 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import { CenterBox, Icon, ToggleButton } from 'resource:///com/github/Aylur/ags/widget.js';
const { Gdk } = imports.gi;
const display = Gdk.Display.get_default();
/** @param {import('types/widgets/revealer').default} rev */
export default (rev) => CenterBox({
center_widget: ToggleButton({
setup: (self) => {
// Open at startup if there are players
const id = Mpris.connect('changed', () => {
self.set_active(Mpris.players.length > 0);
Mpris.disconnect(id);
});
self
.on('toggled', () => {
if (self.get_active()) {
self.child
// @ts-expect-error
?.setCss('-gtk-icon-transform: rotate(0deg);');
rev.reveal_child = true;
}
else {
self.child
// @ts-expect-error
?.setCss('-gtk-icon-transform: rotate(180deg);');
rev.reveal_child = false;
}
})
// OnHover
.on('enter-notify-event', () => {
self.window.set_cursor(Gdk.Cursor.new_from_name(
display,
'pointer',
));
self.toggleClassName('hover', true);
})
// OnHoverLost
.on('leave-notify-event', () => {
self.window.set_cursor(null);
self.toggleClassName('hover', false);
});
},
child: Icon({
icon: `${App.configDir}/icons/down-large.svg`,
class_name: 'arrow',
css: '-gtk-icon-transform: rotate(180deg);',
}),
}),
});

View file

@ -1,36 +0,0 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import { monitorFile, exec } from 'resource:///com/github/Aylur/ags/utils.js';
export const watchAndCompileSass = () => {
const reloadCss = () => {
// Main scss file
const scss = `${App.configDir}/scss/main.scss`;
// Target css file
const css = `${App.configDir}/style.css`;
// Compile, reset, apply
exec(`sassc ${scss} ${css}`);
App.resetCss();
App.applyCss(css);
};
monitorFile(
// Directory that contains the scss files
`${App.configDir}/scss`,
reloadCss,
// Specify that its a directory
'directory',
);
reloadCss();
};
export const compileTypescript = () => {
const ts = `${App.configDir}/ts/main.ts`;
const js = `${App.configDir}/compiled.js`;
exec(`bash -c 'cd ${App.configDir} && nix develop && bun install && tsc ${ts} --outfile ${js}'`);
};

View file

@ -1,25 +0,0 @@
import { Widget, Box } from 'resource:///com/github/Aylur/ags/widget.js';
import WebKit2 from 'gi://WebKit2';
import PopupWindow from './misc/popup.js';
const WebView = Widget.subclass(WebKit2.WebView);
export default () => {
const view = WebView({
hexpand: true,
});
view.load_uri('https://search.nixos.org');
return PopupWindow({
name: 'browser',
visible: true,
focusable: true,
layer: 'top',
child: Box({
css: 'min-height: 600px; min-width: 800px;',
children: [view],
}),
});
};

View file

@ -4,15 +4,6 @@ import Service from 'resource:///com/github/Aylur/ags/service.js';
import { subprocess } from 'resource:///com/github/Aylur/ags/utils.js'; import { subprocess } from 'resource:///com/github/Aylur/ags/utils.js';
const { GUdev } = imports.gi;
const UDEV_POINTERS = [
'ID_INPUT_MOUSE',
'ID_INPUT_POINTINGSTICK',
'ID_INPUT_TOUCHPAD',
'ID_INPUT_TOUCHSCREEN',
'ID_INPUT_TABLET',
];
const ON_RELEASE_TRIGGERS = [ const ON_RELEASE_TRIGGERS = [
'released', 'released',
'TOUCH_UP', 'TOUCH_UP',
@ -23,6 +14,31 @@ const ON_CLICK_TRIGGERS = [
'TOUCH_DOWN', 'TOUCH_DOWN',
]; ];
// Types
import AgsWindow from 'types/widgets/window';
type Subprocess = typeof imports.gi.Gio.Subprocess;
type Layer = {
address: string;
x: number;
y: number;
w: number;
h: number;
namespace: string;
};
type Levels = {
0?: Array<Layer> | null;
1?: Array<Layer> | null;
2?: Array<Layer> | null;
3?: Array<Layer> | null;
};
type Layers = {
levels: Levels;
};
type CursorPos = {
x: number;
y: number;
};
class Pointers extends Service { class Pointers extends Service {
static { static {
@ -36,12 +52,9 @@ class Pointers extends Service {
}); });
} }
/** @type typeof imports.gi.Gio.Subprocess */ #process: Subprocess;
#process;
#lastLine = ''; #lastLine = '';
/** @type Array<string> */ #pointers = [] as Array<String>;
#pointers = [];
#udevClient = GUdev.Client.new(['input']);
get process() { get process() {
return this.#process; return this.#process;
@ -57,67 +70,16 @@ class Pointers extends Service {
constructor() { constructor() {
super(); super();
this.#initUdevConnection();
this.#initAppConnection(); this.#initAppConnection();
} }
// FIXME: logitech mouse screws everything up on disconnect
#initUdevConnection() {
this.#getDevices();
this.#udevClient.connect('uevent',
/**
* @param {typeof imports.gi.GUdev.Client} _
* @param {string} action
*/
(_, action) => {
if (action === 'add' || action === 'remove') {
this.#getDevices();
if (this.#process) {
this.killProc();
this.startProc();
}
}
});
}
#getDevices() {
this.#pointers = [];
this.#udevClient.query_by_subsystem('input').forEach(
/** @param {typeof imports.gi.GUdev.Device} dev */
(dev) => {
const isPointer = UDEV_POINTERS.some(
(p) => dev.has_property(p),
);
if (isPointer) {
const hasEventFile = dev.has_property('DEVNAME') &&
dev.get_property('DEVNAME')
.includes('event');
if (hasEventFile) {
this.#pointers.push(dev.get_property('DEVNAME'));
}
}
},
);
this.emit('device-fetched', true);
}
startProc() { startProc() {
if (this.#process) { if (this.#process) {
return; return;
} }
const args = [];
this.#pointers.forEach((dev) => {
args.push('--device');
args.push(dev);
});
this.#process = subprocess( this.#process = subprocess(
['libinput', 'debug-events', ...args], ['libinput', 'debug-events'],
(output) => { (output) => {
if (output.includes('cancelled')) { if (output.includes('cancelled')) {
return; return;
@ -151,16 +113,15 @@ class Pointers extends Service {
#initAppConnection() { #initAppConnection() {
App.connect('window-toggled', () => { App.connect('window-toggled', () => {
const anyVisibleAndClosable = Array.from(App.windows).some((w) => { const anyVisibleAndClosable =
// @ts-expect-error (Array.from(App.windows) as Array<[string, AgsWindow]>)
const closable = w[1].attribute?.close_on_unfocus && .some((w) => {
// @ts-expect-error const closable = w[1].attribute?.close_on_unfocus &&
!(w[1].attribute?.close_on_unfocus === 'none' || !(w[1].attribute?.close_on_unfocus === 'none' ||
// @ts-expect-error
w[1].attribute?.close_on_unfocus === 'stay'); w[1].attribute?.close_on_unfocus === 'stay');
return w[1].visible && closable; return w[1].visible && closable;
}); });
if (anyVisibleAndClosable) { if (anyVisibleAndClosable) {
this.startProc(); this.startProc();
@ -172,42 +133,38 @@ class Pointers extends Service {
}); });
} }
/** @param {string} clickStage */ static detectClickedOutside(clickStage: string) {
static detectClickedOutside(clickStage) { const toClose = (Array.from(App.windows) as Array<[string, AgsWindow]>)
const toClose = Array.from(App.windows).some((w) => { .some((w) => {
// @ts-expect-error const closable = (w[1].attribute?.close_on_unfocus &&
const closable = (w[1].attribute?.close_on_unfocus &&
// @ts-expect-error
w[1].attribute?.close_on_unfocus === clickStage); w[1].attribute?.close_on_unfocus === clickStage);
return w[1].visible && closable; return w[1].visible && closable;
}); });
if (!toClose) { if (!toClose) {
return; return;
} }
Hyprland.sendMessage('j/layers').then((response) => { Hyprland.sendMessage('j/layers').then((response) => {
// /** @type import('types/service/hyprland').Layer */ const layers = JSON.parse(response) as { Layers: Layers };
const layers = JSON.parse(response);
Hyprland.sendMessage('j/cursorpos').then((res) => { Hyprland.sendMessage('j/cursorpos').then((res) => {
const pos = JSON.parse(res); const pos = JSON.parse(res) as CursorPos;
Object.values(layers).forEach((key) => { Object.values(layers).forEach((key) => {
const bar = key.levels['3'].find( const bar = key.levels['3']?.find(
/** @param {{ namespace: string }} n */
(n) => n.namespace === 'bar', (n) => n.namespace === 'bar',
); ) ||
// Return an empty Layer if bar doesn't exist
{ address: '', x: 0, y: 0, w: 0, h: 0, namespace: '' };
const widgets = key.levels['3'].filter( const widgets = key.levels['3']?.filter(
/** @param {{ namespace: string }} n */
(n) => { (n) => {
const window = App.getWindow(n.namespace); const window =
(App.getWindow(n.namespace) as AgsWindow);
// @ts-expect-error
return window?.attribute?.close_on_unfocus && return window?.attribute?.close_on_unfocus &&
// @ts-expect-error
window?.attribute window?.attribute
?.close_on_unfocus === clickStage; ?.close_on_unfocus === clickStage;
}, },
@ -220,15 +177,7 @@ class Pointers extends Service {
// TODO: make this configurable // TODO: make this configurable
} }
else { else {
widgets.forEach( widgets?.forEach(
/** @param {{
* namespace: string
* x: number
* y: number
* h: number
* w: number
* }} w
*/
(w) => { (w) => {
if (!(pos.x > w.x && pos.x < w.x + w.w && if (!(pos.x > w.x && pos.x < w.x + w.w &&
pos.y > w.y && pos.y < w.y + w.h)) { pos.y > w.y && pos.y < w.y + w.h)) {

View file

@ -15,6 +15,9 @@ const DEVICES = [
'wacom-hid-52eb-pen', 'wacom-hid-52eb-pen',
]; ];
// Types
type Subprocess = typeof imports.gi.Gio.Subprocess;
class Tablet extends Service { class Tablet extends Service {
static { static {
@ -33,10 +36,8 @@ class Tablet extends Service {
#tabletMode = false; #tabletMode = false;
#oskState = false; #oskState = false;
/** @type typeof imports.gi.Gio.Subprocess */ #autorotate: Subprocess;
#autorotate; #blockedInputs: Subprocess;
/** @type typeof imports.gi.Gio.Subprocess */
#blockedInputs;
get tabletMode() { get tabletMode() {
return this.#tabletMode; return this.#tabletMode;
@ -124,7 +125,13 @@ class Tablet extends Service {
['monitor-sensor'], ['monitor-sensor'],
(output) => { (output) => {
if (output.includes('orientation changed')) { if (output.includes('orientation changed')) {
const orientation = ROTATION_MAP[output.split(' ').at(-1)]; const index = output.split(' ').at(-1);
if (!index) {
return;
}
const orientation = ROTATION_MAP[index];
Hyprland.sendMessage( Hyprland.sendMessage(
`keyword monitor ${SCREEN},transform,${orientation}`, `keyword monitor ${SCREEN},transform,${orientation}`,

View file

@ -32,6 +32,9 @@ const DISTANCE_VERIF = [
'L', // Large 'L', // Large
]; ];
// Types
type Subprocess = typeof imports.gi.Gio.Subprocess;
// TODO: add actmode param // TODO: add actmode param
// TODO: support multiple daemons for different thresholds // TODO: support multiple daemons for different thresholds
@ -44,8 +47,7 @@ class TouchGestures extends Service {
} }
#gestures = new Map(); #gestures = new Map();
/** @type typeof imports.gi.Gio.Subprocess */ #gestureDaemon: Subprocess;
#gestureDaemon;
get gestures() { get gestures() {
return this.#gestures; return this.#gestures;

View file

@ -5,9 +5,11 @@ import { lookUpIcon } from 'resource:///com/github/Aylur/ags/utils.js';
import CursorBox from '../misc/cursorbox.js'; import CursorBox from '../misc/cursorbox.js';
// Types
import { Application } from 'types/service/applications.js';
/** @param {import('types/service/applications.js').Application} app */
export default (app) => { export default (app: Application) => {
const icon = Icon({ size: 42 }); const icon = Icon({ size: 42 });
const iconString = app.app.get_string('Icon'); const iconString = app.app.get_string('Icon');

View file

@ -1,46 +1,36 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js'; import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
// TODO: find cleaner way to import this // FIXME: find cleaner way to import this
import { Fzf } from '../../node_modules/fzf/dist/fzf.es.js'; // @ts-expect-error
import { Fzf } from 'file:///home/matt/.config/ags/node_modules/fzf/dist/fzf.es.js';
import { Box, Entry, Icon, Label, ListBox, Revealer, Scrollable } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Entry, Icon, Label, ListBox, Revealer, Scrollable } from 'resource:///com/github/Aylur/ags/widget.js';
import PopupWindow from '../misc/popup.js'; import PopupWindow from '../misc/popup.js';
import AppItem from './app-item.js'; import AppItem from './app-item.js';
/** // Types
* @typedef {import('types/service/applications.js').Application} App import { Application } from 'types/service/applications.js';
* @typedef {typeof imports.gi.Gtk.ListBoxRow} ListBoxRow type ListBoxRow = typeof imports.gi.Gtk.ListBoxRow;
*/
const Applauncher = (window_name = 'applauncher') => { const Applauncher = (window_name = 'applauncher') => {
/** @type Array<any> */ let fzfResults: Array<any>;
let fzfResults;
const list = ListBox({}); const list = ListBox({});
/** @param {String} text */ const setSort = (text: string) => {
const setSort = (text) => {
const fzf = new Fzf(Applications.list, { const fzf = new Fzf(Applications.list, {
selector: /** @param {App} app */ (app) => { selector: (app: Application) => {
return app.name + app.executable; return app.name + app.executable;
}, },
tiebreakers: [ tiebreakers: [
/** (a: Application, b: Application) => b.frequency - a.frequency,
* @param {App} a
* @param {App} b
*/
(a, b) => b.frequency - a.frequency,
], ],
}); });
fzfResults = fzf.find(text); fzfResults = fzf.find(text);
list.set_sort_func( list.set_sort_func(
/** (a: ListBoxRow, b: ListBoxRow) => {
* @param {ListBoxRow} a
* @param {ListBoxRow} b
*/
(a, b) => {
const row1 = a.get_children()[0]?.attribute.app.name; const row1 = a.get_children()[0]?.attribute.app.name;
const row2 = b.get_children()[0]?.attribute.app.name; const row2 = b.get_children()[0]?.attribute.app.name;
@ -55,8 +45,7 @@ const Applauncher = (window_name = 'applauncher') => {
}; };
const makeNewChildren = () => { const makeNewChildren = () => {
/** @type Array<typeof imports.gi.Gtk.ListBoxRow> */ const rows = list.get_children() as Array<ListBoxRow>;
const rows = list.get_children();
rows.forEach((ch) => { rows.forEach((ch) => {
ch.destroy(); ch.destroy();
@ -101,8 +90,7 @@ const Applauncher = (window_name = 'applauncher') => {
setSort(text); setSort(text);
let visibleApps = 0; let visibleApps = 0;
/** @type Array<ListBoxRow> */ const rows = list.get_children() as Array<ListBoxRow>;
const rows = list.get_children();
rows.forEach((row) => { rows.forEach((row) => {
row.changed(); row.changed();

View file

@ -3,9 +3,12 @@ import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import { Box, EventBox, Revealer, Window } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, EventBox, Revealer, Window } from 'resource:///com/github/Aylur/ags/widget.js';
// Types
import { Variable as Var } from 'types/variable';
import AgsBox from 'types/widgets/box';
import { RevealerProps } from 'types/widgets/revealer';
/** @param {import('types/variable.js').Variable} variable */ const BarCloser = (variable: Var<boolean>) => Window({
const BarCloser = (variable) => Window({
name: 'bar-closer', name: 'bar-closer',
visible: false, visible: false,
anchor: ['top', 'bottom', 'left', 'right'], anchor: ['top', 'bottom', 'left', 'right'],
@ -27,8 +30,7 @@ const BarCloser = (variable) => Window({
}), }),
}); });
/** @param {import('types/widgets/revealer').RevealerProps} props */ export default (props: RevealerProps) => {
export default (props) => {
const Revealed = Variable(true); const Revealed = Variable(true);
const barCloser = BarCloser(Revealed); const barCloser = BarCloser(Revealed);
@ -48,11 +50,7 @@ export default (props) => {
} }
}; };
/** const checkGlobalFsState = (_: AgsBox, fullscreen: boolean) => {
* @param {import('types/widgets/box').default} _
* @param {boolean} fullscreen
*/
const checkGlobalFsState = (_, fullscreen) => {
Revealed.value = !fullscreen; Revealed.value = !fullscreen;
}; };

View file

@ -6,13 +6,22 @@ import HoverRevealer from './hover-revealer.js';
const DEFAULT_KB = 'at-translated-set-2-keyboard'; const DEFAULT_KB = 'at-translated-set-2-keyboard';
import AgsLabel from 'types/widgets/label.js';
type Keyboard = {
address: string;
name: string;
rules: string;
model: string;
layout: string;
variant: string;
options: string;
active_keymap: string;
main: boolean;
};
/**
* @param {import('types/widgets/label').default} self
* @param {string} layout const getKbdLayout = (self: AgsLabel, _: string, layout: string) => {
* @param {string} _
*/
const getKbdLayout = (self, _, layout) => {
if (layout) { if (layout) {
if (layout === 'error') { if (layout === 'error') {
return; return;
@ -25,15 +34,21 @@ const getKbdLayout = (self, _, layout) => {
else { else {
// At launch, kb layout is undefined // At launch, kb layout is undefined
Hyprland.sendMessage('j/devices').then((obj) => { Hyprland.sendMessage('j/devices').then((obj) => {
const kb = Array.from(JSON.parse(obj).keyboards) const keyboards = Array.from(JSON.parse(obj)
.find((v) => v.name === DEFAULT_KB); .keyboards) as Array<Keyboard>;
const kb = keyboards.find((v) => v.name === DEFAULT_KB);
layout = kb['active_keymap']; if (kb) {
layout = kb.active_keymap;
const shortName = layout const shortName = layout
.match(/\(([A-Za-z]+)\)/); .match(/\(([A-Za-z]+)\)/);
self.label = shortName ? shortName[1] : layout; self.label = shortName ? shortName[1] : layout;
}
else {
self.label = 'None';
}
}).catch(print); }).catch(print);
} }
}; };

View file

@ -8,16 +8,19 @@ import Separator from '../../misc/separator.js';
const SPACING = 4; const SPACING = 4;
// Types
import AgsWindow from 'types/widgets/window.js';
export default () => CursorBox({ export default () => CursorBox({
class_name: 'toggle-off', class_name: 'toggle-off',
on_primary_click_release: (self) => { on_primary_click_release: (self) => {
// @ts-expect-error (App.getWindow('notification-center') as AgsWindow)
App.getWindow('notification-center')?.attribute.set_x_pos( ?.attribute.set_x_pos(
self.get_allocation(), self.get_allocation(),
'right', 'right',
); );
App.toggleWindow('notification-center'); App.toggleWindow('notification-center');
}, },

View file

@ -20,5 +20,4 @@ export default () => CursorBox({
xalign: 0.6, xalign: 0.6,
label: '󰌌 ', label: '󰌌 ',
}), }),
}); });

View file

@ -13,6 +13,11 @@ import Separator from '../../misc/separator.js';
const SPACING = 4; const SPACING = 4;
// Types
import AgsRevealer from 'types/widgets/revealer.js';
import AgsBox from 'types/widgets/box.js';
import AgsWindow from 'types/widgets/window.js';
export default () => { export default () => {
const hoverRevealers = [ const hoverRevealers = [
@ -31,11 +36,11 @@ export default () => {
class_name: 'toggle-off', class_name: 'toggle-off',
on_primary_click_release: (self) => { on_primary_click_release: (self) => {
// @ts-expect-error (App.getWindow('quick-settings') as AgsWindow)
App.getWindow('quick-settings').attribute.set_x_pos( ?.attribute.set_x_pos(
self.get_allocation(), self.get_allocation(),
'right', 'right',
); );
App.toggleWindow('quick-settings'); App.toggleWindow('quick-settings');
}, },
@ -49,15 +54,15 @@ export default () => {
}, },
attribute: { attribute: {
hoverRevealers: hoverRevealers.map( hoverRevealers: hoverRevealers.map((rev) => {
// @ts-expect-error const box = rev.child as AgsBox;
(rev) => rev.child.children[1],
), return box.children[1];
}),
}, },
on_hover_lost: (self) => { on_hover_lost: (self) => {
self.attribute.hoverRevealers.forEach( self.attribute.hoverRevealers.forEach(
/** @param {import('types/widgets/revealer').default} rev */ (rev: AgsRevealer) => {
(rev) => {
rev.reveal_child = false; rev.reveal_child = false;
}, },
); );

View file

@ -8,35 +8,32 @@ import Separator from '../../misc/separator.js';
const REVEAL_DURATION = 500; const REVEAL_DURATION = 500;
const SPACING = 12; const SPACING = 12;
// Types
import { TrayItem } from 'types/service/systemtray.js';
import AgsRevealer from 'types/widgets/revealer.js';
type Menu = typeof imports.gi.Gtk.Menu;
/** @param {import('types/service/systemtray').TrayItem} item */
const SysTrayItem = (item) => { const SysTrayItem = (item: TrayItem) => {
if (item.id === 'spotify-client') { if (item.id === 'spotify-client') {
return; return;
} }
return MenuItem({ return MenuItem({
// @ts-expect-error submenu: <Menu> item.menu,
submenu: item.menu,
tooltip_markup: item.bind('tooltip_markup'), tooltip_markup: item.bind('tooltip_markup'),
child: Revealer({ child: Revealer({
transition: 'slide_right', transition: 'slide_right',
transition_duration: REVEAL_DURATION, transition_duration: REVEAL_DURATION,
child: Icon({ child: Icon({ size: 24 }).bind('icon', item, 'icon'),
size: 24,
// @ts-expect-error
icon: item.bind('icon'),
}),
}), }),
}); });
}; };
const SysTray = () => MenuBar({ const SysTray = () => MenuBar({
attribute: { attribute: { items: new Map() },
items: new Map(),
},
setup: (self) => { setup: (self) => {
self self
@ -57,8 +54,8 @@ const SysTray = () => MenuBar({
self.attribute.items.set(id, w); self.attribute.items.set(id, w);
self.child = w; self.child = w;
self.show_all(); self.show_all();
// @ts-expect-error
w.child.reveal_child = true; (<AgsRevealer> w.child).reveal_child = true;
}, 'added') }, 'added')
.hook(SystemTray, (_, id) => { .hook(SystemTray, (_, id) => {

View file

@ -7,11 +7,14 @@ import CursorBox from '../../misc/cursorbox.js';
const URGENT_DURATION = 1000; const URGENT_DURATION = 1000;
/** @typedef {import('types/widgets/revealer.js').default} Revealer */ // Types
import AgsBox from 'types/widgets/box.js';
import AgsRevealer from 'types/widgets/revealer.js';
import AgsOverlay from 'types/widgets/overlay.js';
import AgsEventBox from 'types/widgets/eventbox.js';
/** @property {number} id */ const Workspace = ({ id }: { id: number }) => {
const Workspace = ({ id }) => {
return Revealer({ return Revealer({
transition: 'slide_right', transition: 'slide_right',
attribute: { id }, attribute: { id },
@ -28,11 +31,7 @@ const Workspace = ({ id }) => {
class_name: 'button', class_name: 'button',
setup: (self) => { setup: (self) => {
/** const update = (_: AgsBox, addr: string | undefined) => {
* @param {import('types/widgets/box').default} _
* @param {string|undefined} addr
*/
const update = (_, addr) => {
const workspace = Hyprland.getWorkspace(id); const workspace = Hyprland.getWorkspace(id);
const occupied = workspace && workspace.windows > 0; const occupied = workspace && workspace.windows > 0;
@ -80,13 +79,13 @@ export default () => {
const L_PADDING = 16; const L_PADDING = 16;
const WS_WIDTH = 30; const WS_WIDTH = 30;
/** @param {import('types/widgets/box').default} self */ const updateHighlight = (self: AgsBox) => {
const updateHighlight = (self) => {
const currentId = Hyprland.active.workspace.id; const currentId = Hyprland.active.workspace.id;
/** @type Array<Revealer> */ const indicators = (((self.get_parent() as AgsOverlay)
// @ts-expect-error .child as AgsEventBox)
const indicators = self?.get_parent()?.child.child.children; .child as AgsBox)
.children as Array<AgsRevealer>;
const currentIndex = indicators const currentIndex = indicators
.findIndex((w) => w.attribute.id === currentId); .findIndex((w) => w.attribute.id === currentId);
@ -112,18 +111,14 @@ export default () => {
child: Box({ child: Box({
class_name: 'workspaces', class_name: 'workspaces',
attribute: { attribute: { workspaces: [] },
/** @type Array<Revealer> */
workspaces: [],
},
setup: (self) => { setup: (self) => {
/** @type function(void):Array<Revealer> */ const workspaces = (): Array<AgsRevealer> =>
const workspaces = () => self.attribute.workspaces; self.attribute.workspaces;
const refresh = () => { const refresh = () => {
self.children.forEach((rev) => { (self.children as Array<AgsRevealer>).forEach((rev) => {
// @ts-expect-error they are in fact revealers
rev.reveal_child = false; rev.reveal_child = false;
}); });
@ -134,10 +129,8 @@ export default () => {
const updateWorkspaces = () => { const updateWorkspaces = () => {
Hyprland.workspaces.forEach((ws) => { Hyprland.workspaces.forEach((ws) => {
const currentWs = self.children.find((ch) => { const currentWs = (self.children as Array<AgsBox>)
// @ts-expect-error .find((ch) => ch.attribute.id === ws.id);
return ch.attribute.id === ws.id;
});
if (!currentWs && ws.id > 0) { if (!currentWs && ws.id > 0) {
self.add(Workspace({ id: ws.id })); self.add(Workspace({ id: ws.id }));
@ -147,8 +140,7 @@ export default () => {
// Make sure the order is correct // Make sure the order is correct
workspaces().forEach((workspace, i) => { workspaces().forEach((workspace, i) => {
// @ts-expect-error (<AgsBox> workspace.get_parent()).reorder_child(
workspace?.get_parent()?.reorder_child(
workspace, workspace,
i, i,
); );
@ -157,12 +149,10 @@ export default () => {
self.hook(Hyprland, () => { self.hook(Hyprland, () => {
self.attribute.workspaces = self.attribute.workspaces =
self.children.filter((ch) => { (self.children as Array<AgsBox>).filter((ch) => {
return Hyprland.workspaces.find((ws) => { return Hyprland.workspaces.find((ws) => {
// @ts-expect-error
return ws.id === ch.attribute.id; return ws.id === ch.attribute.id;
}); });
// @ts-expect-error
}).sort((a, b) => a.attribute.id - b.attribute.id); }).sort((a, b) => a.attribute.id - b.attribute.id);
updateWorkspaces(); updateWorkspaces();

View file

@ -1,7 +1,6 @@
import { Box, DrawingArea } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, DrawingArea } from 'resource:///com/github/Aylur/ags/widget.js';
import Gtk from 'gi://Gtk'; import Gtk from 'gi://Gtk';
const Lang = imports.lang;
export default ( export default (
place = 'top left', place = 'top left',
@ -27,8 +26,7 @@ export default (
.get_property('border-radius', Gtk.StateFlags.NORMAL); .get_property('border-radius', Gtk.StateFlags.NORMAL);
widget.set_size_request(r, r); widget.set_size_request(r, r);
// @ts-expect-error widget.connect('draw', (_, cr) => {
widget.connect('draw', Lang.bind(widget, (_, cr) => {
const c = widget.get_style_context() const c = widget.get_style_context()
.get_property('background-color', Gtk.StateFlags.NORMAL); .get_property('background-color', Gtk.StateFlags.NORMAL);
@ -38,7 +36,7 @@ export default (
const borderColor = widget.get_style_context() const borderColor = widget.get_style_context()
.get_property('color', Gtk.StateFlags.NORMAL); .get_property('color', Gtk.StateFlags.NORMAL);
// Ur going to write border-width: something anyway // You're going to write border-width: something anyway
const borderWidth = widget.get_style_context() const borderWidth = widget.get_style_context()
.get_border(Gtk.StateFlags.NORMAL).left; .get_border(Gtk.StateFlags.NORMAL).left;
@ -75,7 +73,7 @@ export default (
borderColor.blue, borderColor.blue,
borderColor.alpha); borderColor.alpha);
cr.stroke(); cr.stroke();
})); });
}, },
}), }),
}); });

View file

@ -0,0 +1,50 @@
import Setup from './setup.js';
import AppLauncher from './applauncher/main.js';
import Bar from './bar/main.js';
import BgFade from './misc/background-fade.js';
import Calendar from './date.js';
import Corners from './corners/main.js';
import { NotifPopups, NotifCenter } from './notifications/main.js';
import OSD from './osd/main.js';
import OSK from './on-screen-keyboard/main.js';
import Overview from './overview/main.js';
import Powermenu from './powermenu.js';
import QSettings from './quick-settings/main.js';
Setup();
const closeWinDelay = 800;
export default {
notificationPopupTimeout: 5000,
cacheNotificationActions: true,
closeWindowDelay: {
'applauncher': closeWinDelay,
'calendar': closeWinDelay,
'notification-center': closeWinDelay,
'osd': 300,
'osk': closeWinDelay,
'overview': closeWinDelay,
'powermenu': closeWinDelay,
'quick-settings': closeWinDelay,
},
windows: [
// Put the corners first so they
// don't block the cursor on the bar
...Corners(),
AppLauncher(),
Calendar(),
NotifCenter(),
OSD(),
OSK(),
Overview(),
Powermenu(),
QSettings(),
Bar(),
BgFade(),
NotifPopups(),
],
};

View file

@ -9,21 +9,24 @@ const ANIM_DURATION = 500;
const TRANSITION = `transition: margin ${ANIM_DURATION}ms ease, const TRANSITION = `transition: margin ${ANIM_DURATION}ms ease,
opacity ${ANIM_DURATION}ms ease;`; opacity ${ANIM_DURATION}ms ease;`;
/** // Types
* @typedef {import('types/widgets/overlay').OverlayProps} OverlayProps import AgsOverlay from 'types/widgets/overlay';
* @typedef {import('types/widgets/overlay').default} Overlay import OverlayProps from 'types/widgets/overlay';
*/ import AgsBox from 'types/widgets/box';
import AgsCenterBox from 'types/widgets/centerbox';
import { Connectable } from 'types/widgets/widget';
type Gesture = {
attribute?: Object
setup?(self: Connectable<AgsOverlay> & AgsOverlay): void
props?: OverlayProps
};
/** @param {OverlayProps & {
* setup?: function(Overlay):void
* }} o
*/
export default ({ export default ({
attribute = {}, attribute = {},
setup = () => {/**/}, setup = () => {/**/},
...props ...props
}) => { }: Gesture) => {
const widget = EventBox(); const widget = EventBox();
const gesture = Gtk.GestureDrag.new(widget); const gesture = Gtk.GestureDrag.new(widget);
@ -39,23 +42,15 @@ export default ({
...attribute, ...attribute,
dragging: false, dragging: false,
list: () => content.get_children() includesWidget: (playerW: AgsOverlay) => {
// @ts-expect-error return content.overlays.find((w) => w === playerW);
.filter((ch) => !ch.attribute?.empty),
/** @param {Overlay} playerW */
includesWidget: (playerW) => {
return Array.from(content.attribute.list())
.find((w) => w === playerW);
}, },
showTopOnly: () => Array.from(content.attribute.list()) showTopOnly: () => content.overlays.forEach((over) => {
.forEach((over) => { over.visible = over === content.overlays.at(-1);
over.visible = over === content.attribute.list().at(-1); }),
}),
/** @param {import('types/widgets/centerbox').default} player */ moveToTop: (player: AgsCenterBox) => {
moveToTop: (player) => {
player.visible = true; player.visible = true;
content.reorder_overlay(player, -1); content.reorder_overlay(player, -1);
timeout(ANIM_DURATION, () => { timeout(ANIM_DURATION, () => {
@ -72,23 +67,22 @@ export default ({
self self
.hook(gesture, (_, realGesture) => { .hook(gesture, (_, realGesture) => {
if (realGesture) { if (realGesture) {
Array.from(self.attribute.list()) self.overlays.forEach((over) => {
.forEach((over) => { over.visible = true;
over.visible = true; });
});
} }
else { else {
self.attribute.showTopOnly(); self.attribute.showTopOnly();
} }
// Don't allow gesture when only one player // Don't allow gesture when only one player
if (self.attribute.list().length <= 1) { if (self.overlays.length <= 1) {
return; return;
} }
self.attribute.dragging = true; self.attribute.dragging = true;
let offset = gesture.get_offset()[1]; let offset = gesture.get_offset()[1];
const playerBox = self.attribute.list().at(-1); const playerBox = self.overlays.at(-1) as AgsBox;
if (!offset) { if (!offset) {
return; return;
@ -117,14 +111,14 @@ export default ({
.hook(gesture, () => { .hook(gesture, () => {
// Don't allow gesture when only one player // Don't allow gesture when only one player
if (self.attribute.list().length <= 1) { if (self.overlays.length <= 1) {
return; return;
} }
self.attribute.dragging = false; self.attribute.dragging = false;
const offset = gesture.get_offset()[1]; const offset = gesture.get_offset()[1];
const playerBox = self.attribute.list().at(-1); const playerBox = self.overlays.at(-1) as AgsBox;
// If crosses threshold after letting go, slide away // If crosses threshold after letting go, slide away
if (offset && Math.abs(offset) > MAX_OFFSET) { if (offset && Math.abs(offset) > MAX_OFFSET) {

View file

@ -6,12 +6,6 @@ import { execAsync, lookUpIcon, readFileAsync } from 'resource:///com/github/Ayl
import Separator from '../misc/separator.js'; import Separator from '../misc/separator.js';
import CursorBox from '../misc/cursorbox.js'; import CursorBox from '../misc/cursorbox.js';
/**
* @typedef {import('types/service/mpris').MprisPlayer} Player
* @typedef {import('types/variable').Variable} Variable
* @typedef {import('types/widgets/overlay').default} Overlay
*/
const ICON_SIZE = 32; const ICON_SIZE = 32;
const icons = { const icons = {
@ -34,13 +28,21 @@ const icons = {
}, },
}; };
// Types
import { MprisPlayer } from 'types/service/mpris.js';
import { Variable as Var } from 'types/variable';
import AgsOverlay from 'types/widgets/overlay.js';
import AgsCenterBox, { CenterBoxProps } from 'types/widgets/centerbox.js';
import AgsLabel from 'types/widgets/label.js';
import AgsIcon from 'types/widgets/icon.js';
import AgsStack from 'types/widgets/stack.js';
/**
* @param {Player} player export const CoverArt = (
* @param {Variable} colors player: MprisPlayer,
* @param {import('types/widgets/centerbox').CenterBoxProps=} props colors: Var<any>,
*/ props: CenterBoxProps,
export const CoverArt = (player, colors, props) => CenterBox({ ) => CenterBox({
...props, ...props,
// @ts-expect-error // @ts-expect-error
vertical: true, vertical: true,
@ -92,8 +94,8 @@ export const CoverArt = (player, colors, props) => CenterBox({
background-position: center; background-position: center;
`; `;
// @ts-expect-error if (!(self.get_parent() as AgsCenterBox)
if (!self?.get_parent()?.attribute.dragging) { .attribute.dragging) {
self.setCss(self.attribute.bgStyle); self.setCss(self.attribute.bgStyle);
} }
}).catch((err) => { }).catch((err) => {
@ -105,8 +107,7 @@ export const CoverArt = (player, colors, props) => CenterBox({
}, },
}); });
/** @param {Player} player */ export const TitleLabel = (player: MprisPlayer) => Label({
export const TitleLabel = (player) => Label({
xalign: 0, xalign: 0,
max_width_chars: 40, max_width_chars: 40,
truncate: 'end', truncate: 'end',
@ -115,8 +116,7 @@ export const TitleLabel = (player) => Label({
label: player.bind('track_title'), label: player.bind('track_title'),
}); });
/** @param {Player} player */ export const ArtistLabel = (player: MprisPlayer) => Label({
export const ArtistLabel = (player) => Label({
xalign: 0, xalign: 0,
max_width_chars: 40, max_width_chars: 40,
truncate: 'end', truncate: 'end',
@ -127,17 +127,12 @@ export const ArtistLabel = (player) => Label({
}); });
/** export const PlayerIcon = (player: MprisPlayer, overlay: AgsOverlay) => {
* @param {Player} player const playerIcon = (
* @param {Overlay} overlay p: MprisPlayer,
*/ widget?: AgsOverlay,
export const PlayerIcon = (player, overlay) => { over?: AgsOverlay,
/** ) => CursorBox({
* @param {Player} p
* @param {Overlay=} widget
* @param {Overlay=} over
*/
const playerIcon = (p, widget, over) => CursorBox({
tooltip_text: p.identity || '', tooltip_text: p.identity || '',
on_primary_click_release: () => { on_primary_click_release: () => {
@ -159,10 +154,16 @@ export const PlayerIcon = (player, overlay) => {
}); });
return Box().hook(Mpris, (self) => { return Box().hook(Mpris, (self) => {
const thisIndex = overlay.attribute.list() const grandPa = self.get_parent()?.get_parent();
.indexOf(self?.get_parent()?.get_parent());
self.children = Array.from(overlay.attribute.list()) if (!grandPa) {
return;
}
const thisIndex = overlay.overlays
.indexOf(grandPa);
self.children = (overlay.overlays as Array<AgsOverlay>)
.map((over, i) => { .map((over, i) => {
self.children.push(Separator(2)); self.children.push(Separator(2));
@ -177,11 +178,10 @@ export const PlayerIcon = (player, overlay) => {
const { Gdk } = imports.gi; const { Gdk } = imports.gi;
const display = Gdk.Display.get_default(); const display = Gdk.Display.get_default();
/** export const PositionSlider = (
* @param {Player} player player: MprisPlayer,
* @param {Variable} colors colors: Var<any>,
*/ ) => Slider({
export const PositionSlider = (player, colors) => Slider({
class_name: 'position-slider', class_name: 'position-slider',
vpack: 'center', vpack: 'center',
hexpand: true, hexpand: true,
@ -250,13 +250,20 @@ export const PositionSlider = (player, colors) => Slider({
}, },
}); });
type PlayerButtonType = {
player: MprisPlayer
colors: Var<any>
items: Array<[name: string, widget: AgsLabel | AgsIcon]>
onClick: string
prop: string
};
const PlayerButton = ({ const PlayerButton = ({
player, player,
colors, colors,
items, items,
onClick, onClick,
prop, prop,
}) => CursorBox({ }: PlayerButtonType) => CursorBox({
child: Button({ child: Button({
attribute: { hovered: false }, attribute: { hovered: false },
child: Stack({ items }), child: Stack({ items }),
@ -269,7 +276,7 @@ const PlayerButton = ({
if (prop === 'playBackStatus' && colors.value) { if (prop === 'playBackStatus' && colors.value) {
const c = colors.value; const c = colors.value;
Array.from(items).forEach((item) => { items.forEach((item) => {
item[1].setCss(` item[1].setCss(`
background-color: ${c.hoverAccent}; background-color: ${c.hoverAccent};
color: ${c.buttonText}; color: ${c.buttonText};
@ -287,7 +294,7 @@ const PlayerButton = ({
if (prop === 'playBackStatus' && colors.value) { if (prop === 'playBackStatus' && colors.value) {
const c = colors.value; const c = colors.value;
Array.from(items).forEach((item) => { items.forEach((item) => {
item[1].setCss(` item[1].setCss(`
background-color: ${c.buttonAccent}; background-color: ${c.buttonAccent};
color: ${c.buttonText}; color: ${c.buttonText};
@ -301,8 +308,7 @@ const PlayerButton = ({
setup: (self) => { setup: (self) => {
self self
.hook(player, () => { .hook(player, () => {
// @ts-expect-error (self.child as AgsStack).shown = `${player[prop]}`;
self.child.shown = `${player[prop]}`;
}) })
.hook(colors, () => { .hook(colors, () => {
if (!Mpris.players.find((p) => player === p)) { if (!Mpris.players.find((p) => player === p)) {
@ -326,7 +332,7 @@ const PlayerButton = ({
}); });
} }
else { else {
Array.from(items).forEach((item) => { items.forEach((item) => {
item[1].setCss(` item[1].setCss(`
background-color: ${c.buttonAccent}; background-color: ${c.buttonAccent};
color: ${c.buttonText}; color: ${c.buttonText};
@ -348,11 +354,10 @@ const PlayerButton = ({
}), }),
}); });
/** export const ShuffleButton = (
* @param {Player} player player: MprisPlayer,
* @param {Variable} colors colors: Var<any>,
*/ ) => PlayerButton({
export const ShuffleButton = (player, colors) => PlayerButton({
player, player,
colors, colors,
items: [ items: [
@ -369,11 +374,10 @@ export const ShuffleButton = (player, colors) => PlayerButton({
prop: 'shuffleStatus', prop: 'shuffleStatus',
}); });
/** export const LoopButton = (
* @param {Player} player player: MprisPlayer,
* @param {Variable} colors colors: Var<any>,
*/ ) => PlayerButton({
export const LoopButton = (player, colors) => PlayerButton({
player, player,
colors, colors,
items: [ items: [
@ -394,11 +398,10 @@ export const LoopButton = (player, colors) => PlayerButton({
prop: 'loopStatus', prop: 'loopStatus',
}); });
/** export const PlayPauseButton = (
* @param {Player} player player: MprisPlayer,
* @param {Variable} colors colors: Var<any>,
*/ ) => PlayerButton({
export const PlayPauseButton = (player, colors) => PlayerButton({
player, player,
colors, colors,
items: [ items: [
@ -419,11 +422,10 @@ export const PlayPauseButton = (player, colors) => PlayerButton({
prop: 'playBackStatus', prop: 'playBackStatus',
}); });
/** export const PreviousButton = (
* @param {Player} player player: MprisPlayer,
* @param {Variable} colors colors: Var<any>,
*/ ) => PlayerButton({
export const PreviousButton = (player, colors) => PlayerButton({
player, player,
colors, colors,
items: [ items: [
@ -440,11 +442,10 @@ export const PreviousButton = (player, colors) => PlayerButton({
prop: 'canGoPrev', prop: 'canGoPrev',
}); });
/** export const NextButton = (
* @param {Player} player player: MprisPlayer,
* @param {Variable} colors colors: Var<any>,
*/ ) => PlayerButton({
export const NextButton = (player, colors) => PlayerButton({
player, player,
colors, colors,
items: [ items: [

View file

@ -0,0 +1,217 @@
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import { Box, CenterBox } from 'resource:///com/github/Aylur/ags/widget.js';
import * as mpris from './mpris.js';
import PlayerGesture from './gesture.js';
import Separator from '../misc/separator.js';
const FAVE_PLAYER = 'org.mpris.MediaPlayer2.spotify';
const SPACING = 8;
// Types
import { MprisPlayer } from 'types/service/mpris.js';
import AgsOverlay from 'types/widgets/overlay.js';
import { Variable as Var } from 'types/variable';
import AgsBox from 'types/widgets/box.js';
const Top = (
player: MprisPlayer,
overlay: AgsOverlay,
) => Box({
class_name: 'top',
hpack: 'start',
vpack: 'start',
children: [
mpris.PlayerIcon(player, overlay),
],
});
const Center = (
player: MprisPlayer,
colors: Var<any>,
) => Box({
class_name: 'center',
children: [
CenterBox({
// @ts-expect-error
vertical: true,
children: [
Box({
class_name: 'metadata',
vertical: true,
hpack: 'start',
vpack: 'center',
hexpand: true,
children: [
mpris.TitleLabel(player),
mpris.ArtistLabel(player),
],
}),
null,
null,
],
}),
CenterBox({
// @ts-expect-error
vertical: true,
children: [
null,
mpris.PlayPauseButton(player, colors),
null,
],
}),
],
});
const Bottom = (
player: MprisPlayer,
colors: Var<any>,
) => Box({
class_name: 'bottom',
children: [
mpris.PreviousButton(player, colors),
Separator(SPACING),
mpris.PositionSlider(player, colors),
Separator(SPACING),
mpris.NextButton(player, colors),
Separator(SPACING),
mpris.ShuffleButton(player, colors),
Separator(SPACING),
mpris.LoopButton(player, colors),
],
});
const PlayerBox = (
player: MprisPlayer,
colors: Var<any>,
overlay: AgsOverlay,
) => {
const widget = mpris.CoverArt(player, colors, {
class_name: `player ${player.name}`,
hexpand: true,
start_widget: Top(player, overlay),
center_widget: Center(player, colors),
end_widget: Bottom(player, colors),
});
widget.visible = false;
return widget;
};
export default () => {
const content = PlayerGesture({
attribute: {
players: new Map(),
setup: false,
},
setup: (self) => {
self
.hook(Mpris, (_: AgsOverlay, bus_name: string) => {
const players = self.attribute.players;
if (players.has(bus_name)) {
return;
}
// Sometimes the signal doesn't give the bus_name
if (!bus_name) {
const player = Mpris.players.find((p) => {
return !players.has(p.bus_name);
});
if (player) {
bus_name = player.bus_name;
}
else {
return;
}
}
// Get the one on top so we can move it up later
const previousFirst = self.overlays.at(-1);
// Make the new player
const player = Mpris.getPlayer(bus_name);
const Colors = Variable(null);
if (!player) {
return;
}
players.set(
bus_name,
PlayerBox(
player,
Colors,
content.get_children()[0] as AgsOverlay,
),
);
self.overlays = Array.from(players.values())
.map((widget) => widget) as Array<AgsBox>;
const includes = self.attribute
.includesWidget(previousFirst);
// Select favorite player at startup
const attrs = self.attribute;
if (!attrs.setup && players.has(FAVE_PLAYER)) {
attrs.moveToTop(players.get(FAVE_PLAYER));
attrs.setup = true;
}
// Move previousFirst on top again
else if (includes) {
attrs.moveToTop(previousFirst);
}
}, 'player-added')
.hook(Mpris, (_: AgsOverlay, bus_name: string) => {
const players = self.attribute.players;
if (!bus_name || !players.has(bus_name)) {
return;
}
// Get the one on top so we can move it up later
const previousFirst = self.overlays.at(-1);
// Remake overlays without deleted one
players.delete(bus_name);
self.overlays = Array.from(players.values())
.map((widget) => widget) as Array<AgsBox>;
// Move previousFirst on top again
const includes = self.attribute
.includesWidget(previousFirst);
if (includes) {
self.attribute.moveToTop(previousFirst);
}
}, 'player-closed');
},
});
return Box({
class_name: 'media',
child: content,
});
};

View file

@ -1,11 +1,12 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
// Types
import AgsWindow from 'types/widgets/window';
export default () => { export default () => {
Array.from(App.windows) (Array.from(App.windows) as Array<[string, AgsWindow]>)
// @ts-expect-error
.filter((w) => w[1].attribute?.close_on_unfocus && .filter((w) => w[1].attribute?.close_on_unfocus &&
// @ts-expect-error
w[1].attribute?.close_on_unfocus !== 'stay') w[1].attribute?.close_on_unfocus !== 'stay')
.forEach((w) => { .forEach((w) => {
App.closeWindow(w[0]); App.closeWindow(w[0]);

View file

@ -5,25 +5,21 @@ import { EventBox } from 'resource:///com/github/Aylur/ags/widget.js';
const { Gtk, Gdk } = imports.gi; const { Gtk, Gdk } = imports.gi;
const display = Gdk.Display.get_default(); const display = Gdk.Display.get_default();
/** import * as EventBoxTypes from 'types/widgets/eventbox';
* @typedef {import('types/widgets/eventbox').EventBoxProps} EventBoxProps type CursorBox = EventBoxTypes.EventBoxProps & {
* @typedef {import('types/widgets/eventbox').default} EventBox on_primary_click_release?(self: EventBoxTypes.default): void;
*/ on_hover?(self: EventBoxTypes.default): void;
on_hover_lost?(self: EventBoxTypes.default): void;
};
/** @param {EventBoxProps & {
* on_primary_click_release?: function(EventBox):void
* on_hover?: function(EventBox):void
* on_hover_lost?: function(EventBox):void
* }} o
*/
export default ({ export default ({
on_primary_click_release = () => {/**/}, on_primary_click_release = () => {/**/},
on_hover = () => {/**/}, on_hover = () => {/**/},
on_hover_lost = () => {/**/}, on_hover_lost = () => {/**/},
attribute, attribute,
...props ...props
}) => { }: CursorBox) => {
// Make this variable to know if the function should // Make this variable to know if the function should
// be executed depending on where the click is released // be executed depending on where the click is released
const CanRun = Variable(true); const CanRun = Variable(true);

View file

@ -1,18 +1,17 @@
import { execAsync, readFileAsync, timeout } from 'resource:///com/github/Aylur/ags/utils.js'; import { execAsync, readFileAsync, timeout } from 'resource:///com/github/Aylur/ags/utils.js';
const { get_home_dir } = imports.gi.GLib; const { get_home_dir } = imports.gi.GLib;
type Persist = {
name: string
gobject: typeof imports.gi.GObject
prop: string
condition?: boolean | string // If string, compare following props to this
whenTrue?: boolean | string
whenFalse?: boolean | string
signal?: string
};
/**
* @param {{
* name: string
* gobject: typeof imports.gi.GObject
* prop: string
* condition?: boolean|string // if string, compare following props to this
* whenTrue?: boolean|string
* whenFalse?: boolean|string
* signal?: string
* }} o
*/
export default ({ export default ({
name, name,
gobject, gobject,
@ -21,7 +20,7 @@ export default ({
whenTrue = condition, whenTrue = condition,
whenFalse = false, whenFalse = false,
signal = 'changed', signal = 'changed',
}) => { }: Persist) => {
const cacheFile = `${get_home_dir()}/.cache/ags/.${name}`; const cacheFile = `${get_home_dir()}/.cache/ags/.${name}`;
const stateCmd = () => ['bash', '-c', const stateCmd = () => ['bash', '-c',

View file

@ -5,30 +5,30 @@ import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import { Box, Overlay, Window } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Overlay, Window } from 'resource:///com/github/Aylur/ags/widget.js';
import { timeout } from 'resource:///com/github/Aylur/ags/utils.js'; import { timeout } from 'resource:///com/github/Aylur/ags/utils.js';
/** // Types
* @typedef {import('types/widgets/revealer').RevealerProps} RevProps type Allocation = typeof imports.gi.Gtk.Allocation;
* @typedef {import('types/widgets/window').WindowProps} WinProps type Widget = typeof imports.gi.Gtk.Widget;
* @typedef {import('types/widgets/window').default} Window import { RevealerProps } from 'types/widgets/revealer';
* @typedef {import('types/widgets/box').default} Box import { WindowProps } from 'types/widgets/window';
* @typedef {import('gi://Gtk').Gtk.Widget} Widget import AgsWindow from 'types/widgets/window';
*/ import AgsBox from 'types/widgets/box';
import AgsOverlay from 'types/widgets/overlay';
import { Binding } from 'types/service';
type PopupWindow = WindowProps & {
transition?: RevealerProps['transition']
transition_duration?: number
bezier?: string
on_open?(self: AgsWindow): void
on_close?(self: AgsWindow): void
blur?: boolean
close_on_unfocus?: 'none' | 'stay' | 'released' | 'clicked'
anchor?: Array<string>
name: string
};
// FIXME: deal with overlay children? // FIXME: deal with overlay children?
// TODO: make this a new class to be able to edit props // TODO: make this a new class to be able to edit props
/**
* @param {WinProps & {
* transition?: RevProps['transition']
* transition_duration?: number
* bezier?: string
* on_open?: function
* on_close?: function
* blur?: boolean
* close_on_unfocus?: 'none'|'stay'|'released'|'clicked'
* anchor?: Array<string>
* name: string
* }} o
*/
export default ({ export default ({
transition = 'slide_down', transition = 'slide_down',
transition_duration = 800, transition_duration = 800,
@ -45,7 +45,7 @@ export default ({
blur = false, blur = false,
close_on_unfocus = 'released', close_on_unfocus = 'released',
...props ...props
}) => { }: PopupWindow) => {
const Child = Variable(child); const Child = Variable(child);
const AntiClip = Variable(false); const AntiClip = Variable(false);
@ -53,17 +53,11 @@ export default ({
transition !== 'crossfade'; transition !== 'crossfade';
const attribute = { const attribute = {
/**
* @param {import('gi://Gtk').Gtk.Allocation} alloc
* @param {'left'|'right'} side
*/
set_x_pos: ( set_x_pos: (
alloc, alloc: Allocation,
side = 'right', side = 'right' as 'left' | 'right',
) => { ) => {
/** @type Window */ const window = App.getWindow(name) as AgsWindow;
// @ts-expect-error
const window = App.getWindow(name);
if (!window) { if (!window) {
return; return;
@ -90,13 +84,12 @@ export default ({
get_child: () => Child.value, get_child: () => Child.value,
/** @param {Widget} new_child */ set_child: (new_child: Widget) => {
set_child: (new_child) => {
Child.value = new_child; Child.value = new_child;
App.getWindow(name)?.child.show_all(); App.getWindow(name)?.child.show_all();
}, },
// This is for my custom pointers.js // This is for my custom pointers.js
close_on_unfocus, close_on_unfocus,
}; };
@ -175,8 +168,7 @@ export default ({
} }
if (needsAnticlipping) { if (needsAnticlipping) {
/** @param {number} position */ const reorder_child = (position: number) => {
const reorder_child = (position) => {
// If unanchored, we have another anticlip widget // If unanchored, we have another anticlip widget
// so we can't change the order // so we can't change the order
if (anchor.length !== 0) { if (anchor.length !== 0) {
@ -211,7 +203,6 @@ export default ({
} }
}, },
// @ts-expect-error
children: Child.bind().transform((v) => { children: Child.bind().transform((v) => {
if (needsAnticlipping) { if (needsAnticlipping) {
return [ return [
@ -239,23 +230,19 @@ export default ({
else { else {
return [v]; return [v];
} }
}), }) as Binding<any, any, Widget[]>,
})], })],
setup: (self) => { setup: (self) => {
self.on('get-child-position', (_, ch) => { self.on('get-child-position', (_, ch) => {
/** @type Box */ const overlay = (Child.value as Widget)
// @ts-expect-error .get_parent() as AgsOverlay;
const sizeBox = self.child;
// @ts-expect-error
const overlay = Child.value.get_parent();
if (ch === overlay) { if (ch === overlay) {
const alloc = overlay.get_allocation(); const alloc = overlay.get_allocation();
const setAlloc = /** @param {number} v */ const setAlloc = (v: number) => v - 2 < 0 ? 1 : v;
(v) => v - 2 < 0 ? 1 : v;
sizeBox.css = ` (self.child as AgsBox).css = `
min-height: ${setAlloc(alloc.height - 2)}px; min-height: ${setAlloc(alloc.height - 2)}px;
min-width: ${setAlloc(alloc.width - 2)}px; min-width: ${setAlloc(alloc.width - 2)}px;
`; `;
@ -271,12 +258,12 @@ export default ({
`, `,
setup: (self) => { setup: (self) => {
let currentTimeout; let currentTimeout: number;
self.hook(App, (_, currentName, isOpen) => { self.hook(App, (_, currentName, isOpen) => {
if (currentName === name) { if (currentName === name) {
// @ts-expect-error const overlay = (Child.value as Widget)
const overlay = Child.value.get_parent(); .get_parent() as AgsOverlay;
const alloc = overlay.get_allocation(); const alloc = overlay.get_allocation();
const height = needsAnticlipping ? const height = needsAnticlipping ?
@ -299,7 +286,7 @@ export default ({
currentTimeout = thisTimeout; currentTimeout = thisTimeout;
} }
let css; let css = '';
/* Margin: top | right | bottom | left */ /* Margin: top | right | bottom | left */
switch (transition) { switch (transition) {
@ -388,6 +375,5 @@ export default ({
}), }),
}); });
return window; return window;
}; };

View file

@ -1,15 +1,7 @@
import { Box } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box } from 'resource:///com/github/Aylur/ags/widget.js';
/** export default (size: number, {
* @param {number} size
* @param {{
* vertical?: boolean
* css?: string
* props?: import('types/widgets/box').BoxProps
* }} o
*/
export default (size, {
vertical = false, vertical = false,
css = '', css = '',
...props ...props

View file

@ -8,28 +8,34 @@ import { lookUpIcon } from 'resource:///com/github/Aylur/ags/utils.js';
const { GLib } = imports.gi; const { GLib } = imports.gi;
/** @typedef {import('types/service/notifications').Notification} Notification */ import Gesture from './gesture.js';
import CursorBox from '../misc/cursorbox.js';
/** @param {number} time */ // Types
const setTime = (time) => { import { Notification as NotifObj } from 'types/service/notifications.js';
import AgsEventBox from 'types/widgets/eventbox.js';
import { Client } from 'types/service/hyprland.js';
type NotificationWidget = {
notif: NotifObj
slideIn?: 'Left' | 'Right'
command?(): void
};
const setTime = (time: number) => {
return GLib.DateTime return GLib.DateTime
.new_from_unix_local(time) .new_from_unix_local(time)
.format('%H:%M'); .format('%H:%M');
}; };
/** @param {import('types/widgets/eventbox').default} box */ const getDragState = (box: AgsEventBox) => (box.get_parent()?.get_parent()
const getDragState = (box) => box.get_parent()?.get_parent() ?.get_parent()?.get_parent()?.get_parent() as AgsEventBox)
// @ts-expect-error ?.attribute.dragging;
?.get_parent()?.get_parent()?.get_parent()?.attribute.dragging;
import Gesture from './gesture.js';
import CursorBox from '../misc/cursorbox.js';
/** @param {Notification} notif */ const NotificationIcon = (notif: NotifObj) => {
const NotificationIcon = (notif) => { let iconCmd = (box: AgsEventBox):void => {
/** @type function(import('types/widgets/eventbox').default):void */ console.log(box);
let iconCmd = () => {/**/}; };
if (notif._appEntry && Applications.query(notif._appEntry).length > 0) { if (notif._appEntry && Applications.query(notif._appEntry).length > 0) {
const app = Applications.query(notif._appEntry)[0]; const app = Applications.query(notif._appEntry)[0];
@ -53,10 +59,8 @@ const NotificationIcon = (notif) => {
} }
else { else {
Hyprland.sendMessage('j/clients').then((msg) => { Hyprland.sendMessage('j/clients').then((msg) => {
/** @type {Array<import('types/service/hyprland').Client>} */ const clients = JSON.parse(msg) as Array<Client>;
const clients = JSON.parse(msg); const classes = [] as Array<string>;
const classes = [];
for (const key of clients) { for (const key of clients) {
if (key.class) { if (key.class) {
@ -141,18 +145,11 @@ const NotificationIcon = (notif) => {
// to know when there are notifs or not // to know when there are notifs or not
export const HasNotifs = Variable(false); export const HasNotifs = Variable(false);
/**
* @param {{
* notif: Notification
* slideIn?: 'Left'|'Right'
* command?: () => void
* }} o
*/
export const Notification = ({ export const Notification = ({
notif, notif,
slideIn = 'Left', slideIn = 'Left',
command = () => { /**/ }, command = () => {/**/},
}) => { }: NotificationWidget) => {
if (!notif) { if (!notif) {
return; return;
} }
@ -177,8 +174,7 @@ export const Notification = ({
}); });
// Add body to notif // Add body to notif
// @ts-expect-error (notifWidget.child as AgsEventBox).add(Box({
notifWidget.child.add(Box({
class_name: `notification ${notif.urgency}`, class_name: `notification ${notif.urgency}`,
vexpand: false, vexpand: false,

View file

@ -7,17 +7,12 @@ import { timeout } from 'resource:///com/github/Aylur/ags/utils.js';
import { Notification, HasNotifs } from './base.js'; import { Notification, HasNotifs } from './base.js';
import CursorBox from '../misc/cursorbox.js'; import CursorBox from '../misc/cursorbox.js';
/** // Types
* @typedef {import('types/service/notifications').Notification} NotifObj import AgsBox from 'types/widgets/box.js';
* @typedef {import('types/widgets/box').default} Box import { Notification as NotifObj } from 'resource:///com/github/Aylur/ags/service/notifications.js';
*/
/** const addNotif = (box: AgsBox, notif: NotifObj) => {
* @param {Box} box
* @param {NotifObj} notif
*/
const addNotif = (box, notif) => {
if (notif) { if (notif) {
const NewNotif = Notification({ const NewNotif = Notification({
notif, notif,
@ -58,11 +53,10 @@ const NotificationList = () => Box({
}, 'notified') }, 'notified')
.hook(Notifications, (box, id) => { .hook(Notifications, (box, id) => {
// @ts-expect-error const notif = (box.children as Array<AgsBox>)
const notif = box.children.find((ch) => ch.attribute.id === id); .find((ch) => ch.attribute.id === id);
if (notif?.sensitive) { if (notif?.sensitive) {
// @ts-expect-error
notif.attribute.slideAway('Right'); notif.attribute.slideAway('Right');
} }
}, 'closed'); }, 'closed');

View file

@ -8,6 +8,9 @@ import { HasNotifs } from './base.js';
const { Gdk, Gtk } = imports.gi; const { Gdk, Gtk } = imports.gi;
const display = Gdk.Display.get_default(); const display = Gdk.Display.get_default();
// Types
import AgsBox from 'types/widgets/box.js';
const MAX_OFFSET = 200; const MAX_OFFSET = 200;
const OFFSCREEN = 300; const OFFSCREEN = 300;
const ANIM_DURATION = 500; const ANIM_DURATION = 500;
@ -94,19 +97,17 @@ export default ({
ready: false, ready: false,
id, id,
/** @param {'Left'|'Right'} side */ slideAway: (side: 'Left' | 'Right') => {
slideAway: (side) => {
// Slide away // Slide away
// @ts-expect-error (widget.child as AgsBox)
widget.child.setCss(side === 'Left' ? slideLeft : slideRight); .setCss(side === 'Left' ? slideLeft : slideRight);
// Make it uninteractable // Make it uninteractable
widget.sensitive = false; widget.sensitive = false;
timeout(ANIM_DURATION - 100, () => { timeout(ANIM_DURATION - 100, () => {
// Reduce height after sliding away // Reduce height after sliding away
// @ts-expect-error (widget.child as AgsBox)?.setCss(side === 'Left' ?
widget.child?.setCss(side === 'Left' ?
squeezeLeft : squeezeLeft :
squeezeRight); squeezeRight);
@ -117,8 +118,7 @@ export default ({
HasNotifs.value = Notifications HasNotifs.value = Notifications
.notifications.length > 0; .notifications.length > 0;
// @ts-expect-error (widget.get_parent() as AgsBox)?.remove(widget);
widget.get_parent()?.remove(widget);
}); });
}); });
}, },

View file

@ -9,6 +9,9 @@ import { Notification } from './base.js';
const DELAY = 2000; const DELAY = 2000;
// Types
import AgsBox from 'types/widgets/box.js';
export default () => Box({ export default () => Box({
vertical: true, vertical: true,
@ -16,8 +19,7 @@ export default () => Box({
css: 'padding: 1px;', css: 'padding: 1px;',
setup: (self) => { setup: (self) => {
/** @param {number} id */ const addPopup = (id: number) => {
const addPopup = (id) => {
if (!id) { if (!id) {
return; return;
} }
@ -42,32 +44,23 @@ export default () => Box({
} }
}; };
/** const handleDismiss = (id: number, force = false) => {
* @param {number} id const notif = (self.children as Array<AgsBox>)
* @param {boolean} force .find((ch) => ch.attribute.id === id);
*/
const handleDismiss = (id, force = false) => {
// @ts-expect-error
const notif = self.children.find((ch) => ch.attribute.id === id);
if (!notif) { if (!notif) {
return; return;
} }
// If notif isn't hovered or was closed, slide away // If notif isn't hovered or was closed, slide away
// @ts-expect-error
if (!notif.attribute.hovered || force) { if (!notif.attribute.hovered || force) {
// @ts-expect-error
notif.attribute.slideAway('Left'); notif.attribute.slideAway('Left');
} }
// If notif is hovered, delay close // If notif is hovered, delay close
// @ts-expect-error
else if (notif.attribute.hovered) { else if (notif.attribute.hovered) {
const intervalId = interval(DELAY, () => { const intervalId = interval(DELAY, () => {
// @ts-expect-error
if (!notif.attribute.hovered && intervalId) { if (!notif.attribute.hovered && intervalId) {
// @ts-expect-error
notif.attribute.slideAway('Left'); notif.attribute.slideAway('Left');
GLib.source_remove(intervalId); GLib.source_remove(intervalId);

View file

@ -10,6 +10,10 @@ const KEY_N = 249;
const HIDDEN_MARGIN = 340; const HIDDEN_MARGIN = 340;
const ANIM_DURATION = 700; const ANIM_DURATION = 700;
// Types
import AgsWindow from 'types/widgets/window.js';
import AgsBox from 'types/widgets/box.js';
const releaseAllKeys = () => { const releaseAllKeys = () => {
const keycodes = Array.from(Array(KEY_N).keys()); const keycodes = Array.from(Array(KEY_N).keys());
@ -20,23 +24,21 @@ const releaseAllKeys = () => {
]).catch(print); ]).catch(print);
}; };
/** @param {import('types/widgets/window').default} window */ export default (window: AgsWindow) => {
export default (window) => {
// @ts-expect-error
window.child.setCss(`margin-bottom: -${HIDDEN_MARGIN}px;`);
const gesture = Gtk.GestureDrag.new(window); const gesture = Gtk.GestureDrag.new(window);
const child = window.child as AgsBox;
let signals = []; child.setCss(`margin-bottom: -${HIDDEN_MARGIN}px;`);
let signals = [] as Array<number>;
window.attribute = { window.attribute = {
/** @param {boolean} state */ setVisible: (state: boolean) => {
setVisible: (state) => {
if (state) { if (state) {
window.visible = true; window.visible = true;
window.attribute.setSlideDown(); window.attribute.setSlideDown();
// @ts-expect-error child.setCss(`
window.child.setCss(`
transition: margin-bottom 0.7s transition: margin-bottom 0.7s
cubic-bezier(0.36, 0, 0.66, -0.56); cubic-bezier(0.36, 0, 0.66, -0.56);
margin-bottom: 0px; margin-bottom: 0px;
@ -51,8 +53,7 @@ export default (window) => {
releaseAllKeys(); releaseAllKeys();
window.attribute.setSlideUp(); window.attribute.setSlideUp();
// @ts-expect-error child.setCss(`
window.child.setCss(`
transition: margin-bottom 0.7s transition: margin-bottom 0.7s
cubic-bezier(0.36, 0, 0.66, -0.56); cubic-bezier(0.36, 0, 0.66, -0.56);
margin-bottom: -${HIDDEN_MARGIN}px; margin-bottom: -${HIDDEN_MARGIN}px;
@ -90,8 +91,7 @@ export default (window) => {
return; return;
} }
// @ts-expect-error (window.child as AgsBox).setCss(`
window.child.setCss(`
margin-bottom: ${offset - HIDDEN_MARGIN}px; margin-bottom: ${offset - HIDDEN_MARGIN}px;
`); `);
}); });
@ -101,8 +101,7 @@ export default (window) => {
// End drag // End drag
signals.push( signals.push(
gesture.connect('drag-end', () => { gesture.connect('drag-end', () => {
// @ts-expect-error (window.child as AgsBox).setCss(`
window.child.setCss(`
transition: margin-bottom 0.5s ease-in-out; transition: margin-bottom 0.5s ease-in-out;
margin-bottom: -${HIDDEN_MARGIN}px; margin-bottom: -${HIDDEN_MARGIN}px;
`); `);
@ -133,8 +132,7 @@ export default (window) => {
return; return;
} }
// @ts-expect-error (window.child as AgsBox).setCss(`
window.child.setCss(`
margin-bottom: ${offset}px; margin-bottom: ${offset}px;
`); `);
}); });
@ -144,8 +142,7 @@ export default (window) => {
// End drag // End drag
signals.push( signals.push(
gesture.connect('drag-end', () => { gesture.connect('drag-end', () => {
// @ts-expect-error (window.child as AgsBox).setCss(`
window.child.setCss(`
transition: margin-bottom 0.5s ease-in-out; transition: margin-bottom 0.5s ease-in-out;
margin-bottom: 0px; margin-bottom: 0px;
`); `);

View file

@ -15,9 +15,12 @@ const L_KEY_PER_ROW = [8, 7, 6, 6, 6, 4]; // eslint-disable-line
const COLOR = 'rgba(0, 0, 0, 0.3)'; const COLOR = 'rgba(0, 0, 0, 0.3)';
const SPACING = 4; const SPACING = 4;
// Types
import AgsWindow from 'types/widgets/window.js';
import AgsBox from 'types/widgets/box.js';
/** @param {import('types/widgets/window').default} window */
export default (window) => Box({ export default (window: AgsWindow) => Box({
vertical: true, vertical: true,
children: [ children: [
CenterBox({ CenterBox({
@ -93,7 +96,7 @@ export default (window) => Box({
vertical: true, vertical: true,
children: keyboardJson.keys.map((row, rowIndex) => { children: keyboardJson.keys.map((row, rowIndex) => {
const keys = []; const keys = [] as Array<AgsBox>;
row.forEach((key, keyIndex) => { row.forEach((key, keyIndex) => {
if (keyIndex < L_KEY_PER_ROW[rowIndex]) { if (keyIndex < L_KEY_PER_ROW[rowIndex]) {
@ -135,7 +138,7 @@ export default (window) => Box({
vertical: true, vertical: true,
children: keyboardJson.keys.map((row, rowIndex) => { children: keyboardJson.keys.map((row, rowIndex) => {
const keys = []; const keys = [] as Array<AgsBox>;
row.forEach((key, keyIndex) => { row.forEach((key, keyIndex) => {
if (keyIndex >= L_KEY_PER_ROW[rowIndex]) { if (keyIndex >= L_KEY_PER_ROW[rowIndex]) {

View file

@ -44,10 +44,20 @@ const LSHIFT_CODE = 42;
const LALT_CODE = 56; const LALT_CODE = 56;
const LCTRL_CODE = 29; const LCTRL_CODE = 29;
// Types
import { Variable as Var } from 'types/variable.js';
type Key = {
keytype: 'normal',
label: string,
labelShift?: string,
labelAltGr?: string,
shape: 'normal' | 'modkey',
keycode: number
};
/** @param {Object} key */
const ModKey = (key) => { const ModKey = (key: Key) => {
let Mod; let Mod: Var<any>;
if (key.label === 'Super') { if (key.label === 'Super') {
Mod = Super; Mod = Super;
@ -77,17 +87,20 @@ const ModKey = (key) => {
else if (key.label === 'Ctrl') { else if (key.label === 'Ctrl') {
Mod = RCtrl; Mod = RCtrl;
} }
const label = Label({
class_name: `mod ${key.label}`,
label: key.label,
});
const button = EventBox({ const button = EventBox({
class_name: 'key', class_name: 'key',
on_primary_click_release: (self) => { on_primary_click_release: () => {
console.log('mod toggled'); console.log('mod toggled');
execAsync(`ydotool key ${key.keycode}:${Mod.value ? 0 : 1}`); execAsync(`ydotool key ${key.keycode}:${Mod.value ? 0 : 1}`);
// @ts-expect-error label.toggleClassName('active', !Mod.value);
self.child.toggleClassName('active', !Mod.value);
Mod.value = !Mod.value; Mod.value = !Mod.value;
}, },
@ -96,8 +109,7 @@ const ModKey = (key) => {
.hook(NormalClick, () => { .hook(NormalClick, () => {
Mod.value = false; Mod.value = false;
// @ts-expect-error label.toggleClassName('active', false);
self.child.toggleClassName('active', false);
execAsync(`ydotool key ${key.keycode}:0`); execAsync(`ydotool key ${key.keycode}:0`);
}) })
@ -116,10 +128,7 @@ const ModKey = (key) => {
self.toggleClassName('hover', false); self.toggleClassName('hover', false);
}); });
}, },
child: Label({ child: label,
class_name: `mod ${key.label}`,
label: key.label,
}),
}); });
return Box({ return Box({
@ -130,8 +139,7 @@ const ModKey = (key) => {
}); });
}; };
/** @param {Object} key */ const RegularKey = (key: Key) => {
const RegularKey = (key) => {
const widget = EventBox({ const widget = EventBox({
class_name: 'key', class_name: 'key',
@ -236,7 +244,6 @@ const RegularKey = (key) => {
}); });
}; };
/** @param {Object} key */ export default (key: Key) => key.keytype === 'normal' ?
export default (key) => key.keytype === 'normal' ?
RegularKey(key) : RegularKey(key) :
ModKey(key); ModKey(key);

View file

@ -2,9 +2,29 @@ import { Box, Icon, ProgressBar } from 'resource:///com/github/Aylur/ags/widget.
const Y_POS = 80; const Y_POS = 80;
// Types
import AgsBox from 'types/widgets/box';
import { IconProps } from 'types/widgets/icon';
import { GObject } from 'gi://GObject';
import AgsStack from 'types/widgets/stack';
type Widget = typeof imports.gi.Gtk.Widget;
import { Connectable } from 'types/widgets/widget';
import AgsProgressBar from 'types/widgets/progressbar';
type ConnectFunc = (self?: AgsProgressBar) => void;
type OSD = {
stack: AgsStack
icon: string | IconProps
info: {
mod: GObject.Object
signal?: string
logic?(self: AgsProgressBar): void
widget?: Widget
}
};
export default ({ stack, icon, info }) => {
let connectFunc; export default ({ stack, icon, info }: OSD) => {
let connectFunc: ConnectFunc;
const osd = Box({ const osd = Box({
css: `margin-bottom: ${Y_POS}px;`, css: `margin-bottom: ${Y_POS}px;`,
@ -27,13 +47,10 @@ export default ({ stack, icon, info }) => {
// Handle requests to show the OSD // Handle requests to show the OSD
// Different wether it's a bar or static // Different wether it's a bar or static
if (info.logic) { if (info.logic) {
/** connectFunc = (self) => new Promise<void>((r) => {
* @param {import('types/widgets/box').default} self if (info.logic && self) {
* @returns {Promise<void>} info.logic(self);
*/ }
connectFunc = (self) => new Promise((r) => {
info.logic(self);
// @ts-expect-error
r(); r();
}).then(() => stack.attribute.popup(osd)); }).then(() => stack.attribute.popup(osd));
} }
@ -41,8 +58,8 @@ export default ({ stack, icon, info }) => {
connectFunc = () => stack.attribute.popup(osd); connectFunc = () => stack.attribute.popup(osd);
} }
// @ts-expect-error ((osd.children[0] as AgsBox).children[1] as Connectable<AgsProgressBar>)
osd.children[0].children[1].hook(info.mod, connectFunc, info.signal); .hook(info.mod, connectFunc, info.signal);
return osd; return osd;
}; };

View file

@ -4,9 +4,11 @@ import { timeout } from 'resource:///com/github/Aylur/ags/utils.js';
import { Stack } from 'resource:///com/github/Aylur/ags/widget.js'; import { Stack } from 'resource:///com/github/Aylur/ags/widget.js';
import PopupWindow from '../misc/popup.js'; import PopupWindow from '../misc/popup.js';
import AgsBox from 'types/widgets/box.js';
import AgsStack from 'types/widgets/stack.js';
// Import all the OSDs as an array // Import all the OSDs as an array
const OSDList = []; const OSDList = [] as Array<(stack: AgsStack) => AgsBox>;
import * as Modules from './osds.js'; import * as Modules from './osds.js';
for (const osd in Modules) { for (const osd in Modules) {
@ -22,9 +24,7 @@ const OSDs = () => {
transition: 'over_up_down', transition: 'over_up_down',
transition_duration, transition_duration,
attribute: { attribute: { popup: () => {/**/} },
popup: () => {/**/},
},
}); });
// Send reference of stack to all items // Send reference of stack to all items
@ -35,8 +35,7 @@ const OSDs = () => {
timeout(1000, () => { timeout(1000, () => {
let count = 0; let count = 0;
/** @param {import('types/widgets/box').default} osd */ stack.attribute.popup = (osd: AgsBox) => {
stack.attribute.popup = (osd) => {
++count; ++count;
stack.set_visible_child(osd); stack.set_visible_child(osd);
App.openWindow('osd'); App.openWindow('osd');

View file

@ -16,20 +16,16 @@ globalThis.showSpeaker = () => {
ShowSpeaker.value = !ShowSpeaker.value; ShowSpeaker.value = !ShowSpeaker.value;
}; };
/** // Types
* @typedef {import('types/widgets/stack').default} Stack import AgsStack from 'types/widgets/stack.js';
* @typedef {import('types/widgets/progressbar').default} ProgressBar
*/
/** @param {Stack} stack */ export const SpeakerOSD = (stack: AgsStack) => OSD({
export const SpeakerOSD = (stack) => OSD({
stack, stack,
icon: { icon: SpeakerIcon.bind() }, icon: { icon: SpeakerIcon.bind() },
info: { info: {
mod: ShowSpeaker, mod: ShowSpeaker,
/** @param {ProgressBar} self */
logic: (self) => { logic: (self) => {
if (!Audio.speaker) { if (!Audio.speaker) {
return; return;
@ -44,30 +40,26 @@ export const SpeakerOSD = (stack) => OSD({
}, },
}); });
/** @param {Stack} stack */ export const ScreenBrightnessOSD = (stack: AgsStack) => OSD({
export const ScreenBrightnessOSD = (stack) => OSD({
stack, stack,
icon: { icon: Brightness.bind('screenIcon') }, icon: { icon: Brightness.bind('screenIcon') },
info: { info: {
mod: Brightness, mod: Brightness,
signal: 'screen', signal: 'screen',
/** @param {ProgressBar} self */
logic: (self) => { logic: (self) => {
self.value = Brightness.screen; self.value = Brightness.screen;
}, },
}, },
}); });
/** @param {Stack} stack */ export const KbdBrightnessOSD = (stack: AgsStack) => OSD({
export const KbdBrightnessOSD = (stack) => OSD({
stack, stack,
icon: 'keyboard-brightness-symbolic', icon: 'keyboard-brightness-symbolic',
info: { info: {
mod: Brightness, mod: Brightness,
signal: 'kbd', signal: 'kbd',
/** @param {ProgressBar} self */
logic: (self) => { logic: (self) => {
if (!self.value) { if (!self.value) {
self.value = Brightness.kbd / 2; self.value = Brightness.kbd / 2;
@ -80,15 +72,13 @@ export const KbdBrightnessOSD = (stack) => OSD({
}, },
}); });
/** @param {Stack} stack */ export const MicOSD = (stack: AgsStack) => OSD({
export const MicOSD = (stack) => OSD({
stack, stack,
icon: { icon: MicIcon.bind() }, icon: { icon: MicIcon.bind() },
info: { info: {
mod: Audio, mod: Audio,
signal: 'microphone-changed', signal: 'microphone-changed',
/** @param {ProgressBar} self */
logic: (self) => { logic: (self) => {
if (!Audio.microphone) { if (!Audio.microphone) {
return; return;
@ -100,8 +90,7 @@ export const MicOSD = (stack) => OSD({
}, },
}); });
/** @param {Stack} stack */ export const CapsLockOSD = (stack: AgsStack) => OSD({
export const CapsLockOSD = (stack) => OSD({
stack, stack,
icon: { icon: Brightness.bind('capsIcon') }, icon: { icon: Brightness.bind('capsIcon') },
info: { info: {

View file

@ -7,17 +7,16 @@ import { timeout } from 'resource:///com/github/Aylur/ags/utils.js';
import { WindowButton } from './dragndrop.js'; import { WindowButton } from './dragndrop.js';
import * as VARS from './variables.js'; import * as VARS from './variables.js';
/** // Types
* @typedef {import('types/service/hyprland.js').Client} Client import { Client as HyprClient } from 'types/service/hyprland.js';
* @typedef {import('types/widgets/revealer').default} Revealer import AgsRevealer from 'types/widgets/revealer.js';
* @typedef {import('types/widgets/box').default} Box import AgsBox from 'types/widgets/box.js';
*/ import AgsButton from 'types/widgets/button.js';
import AgsIcon from 'types/widgets/icon.js';
/** @param {number} size */ const scale = (size: number) => (size * VARS.SCALE) - VARS.MARGIN;
const scale = (size) => (size * VARS.SCALE) - VARS.MARGIN;
/** @param {Client} client */ const getFontSize = (client: HyprClient) => {
const getFontSize = (client) => {
const valX = scale(client.size[0]) * VARS.ICON_SCALE; const valX = scale(client.size[0]) * VARS.ICON_SCALE;
const valY = scale(client.size[1]) * VARS.ICON_SCALE; const valY = scale(client.size[1]) * VARS.ICON_SCALE;
@ -26,21 +25,19 @@ const getFontSize = (client) => {
return size <= 0 ? 0.1 : size; return size <= 0 ? 0.1 : size;
}; };
/** @param {Client} client */ const IconStyle = (client: HyprClient) => `
const IconStyle = (client) => `
min-width: ${scale(client.size[0])}px; min-width: ${scale(client.size[0])}px;
min-height: ${scale(client.size[1])}px; min-height: ${scale(client.size[1])}px;
font-size: ${getFontSize(client)}px; font-size: ${getFontSize(client)}px;
`; `;
/** const Client = (
* @param {Client} client client: HyprClient,
* @param {boolean} active active: Boolean,
* @param {Array<Client>} clients clients: Array<HyprClient>,
* @param {Box} box box: AgsBox,
*/ ) => {
const Client = (client, active, clients, box) => {
const wsName = String(client.workspace.name).replace('special:', ''); const wsName = String(client.workspace.name).replace('special:', '');
const wsId = client.workspace.id; const wsId = client.workspace.id;
const addr = `address:${client.address}`; const addr = `address:${client.address}`;
@ -116,20 +113,16 @@ const Client = (client, active, clients, box) => {
}); });
}; };
/** @param {Box} box */ export const updateClients = (box: AgsBox) => {
export const updateClients = (box) => {
Hyprland.sendMessage('j/clients').then((out) => { Hyprland.sendMessage('j/clients').then((out) => {
/** @type Array<Client> */ let clients = JSON.parse(out) as Array<HyprClient>;
let clients = JSON.parse(out);
clients = clients.filter((client) => client.class); clients = clients.filter((client) => client.class);
box.attribute.workspaces.forEach( box.attribute.workspaces.forEach(
/** @param {Revealer} workspace */ (workspace: AgsRevealer) => {
(workspace) => {
const fixed = workspace.attribute.get_fixed(); const fixed = workspace.attribute.get_fixed();
/** @type Array<Revealer> */ const toRemove = fixed.get_children() as Array<AgsRevealer>;
const toRemove = fixed.get_children();
clients.filter((client) => clients.filter((client) =>
client.workspace.id === workspace.attribute.id) client.workspace.id === workspace.attribute.id)
@ -161,14 +154,12 @@ export const updateClients = (box) => {
} }
const newClient = [ const newClient = [
fixed.get_children().find( (fixed.get_children() as Array<AgsRevealer>)
/** @param {typeof WindowButton} ch */ .find((ch) =>
// @ts-expect-error ch.attribute.address === client.address),
(ch) => ch.attribute.address === client.address,
),
client.at[0] * VARS.SCALE, client.at[0] * VARS.SCALE,
client.at[1] * VARS.SCALE, client.at[1] * VARS.SCALE,
]; ] as [AgsRevealer, number, number];
// If it exists already // If it exists already
if (newClient[0]) { if (newClient[0]) {
@ -182,9 +173,12 @@ export const updateClients = (box) => {
// Set a timeout here to have an animation when the icon first appears // Set a timeout here to have an animation when the icon first appears
timeout(1, () => { timeout(1, () => {
newClient[0].child.child.className = ((newClient[0].child as AgsButton)
`window ${active}`; .child as AgsIcon)
newClient[0].child.child.setCss(IconStyle(client)); .class_name = `window ${active}`;
((newClient[0].child as AgsButton)
.child as AgsIcon).setCss(IconStyle(client));
}); });
}); });

View file

@ -11,10 +11,11 @@ const DEFAULT_STYLE = `
border-radius: 10px; border-radius: 10px;
`; `;
/** // Types
* @typedef {import('types/widgets/box').default} Box import AgsBox from 'types/widgets/box.js';
* @typedef {import('types/widgets/revealer').default} Revealer import AgsRevealer from 'types/widgets/revealer.js';
*/ import AgsCenterBox from 'types/widgets/centerbox.js';
import AgsEventBox from 'types/widgets/eventbox.js';
export const Highlighter = () => Box({ export const Highlighter = () => Box({
@ -24,24 +25,18 @@ export const Highlighter = () => Box({
css: DEFAULT_STYLE, css: DEFAULT_STYLE,
}); });
/** export const updateCurrentWorkspace = (main: AgsBox, highlighter: AgsBox) => {
* @param {Box} main
* @param {Box} highlighter
*/
export const updateCurrentWorkspace = (main, highlighter) => {
const currentId = Hyprland.active.workspace.id; const currentId = Hyprland.active.workspace.id;
const row = Math.floor((currentId - 1) / VARS.WORKSPACE_PER_ROW); const row = Math.floor((currentId - 1) / VARS.WORKSPACE_PER_ROW);
// @ts-expect-error const rowObject = (main.children[0] as AgsBox).children[row] as AgsRevealer;
const rowObject = main.children[0].children[row]; const workspaces = ((((rowObject.child as AgsCenterBox)
const workspaces = rowObject.child.centerWidget.child .center_widget as AgsEventBox)
.get_children().filter( .child as AgsBox)
/** @param {Revealer} w */ .get_children() as Array<AgsRevealer>)
(w) => w.reveal_child, .filter((w) => w.reveal_child);
);
const currentIndex = workspaces.findIndex( const currentIndex = workspaces.findIndex(
/** @param {Revealer} w */
(w) => w.attribute.id === currentId, (w) => w.attribute.id === currentId,
); );
const left = currentIndex * ((VARS.SCREEN.X * VARS.SCALE) + PADDING); const left = currentIndex * ((VARS.SCREEN.X * VARS.SCALE) + PADDING);

View file

@ -10,16 +10,19 @@ import { updateClients } from './clients.js';
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)]; const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
const display = Gdk.Display.get_default(); const display = Gdk.Display.get_default();
/** // Types
* @typedef {import('types/widgets/button').default} Button import AgsBox from 'types/widgets/box.js';
* @typedef {import('types/widgets/button').ButtonProps} ButtonProps import AgsButton from 'types/widgets/button.js';
* @typedef {import('types/widgets/eventbox').EventBoxProps=} EventBoxProps import AgsRevealer from 'types/widgets/revealer.js';
* @typedef {import('types/widgets/box').default} Box import { ButtonProps } from 'types/widgets/button.js';
*/ import { EventBoxProps } from 'types/widgets/eventbox.js';
type WindowButtonType = ButtonProps & {
address: string
mainBox: AgsBox
};
/** @param {Button} widget */ const createSurfaceFromWidget = (widget: AgsButton) => {
const createSurfaceFromWidget = (widget) => {
const alloc = widget.get_allocation(); const alloc = widget.get_allocation();
const surface = new Cairo.ImageSurface( const surface = new Cairo.ImageSurface(
Cairo.Format.ARGB32, Cairo.Format.ARGB32,
@ -38,19 +41,16 @@ const createSurfaceFromWidget = (widget) => {
let hidden = 0; let hidden = 0;
/** @params {EventBoxProps} props */ export const WorkspaceDrop = ({ ...props }: EventBoxProps) => EventBox({
export const WorkspaceDrop = ({ ...props }) => EventBox({
...props, ...props,
setup: (self) => { setup: (self) => {
self.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY); self.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
self.on('drag-data-received', (_, _c, _x, _y, data) => { self.on('drag-data-received', (_, _c, _x, _y, data) => {
// @ts-expect-error let id = (self.get_parent() as AgsRevealer)?.attribute.id;
let id = self.get_parent()?.attribute.id;
if (id < -1) { if (id < -1) {
// @ts-expect-error id = (self.get_parent() as AgsRevealer)?.attribute.name;
id = self.get_parent()?.attribute.name;
} }
else if (id === -1) { else if (id === -1) {
@ -68,17 +68,11 @@ export const WorkspaceDrop = ({ ...props }) => EventBox({
}, },
}); });
/**
* @param {ButtonProps & {
* address: string
* mainBox: Box
* }} o
*/
export const WindowButton = ({ export const WindowButton = ({
address, address,
mainBox, mainBox,
...props ...props
}) => Button({ }: WindowButtonType) => Button({
...props, ...props,
setup: (self) => { setup: (self) => {
@ -98,8 +92,7 @@ export const WindowButton = ({
context, context,
createSurfaceFromWidget(self), createSurfaceFromWidget(self),
); );
// @ts-expect-error (self.get_parent() as AgsRevealer)?.set_reveal_child(false);
self.get_parent()?.set_reveal_child(false);
}) })
.on('drag-end', () => { .on('drag-end', () => {

View file

@ -7,6 +7,10 @@ import { WorkspaceRow, getWorkspaces, updateWorkspaces } from './workspaces.js';
import { Highlighter, updateCurrentWorkspace } from './current-workspace.js'; import { Highlighter, updateCurrentWorkspace } from './current-workspace.js';
import { updateClients } from './clients.js'; import { updateClients } from './clients.js';
// Types
import AgsBox from 'types/widgets/box.js';
import AgsOverlay from 'types/widgets/overlay.js';
// TODO: have a 'page' for each monitor, arrows on both sides to loop through // TODO: have a 'page' for each monitor, arrows on both sides to loop through
export const Overview = () => { export const Overview = () => {
@ -80,8 +84,7 @@ export const Overview = () => {
setup: (self) => { setup: (self) => {
self.on('get-child-position', (_, ch) => { self.on('get-child-position', (_, ch) => {
if (ch === mainBox && !self.attribute.closing) { if (ch === mainBox && !self.attribute.closing) {
// @ts-expect-error (self.child as AgsBox).setCss(`
self.child.setCss(`
transition: min-height 0.2s ease, min-width 0.2s ease; transition: min-height 0.2s ease, min-width 0.2s ease;
min-height: ${mainBox.get_allocated_height()}px; min-height: ${mainBox.get_allocated_height()}px;
min-width: ${mainBox.get_allocated_width()}px; min-width: ${mainBox.get_allocated_width()}px;
@ -125,14 +128,12 @@ export default () => {
if (isOpen) { if (isOpen) {
self.child = Overview(); self.child = Overview();
self.show_all(); self.show_all();
// @ts-expect-error (self.child as AgsOverlay)
self.child.attribute.get_child().attribute.update(); .attribute.get_child().attribute.update();
} }
else { else {
// @ts-expect-error (self.child as AgsOverlay).attribute.closing = true;
self.child.attribute.closing = true; ((self.child as AgsOverlay).child as AgsBox).css = `
// @ts-expect-error
self.child.child.css = `
min-height: 1px; min-height: 1px;
min-width: 1px; min-width: 1px;
transition: all transition: all

View file

@ -10,41 +10,35 @@ const DEFAULT_STYLE = `
min-height: ${VARS.SCREEN.Y * VARS.SCALE}px; min-height: ${VARS.SCREEN.Y * VARS.SCALE}px;
`; `;
/** // Types
* @typedef {import('types/widgets/box').default} Box import AgsBox from 'types/widgets/box.js';
* @typedef {import('types/widgets/revealer').default} Revealer import AgsRevealer from 'types/widgets/revealer.js';
*/ import AgsCenterBox from 'types/widgets/centerbox.js';
import AgsEventBox from 'types/widgets/eventbox.js';
/** @param {Box} box */ export const getWorkspaces = (box: AgsBox) => {
export const getWorkspaces = (box) => { const children = [] as Array<AgsRevealer>;
const children = [];
box.children.forEach((type) => { (box.children as Array<AgsBox>).forEach((type) => {
// @ts-expect-error (type.children as Array<AgsRevealer>).forEach(
type.children.forEach(
/** @param {Revealer} row */
(row) => { (row) => {
// @ts-expect-error ((((row.child as AgsCenterBox)
row.child.centerWidget.child.children.forEach( ?.center_widget as AgsEventBox)
/** @param {Revealer} workspace */ ?.child as AgsBox)
(workspace) => { .children as Array<AgsRevealer>)
.forEach((workspace) => {
children.push(workspace); children.push(workspace);
}, });
);
}, },
); );
}); });
box.attribute.workspaces = children.sort((a, b) => box.attribute.workspaces = children.sort((a, b) => {
a.attribute.id - b.attribute.id); return a.attribute.id - b.attribute.id;
});
}; };
/** const Workspace = (id: number, name: string, normal = true) => {
* @param {number} id
* @param {string} name
* @param {boolean} normal
*/
const Workspace = (id, name, normal = true) => {
const fixed = Fixed({}); const fixed = Fixed({});
const workspace = Revealer({ const workspace = Revealer({
@ -100,11 +94,7 @@ const Workspace = (id, name, normal = true) => {
return workspace; return workspace;
}; };
/** export const WorkspaceRow = (class_name: string, i: number) => {
* @param {string} class_name
* @param {number} i
*/
export const WorkspaceRow = (class_name, i) => {
const addWorkspace = Workspace( const addWorkspace = Workspace(
class_name === 'special' ? -1 : 1000, class_name === 'special' ? -1 : 1000,
class_name === 'special' ? 'special' : '', class_name === 'special' ? 'special' : '',
@ -161,11 +151,9 @@ export const WorkspaceRow = (class_name, i) => {
}); });
}; };
/** @param {Box} box */ export const updateWorkspaces = (box: AgsBox) => {
export const updateWorkspaces = (box) => {
Hyprland.workspaces.forEach((ws) => { Hyprland.workspaces.forEach((ws) => {
const currentWs = box.attribute.workspaces.find( const currentWs = (box.attribute.workspaces as Array<AgsRevealer>).find(
/** @param {Revealer} ch */
(ch) => ch.attribute.id === ws.id, (ch) => ch.attribute.id === ws.id,
); );
@ -179,21 +167,22 @@ export const updateWorkspaces = (box) => {
} }
else { else {
rowNo = Math.floor((ws.id - 1) / VARS.WORKSPACE_PER_ROW); rowNo = Math.floor((ws.id - 1) / VARS.WORKSPACE_PER_ROW);
// @ts-expect-error const wsRow = box.children[type] as AgsBox;
const wsQty = box.children[type].children.length; const wsQty = wsRow.children.length;
if (rowNo >= wsQty) { if (rowNo >= wsQty) {
for (let i = wsQty; i <= rowNo; ++i) { for (let i = wsQty; i <= rowNo; ++i) {
// @ts-expect-error wsRow.add(WorkspaceRow(
box.children[type].add(WorkspaceRow(
type ? 'special' : 'normal', i, type ? 'special' : 'normal', i,
)); ));
} }
} }
} }
// @ts-expect-error const row = ((((box.children[type] as AgsBox)
const row = box.children[type].children[rowNo] .children[rowNo] as AgsRevealer)
.child.centerWidget.child; .child as AgsCenterBox)
.center_widget as AgsEventBox)
.child as AgsBox;
row.add(Workspace(ws.id, type ? ws.name : '')); row.add(Workspace(ws.id, type ? ws.name : ''));
} }
@ -201,13 +190,9 @@ export const updateWorkspaces = (box) => {
// Make sure the order is correct // Make sure the order is correct
box.attribute.workspaces.forEach( box.attribute.workspaces.forEach(
/** (workspace: AgsRevealer, i: number) => {
* @param {Revealer} workspace (workspace?.get_parent() as AgsBox)
* @param {number} i ?.reorder_child(workspace, i);
*/
(workspace, i) => {
// @ts-expect-error
workspace?.get_parent()?.reorder_child(workspace, i);
}, },
); );
box.show_all(); box.show_all();

View file

@ -8,13 +8,14 @@ import CursorBox from '../misc/cursorbox.js';
const SCROLL_THRESH_H = 200; const SCROLL_THRESH_H = 200;
const SCROLL_THRESH_N = 7; const SCROLL_THRESH_N = 7;
/** // Types
* @typedef {import('types/widgets/box').default} Box import AgsBox from 'types/widgets/box.js';
* @typedef {import('types/service/bluetooth').BluetoothDevice} BluetoothDevice import AgsScrollable from 'types/widgets/scrollable.js';
*/ type ListBoxRow = typeof imports.gi.Gtk.ListBoxRow;
import { BluetoothDevice as BTDev } from 'types/service/bluetooth.js';
/** @param {BluetoothDevice} dev */
const BluetoothDevice = (dev) => Box({ const BluetoothDevice = (dev: BTDev) => Box({
class_name: 'menu-item', class_name: 'menu-item',
attribute: { dev }, attribute: { dev },
@ -121,14 +122,15 @@ export const BluetoothMenu = () => {
child: ListBox({ child: ListBox({
setup: (self) => { setup: (self) => {
self.set_sort_func( self.set_sort_func((a, b) => {
(a, b) => { const bState = (b.get_children()[0] as AgsBox)
// @ts-expect-error .attribute.dev.paired;
return b.get_children()[0].attribute.dev.paired - // eslint-disable-line
// @ts-expect-error const aState = (a.get_children()[0] as AgsBox)
a.get_children()[0].attribute.dev.paired; .attribute.dev.paired;
},
); return bState - aState;
});
self.hook(Bluetooth, () => { self.hook(Bluetooth, () => {
// Get all devices // Get all devices
@ -174,15 +176,14 @@ export const BluetoothMenu = () => {
SCROLL_THRESH_H, SCROLL_THRESH_H,
); );
const scroll = self.get_parent()?.get_parent(); const scroll = (self.get_parent() as ListBoxRow)
?.get_parent() as AgsScrollable;
if (scroll) { if (scroll) {
const n_child = self.get_children().length; const n_child = self.get_children().length;
if (n_child > SCROLL_THRESH_N) { if (n_child > SCROLL_THRESH_N) {
// @ts-expect-error
scroll.vscroll = 'always'; scroll.vscroll = 'always';
// @ts-expect-error
scroll.setCss(`min-height: ${height}px;`); scroll.setCss(`min-height: ${height}px;`);
// Make bottom scroll indicator appear only // Make bottom scroll indicator appear only
@ -193,9 +194,7 @@ export const BluetoothMenu = () => {
} }
} }
else { else {
// @ts-expect-error
scroll.vscroll = 'never'; scroll.vscroll = 'never';
// @ts-expect-error
scroll.setCss(''); scroll.setCss('');
topArrow.reveal_child = false; topArrow.reveal_child = false;
bottomArrow.reveal_child = false; bottomArrow.reveal_child = false;
@ -203,12 +202,10 @@ export const BluetoothMenu = () => {
} }
// Trigger sort_func // Trigger sort_func
self.get_children().forEach( (self.get_children() as Array<ListBoxRow>)
(ListBoxRow) => { .forEach((ch) => {
// @ts-expect-error ch.changed();
ListBoxRow.changed(); });
},
);
}); });
}, },
}), }),

View file

@ -13,29 +13,36 @@ import Separator from '../misc/separator.js';
import { NetworkMenu } from './network.js'; import { NetworkMenu } from './network.js';
import { BluetoothMenu } from './bluetooth.js'; import { BluetoothMenu } from './bluetooth.js';
// Types
import { GObject } from 'gi://GObject';
import AgsBox from 'types/widgets/box.js';
import AgsIcon from 'types/widgets/icon.js';
import AgsLabel from 'types/widgets/label.js';
import AgsRevealer from 'types/widgets/revealer.js';
import { Variable as Var } from 'types/variable.js';
type IconTuple = [
GObject.Object,
(self: AgsIcon) => void,
signal?: string,
];
type IndicatorTuple = [
GObject.Object,
(self: AgsLabel) => void,
signal?: string,
];
type GridButtonType = {
command?(): void
secondary_command?(): void
on_open?(menu: AgsRevealer): void
icon: string | IconTuple
indicator?: IndicatorTuple
menu?: any
};
const SPACING = 28; const SPACING = 28;
const ButtonStates = []; const ButtonStates = [] as Array<Var<any>>;
/**
* @typedef {import('types/widgets/widget').default} Widget
* @typedef {import('types/widgets/box').default} Box
* @typedef {import('types/widgets/icon').default} Icon
* @typedef {import('types/widgets/label').default} Label
* @typedef {import('types/widgets/revealer').default} Revealer
* @typedef {[any, function, (string|undefined)?]} BindTuple
*/
/**
* @param {{
* command?: function
* secondary_command?: function
* on_open?: function(Revealer):void
* icon: string|BindTuple
* indicator?: BindTuple
* menu?: any
* }} o
*/
const GridButton = ({ const GridButton = ({
command = () => {/**/}, command = () => {/**/},
secondary_command = () => {/**/}, secondary_command = () => {/**/},
@ -43,12 +50,11 @@ const GridButton = ({
icon, icon,
indicator, indicator,
menu, menu,
}) => { }: GridButtonType) => {
const Activated = Variable(false); const Activated = Variable(false);
ButtonStates.push(Activated); ButtonStates.push(Activated);
let iconWidget; let iconWidget = Icon();
/** @type Label */
let indicatorWidget = Label(); let indicatorWidget = Label();
// Allow setting icon dynamically or statically // Allow setting icon dynamically or statically
@ -70,7 +76,6 @@ const GridButton = ({
class_name: 'grid-label', class_name: 'grid-label',
setup: (self) => { setup: (self) => {
self self
// @ts-expect-error
.hook(...icon) .hook(...icon)
.hook(Activated, () => { .hook(Activated, () => {
self.setCss(`color: ${Activated.value ? self.setCss(`color: ${Activated.value ?
@ -88,7 +93,6 @@ const GridButton = ({
truncate: 'end', truncate: 'end',
max_width_chars: 12, max_width_chars: 12,
setup: (self) => { setup: (self) => {
// @ts-expect-error
self.hook(...indicator); self.hook(...indicator);
}, },
}); });
@ -139,17 +143,15 @@ const GridButton = ({
on_hover: (self) => { on_hover: (self) => {
if (menu) { if (menu) {
const rowMenu = const rowMenu =
self.get_parent() ((((self.get_parent() as AgsBox)
?.get_parent() ?.get_parent() as AgsBox)
?.get_parent() ?.get_parent() as AgsBox)
?.get_parent() ?.get_parent() as AgsBox)
// @ts-expect-error ?.children[1] as AgsBox;
?.children[1];
const isSetup = rowMenu.get_children().find( const isSetup = (rowMenu
/** @param {Box} ch */ .get_children() as Array<AgsBox>)
(ch) => ch === menu, .find((ch) => ch === menu);
);
if (!isSetup) { if (!isSetup) {
rowMenu.add(menu); rowMenu.add(menu);
@ -188,29 +190,27 @@ const GridButton = ({
}; };
const Row = ({ buttons }) => { const Row = ({ buttons }) => {
const child = Box({
class_name: 'button-row',
hpack: 'center',
});
const widget = Box({ const widget = Box({
vertical: true, vertical: true,
children: [ children: [
Box({ child,
class_name: 'button-row',
hpack: 'center',
}),
Box({ vertical: true }), Box({ vertical: true }),
], ],
}); });
for (let i = 0; i < buttons.length; ++i) { for (let i = 0; i < buttons.length; ++i) {
if (i === buttons.length - 1) { if (i === buttons.length - 1) {
// @ts-expect-error child.add(buttons[i]);
widget.children[0].add(buttons[i]);
} }
else { else {
// @ts-expect-error child.add(buttons[i]);
widget.children[0].add(buttons[i]); child.add(Separator(SPACING));
// @ts-expect-error
widget.children[0].add(Separator(SPACING));
} }
} }
@ -227,17 +227,13 @@ const FirstRow = () => Row({
// TODO: connection editor // TODO: connection editor
}, },
icon: [Network, icon: [Network, (self) => {
/** @param {Icon} self */ self.icon = Network.wifi?.icon_name;
(self) => { }],
self.icon = Network.wifi?.icon_name;
}],
indicator: [Network, indicator: [Network, (self) => {
/** @param {Label} self */ self.label = Network.wifi?.ssid || Network.wired?.internet;
(self) => { }],
self.label = Network.wifi?.ssid || Network.wired?.internet;
}],
menu: NetworkMenu(), menu: NetworkMenu(),
on_open: () => Network.wifi.scan(), on_open: () => Network.wifi.scan(),
@ -263,26 +259,22 @@ const FirstRow = () => Row({
// TODO: bluetooth connection editor // TODO: bluetooth connection editor
}, },
icon: [Bluetooth, icon: [Bluetooth, (self) => {
/** @param {Icon} self */ if (Bluetooth.enabled) {
(self) => { self.icon = Bluetooth.connected_devices[0] ?
if (Bluetooth.enabled) { Bluetooth.connected_devices[0].icon_name :
self.icon = Bluetooth.connected_devices[0] ? 'bluetooth-active-symbolic';
Bluetooth.connected_devices[0].icon_name : }
'bluetooth-active-symbolic'; else {
} self.icon = 'bluetooth-disabled-symbolic';
else { }
self.icon = 'bluetooth-disabled-symbolic'; }],
}
}],
indicator: [Bluetooth, indicator: [Bluetooth, (self) => {
/** @param {Label} self */ self.label = Bluetooth.connected_devices[0] ?
(self) => { `${Bluetooth.connected_devices[0]}` :
self.label = Bluetooth.connected_devices[0] ? 'Disconnected';
`${Bluetooth.connected_devices[0]}` : }, 'notify::connected-devices'],
'Disconnected';
}, 'notify::connected-devices'],
menu: BluetoothMenu(), menu: BluetoothMenu(),
on_open: (menu) => { on_open: (menu) => {
@ -308,11 +300,9 @@ const SecondRow = () => Row({
.catch(print); .catch(print);
}, },
icon: [SpeakerIcon, icon: [SpeakerIcon, (self) => {
/** @param {Icon} self */ self.icon = SpeakerIcon.value;
(self) => { }],
self.icon = SpeakerIcon.value;
}],
}), }),
GridButton({ GridButton({
@ -326,11 +316,9 @@ const SecondRow = () => Row({
.catch(print); .catch(print);
}, },
icon: [MicIcon, icon: [MicIcon, (self) => {
/** @param {Icon} self */ self.icon = MicIcon.value;
(self) => { }],
self.icon = MicIcon.value;
}],
}), }),
GridButton({ GridButton({

View file

@ -10,11 +10,22 @@ import CursorBox from '../misc/cursorbox.js';
const SCROLL_THRESH_H = 200; const SCROLL_THRESH_H = 200;
const SCROLL_THRESH_N = 7; const SCROLL_THRESH_N = 7;
/** @typedef {import('types/widgets/box').default} Box */ // Types
import AgsBox from 'types/widgets/box.js';
import AgsScrollable from 'types/widgets/scrollable.js';
type ListBoxRow = typeof imports.gi.Gtk.ListBoxRow;
type APType = {
bssid: string
address: string
lastSeen: number
ssid: string
active: boolean
strength: number
iconName: string
};
/** @param {any} ap */ const AccessPoint = (ap: APType) => {
const AccessPoint = (ap) => {
const widget = Box({ const widget = Box({
class_name: 'menu-item', class_name: 'menu-item',
attribute: { attribute: {
@ -138,19 +149,22 @@ export const NetworkMenu = () => {
child: ListBox({ child: ListBox({
setup: (self) => { setup: (self) => {
self.set_sort_func( self.set_sort_func((a, b) => {
(a, b) => { const bState = (b.get_children()[0] as AgsBox)
return b.get_children()[0] .attribute.ap.value.strength;
// @ts-expect-error
.attribute.ap.value.strength - const aState = (a.get_children()[0] as AgsBox)
// @ts-expect-error .attribute.ap.value.strength;
a.get_children()[0].attribute.ap.value.strength;
}, return bState - aState;
); });
self.hook(Network, () => { self.hook(Network, () => {
// Add missing APs // Add missing APs
Network.wifi?.access_points.forEach((ap) => { const currentAPs = Network.wifi
?.access_points as Array<APType>;
currentAPs.forEach((ap) => {
if (ap.ssid !== 'Unknown') { if (ap.ssid !== 'Unknown') {
if (APList.has(ap.ssid)) { if (APList.has(ap.ssid)) {
const accesPoint = APList.get(ap.ssid) const accesPoint = APList.get(ap.ssid)
@ -198,15 +212,14 @@ export const NetworkMenu = () => {
SCROLL_THRESH_H, SCROLL_THRESH_H,
); );
const scroll = self.get_parent()?.get_parent(); const scroll = (self.get_parent() as ListBoxRow)
?.get_parent() as AgsScrollable;
if (scroll) { if (scroll) {
const n_child = self.get_children().length; const n_child = self.get_children().length;
if (n_child > SCROLL_THRESH_N) { if (n_child > SCROLL_THRESH_N) {
// @ts-expect-error
scroll.vscroll = 'always'; scroll.vscroll = 'always';
// @ts-expect-error
scroll.setCss(`min-height: ${height}px;`); scroll.setCss(`min-height: ${height}px;`);
// Make bottom scroll indicator appear only // Make bottom scroll indicator appear only
@ -217,9 +230,7 @@ export const NetworkMenu = () => {
} }
} }
else { else {
// @ts-expect-error
scroll.vscroll = 'never'; scroll.vscroll = 'never';
// @ts-expect-error
scroll.setCss(''); scroll.setCss('');
topArrow.reveal_child = false; topArrow.reveal_child = false;
bottomArrow.reveal_child = false; bottomArrow.reveal_child = false;
@ -227,12 +238,10 @@ export const NetworkMenu = () => {
} }
// Trigger sort_func // Trigger sort_func
self.get_children().forEach( (self.get_children() as Array<ListBoxRow>)
(ListBoxRow) => { .forEach((ch) => {
// @ts-expect-error ch.changed();
ListBoxRow.changed(); });
},
);
}); });
}, },
}), }),

View file

@ -0,0 +1,64 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import { CenterBox, Icon, ToggleButton } from 'resource:///com/github/Aylur/ags/widget.js';
// Types
import AgsRevealer from 'types/widgets/revealer';
const { Gdk } = imports.gi;
const display = Gdk.Display.get_default();
export default (rev: AgsRevealer) => {
const child = Icon({
icon: `${App.configDir}/icons/down-large.svg`,
class_name: 'arrow',
css: '-gtk-icon-transform: rotate(180deg);',
});
const button = CenterBox({
center_widget: ToggleButton({
setup: (self) => {
// Open at startup if there are players
const id = Mpris.connect('changed', () => {
self.set_active(Mpris.players.length > 0);
Mpris.disconnect(id);
});
self
.on('toggled', () => {
if (self.get_active()) {
child
.setCss('-gtk-icon-transform: rotate(0deg);');
rev.reveal_child = true;
}
else {
child
.setCss('-gtk-icon-transform: rotate(180deg);');
rev.reveal_child = false;
}
})
// OnHover
.on('enter-notify-event', () => {
self.window.set_cursor(Gdk.Cursor.new_from_name(
display,
'pointer',
));
self.toggleClassName('hover', true);
})
// OnHoverLost
.on('leave-notify-event', () => {
self.window.set_cursor(null);
self.toggleClassName('hover', false);
});
},
child,
}),
});
return button;
};

View file

@ -26,7 +26,6 @@ in {
package = ags.packages.${pkgs.system}.default; package = ags.packages.${pkgs.system}.default;
extraPackages = with pkgs; [ extraPackages = with pkgs; [
libgudev libgudev
webkitgtk
]; ];
}; };
@ -35,6 +34,7 @@ in {
++ (with pkgs; [ ++ (with pkgs; [
# ags # ags
sassc sassc
bun
playerctl playerctl
## gui ## gui