refactor(ags): switch to TypeScript
All checks were successful
Discord / discord commits (push) Has been skipped
All checks were successful
Discord / discord commits (push) Has been skipped
This commit is contained in:
parent
a65a59e3db
commit
3e0b416a33
74 changed files with 1078 additions and 1327 deletions
|
@ -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"],
|
||||||
|
|
|
@ -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,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
|
@ -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(),
|
|
||||||
];
|
|
|
@ -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,
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -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;
|
|
||||||
};
|
|
|
@ -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);',
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
});
|
|
|
@ -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}'`);
|
|
||||||
};
|
|
|
@ -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],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -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,12 +113,11 @@ 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]>)
|
||||||
|
.some((w) => {
|
||||||
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 === '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;
|
||||||
|
@ -172,12 +133,10 @@ 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;
|
||||||
|
@ -188,26 +147,24 @@ class Pointers extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
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)) {
|
|
@ -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}`,
|
|
@ -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;
|
|
@ -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');
|
||||||
|
|
|
@ -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();
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -8,13 +8,16 @@ 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',
|
||||||
);
|
);
|
|
@ -20,5 +20,4 @@ export default () => CursorBox({
|
||||||
xalign: 0.6,
|
xalign: 0.6,
|
||||||
label: ' ',
|
label: ' ',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
});
|
});
|
|
@ -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,8 +36,8 @@ 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',
|
||||||
);
|
);
|
||||||
|
@ -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;
|
||||||
},
|
},
|
||||||
);
|
);
|
|
@ -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) => {
|
|
@ -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();
|
|
@ -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();
|
||||||
}));
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
50
devices/wim/config/ags/ts/main.ts
Normal file
50
devices/wim/config/ags/ts/main.ts
Normal 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(),
|
||||||
|
],
|
||||||
|
};
|
|
@ -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,8 +67,7 @@ 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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -82,13 +76,13 @@ export default ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
|
@ -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: [
|
217
devices/wim/config/ags/ts/media-player/player.ts
Normal file
217
devices/wim/config/ags/ts/media-player/player.ts
Normal 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,
|
||||||
|
});
|
||||||
|
};
|
|
@ -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]);
|
|
@ -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);
|
|
@ -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',
|
|
@ -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,8 +84,7 @@ 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();
|
||||||
},
|
},
|
||||||
|
@ -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;
|
||||||
};
|
};
|
|
@ -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
|
|
@ -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,
|
||||||
|
|
|
@ -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');
|
|
@ -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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
|
@ -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);
|
|
@ -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;
|
||||||
`);
|
`);
|
|
@ -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]) {
|
|
@ -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);
|
|
@ -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>}
|
|
||||||
*/
|
|
||||||
connectFunc = (self) => new Promise((r) => {
|
|
||||||
info.logic(self);
|
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;
|
||||||
};
|
};
|
|
@ -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');
|
|
@ -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: {
|
|
@ -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));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
|
@ -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', () => {
|
|
@ -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
|
|
@ -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();
|
|
@ -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();
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
|
@ -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,15 +227,11 @@ const FirstRow = () => Row({
|
||||||
// TODO: connection editor
|
// TODO: connection editor
|
||||||
},
|
},
|
||||||
|
|
||||||
icon: [Network,
|
icon: [Network, (self) => {
|
||||||
/** @param {Icon} self */
|
|
||||||
(self) => {
|
|
||||||
self.icon = Network.wifi?.icon_name;
|
self.icon = Network.wifi?.icon_name;
|
||||||
}],
|
}],
|
||||||
|
|
||||||
indicator: [Network,
|
indicator: [Network, (self) => {
|
||||||
/** @param {Label} self */
|
|
||||||
(self) => {
|
|
||||||
self.label = Network.wifi?.ssid || Network.wired?.internet;
|
self.label = Network.wifi?.ssid || Network.wired?.internet;
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
@ -263,9 +259,7 @@ const FirstRow = () => Row({
|
||||||
// TODO: bluetooth connection editor
|
// TODO: bluetooth connection editor
|
||||||
},
|
},
|
||||||
|
|
||||||
icon: [Bluetooth,
|
icon: [Bluetooth, (self) => {
|
||||||
/** @param {Icon} self */
|
|
||||||
(self) => {
|
|
||||||
if (Bluetooth.enabled) {
|
if (Bluetooth.enabled) {
|
||||||
self.icon = Bluetooth.connected_devices[0] ?
|
self.icon = Bluetooth.connected_devices[0] ?
|
||||||
Bluetooth.connected_devices[0].icon_name :
|
Bluetooth.connected_devices[0].icon_name :
|
||||||
|
@ -276,9 +270,7 @@ const FirstRow = () => Row({
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
indicator: [Bluetooth,
|
indicator: [Bluetooth, (self) => {
|
||||||
/** @param {Label} self */
|
|
||||||
(self) => {
|
|
||||||
self.label = Bluetooth.connected_devices[0] ?
|
self.label = Bluetooth.connected_devices[0] ?
|
||||||
`${Bluetooth.connected_devices[0]}` :
|
`${Bluetooth.connected_devices[0]}` :
|
||||||
'Disconnected';
|
'Disconnected';
|
||||||
|
@ -308,9 +300,7 @@ const SecondRow = () => Row({
|
||||||
.catch(print);
|
.catch(print);
|
||||||
},
|
},
|
||||||
|
|
||||||
icon: [SpeakerIcon,
|
icon: [SpeakerIcon, (self) => {
|
||||||
/** @param {Icon} self */
|
|
||||||
(self) => {
|
|
||||||
self.icon = SpeakerIcon.value;
|
self.icon = SpeakerIcon.value;
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
|
@ -326,9 +316,7 @@ const SecondRow = () => Row({
|
||||||
.catch(print);
|
.catch(print);
|
||||||
},
|
},
|
||||||
|
|
||||||
icon: [MicIcon,
|
icon: [MicIcon, (self) => {
|
||||||
/** @param {Icon} self */
|
|
||||||
(self) => {
|
|
||||||
self.icon = MicIcon.value;
|
self.icon = MicIcon.value;
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
|
@ -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();
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
64
devices/wim/config/ags/ts/quick-settings/toggle-button.ts
Normal file
64
devices/wim/config/ags/ts/quick-settings/toggle-button.ts
Normal 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;
|
||||||
|
};
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue