refactor(ags lint): customise eslint and apply to config

This commit is contained in:
matt1432 2023-11-21 01:29:46 -05:00
parent a645fc5857
commit 71108d1852
59 changed files with 1690 additions and 936 deletions

View file

@ -8,33 +8,133 @@
"ecmaVersion": "latest", "ecmaVersion": "latest",
"sourceType": "module" "sourceType": "module"
}, },
"plugins": ["@stylistic"],
"rules": { "rules": {
"arrow-parens": ["error", "as-needed"], "array-callback-return": ["error", {
"comma-dangle": ["error", "always-multiline"], "allowImplicit": true,
"comma-spacing": ["error", { "before": false, "after": true }], "checkForEach": true
"comma-style": ["error", "last"], }],
"curly": ["error", "multi-or-nest", "consistent"], "no-constructor-return": ["error"],
"dot-location": ["error", "property"], "no-unreachable-loop": ["error", { "ignore": [
"eol-last": 0, "ForInStatement", "ForOfStatement"
"indent": ["error", 4, { "SwitchCase": 1 }], ]}],
"keyword-spacing": ["error", { "before": true }], "no-use-before-define": ["error", {
"lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }], "functions": false
"padded-blocks": ["error", "never", { "allowSingleLineBlocks": false }], }],
"prefer-const": "error",
"quotes": ["error", "single", { "avoidEscape": true }],
"semi": ["error", "always"], "block-scoped-var": ["error"],
"nonblock-statement-body-position": ["error", "below"], "capitalized-comments": ["warn", "always", {
"no-trailing-spaces": ["error"], "ignoreConsecutiveComments": true
"no-dupe-class-members": 0, }],
"array-bracket-spacing": ["error", "never"], "class-methods-use-this": ["error"],
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }], "curly": ["warn"],
"object-curly-spacing": ["error", "always"], "default-case-last": ["warn"],
"no-useless-escape": ["off"] "default-param-last": ["error"],
"dot-notation": ["warn", { "allowPattern": ".*-|_.*" }],
"eqeqeq": ["error", "smart"],
"func-names": ["warn", "never"],
"func-style": ["warn", "expression"],
"logical-assignment-operators": ["warn", "always"],
"no-array-constructor": ["error"],
"no-else-return": ["error"],
"no-empty-function": ["warn"],
"no-empty-static-block": ["warn"],
"no-extend-native": ["error"],
"no-extra-bind": ["warn"],
"no-implicit-coercion": ["warn"],
"no-iterator": ["error"],
"no-labels": ["error"],
"no-lone-blocks": ["error"],
"no-lonely-if": ["error"],
"no-loop-func": ["error"],
"no-magic-numbers": ["error", {
"ignore": [-1, 0.1, 0, 1, 2, 3, 10, 33, 66, 100, 255, 360, 450, 1000],
"ignoreDefaultValues": true
}],
"no-multi-assign": ["error"],
"no-negated-condition": ["warn"],
"no-new": ["error"],
"no-new-func": ["error"],
"no-new-wrappers": ["error"],
"no-object-constructor": ["error"],
"no-proto": ["error"],
"no-return-assign": ["error"],
"no-sequences": ["error"],
"no-shadow": ["error", { "builtinGlobals": true }],
"no-undef-init": ["warn"],
"no-undefined": ["error"],
"no-useless-constructor": ["warn"],
"no-useless-escape": ["off"],
"no-useless-return": ["error"],
"no-var": ["error"],
"no-void": ["error"],
"no-with": ["error"],
"object-shorthand": ["error", "always"],
"one-var": ["error", "never"],
"operator-assignment": ["warn", "always"],
"prefer-arrow-callback": ["error"],
"prefer-const": ["error"],
"prefer-object-has-own": ["error"],
"prefer-regex-literals": ["error"],
"prefer-template": ["warn"],
"@stylistic/array-bracket-newline": ["warn", "consistent"],
"@stylistic/array-bracket-spacing": ["warn", "never"],
"@stylistic/arrow-parens": ["warn", "always"],
"@stylistic/brace-style": ["warn", "stroustrup"],
"@stylistic/comma-dangle": ["warn", "always-multiline"],
"@stylistic/comma-spacing": ["warn", { "before": false, "after": true }],
"@stylistic/comma-style": ["error", "last"],
"@stylistic/dot-location": ["error", "property"],
"@stylistic/function-call-argument-newline": ["warn", "consistent"],
"@stylistic/function-paren-newline": ["warn", "consistent"],
"@stylistic/indent": ["warn", 4, {
"SwitchCase": 1,
"ignoreComments": true
}],
"@stylistic/key-spacing": ["warn", { "beforeColon": false, "afterColon": true }],
"@stylistic/keyword-spacing": ["warn", { "before": true }],
"@stylistic/linebreak-style": ["error", "unix"],
"@stylistic/lines-between-class-members": ["warn", "always", { "exceptAfterSingleLine": true }],
"@stylistic/max-len": ["warn", {
"code": 80,
"ignoreComments": true,
"ignoreTrailingComments": true,
"ignoreUrls": true
}],
"@stylistic/multiline-ternary": ["warn", "always-multiline"],
"@stylistic/new-parens": ["error"],
"@stylistic/no-mixed-operators": ["warn"],
"@stylistic/no-mixed-spaces-and-tabs": ["error"],
"@stylistic/no-multi-spaces": ["error"],
"@stylistic/no-tabs": ["error"],
"@stylistic/no-trailing-spaces": ["error"],
"@stylistic/no-whitespace-before-property": ["warn"],
"@stylistic/nonblock-statement-body-position": ["error", "below"],
"@stylistic/object-curly-newline": ["warn", { "consistent": true }],
"@stylistic/object-curly-spacing": ["warn", "always"],
"@stylistic/operator-linebreak": ["warn", "after"],
"@stylistic/padded-blocks": ["error", "never"],
"@stylistic/padding-line-between-statements": ["warn",
{ "blankLine": "always", "prev": "*", "next": "return" },
{ "blankLine": "always", "prev": ["const", "let", "var"], "next": "*"},
{ "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"]},
{ "blankLine": "always", "prev": ["case", "default"], "next": "*" }
],
"@stylistic/quote-props": ["error", "consistent-as-needed"],
"@stylistic/quotes": ["error", "single", { "avoidEscape": true }],
"@stylistic/semi": ["error", "always"],
"@stylistic/semi-spacing": ["warn"],
"@stylistic/space-before-blocks": ["warn"],
"@stylistic/space-before-function-paren": ["warn", "never"],
"@stylistic/space-infix-ops": ["warn"],
"@stylistic/spaced-comment": ["warn", "always"],
"@stylistic/switch-colon-spacing": ["warn"],
"@stylistic/wrap-regex": ["warn"]
}, },
"globals": { "globals": {
"Tablet": "readonly",
"Pointers": "readonly",
"Brightness": "readonly",
"ARGV": "readonly", "ARGV": "readonly",
"imports": "readonly", "imports": "readonly",
"print": "readonly", "print": "readonly",

View file

@ -1,13 +1,14 @@
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { Box, Button, Icon, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import { lookUpIcon } from 'resource:///com/github/Aylur/ags/utils.js'; import { lookUpIcon } from 'resource:///com/github/Aylur/ags/utils.js';
import Separator from '../misc/separator.js'; import Separator from '../misc/separator.js';
export default app => { export default (app) => {
const title = Widget.Label({ const title = Label({
class_name: 'title', class_name: 'title',
label: app.name, label: app.name,
xalign: 0, xalign: 0,
@ -15,7 +16,7 @@ export default app => {
truncate: 'end', truncate: 'end',
}); });
const description = Widget.Label({ const description = Label({
class_name: 'description', class_name: 'description',
label: app.description || '', label: app.description || '',
wrap: true, wrap: true,
@ -24,28 +25,42 @@ export default app => {
vpack: 'center', vpack: 'center',
}); });
const icon = Widget.Icon({ const icon = Icon({
icon: lookUpIcon(app.icon_name) ? app.icon_name : icon: lookUpIcon(app.icon_name) ?
app.app.get_string('Icon') !== 'nix-snowflake' ? app.app.get_string('Icon') : '', app.icon_name :
app.app.get_string('Icon') === 'nix-snowflake' ?
'' :
app.app.get_string('Icon'),
size: 42, size: 42,
}); });
const textBox = Widget.Box({ const textBox = Box({
vertical: true, vertical: true,
vpack: 'center', vpack: 'center',
children: [title, description], children: [title, description],
}); });
return Widget.Button({ const ENTRY_SPACING = 16;
return Button({
class_name: 'app', class_name: 'app',
setup: self => self.app = app,
setup: (self) => {
self.app = app;
},
on_clicked: () => { on_clicked: () => {
App.closeWindow('applauncher'); App.closeWindow('applauncher');
Hyprland.sendMessage(`dispatch exec sh -c ${app.executable}`); Hyprland.sendMessage(`dispatch exec sh -c ${app.executable}`);
++app.frequency; ++app.frequency;
}, },
child: Widget.Box({
children: [icon, Separator(16), textBox], child: Box({
children: [
icon,
Separator(ENTRY_SPACING),
textBox,
],
}), }),
}); });
}; };

View file

@ -10,17 +10,20 @@ import AppItem from './app-item.js';
const Applauncher = ({ window_name = 'applauncher' } = {}) => { const Applauncher = ({ window_name = 'applauncher' } = {}) => {
const ICON_SEPARATION = 4;
const children = () => [ const children = () => [
...Applications.query('').flatMap(app => { ...Applications.query('').flatMap((app) => {
const item = AppItem(app); const item = AppItem(app);
return [ return [
Separator(4, { Separator(ICON_SEPARATION, {
binds: [['visible', item, 'visible']], binds: [['visible', item, 'visible']],
}), }),
item, item,
]; ];
}), }),
Separator(4), Separator(ICON_SEPARATION),
]; ];
const list = Box({ const list = Box({
@ -28,43 +31,46 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => {
children: children(), children: children(),
}); });
const entry = Entry({ const placeholder = Label({
hexpand: true, label: " Couldn't find a match",
placeholder_text: 'Search', className: 'placeholder',
});
// set some text so on-change works the first time const entry = Entry({
// Set some text so on-change works the first time
text: '-', text: '-',
hexpand: true,
on_accept: ({ text }) => { on_accept: ({ text }) => {
const list = Applications.query(text || ''); const appList = Applications.query(text || '');
if (list[0]) {
if (appList[0]) {
App.toggleWindow(window_name); App.toggleWindow(window_name);
Hyprland.sendMessage(`dispatch exec sh -c ${list[0]}`); Hyprland.sendMessage(`dispatch exec sh -c ${appList[0]}`);
++list[0].frequency; ++appList[0].frequency;
} }
}, },
on_change: ({ text }) => { on_change: ({ text }) => {
let visibleApps = 0; let visibleApps = 0;
list.children.map(item => {
list.children.forEach((item) => {
if (item.app) { if (item.app) {
item.visible = item.app.match(text); item.visible = item.app.match(text);
if (item.app.match(text)) if (item.app.match(text)) {
++visibleApps; ++visibleApps;
} }
}
}); });
placeholder.visible = visibleApps <= 0; placeholder.visible = visibleApps <= 0;
}, },
}); });
const placeholder = Label({
label: " Couldn't find a match",
className: 'placeholder',
});
return Box({ return Box({
className: 'applauncher', className: 'applauncher',
vertical: true, vertical: true,
children: [ children: [
Box({ Box({
className: 'header', className: 'header',
@ -82,15 +88,20 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => {
}), }),
}), }),
], ],
connections: [[App, (_, name, visible) => { connections: [[App, (_, name, visible) => {
if (name !== window_name) if (name !== window_name) {
return; return;
}
entry.text = ''; entry.text = '';
if (visible)
if (visible) {
entry.grab_focus(); entry.grab_focus();
else }
else {
list.children = children(); list.children = children();
}
}]], }]],
}); });
}; };

View file

@ -1,4 +1,5 @@
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js'; import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import { Label, Box, Icon } from 'resource:///com/github/Aylur/ags/widget.js'; import { Label, Box, Icon } from 'resource:///com/github/Aylur/ags/widget.js';
import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js'; import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
@ -7,27 +8,32 @@ import Separator from '../../misc/separator.js';
import EventBox from '../../misc/cursorbox.js'; import EventBox from '../../misc/cursorbox.js';
const SpeakerIndicator = props => Icon({ const SpeakerIndicator = (props) => Icon({
...props, ...props,
binds: [['icon', SpeakerIcon, 'value']], binds: [['icon', SpeakerIcon, 'value']],
}); });
const SpeakerPercentLabel = props => Label({ const SpeakerPercentLabel = (props) => Label({
...props, ...props,
connections: [[Audio, label => { connections: [[Audio, (label) => {
if (Audio.speaker) if (Audio.speaker) {
label.label = Math.round(Audio.speaker.volume * 100) + '%'; label.label = `${Math.round(Audio.speaker.volume * 100)}%`;
}
}, 'speaker-changed']], }, 'speaker-changed']],
}); });
const SPACING = 5;
export default () => EventBox({ export default () => EventBox({
onPrimaryClickRelease: () => execAsync(['pavucontrol']).catch(print),
className: 'toggle-off', className: 'toggle-off',
onPrimaryClickRelease: () => execAsync(['pavucontrol']).catch(print),
child: Box({ child: Box({
className: 'audio', className: 'audio',
children: [ children: [
SpeakerIndicator(), SpeakerIndicator(),
Separator(5), Separator(SPACING),
SpeakerPercentLabel(), SpeakerPercentLabel(),
], ],
}), }),

View file

@ -1,30 +1,41 @@
import Battery from 'resource:///com/github/Aylur/ags/service/battery.js'; import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
import { Label, Icon, Box } from 'resource:///com/github/Aylur/ags/widget.js'; import { Label, Icon, Box } from 'resource:///com/github/Aylur/ags/widget.js';
import Separator from '../../misc/separator.js'; import Separator from '../../misc/separator.js';
const LOW_BATT = 20;
const Indicator = () => Icon({ const Indicator = () => Icon({
className: 'battery-indicator', className: 'battery-indicator',
binds: [['icon', Battery, 'icon-name']], binds: [['icon', Battery, 'icon-name']],
connections: [[Battery, self => {
connections: [[Battery, (self) => {
self.toggleClassName('charging', Battery.charging); self.toggleClassName('charging', Battery.charging);
self.toggleClassName('charged', Battery.charged); self.toggleClassName('charged', Battery.charged);
self.toggleClassName('low', Battery.percent < 20); self.toggleClassName('low', Battery.percent < LOW_BATT);
}]], }]],
}); });
const LevelLabel = props => Label({ const LevelLabel = (props) => Label({
...props, ...props,
className: 'label', className: 'label',
connections: [[Battery, self => self.label = `${Battery.percent}%`]],
connections: [[Battery, (self) => {
self.label = `${Battery.percent}%`;
}]],
}); });
const SPACING = 5;
export default () => Box({ export default () => Box({
className: 'toggle-off battery', className: 'toggle-off battery',
children: [ children: [
Indicator(), Indicator(),
Separator(5), Separator(SPACING),
LevelLabel(), LevelLabel(),
], ],
}); });

View file

@ -1,22 +1,30 @@
import { ProgressBar, Overlay, Box } from 'resource:///com/github/Aylur/ags/widget.js'; import { ProgressBar, Overlay, Box } from 'resource:///com/github/Aylur/ags/widget.js';
import Brightness from '../../../services/brightness.js';
import Separator from '../../misc/separator.js'; import Separator from '../../misc/separator.js';
import Heart from './heart.js'; import Heart from './heart.js';
const SPACING = 25;
const BAR_CUTOFF = 0.33;
export default () => Overlay({ export default () => Overlay({
tooltipText: 'Brightness', tooltipText: 'Brightness',
child: ProgressBar({ child: ProgressBar({
className: 'toggle-off brightness', className: 'toggle-off brightness',
connections: [[Brightness, self => { connections: [[Brightness, (self) => {
self.value = Brightness.screen > 0.33 ? Brightness.screen : 0.33; self.value = Brightness.screen > BAR_CUTOFF ?
Brightness.screen :
BAR_CUTOFF;
}, 'screen']], }, 'screen']],
}), }),
overlays: [ overlays: [
Box({ Box({
css: 'color: #CBA6F7;', css: 'color: #CBA6F7;',
children: [ children: [
Separator(25), Separator(SPACING),
Heart(), Heart(),
], ],
}), }),

View file

@ -1,5 +1,6 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import { Box, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import { Label } from 'resource:///com/github/Aylur/ags/widget.js';
import GLib from 'gi://GLib'; import GLib from 'gi://GLib';
const { DateTime } = GLib; const { DateTime } = GLib;
@ -10,29 +11,33 @@ import EventBox from '../../misc/cursorbox.js';
const ClockModule = ({ const ClockModule = ({
interval = 1000, interval = 1000,
...props ...props
}) => Label({ } = {}) => {
return Label({
...props, ...props,
className: 'clock', className: 'clock',
connections: [
[interval, self => { connections: [[interval, (self) => {
var time = DateTime.new_now_local(); const time = DateTime.new_now_local();
self.label = time.format('%a. ') + self.label = time.format('%a. ') +
time.get_day_of_month() + time.get_day_of_month() +
time.format(' %b. %H:%M'); time.format(' %b. %H:%M');
}], }]],
],
}); });
};
export default () => EventBox({ export default () => EventBox({
className: 'toggle-off', className: 'toggle-off',
onPrimaryClickRelease: () => App.toggleWindow('calendar'), onPrimaryClickRelease: () => App.toggleWindow('calendar'),
connections: [ connections: [
[App, (self, windowName, visible) => { [App, (self, windowName, visible) => {
if (windowName == 'calendar') if (windowName === 'calendar') {
self.toggleClassName('toggle-on', visible); self.toggleClassName('toggle-on', visible);
}
}], }],
], ],
child: Box({
child: ClockModule({}), child: ClockModule(),
}),
}); });

View file

@ -1,4 +1,5 @@
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { Label } from 'resource:///com/github/Aylur/ags/widget.js'; import { Label } from 'resource:///com/github/Aylur/ags/widget.js';

View file

@ -17,10 +17,13 @@ export default () => EventBox({
child: Label({ child: Label({
label: '', label: '',
setup: self => {
setup: (self) => {
subprocess( subprocess(
['bash', '-c', 'tail -f /home/matt/.config/.heart'], ['bash', '-c', 'tail -f /home/matt/.config/.heart'],
output => self.label = ' ' + output, (output) => {
self.label = ` ${output}`;
},
); );
}, },
}), }),

View file

@ -1,4 +1,5 @@
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { Box, Icon, Label } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Icon, Label } from 'resource:///com/github/Aylur/ags/widget.js';
const DEFAULT_KB = 'at-translated-set-2-keyboard'; const DEFAULT_KB = 'at-translated-set-2-keyboard';
@ -7,17 +8,29 @@ const DEFAULT_KB = 'at-translated-set-2-keyboard';
export default () => Box({ export default () => Box({
className: 'toggle-off', className: 'toggle-off',
css: 'padding: 0 10px;', css: 'padding: 0 10px;',
children: [ children: [
Icon({ Icon({
icon: 'input-keyboard-symbolic', icon: 'input-keyboard-symbolic',
css: 'margin-right: 4px;', css: 'margin-right: 4px;',
}), }),
Label({ Label({
connections: [[Hyprland, (self, _n, layout) => { connections: [[Hyprland, (self, _n, layout) => {
if (!layout) { if (layout) {
Hyprland.sendMessage('j/devices').then(obj => { if (layout === 'error') {
const kb = JSON.parse(obj)['keyboards'] return;
.find(val => val.name === DEFAULT_KB); }
const shortName = layout.match(/\(([A-Za-z]+)\)/);
self.label = shortName ? shortName[1] : layout;
}
else {
// At launch, kb layout is undefined
Hyprland.sendMessage('j/devices').then((obj) => {
const kb = JSON.parse(obj).keyboards
.find((val) => val.name === DEFAULT_KB);
layout = kb['active_keymap']; layout = kb['active_keymap'];
@ -26,14 +39,6 @@ export default () => Box({
self.label = shortName ? shortName[1] : layout; self.label = shortName ? shortName[1] : layout;
}).catch(print); }).catch(print);
} }
else {
if (layout === 'error')
return;
const shortName = layout.match(/\(([A-Za-z]+)\)/);
self.label = shortName ? shortName[1] : layout;
}
}, 'keyboard-layout']], }, 'keyboard-layout']],
}), }),
], ],

View file

@ -1,44 +1,52 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js'; import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
import { Box, Icon, Label } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Icon, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import Separator from '../../misc/separator.js'; import Separator from '../../misc/separator.js';
import EventBox from '../../misc/cursorbox.js'; import EventBox from '../../misc/cursorbox.js';
const L_PADDING = 28;
const R_PADDING = 8;
export default () => EventBox({ export default () => EventBox({
className: 'toggle-off', className: 'toggle-off',
onPrimaryClickRelease: () => App.toggleWindow('notification-center'), onPrimaryClickRelease: () => App.toggleWindow('notification-center'),
connections: [[App, (self, windowName, visible) => { connections: [[App, (self, windowName, visible) => {
if (windowName == 'notification-center') if (windowName === 'notification-center') {
self.toggleClassName('toggle-on', visible); self.toggleClassName('toggle-on', visible);
}
}]], }]],
child: Box({ child: Box({
className: 'notif-panel', className: 'notif-panel',
vertical: false, vertical: false,
children: [ children: [
Separator(28), Separator(L_PADDING),
Icon({ Icon({
connections: [[Notifications, self => { connections: [[Notifications, (self) => {
if (Notifications.dnd) { if (Notifications.dnd) {
self.icon = 'notification-disabled-symbolic'; self.icon = 'notification-disabled-symbolic';
} }
else { else if (Notifications.notifications.length > 0) {
if (Notifications.notifications.length > 0)
self.icon = 'notification-new-symbolic'; self.icon = 'notification-new-symbolic';
else }
else {
self.icon = 'notification-symbolic'; self.icon = 'notification-symbolic';
} }
}]], }]],
}), }),
Separator(8), Separator(R_PADDING),
Label({ Label({
binds: [ binds: [['label', Notifications, 'notifications',
['label', Notifications, 'notifications', n => String(n.length)], (n) => String(n.length)]],
],
}), }),
], ],

View file

@ -1,13 +1,15 @@
import { Box, Label } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import Tablet from '../../../services/tablet.js';
import EventBox from '../../misc/cursorbox.js'; import EventBox from '../../misc/cursorbox.js';
export default () => EventBox({ export default () => EventBox({
className: 'toggle-off', className: 'toggle-off',
onPrimaryClickRelease: () => Tablet.toggleOsk(), onPrimaryClickRelease: () => Tablet.toggleOsk(),
connections: [[Tablet, self => { connections: [[Tablet, (self) => {
self.toggleClassName('toggle-on', Tablet.oskState); self.toggleClassName('toggle-on', Tablet.oskState);
}, 'osk-toggled']], }, 'osk-toggled']],

View file

@ -1,4 +1,5 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import { Box, Label } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import EventBox from '../../misc/cursorbox.js'; import EventBox from '../../misc/cursorbox.js';
@ -6,11 +7,15 @@ import EventBox from '../../misc/cursorbox.js';
export default () => EventBox({ export default () => EventBox({
className: 'toggle-off', className: 'toggle-off',
onPrimaryClickRelease: () => App.toggleWindow('quick-settings'), onPrimaryClickRelease: () => App.toggleWindow('quick-settings'),
connections: [[App, (self, windowName, visible) => { connections: [[App, (self, windowName, visible) => {
if (windowName == 'quick-settings') if (windowName === 'quick-settings') {
self.toggleClassName('toggle-on', visible); self.toggleClassName('toggle-on', visible);
}
}]], }]],
child: Box({ child: Box({
className: 'quick-settings-toggle', className: 'quick-settings-toggle',
vertical: false, vertical: false,

View file

@ -1,17 +1,24 @@
import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js'; import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
import { timeout } from 'resource:///com/github/Aylur/ags/utils.js'; import { timeout } from 'resource:///com/github/Aylur/ags/utils.js';
import { Box, Icon, MenuItem, MenuBar, Revealer } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Icon, MenuItem, MenuBar, Revealer } from 'resource:///com/github/Aylur/ags/widget.js';
import Separator from '../../misc/separator.js'; import Separator from '../../misc/separator.js';
const REVEAL_DURATION = 500;
const SPACING = 12;
const SysTrayItem = item => {
if (item.id === 'spotify-client') const SysTrayItem = (item) => {
if (item.id === 'spotify-client') {
return; return;
}
return MenuItem({ return MenuItem({
child: Revealer({ child: Revealer({
transition: 'slide_right', transition: 'slide_right',
transitionDuration: REVEAL_DURATION,
child: Icon({ child: Icon({
size: 24, size: 24,
binds: [['icon', item, 'icon']], binds: [['icon', item, 'icon']],
@ -23,18 +30,22 @@ const SysTrayItem = item => {
}; };
const SysTray = () => MenuBar({ const SysTray = () => MenuBar({
setup: self => { setup: (self) => {
self.items = new Map(); self.items = new Map();
self.onAdded = id => { self.onAdded = (id) => {
const item = SystemTray.getItem(id); const item = SystemTray.getItem(id);
if (self.items.has(id) || !item)
if (self.items.has(id) || !item) {
return; return;
}
const w = SysTrayItem(item); const w = SysTrayItem(item);
// Early return if item is in blocklist // Early return if item is in blocklist
if (!w) if (!w) {
return; return;
}
self.items.set(id, w); self.items.set(id, w);
self.add(w); self.add(w);
@ -42,12 +53,13 @@ const SysTray = () => MenuBar({
w.child.revealChild = true; w.child.revealChild = true;
}; };
self.onRemoved = id => { self.onRemoved = (id) => {
if (!self.items.has(id)) if (!self.items.has(id)) {
return; return;
}
self.items.get(id).child.revealChild = false; self.items.get(id).child.revealChild = false;
timeout(400, () => { timeout(REVEAL_DURATION, () => {
self.items.get(id).destroy(); self.items.get(id).destroy();
self.items.delete(id); self.items.delete(id);
}); });
@ -59,20 +71,25 @@ const SysTray = () => MenuBar({
], ],
}); });
export default () => Revealer({ export default () => {
const systray = SysTray();
return Revealer({
transition: 'slide_right', transition: 'slide_right',
connections: [[SystemTray, rev => {
rev.revealChild = rev.child.children[0].get_children().length > 0; connections: [[SystemTray, (rev) => {
rev.revealChild = systray.get_children().length > 0;
}]], }]],
child: Box({ child: Box({
children: [ children: [
Box({ Box({
className: 'sys-tray', className: 'sys-tray',
children: [ children: [systray],
SysTray(),
],
}), }),
Separator(12),
Separator(SPACING),
], ],
}), }),
}); });
};

View file

@ -1,13 +1,15 @@
import { Box, Label } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import Tablet from '../../../services/tablet.js';
import EventBox from '../../misc/cursorbox.js'; import EventBox from '../../misc/cursorbox.js';
export default () => EventBox({ export default () => EventBox({
className: 'toggle-off', className: 'toggle-off',
onPrimaryClickRelease: () => Tablet.toggleMode(), onPrimaryClickRelease: () => Tablet.toggleMode(),
connections: [[Tablet, self => { connections: [[Tablet, (self) => {
self.toggleClassName('toggle-on', Tablet.tabletMode); self.toggleClassName('toggle-on', Tablet.tabletMode);
}, 'mode-toggled']], }, 'mode-toggled']],

View file

@ -1,24 +1,33 @@
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { timeout } from 'resource:///com/github/Aylur/ags/utils.js'; import { timeout } from 'resource:///com/github/Aylur/ags/utils.js';
import { Box, Overlay, Revealer } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Overlay, Revealer } from 'resource:///com/github/Aylur/ags/widget.js';
import EventBox from '../../misc/cursorbox.js'; import EventBox from '../../misc/cursorbox.js';
const URGENT_DURATION = 1000;
const Workspace = ({ i } = {}) =>
Revealer({ const Workspace = ({ i } = {}) => {
return Revealer({
transition: 'slide_right', transition: 'slide_right',
properties: [['id', i]], properties: [['id', i]],
child: EventBox({ child: EventBox({
tooltipText: `${i}`, tooltipText: `${i}`,
onPrimaryClickRelease: () => Hyprland.sendMessage(`dispatch workspace ${i}`),
onPrimaryClickRelease: () => {
Hyprland.sendMessage(`dispatch workspace ${i}`);
},
child: Box({ child: Box({
vpack: 'center', vpack: 'center',
className: 'button', className: 'button',
setup: self => {
self.update = addr => { setup: (self) => {
self.update = (addr) => {
const occupied = Hyprland.getWorkspace(i)?.windows > 0; const occupied = Hyprland.getWorkspace(i)?.windows > 0;
self.toggleClassName('occupied', occupied); self.toggleClassName('occupied', occupied);
self.toggleClassName('empty', !occupied); self.toggleClassName('empty', !occupied);
@ -27,36 +36,47 @@ const Workspace = ({ i } = {}) =>
self.toggleClassName('urgent', true); self.toggleClassName('urgent', true);
// Only show for a sec when urgent is current workspace // Only show for a sec when urgent is current workspace
if (Hyprland.active.workspace.id === i) if (Hyprland.active.workspace.id === i) {
timeout(1000, () => self.toggleClassName('urgent', false)); timeout(URGENT_DURATION, () => {
self.toggleClassName('urgent', false);
});
}
} }
}; };
}, },
connections: [ connections: [
[Hyprland, self => self.update()], [Hyprland, (self) => self.update()],
// Deal with urgent windows // Deal with urgent windows
[Hyprland, (self, addr) => self.update(addr), 'urgent-window'], [Hyprland, (self, a) => self.update(a), 'urgent-window'],
[Hyprland.active.workspace, self => { [Hyprland.active.workspace, (self) => {
if (Hyprland.active.workspace.id === i) if (Hyprland.active.workspace.id === i) {
self.toggleClassName('urgent', false); self.toggleClassName('urgent', false);
}
}], }],
], ],
}), }),
}), }),
}); });
};
export default () => { export default () => {
const updateHighlight = () => { const L_PADDING = 16;
const WS_WIDTH = 30;
const updateHighlight = (self) => {
const currentId = Hyprland.active.workspace.id; const currentId = Hyprland.active.workspace.id;
const indicators = highlight.get_parent().get_children()[0].child.children; const indicators = self.get_parent().get_children()[0].child.children;
const currentIndex = indicators.findIndex(w => w._id == currentId); const currentIndex = indicators.findIndex((w) => w._id === currentId);
if (currentIndex < 0) if (currentIndex < 0) {
return; return;
}
highlight.setCss(`margin-left: ${16 + currentIndex * 30}px`); self.setCss(`margin-left: ${L_PADDING + (currentIndex * WS_WIDTH)}px`);
}; };
const highlight = Box({ const highlight = Box({
vpack: 'center', vpack: 'center',
hpack: 'start', hpack: 'start',
@ -70,21 +90,28 @@ export default () => {
child: EventBox({ child: EventBox({
child: Box({ child: Box({
className: 'workspaces', className: 'workspaces',
properties: [ properties: [
['workspaces'], ['workspaces'],
['refresh', self => { ['refresh', (self) => {
self.children.forEach(rev => rev.reveal_child = false); self.children.forEach((rev) => {
self._workspaces.forEach(ws => { rev.reveal_child = false;
});
self._workspaces.forEach((ws) => {
ws.revealChild = true; ws.revealChild = true;
}); });
}], }],
['updateWorkspaces', self => { ['updateWorkspaces', (self) => {
Hyprland.workspaces.forEach(ws => { Hyprland.workspaces.forEach((ws) => {
const currentWs = self.children.find(ch => ch._id == ws.id); const currentWs = self.children.find((ch) => {
if (!currentWs && ws.id > 0) return ch._id === ws.id;
});
if (!currentWs && ws.id > 0) {
self.add(Workspace({ i: ws.id })); self.add(Workspace({ i: ws.id }));
}
}); });
self.show_all(); self.show_all();
@ -94,16 +121,21 @@ export default () => {
}); });
}], }],
], ],
connections: [[Hyprland, self => {
self._workspaces = self.children.filter(ch => { connections: [[Hyprland, (self) => {
return Hyprland.workspaces.find(ws => ws.id == ch._id); self._workspaces = self.children.filter((ch) => {
return Hyprland.workspaces.find((ws) => {
return ws.id === ch._id;
});
}).sort((a, b) => a._id - b._id); }).sort((a, b) => a._id - b._id);
self._updateWorkspaces(self); self._updateWorkspaces(self);
self._refresh(self); self._refresh(self);
// Make sure the highlight doesn't go too far // Make sure the highlight doesn't go too far
timeout(10, updateHighlight); const TEMP_TIMEOUT = 10;
timeout(TEMP_TIMEOUT, () => updateHighlight(highlight));
}]], }]],
}), }),
}), }),

View file

@ -4,23 +4,25 @@ 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';
const BarCloser = variable => 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'],
layer: 'overlay', layer: 'overlay',
child: EventBox({ child: EventBox({
onHover: self => { onHover: (self) => {
variable.value = false; variable.value = false;
self.get_parent().visible = false; self.get_parent().visible = false;
}, },
child: Box({ child: Box({
css: 'padding: 1px', css: 'padding: 1px',
}), }),
}), }),
}); });
export default props => { export default (props) => {
const Revealed = Variable(true); const Revealed = Variable(true);
const barCloser = BarCloser(Revealed); const barCloser = BarCloser(Revealed);
@ -28,28 +30,39 @@ export default props => {
css: 'min-height: 1px', css: 'min-height: 1px',
hexpand: true, hexpand: true,
vertical: true, vertical: true,
connections: [ connections: [
[Hyprland.active, () => { [Hyprland.active, () => {
const workspace = Hyprland.getWorkspace(Hyprland.active.workspace.id); const workspace = Hyprland.getWorkspace(
Hyprland.active.workspace.id,
);
Revealed.value = !workspace?.hasfullscreen; Revealed.value = !workspace?.hasfullscreen;
}], }],
[Hyprland, (_, fullscreen) => Revealed.value = !fullscreen, 'fullscreen'],
[Hyprland, (_, fullscreen) => {
Revealed.value = !fullscreen;
}, 'fullscreen'],
], ],
children: [ children: [
Revealer({ Revealer({
...props,
transition: 'slide_down', transition: 'slide_down',
revealChild: true, revealChild: true,
binds: [['revealChild', Revealed, 'value']], binds: [['revealChild', Revealed, 'value']],
...props,
}), }),
Revealer({ Revealer({
binds: [['revealChild', Revealed, 'value', v => !v]], binds: [['revealChild', Revealed, 'value', (v) => !v]],
child: EventBox({ child: EventBox({
onHover: () => { onHover: () => {
barCloser.visible = true; barCloser.visible = true;
Revealed.value = true; Revealed.value = true;
}, },
child: Box({ child: Box({
css: 'min-height: 5px;', css: 'min-height: 5px;',
}), }),

View file

@ -1,21 +1,24 @@
import { Window, CenterBox, Box } from 'resource:///com/github/Aylur/ags/widget.js'; import { Window, CenterBox, Box } from 'resource:///com/github/Aylur/ags/widget.js';
import Separator from '../misc/separator.js'; import Separator from '../misc/separator.js';
import CurrentWindow from './buttons/current-window.js';
import Workspaces from './buttons/workspaces.js'; import Audio from './buttons/audio.js';
import OskToggle from './buttons/osk-toggle.js';
import TabletToggle from './buttons/tablet-toggle.js';
import QsToggle from './buttons/quick-settings.js';
import NotifButton from './buttons/notif-button.js';
import Clock from './buttons/clock.js';
import SysTray from './buttons/systray.js';
import Battery from './buttons/battery.js'; import Battery from './buttons/battery.js';
import Brightness from './buttons/brightness.js'; import Brightness from './buttons/brightness.js';
import Audio from './buttons/audio.js'; import Clock from './buttons/clock.js';
import CurrentWindow from './buttons/current-window.js';
import KeyboardLayout from './buttons/keyboard-layout.js'; import KeyboardLayout from './buttons/keyboard-layout.js';
import NotifButton from './buttons/notif-button.js';
import OskToggle from './buttons/osk-toggle.js';
import QsToggle from './buttons/quick-settings.js';
import SysTray from './buttons/systray.js';
import TabletToggle from './buttons/tablet-toggle.js';
import Workspaces from './buttons/workspaces.js';
import BarReveal from './fullscreen.js'; import BarReveal from './fullscreen.js';
const SPACING = 12;
export default () => Window({ export default () => Window({
name: 'bar', name: 'bar',
@ -33,21 +36,21 @@ export default () => Window({
OskToggle(), OskToggle(),
Separator(12), Separator(SPACING),
TabletToggle(), TabletToggle(),
Separator(12), Separator(SPACING),
SysTray(), SysTray(),
Audio(), Audio(),
Separator(12), Separator(SPACING),
Brightness(), Brightness(),
Separator(12), Separator(SPACING),
Workspaces(), Workspaces(),
@ -56,11 +59,11 @@ export default () => Window({
centerWidget: Box({ centerWidget: Box({
children: [ children: [
Separator(12), Separator(SPACING),
CurrentWindow(), CurrentWindow(),
Separator(12), Separator(SPACING),
], ],
}), }),
@ -69,19 +72,19 @@ export default () => Window({
children: [ children: [
Battery(), Battery(),
Separator(12), Separator(SPACING),
KeyboardLayout(), KeyboardLayout(),
Separator(12), Separator(SPACING),
Clock(), Clock(),
Separator(12), Separator(SPACING),
NotifButton(), NotifButton(),
Separator(12), Separator(SPACING),
QsToggle(), QsToggle(),
], ],

View file

@ -11,6 +11,7 @@ const TopLeft = () => Window({
visible: true, visible: true,
child: RoundedCorner('topleft'), child: RoundedCorner('topleft'),
}); });
const TopRight = () => Window({ const TopRight = () => Window({
name: 'cornertr', name: 'cornertr',
layer: 'overlay', layer: 'overlay',
@ -19,6 +20,7 @@ const TopRight = () => Window({
visible: true, visible: true,
child: RoundedCorner('topright'), child: RoundedCorner('topright'),
}); });
const BottomLeft = () => Window({ const BottomLeft = () => Window({
name: 'cornerbl', name: 'cornerbl',
layer: 'overlay', layer: 'overlay',
@ -27,6 +29,7 @@ const BottomLeft = () => Window({
visible: true, visible: true,
child: RoundedCorner('bottomleft'), child: RoundedCorner('bottomleft'),
}); });
const BottomRight = () => Window({ const BottomRight = () => Window({
name: 'cornerbr', name: 'cornerbr',
layer: 'overlay', layer: 'overlay',

View file

@ -1,4 +1,5 @@
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; const Lang = imports.lang;
@ -19,25 +20,27 @@ export default (
css: ` css: `
border-radius: 18px; border-radius: 18px;
border-width: 0.068rem; border-width: 0.068rem;
` + css, ${css}
setup: widget => { `,
const r = widget.get_style_context() setup: (widget) => {
let r = widget.get_style_context()
.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);
widget.connect('draw', Lang.bind(widget, (widget, 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);
const r = widget.get_style_context() r = widget.get_style_context()
.get_property('border-radius', Gtk.StateFlags.NORMAL); .get_property('border-radius', Gtk.StateFlags.NORMAL);
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 // Ur 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;
widget.set_size_request(r, r); widget.set_size_request(r, r);
switch (place) { switch (place) {

View file

@ -25,7 +25,7 @@ const Time = () => Box({
Label({ Label({
className: 'content', className: 'content',
label: 'hour', label: 'hour',
connections: [[1000, self => { connections: [[1000, (self) => {
self.label = DateTime.new_now_local().format('%H'); self.label = DateTime.new_now_local().format('%H');
}]], }]],
}), }),
@ -35,7 +35,7 @@ const Time = () => Box({
Label({ Label({
className: 'content', className: 'content',
label: 'minute', label: 'minute',
connections: [[1000, self => { connections: [[1000, (self) => {
self.label = DateTime.new_now_local().format('%M'); self.label = DateTime.new_now_local().format('%M');
}]], }]],
}), }),
@ -49,8 +49,9 @@ const Time = () => Box({
child: Label({ child: Label({
css: 'font-size: 20px', css: 'font-size: 20px',
label: 'complete date', label: 'complete date',
connections: [[1000, self => { connections: [[1000, (self) => {
var time = DateTime.new_now_local(); const time = DateTime.new_now_local();
self.label = time.format('%A, %B ') + self.label = time.format('%A, %B ') +
time.get_day_of_month() + time.get_day_of_month() +
time.format(', %Y'); time.format(', %Y');
@ -70,9 +71,12 @@ const CalendarWidget = () => Box({
}), }),
}); });
const TOP_MARGIN = 6;
const RIGHT_MARGIN = 182;
export default () => PopupWindow({ export default () => PopupWindow({
anchor: ['top', 'right'], anchor: ['top', 'right'],
margins: [6, 182, 0, 0], margins: [TOP_MARGIN, RIGHT_MARGIN, 0, 0],
name: 'calendar', name: 'calendar',
child: Box({ child: Box({
className: 'date', className: 'date',

View file

@ -5,7 +5,9 @@ import Gtk from 'gi://Gtk';
const MAX_OFFSET = 200; const MAX_OFFSET = 200;
const OFFSCREEN = 500; const OFFSCREEN = 500;
const TRANSITION = 'transition: margin 0.5s ease, opacity 3s ease;'; const ANIM_DURATION = 500;
const TRANSITION = `transition: margin ${ANIM_DURATION}ms ease,
opacity 3s ease;`;
export default ({ export default ({
@ -25,16 +27,18 @@ export default ({
// Have empty PlayerBox to define the size of the widget // Have empty PlayerBox to define the size of the widget
child: Box({ className: 'player' }), child: Box({ className: 'player' }),
connections: [ connections: [
...connections, ...connections,
[gesture, overlay => { [gesture, (overlay) => {
// Don't allow gesture when only one player // Don't allow gesture when only one player
if (overlay.list().length <= 1) if (overlay.list().length <= 1) {
return; return;
}
overlay._dragging = true; overlay._dragging = true;
var offset = gesture.get_offset()[1]; let offset = gesture.get_offset()[1];
const playerBox = overlay.list().at(-1); const playerBox = overlay.list().at(-1);
@ -58,10 +62,11 @@ export default ({
} }
}, 'drag-update'], }, 'drag-update'],
[gesture, overlay => { [gesture, (overlay) => {
// Don't allow gesture when only one player // Don't allow gesture when only one player
if (overlay.list().length <= 1) if (overlay.list().length <= 1) {
return; return;
}
overlay._dragging = false; overlay._dragging = false;
const offset = gesture.get_offset()[1]; const offset = gesture.get_offset()[1];
@ -92,7 +97,7 @@ export default ({
opacity: 0; ${playerBox._bgStyle}`); opacity: 0; ${playerBox._bgStyle}`);
} }
timeout(500, () => { timeout(ANIM_DURATION, () => {
// Put the player in the back after anim // Put the player in the back after anim
overlay.reorder_overlay(playerBox, 0); overlay.reorder_overlay(playerBox, 0);
// Recenter player // Recenter player
@ -108,7 +113,9 @@ export default ({
}, 'drag-end'], }, 'drag-end'],
], ],
})); }));
widget.child.list = () => widget.child.get_children().filter(ch => ch._bgStyle !== undefined);
widget.child.list = () => widget.child.get_children()
.filter((ch) => ch._bgStyle);
return widget; return widget;
}; };

View file

@ -1,4 +1,5 @@
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js'; import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import { Button, Icon, Label, Stack, Slider, CenterBox, Box } from 'resource:///com/github/Aylur/ags/widget.js'; import { Button, Icon, Label, Stack, Slider, CenterBox, Box } from 'resource:///com/github/Aylur/ags/widget.js';
import { execAsync, lookUpIcon, readFileAsync } from 'resource:///com/github/Aylur/ags/utils.js'; import { execAsync, lookUpIcon, readFileAsync } from 'resource:///com/github/Aylur/ags/utils.js';
@ -29,16 +30,18 @@ const icons = {
export const CoverArt = (player, props) => CenterBox({ export const CoverArt = (player, props) => CenterBox({
...props, ...props,
vertical: true, vertical: true,
properties: [['bgStyle', '']], properties: [['bgStyle', '']],
setup: self => {
setup: (self) => {
// Give temp cover art // Give temp cover art
readFileAsync(player.coverPath).catch(() => { readFileAsync(player.coverPath).catch(() => {
if (!player.colors.value && !player.trackCoverUrl) { if (!player.colors.value && !player.trackCoverUrl) {
player.colors.value = { player.colors.value = {
'imageAccent': '#6b4fa2', imageAccent: '#6b4fa2',
'buttonAccent': '#ecdcff', buttonAccent: '#ecdcff',
'buttonText': '#25005a', buttonText: '#25005a',
'hoverAccent': '#d4baff', hoverAccent: '#d4baff',
}; };
self._bgStyle = ` self._bgStyle = `
@ -53,12 +56,14 @@ export const CoverArt = (player, props) => CenterBox({
} }
}); });
}, },
connections: [[player, self => {
connections: [[player, (self) => {
execAsync(['bash', '-c', `[[ -f "${player.coverPath}" ]] && execAsync(['bash', '-c', `[[ -f "${player.coverPath}" ]] &&
coloryou "${player.coverPath}" | grep -v Warning`]) coloryou "${player.coverPath}" | grep -v Warning`])
.then(out => { .then((out) => {
if (!Mpris.players.find(p => player === p)) if (!Mpris.players.find((p) => player === p)) {
return; return;
}
player.colors.value = JSON.parse(out); player.colors.value = JSON.parse(out);
@ -71,11 +76,13 @@ export const CoverArt = (player, props) => CenterBox({
background-position: center; background-position: center;
`; `;
if (!self.get_parent()._dragging) if (!self.get_parent()._dragging) {
self.setCss(self._bgStyle); self.setCss(self._bgStyle);
}).catch(err => { }
if (err !== '') }).catch((err) => {
if (err !== '') {
print(err); print(err);
}
}); });
}]], }]],
}); });
@ -87,6 +94,7 @@ export const TitleLabel = (player, props) => Label({
truncate: 'end', truncate: 'end',
justification: 'left', justification: 'left',
className: 'title', className: 'title',
binds: [['label', player, 'track-title']], binds: [['label', player, 'track-title']],
}); });
@ -97,7 +105,8 @@ export const ArtistLabel = (player, props) => Label({
truncate: 'end', truncate: 'end',
justification: 'left', justification: 'left',
className: 'artist', className: 'artist',
binds: [['label', player, 'track-artists', a => a.join(', ') || '']],
binds: [['label', player, 'track-artists', (a) => a.join(', ') || '']],
}); });
export const PlayerIcon = (player, { symbolic = true, ...props } = {}) => { export const PlayerIcon = (player, { symbolic = true, ...props } = {}) => {
@ -107,28 +116,34 @@ export const PlayerIcon = (player, { symbolic = true, ...props } = {}) => {
className: 'player-icon', className: 'player-icon',
size: 32, size: 32,
tooltipText: player.identity || '', tooltipText: player.identity || '',
connections: [[player, self => {
connections: [[player, (self) => {
const name = `${player.entry}${symbolic ? '-symbolic' : ''}`; const name = `${player.entry}${symbolic ? '-symbolic' : ''}`;
lookUpIcon(name) ? self.icon = name
: self.icon = icons.mpris.fallback; lookUpIcon(name) ?
self.icon = name :
self.icon = icons.mpris.fallback;
}]], }]],
}); });
// Multiple player indicators // Multiple player indicators
return Box({ return Box({
properties: [['overlay']], properties: [['overlay']],
connections: [[Mpris, self => { connections: [[Mpris, (self) => {
if (!self._overlay) if (!self._overlay) {
self._overlay = self.get_parent().get_parent().get_parent(); self._overlay = self.get_parent().get_parent().get_parent();
}
const overlays = self._overlay.list(); const overlays = self._overlay.list();
const player = overlays.find(overlay => { const playerWidget = overlays.find((overlay) => {
return overlay === self.get_parent().get_parent(); return overlay === self.get_parent().get_parent();
}); });
const index = overlays.indexOf(player); const index = overlays.indexOf(playerWidget);
const children = []; const children = [];
for (let i = 0; i < overlays.length; ++i) { for (let i = 0; i < overlays.length; ++i) {
if (i === index) { if (i === index) {
children.push(Separator(2)); children.push(Separator(2));
@ -152,22 +167,27 @@ export const PositionSlider = (player, props) => Slider({
vpack: 'center', vpack: 'center',
hexpand: true, hexpand: true,
drawValue: false, drawValue: false,
onChange: ({ value }) => { onChange: ({ value }) => {
player.position = player.length * value; player.position = player.length * value;
}, },
properties: [['update', slider => {
properties: [['update', (slider) => {
if (!slider.dragging) { if (!slider.dragging) {
slider.visible = player.length > 0; slider.visible = player.length > 0;
if (player.length > 0) if (player.length > 0) {
slider.value = player.position / player.length; slider.value = player.position / player.length;
} }
}
}]], }]],
connections: [ connections: [
[player, s => s._update(s), 'position'], [1000, (s) => s._update(s)],
[1000, s => s._update(s)], [player, (s) => s._update(s), 'position'],
[player.colors, s => { [player.colors, (s) => {
const c = player.colors.value;
if (player.colors.value) { if (player.colors.value) {
const c = player.colors.value;
s.setCss(` s.setCss(`
highlight { background-color: ${c.buttonAccent}; } highlight { background-color: ${c.buttonAccent}; }
slider { background-color: ${c.buttonAccent}; } slider { background-color: ${c.buttonAccent}; }
@ -177,21 +197,27 @@ export const PositionSlider = (player, props) => Slider({
} }
}], }],
['button-press-event', s => { s.cursor = 'grabbing'; }], ['button-press-event', (s) => {
['button-release-event', s => { s.cursor = 'pointer'; }], s.cursor = 'grabbing';
}],
['button-release-event', (s) => {
s.cursor = 'pointer';
}],
], ],
}); });
const PlayerButton = ({ player, items, onClick, prop }) => EventBox({ const PlayerButton = ({ player, items, onClick, prop }) => EventBox({
child: Button({ child: Button({
child: Stack({ items }),
onPrimaryClickRelease: () => player[onClick](),
properties: [['hovered', false]], properties: [['hovered', false]],
onHover: self => { child: Stack({ items }),
onPrimaryClickRelease: () => player[onClick](),
onHover: (self) => {
self._hovered = true; self._hovered = true;
if (prop == 'playBackStatus') { if (prop === 'playBackStatus') {
items.forEach(item => { items.forEach((item) => {
item[1].setCss(` item[1].setCss(`
background-color: ${player.colors.value.hoverAccent}; background-color: ${player.colors.value.hoverAccent};
color: ${player.colors.value.buttonText}; color: ${player.colors.value.buttonText};
@ -203,10 +229,11 @@ const PlayerButton = ({ player, items, onClick, prop }) => EventBox({
}); });
} }
}, },
onHoverLost: self => {
onHoverLost: (self) => {
self._hovered = false; self._hovered = false;
if (prop == 'playBackStatus') { if (prop === 'playBackStatus') {
items.forEach(item => { items.forEach((item) => {
item[1].setCss(` item[1].setCss(`
background-color: ${player.colors.value.buttonAccent}; background-color: ${player.colors.value.buttonAccent};
color: ${player.colors.value.buttonText}; color: ${player.colors.value.buttonText};
@ -216,22 +243,26 @@ const PlayerButton = ({ player, items, onClick, prop }) => EventBox({
}); });
} }
}, },
connections: [ connections: [
[player, button => { [player, (button) => {
button.child.shown = `${player[prop]}`; button.child.shown = `${player[prop]}`;
}], }],
[player.colors, button => { [player.colors, (button) => {
if (!Mpris.players.find(p => player === p)) if (!Mpris.players.find((p) => player === p)) {
return; return;
}
if (player.colors.value) { if (player.colors.value) {
if (prop == 'playBackStatus') { const c = player.colors.value;
if (prop === 'playBackStatus') {
if (button._hovered) { if (button._hovered) {
items.forEach(item => { items.forEach((item) => {
item[1].setCss(` item[1].setCss(`
background-color: ${player.colors.value.hoverAccent}; background-color: ${c.hoverAccent};
color: ${player.colors.value.buttonText}; color: ${c.buttonText};
min-height: 40px; min-height: 40px;
min-width: 36px; min-width: 36px;
margin-bottom: 1px; margin-bottom: 1px;
@ -240,10 +271,10 @@ const PlayerButton = ({ player, items, onClick, prop }) => EventBox({
}); });
} }
else { else {
items.forEach(item => { items.forEach((item) => {
item[1].setCss(` item[1].setCss(`
background-color: ${player.colors.value.buttonAccent}; background-color: ${c.buttonAccent};
color: ${player.colors.value.buttonText}; color: ${c.buttonText};
min-height: 42px; min-height: 42px;
min-width: 38px;`); min-width: 38px;`);
}); });
@ -251,8 +282,8 @@ const PlayerButton = ({ player, items, onClick, prop }) => EventBox({
} }
else { else {
button.setCss(` button.setCss(`
* { color: ${player.colors.value.buttonAccent}; } * { color: ${c.buttonAccent}; }
*:hover { color: ${player.colors.value.hoverAccent}; } *:hover { color: ${c.hoverAccent}; }
`); `);
} }
} }
@ -261,7 +292,7 @@ const PlayerButton = ({ player, items, onClick, prop }) => EventBox({
}), }),
}); });
export const ShuffleButton = player => PlayerButton({ export const ShuffleButton = (player) => PlayerButton({
player, player,
items: [ items: [
['true', Label({ ['true', Label({
@ -277,7 +308,7 @@ export const ShuffleButton = player => PlayerButton({
prop: 'shuffleStatus', prop: 'shuffleStatus',
}); });
export const LoopButton = player => PlayerButton({ export const LoopButton = (player) => PlayerButton({
player, player,
items: [ items: [
['None', Label({ ['None', Label({
@ -297,7 +328,7 @@ export const LoopButton = player => PlayerButton({
prop: 'loopStatus', prop: 'loopStatus',
}); });
export const PlayPauseButton = player => PlayerButton({ export const PlayPauseButton = (player) => PlayerButton({
player, player,
items: [ items: [
['Playing', Label({ ['Playing', Label({
@ -317,7 +348,7 @@ export const PlayPauseButton = player => PlayerButton({
prop: 'playBackStatus', prop: 'playBackStatus',
}); });
export const PreviousButton = player => PlayerButton({ export const PreviousButton = (player) => PlayerButton({
player, player,
items: [ items: [
['true', Label({ ['true', Label({
@ -333,7 +364,7 @@ export const PreviousButton = player => PlayerButton({
prop: 'canGoPrev', prop: 'canGoPrev',
}); });
export const NextButton = player => PlayerButton({ export const NextButton = (player) => PlayerButton({
player, player,
items: [ items: [
['true', Label({ ['true', Label({

View file

@ -1,5 +1,6 @@
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js'; import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js'; import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import { Box, CenterBox } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, CenterBox } from 'resource:///com/github/Aylur/ags/widget.js';
import * as mpris from './mpris.js'; import * as mpris from './mpris.js';
@ -9,10 +10,11 @@ import Separator from '../misc/separator.js';
const FAVE_PLAYER = 'org.mpris.MediaPlayer2.spotify'; const FAVE_PLAYER = 'org.mpris.MediaPlayer2.spotify';
const Top = player => Box({ const Top = (player) => Box({
className: 'top', className: 'top',
hpack: 'start', hpack: 'start',
vpack: 'start', vpack: 'start',
children: [ children: [
mpris.PlayerIcon(player, { mpris.PlayerIcon(player, {
symbolic: false, symbolic: false,
@ -20,12 +22,13 @@ const Top = player => Box({
], ],
}); });
const Center = player => Box({ const Center = (player) => Box({
className: 'center', className: 'center',
children: [
children: [
CenterBox({ CenterBox({
vertical: true, vertical: true,
children: [ children: [
Box({ Box({
className: 'metadata', className: 'metadata',
@ -33,6 +36,7 @@ const Center = player => Box({
hpack: 'start', hpack: 'start',
vpack: 'center', vpack: 'center',
hexpand: true, hexpand: true,
children: [ children: [
mpris.TitleLabel(player), mpris.TitleLabel(player),
mpris.ArtistLabel(player), mpris.ArtistLabel(player),
@ -45,6 +49,7 @@ const Center = player => Box({
CenterBox({ CenterBox({
vertical: true, vertical: true,
children: [ children: [
null, null,
mpris.PlayPauseButton(player), mpris.PlayPauseButton(player),
@ -55,31 +60,35 @@ const Center = player => Box({
], ],
}); });
const Bottom = player => Box({ const SPACING = 8;
const Bottom = (player) => Box({
className: 'bottom', className: 'bottom',
children: [ children: [
mpris.PreviousButton(player, { mpris.PreviousButton(player, {
vpack: 'end', vpack: 'end',
hpack: 'start', hpack: 'start',
}), }),
Separator(8), Separator(SPACING),
mpris.PositionSlider(player), mpris.PositionSlider(player),
Separator(8), Separator(SPACING),
mpris.NextButton(player), mpris.NextButton(player),
Separator(8), Separator(SPACING),
mpris.ShuffleButton(player), mpris.ShuffleButton(player),
Separator(8), Separator(SPACING),
mpris.LoopButton(player), mpris.LoopButton(player),
], ],
}); });
const PlayerBox = player => mpris.CoverArt(player, { const PlayerBox = (player) => mpris.CoverArt(player, {
className: `player ${player.name}`, className: `player ${player.name}`,
hexpand: true, hexpand: true,
children: [ children: [
Top(player), Top(player),
Center(player), Center(player),
@ -89,27 +98,36 @@ const PlayerBox = player => mpris.CoverArt(player, {
export default () => Box({ export default () => Box({
className: 'media', className: 'media',
child: PlayerGesture({ child: PlayerGesture({
properties: [ properties: [
['players', new Map()], ['players', new Map()],
['setup', false], ['setup', false],
], ],
connections: [ connections: [
[Mpris, (overlay, busName) => { [Mpris, (overlay, busName) => {
if (overlay._players.has(busName)) if (overlay._players.has(busName)) {
return;
// Sometimes the signal doesn't give the busName
if (!busName) {
const player = Mpris.players.find(p => !overlay._players.has(p.busName));
if (player)
busName = player.busName;
else
return; return;
} }
// Sometimes the signal doesn't give the busName
if (!busName) {
const player = Mpris.players.find((p) => {
return !overlay._players.has(p.busName);
});
if (player) {
busName = player.busName;
}
else {
return;
}
}
// Get the one on top so it stays there // Get the one on top so it stays there
var previousFirst = overlay.get_children().at(-1); let previousFirst = overlay.get_children().at(-1);
for (const [key, value] of overlay._players.entries()) { for (const [key, value] of overlay._players.entries()) {
if (value === previousFirst) { if (value === previousFirst) {
previousFirst = key; previousFirst = key;
@ -118,12 +136,14 @@ export default () => Box({
} }
const player = Mpris.getPlayer(busName); const player = Mpris.getPlayer(busName);
player.colors = Variable(); player.colors = Variable();
overlay._players.set(busName, PlayerBox(player)); overlay._players.set(busName, PlayerBox(player));
const result = []; const result = [];
overlay._players.forEach(widget => {
overlay._players.forEach((widget) => {
result.push(widget); result.push(widget);
}); });
@ -131,21 +151,29 @@ export default () => Box({
// Select favorite player at startup // Select favorite player at startup
if (!overlay._setup && overlay._players.has(FAVE_PLAYER)) { if (!overlay._setup && overlay._players.has(FAVE_PLAYER)) {
overlay.reorder_overlay(overlay._players.get(FAVE_PLAYER), -1); overlay.reorder_overlay(
overlay._players.get(FAVE_PLAYER),
-1,
);
overlay._setup = true; overlay._setup = true;
} }
else if (overlay._players.get(previousFirst)) { else if (overlay._players.get(previousFirst)) {
overlay.reorder_overlay(overlay._players.get(previousFirst), -1); overlay.reorder_overlay(
overlay._players.get(previousFirst),
-1,
);
} }
}, 'player-added'], }, 'player-added'],
[Mpris, (overlay, busName) => { [Mpris, (overlay, busName) => {
if (!busName || !overlay._players.has(busName)) if (!busName || !overlay._players.has(busName)) {
return; return;
}
// Get the one on top so it stays there // Get the one on top so it stays there
var previousFirst = overlay.get_children().at(-1); let previousFirst = overlay.get_children().at(-1);
for (const [key, value] of overlay._players.entries()) { for (const [key, value] of overlay._players.entries()) {
if (value === previousFirst) { if (value === previousFirst) {
previousFirst = key; previousFirst = key;
@ -156,14 +184,19 @@ export default () => Box({
overlay._players.delete(busName); overlay._players.delete(busName);
const result = []; const result = [];
overlay._players.forEach(widget => {
overlay._players.forEach((widget) => {
result.push(widget); result.push(widget);
}); });
overlay.overlays = result; overlay.overlays = result;
if (overlay._players.has(previousFirst)) if (overlay._players.has(previousFirst)) {
overlay.reorder_overlay(overlay._players.get(previousFirst), -1); overlay.reorder_overlay(
overlay._players.get(previousFirst),
-1,
);
}
}, 'player-closed'], }, 'player-closed'],
], ],
}), }),

View file

@ -19,8 +19,9 @@ const micIcons = {
export const SpeakerIcon = Variable(); export const SpeakerIcon = Variable();
Audio.connect('speaker-changed', () => { Audio.connect('speaker-changed', () => {
if (!Audio.speaker) if (!Audio.speaker) {
return; return;
}
if (Audio.speaker.stream.isMuted) { if (Audio.speaker.stream.isMuted) {
SpeakerIcon.value = speakerIcons[0]; SpeakerIcon.value = speakerIcons[0];
@ -29,16 +30,18 @@ Audio.connect('speaker-changed', () => {
const vol = Audio.speaker.volume * 100; const vol = Audio.speaker.volume * 100;
for (const threshold of [-1, 0, 33, 66, 100]) { for (const threshold of [-1, 0, 33, 66, 100]) {
if (vol > threshold + 1) if (vol > threshold + 1) {
SpeakerIcon.value = speakerIcons[threshold + 1]; SpeakerIcon.value = speakerIcons[threshold + 1];
} }
} }
}
}); });
export const MicIcon = Variable(); export const MicIcon = Variable();
Audio.connect('microphone-changed', () => { Audio.connect('microphone-changed', () => {
if (!Audio.microphone) if (!Audio.microphone) {
return; return;
}
if (Audio.microphone.stream.isMuted) { if (Audio.microphone.stream.isMuted) {
MicIcon.value = micIcons[0]; MicIcon.value = micIcons[0];
@ -47,8 +50,9 @@ Audio.connect('microphone-changed', () => {
const vol = Audio.microphone.volume * 100; const vol = Audio.microphone.volume * 100;
for (const threshold of [-1, 0, 33, 66]) { for (const threshold of [-1, 0, 33, 66]) {
if (vol > threshold + 1) if (vol > threshold + 1) {
MicIcon.value = micIcons[threshold + 1]; MicIcon.value = micIcons[threshold + 1];
} }
} }
}
}); });

View file

@ -3,8 +3,8 @@ import App from 'resource:///com/github/Aylur/ags/app.js';
export default () => { export default () => {
Array.from(App.windows) Array.from(App.windows)
.filter(w => w[1].closeOnUnfocus && w[1].closeOnUnfocus !== 'stay') .filter((w) => w[1].closeOnUnfocus && w[1].closeOnUnfocus !== 'stay')
.forEach(w => { .forEach((w) => {
App.closeWindow(w[0]); App.closeWindow(w[0]);
}); });
}; };

View file

@ -1,4 +1,5 @@
import Variable from 'resource:///com/github/Aylur/ags/variable.js'; import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import { Button, EventBox } from 'resource:///com/github/Aylur/ags/widget.js'; import { Button, EventBox } from 'resource:///com/github/Aylur/ags/widget.js';
import Gtk from 'gi://Gtk'; import Gtk from 'gi://Gtk';
@ -7,7 +8,7 @@ import Gtk from 'gi://Gtk';
// TODO: wrap in another EventBox for disabled cursor // TODO: wrap in another EventBox for disabled cursor
export default ({ export default ({
isButton = false, isButton = false,
onPrimaryClickRelease = () => {}, onPrimaryClickRelease = () => { /**/ },
...props ...props
}) => { }) => {
// Make this variable to know if the function should // Make this variable to know if the function should
@ -17,12 +18,13 @@ export default ({
const properties = { const properties = {
...props, ...props,
cursor: 'pointer', cursor: 'pointer',
onPrimaryClickRelease: self => { onPrimaryClickRelease: (self) => {
// Every click, do a one shot connect to // Every click, do a one shot connect to
// CanRun to wait for location of click // CanRun to wait for location of click
const id = CanRun.connect('changed', () => { const id = CanRun.connect('changed', () => {
if (CanRun.value) if (CanRun.value) {
onPrimaryClickRelease(self); onPrimaryClickRelease(self);
}
CanRun.disconnect(id); CanRun.disconnect(id);
}); });
@ -30,10 +32,13 @@ export default ({
}; };
let widget; let widget;
if (!isButton)
widget = EventBox(properties); if (isButton) {
else
widget = Button(properties); widget = Button(properties);
}
else {
widget = EventBox(properties);
}
const gesture = Gtk.GestureLongPress.new(widget); const gesture = Gtk.GestureLongPress.new(widget);
@ -42,8 +47,9 @@ export default ({
const x = pointer[1]; const x = pointer[1];
const y = pointer[2]; const y = pointer[2];
if ((!x || !y) || x === 0 && y === 0) if ((!x || !y) || (x === 0 && y === 0)) {
return; return;
}
CanRun.value = !( CanRun.value = !(
x > widget.get_allocated_width() || x > widget.get_allocated_width() ||

View file

@ -1,4 +1,5 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import { Revealer, Box, Window } from 'resource:///com/github/Aylur/ags/widget.js'; import { Revealer, Box, Window } from 'resource:///com/github/Aylur/ags/widget.js';
@ -9,8 +10,8 @@ export default ({
// Optional: execute a function whenever // Optional: execute a function whenever
// the window pops up or goes away // the window pops up or goes away
onOpen = () => {}, onOpen = () => { /**/ },
onClose = () => {}, onClose = () => { /**/ },
// Window props // Window props
name, name,
@ -29,8 +30,9 @@ export default ({
// Add way to make window open on startup // Add way to make window open on startup
setup: () => { setup: () => {
const id = App.connect('config-parsed', () => { const id = App.connect('config-parsed', () => {
if (visible) if (visible) {
App.openWindow(name); App.openWindow(name);
}
App.disconnect(id); App.disconnect(id);
}); });
}, },
@ -38,32 +40,39 @@ export default ({
// Wrapping the revealer inside a box is needed // Wrapping the revealer inside a box is needed
// to allocate some space for it even when not revealed // to allocate some space for it even when not revealed
child: Box({ child: Box({
css: `min-height:1px; css: `
min-height:1px;
min-width:1px; min-width:1px;
padding: 1px;`, padding: 1px;
`,
child: Revealer({ child: Revealer({
transition, transition,
transitionDuration, transitionDuration,
connections: [[App, (rev, currentName, visible) => {
if (currentName === name) {
rev.revealChild = visible;
if (visible) connections: [[App, (rev, currentName, isOpen) => {
if (currentName === name) {
rev.revealChild = isOpen;
if (isOpen) {
onOpen(child); onOpen(child);
else }
else {
onClose(child); onClose(child);
} }
}
}]], }]],
child: child || Box(), child: child || Box(),
}), }),
}), }),
}); });
// Make getting the original child passed in // Make getting the original child passed in this
// this function easier when making more code // function easier when making more code for the widget
// for the widget
window.getChild = () => window.child.children[0].child; window.getChild = () => window.child.children[0].child;
window.setChild = newChild => window.child.children[0].child = newChild; window.setChild = (newChild) => {
window.child.children[0].child = newChild;
};
// This is for my custom pointers.js // This is for my custom pointers.js
window.closeOnUnfocus = closeOnUnfocus; window.closeOnUnfocus = closeOnUnfocus;

View file

@ -1,17 +1,13 @@
import { Box } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box } from 'resource:///com/github/Aylur/ags/widget.js';
export default (size, { vertical = false, css = '', ...props } = {}) => { export default (size, {
if (vertical) { vertical = false,
css = '',
...props
} = {}) => {
return Box({ return Box({
css: `min-height: ${size}px; ${css}`, css: `${vertical ? 'min-height' : 'min-width'}: ${size}px; ${css}`,
...props, ...props,
}); });
}
else {
return Box({
css: `min-width: ${size}px; ${css}`,
...props,
});
}
}; };

View file

@ -2,54 +2,62 @@ import Applications from 'resource:///com/github/Aylur/ags/service/applications.
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js'; import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js'; import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import { Box, Icon, Label, Button } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Icon, Label, Button } from 'resource:///com/github/Aylur/ags/widget.js';
import { lookUpIcon } from 'resource:///com/github/Aylur/ags/utils.js'; import { lookUpIcon } from 'resource:///com/github/Aylur/ags/utils.js';
import GLib from 'gi://GLib'; import GLib from 'gi://GLib';
const setTime = time => { const setTime = (time) => {
return GLib.DateTime return GLib.DateTime
.new_from_unix_local(time) .new_from_unix_local(time)
.format('%H:%M'); .format('%H:%M');
}; };
const getDragState = box => box.get_parent().get_parent() const getDragState = (box) => box.get_parent().get_parent()
.get_parent().get_parent().get_parent()._dragging; .get_parent().get_parent().get_parent()._dragging;
import Gesture from './gesture.js'; import Gesture from './gesture.js';
import EventBox from '../misc/cursorbox.js'; import EventBox from '../misc/cursorbox.js';
const NotificationIcon = notif => { const NotificationIcon = (notif) => {
let iconCmd = () => {}; let iconCmd = () => { /**/ };
if (Applications.query(notif.appEntry).length > 0) { if (Applications.query(notif.appEntry).length > 0) {
const app = Applications.query(notif.appEntry)[0]; const app = Applications.query(notif.appEntry)[0];
let wmClass = app.app.get_string('StartupWMClass'); let wmClass = app.app.get_string('StartupWMClass');
if (app.app.get_filename().includes('discord'))
if (app.app.get_filename().includes('discord')) {
wmClass = 'discord'; wmClass = 'discord';
}
if (wmClass != null) { if (wmClass != null) {
iconCmd = box => { iconCmd = (box) => {
if (!getDragState(box)) { if (!getDragState(box)) {
if (wmClass === 'thunderbird') { if (wmClass === 'thunderbird') {
Hyprland.sendMessage('dispatch togglespecialworkspace thunder'); Hyprland.sendMessage('dispatch ' +
'togglespecialworkspace thunder');
} }
else if (wmClass === 'Spotify') { else if (wmClass === 'Spotify') {
Hyprland.sendMessage('dispatch togglespecialworkspace spot'); Hyprland.sendMessage('dispatch ' +
'togglespecialworkspace spot');
} }
else { else {
Hyprland.sendMessage('j/clients').then(out => { Hyprland.sendMessage('j/clients').then((out) => {
out = JSON.parse(out); out = JSON.parse(out);
const classes = []; const classes = [];
for (const key of out) { for (const key of out) {
if (key.class) if (key.class) {
classes.push(key.class); classes.push(key.class);
} }
}
if (classes.includes(wmClass)) { if (classes.includes(wmClass)) {
Hyprland.sendMessage(`dispatch focuswindow ^(${wmClass})`); Hyprland.sendMessage('dispatch ' +
`focuswindow ^(${wmClass})`);
} }
else { else {
Hyprland.sendMessage('[[BATCH]] ' + Hyprland.sendMessage('[[BATCH]] ' +
@ -69,6 +77,7 @@ const NotificationIcon = notif => {
if (notif.image) { if (notif.image) {
return EventBox({ return EventBox({
onPrimaryClickRelease: iconCmd, onPrimaryClickRelease: iconCmd,
child: Box({ child: Box({
vpack: 'start', vpack: 'start',
hexpand: false, hexpand: false,
@ -86,22 +95,28 @@ const NotificationIcon = notif => {
} }
let icon = 'dialog-information-symbolic'; let icon = 'dialog-information-symbolic';
if (lookUpIcon(notif.appIcon))
if (lookUpIcon(notif.appIcon)) {
icon = notif.appIcon; icon = notif.appIcon;
}
if (lookUpIcon(notif.appEntry)) if (lookUpIcon(notif.appEntry)) {
icon = notif.appEntry; icon = notif.appEntry;
}
return EventBox({ return EventBox({
onPrimaryClickRelease: iconCmd, onPrimaryClickRelease: iconCmd,
child: Box({ child: Box({
vpack: 'start', vpack: 'start',
hexpand: false, hexpand: false,
className: 'icon', className: 'icon',
css: `min-width: 78px; css: `
min-height: 78px;`, min-width: 78px;
min-height: 78px;
`,
children: [Icon({ children: [Icon({
icon, size: 58, icon, size: 58,
hpack: 'center', hpack: 'center',
@ -120,22 +135,24 @@ export const HasNotifs = Variable(false);
export const Notification = ({ export const Notification = ({
notif, notif,
slideIn = 'Left', slideIn = 'Left',
command = () => {}, command = () => { /**/ },
} = {}) => { } = {}) => {
if (!notif) if (!notif) {
return; return;
}
HasNotifs.value = Notifications.notifications.length > 0;
const BlockedApps = [ const BlockedApps = [
'Spotify', 'Spotify',
]; ];
if (BlockedApps.find(app => app == notif.appName)) { if (BlockedApps.find((app) => app === notif.appName)) {
notif.close(); notif.close();
return; return;
} }
HasNotifs.value = Notifications.notifications.length > 0;
// Init notif // Init notif
const notifWidget = Gesture({ const notifWidget = Gesture({
command, command,
@ -147,10 +164,12 @@ export const Notification = ({
notifWidget.child.add(Box({ notifWidget.child.add(Box({
className: `notification ${notif.urgency}`, className: `notification ${notif.urgency}`,
vexpand: false, vexpand: false,
// Notification // Notification
child: Box({ child: Box({
vertical: true, vertical: true,
children: [ children: [
// Content // Content
Box({ Box({
children: [ children: [
@ -159,9 +178,12 @@ export const Notification = ({
hexpand: true, hexpand: true,
vertical: true, vertical: true,
children: [ children: [
// Top of Content // Top of Content
Box({ Box({
children: [ children: [
// Title
Label({ Label({
className: 'title', className: 'title',
xalign: 0, xalign: 0,
@ -171,23 +193,31 @@ export const Notification = ({
truncate: 'end', truncate: 'end',
wrap: true, wrap: true,
label: notif.summary, label: notif.summary,
useMarkup: notif.summary.startsWith('<'), useMarkup: notif.summary
.startsWith('<'),
}), }),
// Time
Label({ Label({
className: 'time', className: 'time',
vpack: 'start', vpack: 'start',
label: setTime(notif.time), label: setTime(notif.time),
}), }),
// Close button
EventBox({ EventBox({
child: Button({ child: Button({
className: 'close-button', className: 'close-button',
vpack: 'start', vpack: 'start',
onClicked: () => notif.close(), onClicked: () => notif.close(),
child: Icon('window-close-symbolic'), child: Icon('window-close' +
'-symbolic'),
}), }),
}), }),
], ],
}), }),
// Description
Label({ Label({
className: 'description', className: 'description',
hexpand: true, hexpand: true,
@ -201,10 +231,11 @@ export const Notification = ({
}), }),
], ],
}), }),
// Actions // Actions
Box({ Box({
className: 'actions', className: 'actions',
children: notif.actions.map(action => Button({ children: notif.actions.map((action) => Button({
className: 'action-button', className: 'action-button',
onClicked: () => notif.invoke(action.id), onClicked: () => notif.invoke(action.id),
hexpand: true, hexpand: true,
@ -214,5 +245,6 @@ export const Notification = ({
], ],
}), }),
})); }));
return notifWidget; return notifWidget;
}; };

View file

@ -1,4 +1,5 @@
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js'; import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
import { Button, Label, Box, Icon, Scrollable, Revealer } from 'resource:///com/github/Aylur/ags/widget.js'; import { Button, Label, Box, Icon, Scrollable, Revealer } from 'resource:///com/github/Aylur/ags/widget.js';
import { Notification, HasNotifs } from './base.js'; import { Notification, HasNotifs } from './base.js';
@ -7,9 +8,7 @@ import EventBox from '../misc/cursorbox.js';
const addNotif = (box, notif) => { const addNotif = (box, notif) => {
if (!notif) if (notif) {
return;
const NewNotif = Notification({ const NewNotif = Notification({
notif, notif,
slideIn: 'Right', slideIn: 'Right',
@ -20,6 +19,7 @@ const addNotif = (box, notif) => {
box.pack_end(NewNotif, false, false, 0); box.pack_end(NewNotif, false, false, 0);
box.show_all(); box.show_all();
} }
}
}; };
const NotificationList = () => Box({ const NotificationList = () => Box({
@ -30,17 +30,23 @@ const NotificationList = () => Box({
connections: [ connections: [
[Notifications, (box, id) => { [Notifications, (box, id) => {
// Handle cached notifs // Handle cached notifs
if (box.children.length == 0) if (box.children.length === 0) {
Notifications.notifications.forEach(n => addNotif(box, n)); Notifications.notifications.forEach((n) => {
addNotif(box, n);
});
}
else if (id) else if (id) {
addNotif(box, Notifications.getNotification(id)); addNotif(box, Notifications.getNotification(id));
}
}, 'notified'], }, 'notified'],
[Notifications, (box, id) => { [Notifications, (box, id) => {
const notif = box.children.find(ch => ch._id === id); const notif = box.children.find((ch) => ch._id === id);
if (notif?.sensitive)
if (notif?.sensitive) {
notif.slideAway('Right'); notif.slideAway('Right');
}
}, 'closed'], }, 'closed'],
], ],
}); });
@ -54,9 +60,10 @@ const ClearButton = () => EventBox({
children: [ children: [
Label('Clear '), Label('Clear '),
Icon({ Icon({
connections: [[Notifications, self => { connections: [[Notifications, (self) => {
self.icon = Notifications.notifications.length > 0 self.icon = Notifications.notifications.length > 0 ?
? 'user-trash-full-symbolic' : 'user-trash-symbolic'; 'user-trash-full-symbolic' :
'user-trash-symbolic';
}]], }]],
}), }),
], ],
@ -78,7 +85,7 @@ const Header = () => Box({
const Placeholder = () => Revealer({ const Placeholder = () => Revealer({
transition: 'crossfade', transition: 'crossfade',
binds: [['revealChild', HasNotifs, 'value', value => !value]], binds: [['revealChild', HasNotifs, 'value', (value) => !value]],
child: Box({ child: Box({
className: 'placeholder', className: 'placeholder',
vertical: true, vertical: true,
@ -118,9 +125,12 @@ const NotificationCenterWidget = () => Box({
], ],
}); });
const TOP_MARGIN = 6;
const RIGHT_MARGIN = 60;
export default () => PopupWindow({ export default () => PopupWindow({
name: 'notification-center', name: 'notification-center',
anchor: ['top', 'right'], anchor: ['top', 'right'],
margins: [6, 60, 0, 0], margins: [TOP_MARGIN, RIGHT_MARGIN, 0, 0],
child: NotificationCenterWidget(), child: NotificationCenterWidget(),
}); });

View file

@ -1,4 +1,5 @@
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js'; import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
import { Box, EventBox } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, EventBox } 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';
@ -6,26 +7,30 @@ import { HasNotifs } from './base.js';
import Gtk from 'gi://Gtk'; import Gtk from 'gi://Gtk';
const MAX_OFFSET = 200;
const OFFSCREEN = 300;
const ANIM_DURATION = 500;
const SLIDE_MIN_THRESHOLD = 10;
export default ({ export default ({
id, id,
slideIn = 'Left', slideIn = 'Left',
maxOffset = 200, command = () => { /**/ },
startMargin = 0,
endMargin = 300,
command = () => {},
...props ...props
}) => { }) => {
const widget = EventBox({ const widget = EventBox({
...props, ...props,
cursor: 'grab', cursor: 'grab',
onHover: self => { onHover: (self) => {
if (!self._hovered) if (!self._hovered) {
self._hovered = true; self._hovered = true;
}
}, },
onHoverLost: self => { onHoverLost: (self) => {
if (self._hovered) if (self._hovered) {
self._hovered = false; self._hovered = false;
}
}, },
}); });
@ -40,33 +45,42 @@ export default ({
const TRANSITION = 'transition: margin 0.5s ease, opacity 0.5s ease;'; const TRANSITION = 'transition: margin 0.5s ease, opacity 0.5s ease;';
const SQUEEZED = 'margin-bottom: -70px; margin-top: -70px;'; const SQUEEZED = 'margin-bottom: -70px; margin-top: -70px;';
const MAX_LEFT = ` const MAX_LEFT = `
margin-left: -${Number(maxOffset + endMargin)}px; margin-left: -${Number(MAX_OFFSET + OFFSCREEN)}px;
margin-right: ${Number(maxOffset + endMargin)}px; margin-right: ${Number(MAX_OFFSET + OFFSCREEN)}px;
`; `;
const MAX_RIGHT = ` const MAX_RIGHT = `
margin-left: ${Number(maxOffset + endMargin)}px; margin-left: ${Number(MAX_OFFSET + OFFSCREEN)}px;
margin-right: -${Number(maxOffset + endMargin)}px; margin-right: -${Number(MAX_OFFSET + OFFSCREEN)}px;
`; `;
const slideLeft = `${TRANSITION} ${MAX_LEFT} margin-top: 0px; margin-bottom: 0px; opacity: 0;`; const slideLeft = `${TRANSITION} ${MAX_LEFT}
margin-top: 0px;
margin-bottom: 0px;
opacity: 0;`;
const squeezeLeft = `${TRANSITION} ${MAX_LEFT} ${SQUEEZED} opacity: 0;`; const squeezeLeft = `${TRANSITION} ${MAX_LEFT} ${SQUEEZED} opacity: 0;`;
const slideRight = `${TRANSITION} ${MAX_RIGHT} margin-top: 0px; margin-bottom: 0px; opacity: 0;`;
const slideRight = `${TRANSITION} ${MAX_RIGHT}
margin-top: 0px;
margin-bottom: 0px;
opacity: 0;`;
const squeezeRight = `${TRANSITION} ${MAX_RIGHT} ${SQUEEZED} opacity: 0;`; const squeezeRight = `${TRANSITION} ${MAX_RIGHT} ${SQUEEZED} opacity: 0;`;
const defaultStyle = `${TRANSITION} margin: unset; opacity: 1;`; const defaultStyle = `${TRANSITION} margin: unset; opacity: 1;`;
// Notif methods // Notif methods
widget.slideAway = side => { widget.slideAway = (side) => {
// Slide away // Slide away
widget.child.setCss(side === 'Left' ? slideLeft : slideRight); widget.child.setCss(side === 'Left' ? slideLeft : slideRight);
// Makie it uninteractable // Makie it uninteractable
widget.sensitive = false; widget.sensitive = false;
timeout(400, () => { timeout(ANIM_DURATION - 100, () => {
// Reduce height after sliding away // Reduce height after sliding away
widget.child?.setCss(side === 'Left' ? squeezeLeft : squeezeRight); widget.child?.setCss(side === 'Left' ? squeezeLeft : squeezeRight);
timeout(500, () => { timeout(ANIM_DURATION, () => {
// Kill notif and update HasNotifs after anim is done // Kill notif and update HasNotifs after anim is done
command(); command();
HasNotifs.value = Notifications.notifications.length > 0; HasNotifs.value = Notifications.notifications.length > 0;
@ -80,17 +94,20 @@ export default ({
connections: [ connections: [
// When dragging // When dragging
[gesture, self => { [gesture, (self) => {
var offset = gesture.get_offset()[1]; let offset = gesture.get_offset()[1];
if (offset === 0)
if (offset === 0) {
return; return;
}
// Slide right // Slide right
if (offset > 0) { if (offset > 0) {
self.setCss(` self.setCss(`
margin-top: 0px; margin-bottom: 0px; opacity: 1; transition: none; margin-top: 0px; margin-bottom: 0px;
margin-left: ${Number(offset + startMargin)}px; opacity: 1; transition: none;
margin-right: -${Number(offset + startMargin)}px; margin-left: ${offset}px;
margin-right: -${offset}px;
`); `);
} }
@ -98,45 +115,51 @@ export default ({
else { else {
offset = Math.abs(offset); offset = Math.abs(offset);
self.setCss(` self.setCss(`
margin-top: 0px; margin-bottom: 0px; opacity: 1; transition: none; margin-top: 0px; margin-bottom: 0px;
margin-right: ${Number(offset + startMargin)}px; opacity: 1; transition: none;
margin-left: -${Number(offset + startMargin)}px; margin-right: ${offset}px;
margin-left: -${offset}px;
`); `);
} }
// Put a threshold on if a click is actually dragging // Put a threshold on if a click is actually dragging
widget._dragging = Math.abs(offset) > 10; widget._dragging = Math.abs(offset) > SLIDE_MIN_THRESHOLD;
widget.cursor = 'grabbing'; widget.cursor = 'grabbing';
}, 'drag-update'], }, 'drag-update'],
// On drag end // On drag end
[gesture, self => { [gesture, (self) => {
// Make it slide in on init // Make it slide in on init
if (!widget.ready) { if (!widget.ready) {
// Reverse of slideAway, so it started at squeeze, then we go to slide // Reverse of slideAway, so it started at squeeze, then we go to slide
self.setCss(slideIn === 'Left' ? slideLeft : slideRight); self.setCss(slideIn === 'Left' ? slideLeft : slideRight);
timeout(500, () => { timeout(ANIM_DURATION, () => {
// Then we got to center // Then we go to center
self.setCss(defaultStyle); self.setCss(defaultStyle);
timeout(500, () => widget.ready = true); timeout(ANIM_DURATION, () => {
widget.ready = true;
}); });
});
return; return;
} }
const offset = gesture.get_offset()[1]; const offset = gesture.get_offset()[1];
// If crosses threshold after letting go, slide away // If crosses threshold after letting go, slide away
if (Math.abs(offset) > maxOffset) { if (Math.abs(offset) > MAX_OFFSET) {
if (offset > 0) if (offset > 0) {
widget.slideAway('Right'); widget.slideAway('Right');
else }
else {
widget.slideAway('Left'); widget.slideAway('Left');
} }
}
else { else {
self.setCss(defaultStyle); self.setCss(defaultStyle);
widget.cursor = 'grab', widget.cursor = 'grab';
widget._dragging = false; widget._dragging = false;
} }
}, 'drag-end'], }, 'drag-end'],

View file

@ -1,4 +1,5 @@
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js'; import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
import { Box } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box } from 'resource:///com/github/Aylur/ags/widget.js';
import { interval } from 'resource:///com/github/Aylur/ags/utils.js'; import { interval } from 'resource:///com/github/Aylur/ags/utils.js';
@ -7,10 +8,13 @@ import GLib from 'gi://GLib';
import { Notification } from './base.js'; import { Notification } from './base.js';
import PopupWindow from '../misc/popup.js'; import PopupWindow from '../misc/popup.js';
const DELAY = 2000;
const addPopup = (box, id) => { const addPopup = (box, id) => {
if (!id) if (!id) {
return; return;
}
const notif = Notifications.getNotification(id); const notif = Notifications.getNotification(id);
@ -20,32 +24,32 @@ const addPopup = (box, id) => {
}); });
if (NewNotif) { if (NewNotif) {
// use this instead of add to put it at the top // Use this instead of add to put it at the top
box.pack_end(NewNotif, false, false, 0); box.pack_end(NewNotif, false, false, 0);
box.show_all(); box.show_all();
} }
}; };
const handleDismiss = (box, id, force = false) => { const handleDismiss = (box, id, force = false) => {
const notif = box.children.find(ch => ch._id === id); const notif = box.children.find((ch) => ch._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
if (!notif._hovered || force) { if (!notif._hovered || force) {
notif.slideAway('Left'); notif.slideAway('Left');
return;
} }
// If notif is hovered, delay close // If notif is hovered, delay close
else if (notif._hovered) { else if (notif._hovered) {
notif.interval = interval(2000, () => { notif.interval = interval(DELAY, () => {
if (!notif._hovered && notif.interval) { if (!notif._hovered && notif.interval) {
notif.slideAway('Left'); notif.slideAway('Left');
GLib.source_remove(notif.interval); GLib.source_remove(notif.interval);
notif.interval = undefined; notif.interval = null;
return;
} }
}); });
} }
@ -57,12 +61,14 @@ export default () => PopupWindow({
visible: true, visible: true,
transition: 'none', transition: 'none',
closeOnUnfocus: 'stay', closeOnUnfocus: 'stay',
child: Box({ child: Box({
vertical: true, vertical: true,
connections: [ connections: [
[Notifications, (box, id) => addPopup(box, id), 'notified'], [Notifications, (s, id) => addPopup(s, id), 'notified'],
[Notifications, (box, id) => handleDismiss(box, id), 'dismissed'], [Notifications, (s, id) => handleDismiss(s, id), 'dismissed'],
[Notifications, (box, id) => handleDismiss(box, id, true), 'closed'], [Notifications, (s, id) => handleDismiss(s, id, true), 'closed'],
], ],
}), }),
}); });

View file

@ -1,49 +1,60 @@
import { execAsync, timeout } from 'resource:///com/github/Aylur/ags/utils.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { execAsync, timeout } from 'resource:///com/github/Aylur/ags/utils.js';
import Gtk from 'gi://Gtk'; import Gtk from 'gi://Gtk';
import Tablet from '../../services/tablet.js';
const KEY_N = 249;
const HIDDEN_MARGIN = 340;
const ANIM_DURATION = 700;
const releaseAllKeys = () => { const releaseAllKeys = () => {
const keycodes = Array.from(Array(249).keys()); const keycodes = Array.from(Array(KEY_N).keys());
execAsync([ execAsync([
'ydotool', 'key', 'ydotool', 'key',
...keycodes.map(keycode => `${keycode}:0`), ...keycodes.map((keycode) => `${keycode}:0`),
]).catch(print); ]).catch(print);
}; };
const hidden = 340; export default (window) => {
export default window => { window.child.setCss(`margin-bottom: -${HIDDEN_MARGIN}px;`);
window.child.setCss(`margin-bottom: -${hidden}px;`);
const gesture = Gtk.GestureDrag.new(window); const gesture = Gtk.GestureDrag.new(window);
window.setVisible = state => { window.setVisible = (state) => {
if (state) { if (state) {
window.visible = true; window.visible = true;
window.setSlideDown(); window.setSlideDown();
window.child.setCss(` window.child.setCss(`
transition: margin-bottom 0.7s cubic-bezier(0.36, 0, 0.66, -0.56); transition: margin-bottom 0.7s
cubic-bezier(0.36, 0, 0.66, -0.56);
margin-bottom: 0px; margin-bottom: 0px;
`); `);
} }
else { else {
timeout(710, () => { timeout(ANIM_DURATION + 10, () => {
if (!Tablet.tabletMode) if (!Tablet.tabletMode) {
window.visible = false; window.visible = false;
}
}); });
releaseAllKeys(); releaseAllKeys();
window.setSlideUp(); window.setSlideUp();
window.child.setCss(` window.child.setCss(`
transition: margin-bottom 0.7s cubic-bezier(0.36, 0, 0.66, -0.56); transition: margin-bottom 0.7s
margin-bottom: -${hidden}px; cubic-bezier(0.36, 0, 0.66, -0.56);
margin-bottom: -${HIDDEN_MARGIN}px;
`); `);
} }
}; };
gesture.signals = []; gesture.signals = [];
window.killGestureSigs = () => { window.killGestureSigs = () => {
gesture.signals.forEach(id => gesture.disconnect(id)); gesture.signals.forEach((id) => {
gesture.disconnect(id);
});
gesture.signals = []; gesture.signals = [];
}; };
@ -53,7 +64,7 @@ export default window => {
// Begin drag // Begin drag
gesture.signals.push( gesture.signals.push(
gesture.connect('drag-begin', () => { gesture.connect('drag-begin', () => {
Hyprland.sendMessage('j/cursorpos').then(out => { Hyprland.sendMessage('j/cursorpos').then((out) => {
gesture.startY = JSON.parse(out).y; gesture.startY = JSON.parse(out).y;
}); });
}), }),
@ -62,15 +73,16 @@ export default window => {
// Update drag // Update drag
gesture.signals.push( gesture.signals.push(
gesture.connect('drag-update', () => { gesture.connect('drag-update', () => {
Hyprland.sendMessage('j/cursorpos').then(out => { Hyprland.sendMessage('j/cursorpos').then((out) => {
const currentY = JSON.parse(out).y; const currentY = JSON.parse(out).y;
const offset = gesture.startY - currentY; const offset = gesture.startY - currentY;
if (offset < 0) if (offset < 0) {
return; return;
}
window.child.setCss(` window.child.setCss(`
margin-bottom: ${offset - hidden}px; margin-bottom: ${offset - HIDDEN_MARGIN}px;
`); `);
}); });
}), }),
@ -81,7 +93,7 @@ export default window => {
gesture.connect('drag-end', () => { gesture.connect('drag-end', () => {
window.child.setCss(` window.child.setCss(`
transition: margin-bottom 0.5s ease-in-out; transition: margin-bottom 0.5s ease-in-out;
margin-bottom: -${hidden}px; margin-bottom: -${HIDDEN_MARGIN}px;
`); `);
}), }),
); );
@ -93,7 +105,7 @@ export default window => {
// Begin drag // Begin drag
gesture.signals.push( gesture.signals.push(
gesture.connect('drag-begin', () => { gesture.connect('drag-begin', () => {
Hyprland.sendMessage('j/cursorpos').then(out => { Hyprland.sendMessage('j/cursorpos').then((out) => {
gesture.startY = JSON.parse(out).y; gesture.startY = JSON.parse(out).y;
}); });
}), }),
@ -102,12 +114,13 @@ export default window => {
// Update drag // Update drag
gesture.signals.push( gesture.signals.push(
gesture.connect('drag-update', () => { gesture.connect('drag-update', () => {
Hyprland.sendMessage('j/cursorpos').then(out => { Hyprland.sendMessage('j/cursorpos').then((out) => {
const currentY = JSON.parse(out).y; const currentY = JSON.parse(out).y;
const offset = gesture.startY - currentY; const offset = gesture.startY - currentY;
if (offset > 0) if (offset > 0) {
return; return;
}
window.child.setCss(` window.child.setCss(`
margin-bottom: ${offset}px; margin-bottom: ${offset}px;

View file

@ -8,48 +8,59 @@ import { defaultOskLayout, oskLayouts } from './keyboard-layouts.js';
const keyboardLayout = defaultOskLayout; const keyboardLayout = defaultOskLayout;
const keyboardJson = oskLayouts[keyboardLayout]; const keyboardJson = oskLayouts[keyboardLayout];
const L_KEY_PER_ROW = [8, 7, 6, 6, 6, 4]; const L_KEY_PER_ROW = [8, 7, 6, 6, 6, 4]; // eslint-disable-line
const COLOR = 'rgba(0, 0, 0, 0.3)';
const SPACING = 4;
const color = 'rgba(0, 0, 0, 0.3)'; export default (window) => Box({
export default window => Box({
vertical: true, vertical: true,
children: [ children: [
CenterBox({ CenterBox({
hpack: 'center', hpack: 'center',
start_widget: RoundedCorner('bottomright', ` start_widget: RoundedCorner('bottomright', `
background-color: ${color}; background-color: ${COLOR};
`), `),
center_widget: Box({ center_widget: Box({
class_name: 'thingy', class_name: 'thingy',
css: `background: ${color};`, css: `background: ${COLOR};`,
}), }),
end_widget: RoundedCorner('bottomleft', ` end_widget: RoundedCorner('bottomleft', `
background-color: ${color}; background-color: ${COLOR};
`), `),
}), }),
CenterBox({ CenterBox({
css: `background: ${color};`, css: `background: ${COLOR};`,
class_name: 'osk', class_name: 'osk',
hexpand: true, hexpand: true,
start_widget: Box({ start_widget: Box({
class_name: 'left-side side', class_name: 'left-side side',
hpack: 'start', hpack: 'start',
vertical: true, vertical: true,
children: keyboardJson.keys.map((row, rowIndex) => Box({ children: keyboardJson.keys.map((row, rowIndex) => Box({
vertical: true, vertical: true,
children: [ children: [
Box({ Box({
class_name: 'row', class_name: 'row',
children: [ children: [
Separator(4), Separator(SPACING),
...row.map((key, keyIndex) => { ...row.map((key, keyIndex) => {
return keyIndex < L_KEY_PER_ROW[rowIndex] ? Key(key) : null; return keyIndex < L_KEY_PER_ROW[rowIndex] ?
Key(key) :
null;
}), }),
], ],
}), }),
Separator(4, { vertical: true }),
Separator(SPACING, { vertical: true }),
], ],
})), })),
}), }),
@ -57,19 +68,28 @@ export default window => Box({
center_widget: Box({ center_widget: Box({
hpack: 'center', hpack: 'center',
vpack: 'center', vpack: 'center',
children: [ children: [
Box({ Box({
class_name: 'settings', class_name: 'settings',
children: [ children: [
ToggleButton({ ToggleButton({
cursor: 'pointer', cursor: 'pointer',
class_name: 'button', class_name: 'button',
active: true, active: true,
vpack: 'center', vpack: 'center',
connections: [['toggled', self => {
self.toggleClassName('toggled', self.get_active()); connections: [['toggled', (self) => {
window.exclusivity = self.get_active() ? 'exclusive' : 'normal'; self.toggleClassName(
'toggled',
self.get_active(),
);
window.exclusivity = self.get_active() ?
'exclusive' :
'normal';
}]], }]],
child: Label('Exclusive'), child: Label('Exclusive'),
}), }),
], ],
@ -81,17 +101,23 @@ export default window => Box({
class_name: 'right-side side', class_name: 'right-side side',
hpack: 'end', hpack: 'end',
vertical: true, vertical: true,
children: keyboardJson.keys.map((row, rowIndex) => Box({ children: keyboardJson.keys.map((row, rowIndex) => Box({
vertical: true, vertical: true,
children: [ children: [
Box({ Box({
hpack: 'end', hpack: 'end',
class_name: 'row', class_name: 'row',
children: row.map((key, keyIndex) => { children: row.map((key, keyIndex) => {
return keyIndex >= L_KEY_PER_ROW[rowIndex] ? Key(key) : null; return keyIndex >= L_KEY_PER_ROW[rowIndex] ?
Key(key) :
null;
}), }),
}), }),
Separator(4, { vertical: true }),
Separator(SPACING, { vertical: true }),
], ],
})), })),
}), }),

View file

@ -1,5 +1,6 @@
import Variable from 'resource:///com/github/Aylur/ags/variable.js'; import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import Brightness from '../../services/brightness.js'; import Brightness from '../../services/brightness.js';
import { Box, EventBox, Label } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, EventBox, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js'; import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
@ -19,59 +20,73 @@ const AltGr = Variable(false);
const RCtrl = Variable(false); const RCtrl = Variable(false);
const Caps = Variable(false); const Caps = Variable(false);
Brightness.connect('caps', (_, state) => Caps.value = state);
Brightness.connect('caps', (_, state) => {
Caps.value = state;
});
// Assume both shifts are the same for key.labelShift // Assume both shifts are the same for key.labelShift
const LShift = Variable(false); const LShift = Variable(false);
const RShift = Variable(false); const RShift = Variable(false);
const Shift = Variable(false); const Shift = Variable(false);
LShift.connect('changed', () => Shift.value = LShift.value || RShift.value);
RShift.connect('changed', () => Shift.value = LShift.value || RShift.value); LShift.connect('changed', () => {
Shift.value = LShift.value || RShift.value;
});
RShift.connect('changed', () => {
Shift.value = LShift.value || RShift.value;
});
const SPACING = 4;
const LSHIFT_CODE = 42;
const LALT_CODE = 56;
const LCTRL_CODE = 29;
export default key => { const ModKey = (key) => {
if (key.keytype === 'normal')
return RegularKey(key);
else
return ModKey(key);
};
const ModKey = key => {
let Mod; let Mod;
if (key.label === 'Super')
if (key.label === 'Super') {
Mod = Super; Mod = Super;
}
// Differentiate left and right mods // Differentiate left and right mods
else if (key.label === 'Shift' && key.keycode === 42) else if (key.label === 'Shift' && key.keycode === LSHIFT_CODE) {
Mod = LShift; Mod = LShift;
}
else if (key.label === 'Alt' && key.keycode === 56) else if (key.label === 'Alt' && key.keycode === LALT_CODE) {
Mod = LAlt; Mod = LAlt;
}
else if (key.label === 'Ctrl' && key.keycode === 29) else if (key.label === 'Ctrl' && key.keycode === LCTRL_CODE) {
Mod = LCtrl; Mod = LCtrl;
}
else if (key.label === 'Shift') else if (key.label === 'Shift') {
Mod = RShift; Mod = RShift;
}
else if (key.label === 'AltGr') else if (key.label === 'AltGr') {
Mod = AltGr; Mod = AltGr;
}
else if (key.label === 'Ctrl') else if (key.label === 'Ctrl') {
Mod = RCtrl; Mod = RCtrl;
}
const button = EventBox({ const button = EventBox({
cursor: 'pointer', cursor: 'pointer',
class_name: 'key', class_name: 'key',
onPrimaryClickRelease: self => { onPrimaryClickRelease: (self) => {
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}`);
self.child.toggleClassName('active', !Mod.value); self.child.toggleClassName('active', !Mod.value);
Mod.value = !Mod.value; Mod.value = !Mod.value;
}, },
connections: [[NormalClick, self => { connections: [[NormalClick, (self) => {
Mod.value = false; Mod.value = false;
self.child.toggleClassName('active', false); self.child.toggleClassName('active', false);
execAsync(`ydotool key ${key.keycode}:0`); execAsync(`ydotool key ${key.keycode}:0`);
@ -85,42 +100,49 @@ const ModKey = key => {
return Box({ return Box({
children: [ children: [
button, button,
Separator(4), Separator(SPACING),
], ],
}); });
}; };
const RegularKey = key => { const RegularKey = (key) => {
const widget = EventBox({ const widget = EventBox({
cursor: 'pointer', cursor: 'pointer',
class_name: 'key', class_name: 'key',
child: Label({ child: Label({
class_name: `normal ${key.label}`, class_name: `normal ${key.label}`,
label: key.label, label: key.label,
connections: [ connections: [
[Shift, self => { [Shift, (self) => {
if (!key.labelShift) if (!key.labelShift) {
return; return;
}
self.label = Shift.value ? key.labelShift : key.label; self.label = Shift.value ? key.labelShift : key.label;
}], }],
[Caps, self => { [Caps, (self) => {
if (key.label === 'Caps') { if (key.label === 'Caps') {
self.toggleClassName('active', Caps.value); self.toggleClassName('active', Caps.value);
return; return;
} }
if (!key.labelShift) if (!key.labelShift) {
return; return;
}
if (key.label.match(/[A-Za-z]/)) if (key.label.match(/[A-Za-z]/)) {
self.label = Caps.value ? key.labelShift : key.label; self.label = Caps.value ? key.labelShift : key.label;
}
}], }],
[AltGr, self => { [AltGr, (self) => {
if (!key.labelAltGr) if (!key.labelAltGr) {
return; return;
}
self.toggleClassName('altgr', AltGr.value); self.toggleClassName('altgr', AltGr.value);
self.label = AltGr.value ? key.labelAltGr : key.label; self.label = AltGr.value ? key.labelAltGr : key.label;
@ -130,6 +152,7 @@ const RegularKey = key => {
}); });
const gesture = Gtk.GestureLongPress.new(widget); const gesture = Gtk.GestureLongPress.new(widget);
gesture.delay_factor = 1.0; gesture.delay_factor = 1.0;
// Long press // Long press
@ -138,22 +161,24 @@ const RegularKey = key => {
const x = pointer[1]; const x = pointer[1];
const y = pointer[2]; const y = pointer[2];
if ((!x || !y) || x === 0 && y === 0) if ((!x || !y) || (x === 0 && y === 0)) {
return; return;
}
console.log('Not implemented yet'); console.log('Not implemented yet');
// TODO: popup menu for accents // TODO: popup menu for accents
}, 'pressed'); }, 'pressed');
// onPrimaryClickRelease // OnPrimaryClickRelease
widget.connectTo(gesture, () => { widget.connectTo(gesture, () => {
const pointer = gesture.get_point(null); const pointer = gesture.get_point(null);
const x = pointer[1]; const x = pointer[1];
const y = pointer[2]; const y = pointer[2];
if ((!x || !y) || x === 0 && y === 0) if ((!x || !y) || (x === 0 && y === 0)) {
return; return;
}
console.log('key clicked'); console.log('key clicked');
@ -165,7 +190,11 @@ const RegularKey = key => {
return Box({ return Box({
children: [ children: [
widget, widget,
Separator(4), Separator(SPACING),
], ],
}); });
}; };
export default (key) => key.keytype === 'normal' ?
RegularKey(key) :
ModKey(key);

View file

@ -1,11 +1,12 @@
import { Window } from 'resource:///com/github/Aylur/ags/widget.js'; import { Window } from 'resource:///com/github/Aylur/ags/widget.js';
import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js'; import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
import Tablet from '../../services/tablet.js';
import Gesture from './gesture.js'; import Gesture from './gesture.js';
import Keyboard from './keyboard.js'; import Keyboard from './keyboard.js';
// start ydotool daemon // Start ydotool daemon
execAsync('ydotoold').catch(print); execAsync('ydotoold').catch(print);
// Window // Window
@ -14,17 +15,20 @@ export default () => {
name: 'osk', name: 'osk',
visible: false, visible: false,
anchor: ['left', 'bottom', 'right'], anchor: ['left', 'bottom', 'right'],
connections: [ connections: [
[Tablet, (self, state) => { [Tablet, (self, state) => {
self.setVisible(state); self.setVisible(state);
}, 'osk-toggled'], }, 'osk-toggled'],
[Tablet, () => { [Tablet, () => {
if (!Tablet.tabletMode && !Tablet.oskState) if (!Tablet.tabletMode && !Tablet.oskState) {
window.visible = false; window.visible = false;
}
}, 'mode-toggled'], }, 'mode-toggled'],
], ],
}); });
window.child = Keyboard(window); window.child = Keyboard(window);
return Gesture(window); return Gesture(window);

View file

@ -1,8 +1,11 @@
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js'; import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import { Box, Icon, ProgressBar } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Icon, ProgressBar } from 'resource:///com/github/Aylur/ags/widget.js';
import { SpeakerIcon } from '../misc/audio-icons.js'; import { SpeakerIcon } from '../misc/audio-icons.js';
const AUDIO_MAX = 1.5;
export default () => Box({ export default () => Box({
className: 'osd', className: 'osd',
@ -14,14 +17,20 @@ export default () => Box({
ProgressBar({ ProgressBar({
vpack: 'center', vpack: 'center',
connections: [[Audio, self => {
if (!Audio.speaker)
return;
self.value = Audio.speaker ? Audio.speaker.volume / 1.5 : 0; connections: [[Audio, (self) => {
if (!Audio.speaker) {
return;
}
self.value = Audio.speaker ?
Audio.speaker.volume / AUDIO_MAX :
0;
self.sensitive = !Audio.speaker?.stream.isMuted; self.sensitive = !Audio.speaker?.stream.isMuted;
const stack = self.get_parent().get_parent(); const stack = self.get_parent().get_parent();
stack.shown = 'audio'; stack.shown = 'audio';
stack.resetTimer(); stack.resetTimer();
}, 'speaker-changed']], }, 'speaker-changed']],

View file

@ -1,5 +1,7 @@
import { Box, Icon, ProgressBar } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Icon, ProgressBar } from 'resource:///com/github/Aylur/ags/widget.js';
import Brightness from '../../services/brightness.js';
export default () => Box({ export default () => Box({
className: 'osd', className: 'osd',
@ -11,10 +13,12 @@ export default () => Box({
ProgressBar({ ProgressBar({
vpack: 'center', vpack: 'center',
connections: [[Brightness, self => {
connections: [[Brightness, (self) => {
self.value = Brightness.screen; self.value = Brightness.screen;
const stack = self.get_parent().get_parent(); const stack = self.get_parent().get_parent();
stack.shown = 'brightness'; stack.shown = 'brightness';
stack.resetTimer(); stack.resetTimer();
}, 'screen']], }, 'screen']],

View file

@ -1,5 +1,7 @@
import { Box, Icon, Label } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Icon, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import Brightness from '../../services/brightness.js';
export default () => Box({ export default () => Box({
className: 'osd', className: 'osd',
@ -7,10 +9,14 @@ export default () => Box({
Icon({ Icon({
hpack: 'start', hpack: 'start',
icon: 'caps-lock-symbolic', icon: 'caps-lock-symbolic',
connections: [[Brightness, (self, state) => { connections: [[Brightness, (self, state) => {
self.icon = state ? 'caps-lock-symbolic' : 'capslock-disabled-symbolic'; self.icon = state ?
'caps-lock-symbolic' :
'capslock-disabled-symbolic';
const stack = self.get_parent().get_parent(); const stack = self.get_parent().get_parent();
stack.shown = 'caps'; stack.shown = 'caps';
stack.resetTimer(); stack.resetTimer();
}, 'caps']], }, 'caps']],

View file

@ -1,8 +1,11 @@
import { Box, Icon, ProgressBar } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Icon, ProgressBar } from 'resource:///com/github/Aylur/ags/widget.js';
import Brightness from '../../services/brightness.js';
export default () => Box({ export default () => Box({
className: 'osd', className: 'osd',
children: [ children: [
Icon({ Icon({
hpack: 'start', hpack: 'start',
@ -11,15 +14,18 @@ export default () => Box({
ProgressBar({ ProgressBar({
vpack: 'center', vpack: 'center',
connections: [[Brightness, self => {
connections: [[Brightness, (self) => {
if (!self.value) { if (!self.value) {
self.value = Brightness.kbd / 2; self.value = Brightness.kbd / 2;
return; return;
} }
self.value = Brightness.kbd / 2; self.value = Brightness.kbd / 2;
self.sensitive = Brightness.kbd !== 0; self.sensitive = Brightness.kbd !== 0;
const stack = self.get_parent().get_parent(); const stack = self.get_parent().get_parent();
stack.shown = 'kbd'; stack.shown = 'kbd';
stack.resetTimer(); stack.resetTimer();
}, 'kbd']], }, 'kbd']],

View file

@ -1,4 +1,5 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import { timeout } from 'resource:///com/github/Aylur/ags/utils.js'; 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';
@ -11,6 +12,8 @@ import CapsLock from './caps.js';
import Keyboard from './kbd.js'; import Keyboard from './kbd.js';
import Microphone from './mic.js'; import Microphone from './mic.js';
const HIDE_DELAY = 2000;
export default () => { export default () => {
let setup = 0; let setup = 0;
@ -37,20 +40,23 @@ export default () => {
}); });
let timer; let timer;
stack.resetTimer = () => { stack.resetTimer = () => {
// Each osd calls resetTimer once at startup // Each osd calls resetTimer once at startup
if (setup <= stack.items.length + 1) { if (setup <= stack.items.length + 1) {
++setup; ++setup;
return; return;
} }
App.openWindow('osd'); App.openWindow('osd');
if (timer) if (timer) {
GLib.source_remove(timer); GLib.source_remove(timer);
}
timer = timeout(2000, () => { timer = timeout(HIDE_DELAY, () => {
App.closeWindow('osd'); App.closeWindow('osd');
timer = undefined; timer = null;
}); });
}; };

View file

@ -1,4 +1,5 @@
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js'; import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import { Box, Icon, ProgressBar } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Icon, ProgressBar } from 'resource:///com/github/Aylur/ags/widget.js';
import { MicIcon } from '../misc/audio-icons.js'; import { MicIcon } from '../misc/audio-icons.js';
@ -6,6 +7,7 @@ import { MicIcon } from '../misc/audio-icons.js';
export default () => Box({ export default () => Box({
className: 'osd', className: 'osd',
children: [ children: [
Icon({ Icon({
hpack: 'start', hpack: 'start',
@ -14,14 +16,17 @@ export default () => Box({
ProgressBar({ ProgressBar({
vpack: 'center', vpack: 'center',
connections: [[Audio, self => {
if (!Audio.microphone) connections: [[Audio, (self) => {
if (!Audio.microphone) {
return; return;
}
self.value = Audio.microphone ? Audio.microphone.volume : 0; self.value = Audio.microphone ? Audio.microphone.volume : 0;
self.sensitive = !Audio.microphone?.stream.isMuted; self.sensitive = !Audio.microphone?.stream.isMuted;
const stack = self.get_parent().get_parent(); const stack = self.get_parent().get_parent();
stack.shown = 'mic'; stack.shown = 'mic';
stack.resetTimer(); stack.resetTimer();
}, 'microphone-changed']], }, 'microphone-changed']],

View file

@ -1,21 +1,23 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { Icon, Revealer } from 'resource:///com/github/Aylur/ags/widget.js'; import { Icon, Revealer } 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';
import { WindowButton } from './dragndrop.js'; import { WindowButton } from './dragndrop.js';
import * as VARS from './variables.js'; import * as VARS from './variables.js';
const scale = size => size * VARS.SCALE - VARS.MARGIN; const scale = (size) => (size * VARS.SCALE) - VARS.MARGIN;
const getFontSize = client => { 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;
var size = Math.min(valX, valY); const size = Math.min(valX, valY);
return size <= 0 ? 0.1 : size; return size <= 0 ? 0.1 : size;
}; };
const IconStyle = client => ` 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;
@ -29,43 +31,61 @@ const Client = (client, active, clients, box) => {
return Revealer({ return Revealer({
transition: 'crossfade', transition: 'crossfade',
setup: rev => rev.revealChild = true,
setup: (rev) => {
rev.revealChild = true;
},
properties: [ properties: [
['address', client.address], ['address', client.address],
['toDestroy', false], ['toDestroy', false],
], ],
child: WindowButton({ child: WindowButton({
mainBox: box, mainBox: box,
address: client.address, address: client.address,
onSecondaryClickRelease: () => Hyprland.sendMessage(`dispatch closewindow ${addr}`),
onSecondaryClickRelease: () => {
Hyprland.sendMessage(`dispatch closewindow ${addr}`);
},
onPrimaryClickRelease: () => { onPrimaryClickRelease: () => {
if (wsId < 0) { if (wsId < 0) {
if (client.workspace.name === 'special') { if (client.workspace.name === 'special') {
Hyprland.sendMessage(`dispatch movetoworkspacesilent special:${wsId},${addr}`) Hyprland.sendMessage('dispatch ' +
`movetoworkspacesilent special:${wsId},${addr}`)
.then( .then(
Hyprland.sendMessage(`dispatch togglespecialworkspace ${wsId}`) Hyprland.sendMessage('dispatch ' +
`togglespecialworkspace ${wsId}`)
.then( .then(
() => App.closeWindow('overview'), () => App.closeWindow('overview'),
).catch(print), ).catch(print),
).catch(print); ).catch(print);
} }
else { else {
Hyprland.sendMessage(`dispatch togglespecialworkspace ${wsName}`).then( Hyprland.sendMessage('dispatch ' +
`togglespecialworkspace ${wsName}`).then(
() => App.closeWindow('overview'), () => App.closeWindow('overview'),
).catch(print); ).catch(print);
} }
} }
else { else {
// close special workspace if one is opened // Close special workspace if one is opened
const activeAddress = Hyprland.active.client.address; const activeAddress = Hyprland.active.client.address;
const currentActive = clients.find(c => c.address === activeAddress);
const currentSpecial = String(currentActive.workspace.name).replace('special:', ''); const currentActive = clients.find((c) => {
return c.address === activeAddress;
});
if (currentActive && currentActive.workspace.id < 0) { if (currentActive && currentActive.workspace.id < 0) {
Hyprland.sendMessage(`dispatch togglespecialworkspace ${currentSpecial}`) const currentSpecial = `${currentActive.workspace.name}`
.replace('special:', '');
Hyprland.sendMessage('dispatch ' +
`togglespecialworkspace ${currentSpecial}`)
.catch(print); .catch(print);
} }
Hyprland.sendMessage(`dispatch focuswindow ${addr}`).then( Hyprland.sendMessage(`dispatch focuswindow ${addr}`).then(
() => App.closeWindow('overview'), () => App.closeWindow('overview'),
).catch(print); ).catch(print);
@ -74,26 +94,28 @@ const Client = (client, active, clients, box) => {
child: Icon({ child: Icon({
className: `window ${active}`, className: `window ${active}`,
css: IconStyle(client) + 'font-size: 10px;', css: `${IconStyle(client) }font-size: 10px;`,
icon: client.class, icon: client.class,
}), }),
}), }),
}); });
}; };
export function updateClients(box) { export const updateClients = (box) => {
Hyprland.sendMessage('j/clients').then(out => { Hyprland.sendMessage('j/clients').then((out) => {
const clients = JSON.parse(out).filter(client => client.class); const clients = JSON.parse(out).filter((client) => client.class);
box._workspaces.forEach(workspace => { box._workspaces.forEach((workspace) => {
const fixed = workspace.getFixed(); const fixed = workspace.getFixed();
const toRemove = fixed.get_children(); const toRemove = fixed.get_children();
clients.filter(client => client.workspace.id == workspace._id) clients.filter((client) => client.workspace.id === workspace._id)
.forEach(client => { .forEach((client) => {
let active = ''; let active = '';
if (client.address == Hyprland.active.client.address)
if (client.address === Hyprland.active.client.address) {
active = 'active'; active = 'active';
}
// TODO: fix multi monitor issue. this is just a temp fix // TODO: fix multi monitor issue. this is just a temp fix
client.at[1] -= 2920; client.at[1] -= 2920;
@ -109,19 +131,21 @@ export function updateClients(box) {
} }
const newClient = [ const newClient = [
fixed.get_children().find(ch => ch._address == client.address), fixed.get_children()
.find((ch) => ch._address === client.address),
client.at[0] * VARS.SCALE, client.at[0] * VARS.SCALE,
client.at[1] * VARS.SCALE, client.at[1] * VARS.SCALE,
]; ];
if (!newClient[0]) { // If it exists already
newClient[0] = Client(client, active, clients, box); if (newClient[0]) {
fixed.put(...newClient);
}
else {
toRemove.splice(toRemove.indexOf(newClient[0]), 1); toRemove.splice(toRemove.indexOf(newClient[0]), 1);
fixed.move(...newClient); fixed.move(...newClient);
} }
else {
newClient[0] = Client(client, active, clients, box);
fixed.put(...newClient);
}
// 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, () => {
@ -131,7 +155,7 @@ export function updateClients(box) {
}); });
fixed.show_all(); fixed.show_all();
toRemove.forEach(ch => { toRemove.forEach((ch) => {
if (ch._toDestroy) { if (ch._toDestroy) {
ch.destroy(); ch.destroy();
} }
@ -142,4 +166,4 @@ export function updateClients(box) {
}); });
}); });
}).catch(print); }).catch(print);
} };

View file

@ -1,10 +1,13 @@
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { Box } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box } from 'resource:///com/github/Aylur/ags/widget.js';
import * as VARS from './variables.js'; import * as VARS from './variables.js';
const PADDING = 34;
const MARGIN = 9;
const DEFAULT_STYLE = ` const DEFAULT_STYLE = `
min-width: ${VARS.SCREEN.X * VARS.SCALE}px; min-width: ${VARS.SCREEN.X * VARS.SCALE}px;
min-height: ${VARS.SCREEN.Y * VARS.SCALE - 4}px; min-height: ${(VARS.SCREEN.Y * VARS.SCALE) - (VARS.MARGIN / 2)}px;
border-radius: 10px; border-radius: 10px;
`; `;
@ -21,14 +24,16 @@ export const updateCurrentWorkspace = (main, highlighter) => {
const row = Math.floor((currentId - 1) / VARS.WORKSPACE_PER_ROW); const row = Math.floor((currentId - 1) / VARS.WORKSPACE_PER_ROW);
const rowObject = main.children[0].children[row]; const rowObject = main.children[0].children[row];
const workspaces = rowObject.child.centerWidget.child.get_children().filter(w => w.revealChild); const workspaces = rowObject.child.centerWidget.child
.get_children().filter((w) => w.revealChild);
const height = row * (VARS.SCREEN.Y * VARS.SCALE + 17); const currentIndex = workspaces.findIndex((w) => w._id === currentId);
const currentIndex = workspaces.findIndex(w => w._id == currentId); const left = currentIndex * ((VARS.SCREEN.X * VARS.SCALE) + PADDING);
const height = row * ((VARS.SCREEN.Y * VARS.SCALE) + (PADDING / 2));
highlighter.setCss(` highlighter.setCss(`
${DEFAULT_STYLE} ${DEFAULT_STYLE}
margin-left: ${9 + currentIndex * (VARS.SCREEN.X * VARS.SCALE + 34)}px; margin-left: ${MARGIN + left}px;
margin-top: ${9 + height}px; margin-top: ${MARGIN + height}px;
`); `);
}; };

View file

@ -1,4 +1,5 @@
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { EventBox } from 'resource:///com/github/Aylur/ags/widget.js'; import { EventBox } from 'resource:///com/github/Aylur/ags/widget.js';
import Gtk from 'gi://Gtk'; import Gtk from 'gi://Gtk';
@ -11,7 +12,7 @@ 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)];
function createSurfaceFromWidget(widget) { 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,
@ -19,33 +20,40 @@ function createSurfaceFromWidget(widget) {
alloc.height, alloc.height,
); );
const cr = new Cairo.Context(surface); const cr = new Cairo.Context(surface);
cr.setSourceRGBA(255, 255, 255, 0); cr.setSourceRGBA(255, 255, 255, 0);
cr.rectangle(0, 0, alloc.width, alloc.height); cr.rectangle(0, 0, alloc.width, alloc.height);
cr.fill(); cr.fill();
widget.draw(cr); widget.draw(cr);
return surface; return surface;
} };
let hidden = 0; let hidden = 0;
export const WorkspaceDrop = props => EventBox({
export const WorkspaceDrop = (props) => EventBox({
...props, ...props,
connections: [['drag-data-received', (self, _c, _x, _y, data) => { connections: [['drag-data-received', (self, _c, _x, _y, data) => {
let id = self.get_parent()._id; let id = self.get_parent()._id;
if (id < -1) if (id < -1) {
id = self.get_parent()._name; id = self.get_parent()._name;
}
else if (id === -1) else if (id === -1) {
id = `special:${++hidden}`; id = `special:${++hidden}`;
}
else if (id === 1000) else if (id === 1000) {
id = 'empty'; id = 'empty';
}
Hyprland.sendMessage(`dispatch movetoworkspacesilent ${id},address:${data.get_text()}`) Hyprland.sendMessage('dispatch ' +
`movetoworkspacesilent ${id},address:${data.get_text()}`)
.catch(print); .catch(print);
}]], }]],
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);
}, },
}); });
@ -53,15 +61,23 @@ export const WorkspaceDrop = props => EventBox({
export const WindowButton = ({ address, mainBox, ...props } = {}) => Button({ export const WindowButton = ({ address, mainBox, ...props } = {}) => Button({
isButton: true, isButton: true,
...props, ...props,
setup: self => {
self.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.COPY); setup: (self) => {
self.drag_source_set(
Gdk.ModifierType.BUTTON1_MASK,
TARGET,
Gdk.DragAction.COPY,
);
self.connect('drag-data-get', (_w, _c, data) => { self.connect('drag-data-get', (_w, _c, data) => {
data.set_text(address, address.length); data.set_text(address, address.length);
}); });
self.connect('drag-begin', (_, context) => { self.connect('drag-begin', (_, context) => {
Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(self)); Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(self));
self.get_parent().revealChild = false; self.get_parent().revealChild = false;
}); });
self.connect('drag-end', () => { self.connect('drag-end', () => {
self.get_parent().destroy(); self.get_parent().destroy();

View file

@ -1,5 +1,6 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { Box, Overlay } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Overlay } from 'resource:///com/github/Aylur/ags/widget.js';
import PopupWindow from '../misc/popup.js'; import PopupWindow from '../misc/popup.js';
@ -8,25 +9,26 @@ import { Highlighter, updateCurrentWorkspace } from './current-workspace.js';
import { updateClients } from './clients.js'; import { updateClients } from './clients.js';
function update(box, highlight) { const update = (box, highlight) => {
getWorkspaces(box); getWorkspaces(box);
updateWorkspaces(box); updateWorkspaces(box);
updateClients(box); updateClients(box);
updateCurrentWorkspace(box, highlight); updateCurrentWorkspace(box, highlight);
} };
// 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 default () => { export default () => {
const highlighter = Highlighter(); const highlighter = Highlighter();
const mainBox = Box({ const mainBox = Box({
// do this for scss hierarchy // Do this for scss hierarchy
className: 'overview', className: 'overview',
css: 'all: unset', css: 'all: unset',
vertical: true, vertical: true,
vpack: 'center', vpack: 'center',
hpack: 'center', hpack: 'center',
children: [ children: [
Box({ Box({
vertical: true, vertical: true,
@ -34,6 +36,7 @@ export default () => {
WorkspaceRow('normal', 0), WorkspaceRow('normal', 0),
], ],
}), }),
Box({ Box({
vertical: true, vertical: true,
children: [ children: [
@ -41,12 +44,15 @@ export default () => {
], ],
}), }),
], ],
connections: [[Hyprland, self => {
if (!App.getWindow('overview').visible) connections: [[Hyprland, (self) => {
if (!App.getWindow('overview').visible) {
return; return;
}
update(self, highlighter); update(self, highlighter);
}]], }]],
properties: [ properties: [
['workspaces'], ['workspaces'],
], ],
@ -67,6 +73,7 @@ export default () => {
min-width: ${mainBox.get_allocated_width()}px; min-width: ${mainBox.get_allocated_width()}px;
`, `,
}), }),
// TODO: throttle his? // TODO: throttle his?
connections: [['get-child-position', (self, ch) => { connections: [['get-child-position', (self, ch) => {
if (ch === mainBox) { if (ch === mainBox) {
@ -80,5 +87,6 @@ export default () => {
}), }),
}); });
return window; return window;
}; };

View file

@ -1,91 +1,65 @@
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { Revealer, CenterBox, Box, EventBox, Fixed, Label } from 'resource:///com/github/Aylur/ags/widget.js'; import { Revealer, CenterBox, Box, EventBox, Fixed, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import { WorkspaceDrop } from './dragndrop.js'; import { WorkspaceDrop } from './dragndrop.js';
import * as VARS from './variables.js'; import * as VARS from './variables.js';
const DEFAULT_STYLE = `min-width: ${VARS.SCREEN.X * VARS.SCALE}px; const DEFAULT_STYLE = `
min-height: ${VARS.SCREEN.Y * VARS.SCALE}px;`; min-width: ${VARS.SCREEN.X * VARS.SCALE}px;
min-height: ${VARS.SCREEN.Y * VARS.SCALE}px;
`;
export function getWorkspaces(box) { export const getWorkspaces = (box) => {
const children = []; const children = [];
box.children.forEach(type => {
type.children.forEach(row => { box.children.forEach((type) => {
row.child.centerWidget.child.children.forEach(ch => { type.children.forEach((row) => {
row.child.centerWidget.child.children.forEach((ch) => {
children.push(ch); children.push(ch);
}); });
}); });
}); });
box._workspaces = children.sort((a, b) => a._id - b._id); box._workspaces = children.sort((a, b) => a._id - b._id);
} };
export const WorkspaceRow = (className, i) => Revealer({ const Workspace = (id, name, normal = true) => {
transition: 'slide_down',
hpack: className === 'special' ? '' : 'start',
connections: [[Hyprland, rev => {
const minId = i * VARS.WORKSPACE_PER_ROW;
const activeId = Hyprland.active.workspace.id;
rev.revealChild = Hyprland.workspaces
.some(ws => ws.id > minId &&
(ws.windows > 0 || ws.id === activeId));
}]],
child: CenterBox({
children: [null, EventBox({
connections: [[Hyprland, eventbox => {
const maxId = i * VARS.WORKSPACE_PER_ROW + VARS.WORKSPACE_PER_ROW;
const activeId = Hyprland.active.workspace.id;
eventbox.child.children[0].revealChild = className === 'special' ||
!Hyprland.workspaces.some(ws => ws.id > maxId &&
(ws.windows > 0 || ws.id === activeId));
}]],
child: Box({
className: className,
children: [
// the 'add' workspace
Workspace(className === 'special' ? -1 : 1000,
className === 'special' ? 'special' : '',
true),
],
}),
}), null],
}),
});
const Workspace = (id, name, extra = false) => {
let workspace;
const fixed = Fixed(); const fixed = Fixed();
if (!extra) { const workspace = Revealer({
workspace = Revealer({
transition: 'slide_right', transition: 'slide_right',
transitionDuration: 500, transitionDuration: 500,
connections: [[Hyprland, box => {
connections: normal ?
[[Hyprland, (box) => {
const activeId = Hyprland.active.workspace.id; const activeId = Hyprland.active.workspace.id;
const active = activeId === box._id; const active = activeId === box._id;
box.revealChild = Hyprland.getWorkspace(box._id)?.windows > 0 || active; box.revealChild = Hyprland.getWorkspace(box._id)
}]], ?.windows > 0 || active;
}]] :
[],
child: WorkspaceDrop({ child: WorkspaceDrop({
child: Box({ child: Box({
className: 'workspace', className: 'workspace',
css: DEFAULT_STYLE, css: normal ?
child: fixed,
}), DEFAULT_STYLE :
}),
}); `
} min-width: ${VARS.SCREEN.X * VARS.SCALE / 2}px;
// 'add' workspace min-height: ${VARS.SCREEN.Y * VARS.SCALE}px;
else { `,
workspace = Revealer({
transition: 'slide_right', children: normal ?
child: WorkspaceDrop({
child: Box({ [fixed] :
css: `min-width: ${VARS.SCREEN.X * VARS.SCALE / 2}px;
min-height: ${VARS.SCREEN.Y * VARS.SCALE}px;`, [
children: [
fixed, fixed,
Label({ Label({
label: ' +', label: ' +',
@ -95,20 +69,74 @@ const Workspace = (id, name, extra = false) => {
}), }),
}), }),
}); });
}
workspace._id = id; workspace._id = id;
workspace._name = name; workspace._name = name;
workspace.getFixed = () => fixed; workspace.getFixed = () => fixed;
return workspace; return workspace;
}; };
export function updateWorkspaces(box) { export const WorkspaceRow = (className, i) => {
Hyprland.workspaces.forEach(ws => { const addWorkspace = Workspace(
const currentWs = box._workspaces.find(ch => ch._id == ws.id); className === 'special' ? -1 : 1000,
className === 'special' ? 'special' : '',
false,
);
return Revealer({
transition: 'slide_down',
hpack: className === 'special' ? '' : 'start',
connections: [[Hyprland, (rev) => {
const minId = i * VARS.WORKSPACE_PER_ROW;
const activeId = Hyprland.active.workspace.id;
const rowExists = Hyprland.workspaces.some((ws) => {
const isInRow = ws.id > minId;
const hasClients = ws.windows > 0;
const isActive = ws.id === activeId;
return isInRow && (hasClients || isActive);
});
rev.revealChild = rowExists;
}]],
child: CenterBox({
children: [null, EventBox({
connections: [[Hyprland, () => {
const maxId = (i + 1) * VARS.WORKSPACE_PER_ROW;
const activeId = Hyprland.active.workspace.id;
const isSpecial = className === 'special';
const nextRowExists = Hyprland.workspaces.some((ws) => {
const isInNextRow = ws.id > maxId;
const hasClients = ws.windows > 0;
const isActive = ws.id === activeId;
return isInNextRow && (hasClients || isActive);
});
addWorkspace.revealChild = isSpecial || !nextRowExists;
}]],
child: Box({
className,
children: [addWorkspace],
}),
}), null],
}),
});
};
export const updateWorkspaces = (box) => {
Hyprland.workspaces.forEach((ws) => {
const currentWs = box._workspaces.find((ch) => ch._id === ws.id);
if (!currentWs) { if (!currentWs) {
var type = 0; let type = 0;
var rowNo = 0; let rowNo = 0;
if (ws.id < 0) { if (ws.id < 0) {
// This means it's a special workspace // This means it's a special workspace
@ -116,12 +144,19 @@ export function updateWorkspaces(box) {
} }
else { else {
rowNo = Math.floor((ws.id - 1) / VARS.WORKSPACE_PER_ROW); rowNo = Math.floor((ws.id - 1) / VARS.WORKSPACE_PER_ROW);
if (rowNo >= box.children[type].children.length) { const wsQty = box.children[type].children.length;
for (let i = box.children[type].children.length; i <= rowNo; ++i)
box.children[type].add(WorkspaceRow(type ? 'special' : 'normal', i)); if (rowNo >= wsQty) {
for (let i = wsQty; i <= rowNo; ++i) {
box.children[type].add(WorkspaceRow(
type ? 'special' : 'normal', i,
));
} }
} }
var row = box.children[type].children[rowNo].child.centerWidget.child; }
const row = box.children[type].children[rowNo]
.child.centerWidget.child;
row.add(Workspace(ws.id, type ? ws.name : '')); row.add(Workspace(ws.id, type ? ws.name : ''));
} }
}); });
@ -131,4 +166,4 @@ export function updateWorkspaces(box) {
workspace.get_parent().reorder_child(workspace, i); workspace.get_parent().reorder_child(workspace, i);
}); });
box.show_all(); box.show_all();
} };

View file

@ -1,4 +1,5 @@
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { CenterBox, Label } from 'resource:///com/github/Aylur/ags/widget.js'; import { CenterBox, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js'; import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
@ -13,7 +14,8 @@ const PowermenuWidget = () => CenterBox({
startWidget: Button({ startWidget: Button({
isButton: true, isButton: true,
className: 'shutdown', className: 'shutdown',
onPrimaryClickRelease: () => execAsync(['systemctl', 'poweroff']).catch(print), onPrimaryClickRelease: () => execAsync(['systemctl', 'poweroff'])
.catch(print),
child: Label({ child: Label({
label: '襤', label: '襤',
@ -23,7 +25,8 @@ const PowermenuWidget = () => CenterBox({
centerWidget: Button({ centerWidget: Button({
isButton: true, isButton: true,
className: 'reboot', className: 'reboot',
onPrimaryClickRelease: () => execAsync(['systemctl', 'reboot']).catch(print), onPrimaryClickRelease: () => execAsync(['systemctl', 'reboot'])
.catch(print),
child: Label({ child: Label({
label: '勒', label: '勒',
@ -33,7 +36,8 @@ const PowermenuWidget = () => CenterBox({
endWidget: Button({ endWidget: Button({
isButton: true, isButton: true,
className: 'logout', className: 'logout',
onPrimaryClickRelease: () => Hyprland.sendMessage('dispatch exit').catch(print), onPrimaryClickRelease: () => Hyprland.sendMessage('dispatch exit')
.catch(print),
child: Label({ child: Label({
label: '', label: '',

View file

@ -2,6 +2,7 @@ import App from 'resource:///com/github/Aylur/ags/app.js';
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js'; import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
import Network from 'resource:///com/github/Aylur/ags/service/network.js'; import Network from 'resource:///com/github/Aylur/ags/service/network.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js'; import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import { Box, Icon, Label, Revealer } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Icon, Label, Revealer } from 'resource:///com/github/Aylur/ags/widget.js';
import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js'; import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
@ -9,25 +10,30 @@ import { SpeakerIcon, MicIcon } from '../misc/audio-icons.js';
import EventBox from '../misc/cursorbox.js'; import EventBox from '../misc/cursorbox.js';
import Separator from '../misc/separator.js'; import Separator from '../misc/separator.js';
const SPACING = 28;
const ButtonStates = []; const ButtonStates = [];
const GridButton = ({ const GridButton = ({
command = () => {}, command = () => { /**/ },
secondaryCommand = () => {}, secondaryCommand = () => { /**/ },
icon, icon,
indicator, indicator,
menu, menu,
} = {}) => { } = {}) => {
const Activated = Variable(false); const Activated = Variable(false);
ButtonStates.push(Activated); ButtonStates.push(Activated);
// allow setting icon dynamically or statically // Allow setting icon dynamically or statically
if (typeof icon === 'string') { if (typeof icon === 'string') {
icon = Icon({ icon = Icon({
className: 'grid-label', className: 'grid-label',
icon: icon, icon,
connections: [[Activated, self => { connections: [[Activated, (self) => {
self.setCss(`color: ${Activated.value ? 'rgba(189, 147, 249, 0.8)' : 'unset'};`); self.setCss(`color: ${Activated.value ?
'rgba(189, 147, 249, 0.8)' :
'unset'};`);
}]], }]],
}); });
} }
@ -36,8 +42,10 @@ const GridButton = ({
className: 'grid-label', className: 'grid-label',
connections: [ connections: [
icon, icon,
[Activated, self => { [Activated, (self) => {
self.setCss(`color: ${Activated.value ? 'rgba(189, 147, 249, 0.8)' : 'unset'};`); self.setCss(`color: ${Activated.value ?
'rgba(189, 147, 249, 0.8)' :
'unset'};`);
}], }],
], ],
}); });
@ -70,44 +78,60 @@ const GridButton = ({
EventBox({ EventBox({
className: 'left-part', className: 'left-part',
onPrimaryClickRelease: () => { onPrimaryClickRelease: () => {
if (!Activated.value) if (Activated.value) {
command();
else
secondaryCommand(); secondaryCommand();
}
else {
command();
}
}, },
child: icon, child: icon,
}), }),
EventBox({ EventBox({
className: 'right-part', className: 'right-part',
onPrimaryClickRelease: () => { onPrimaryClickRelease: () => {
ButtonStates.forEach(state => { ButtonStates.forEach((state) => {
if (state !== Activated) if (state !== Activated) {
state.value = false; state.value = false;
}
}); });
Activated.value = !Activated.value; Activated.value = !Activated.value;
}, },
onHover: self => {
onHover: (self) => {
if (menu) { if (menu) {
const rowMenu = self.get_parent().get_parent() const rowMenu = self.get_parent().get_parent()
.get_parent().get_parent().children[1]; .get_parent().get_parent().children[1];
if (!rowMenu.get_children().find(ch => ch === menu)) { const isSetup = rowMenu.get_children()
.find((ch) => ch === menu);
if (!isSetup) {
rowMenu.add(menu); rowMenu.add(menu);
rowMenu.show_all(); rowMenu.show_all();
} }
} }
}, },
child: Icon({ child: Icon({
icon: App.configDir + '/icons/down-large.svg', icon: `${App.configDir }/icons/down-large.svg`,
connections: [[Activated, self => {
let deg = 270;
if (Activated.value)
deg = menu ? 360 : 450;
self.setCss(`-gtk-icon-transform: rotate(${deg}deg);`);
}]],
className: 'grid-chev', className: 'grid-chev',
connections: [[Activated, (self) => {
let deg = 270;
if (Activated.value) {
deg = menu ? 360 : 450;
}
self.setCss(`
-gtk-icon-transform: rotate(${deg}deg);
`);
}]],
}), }),
}), }),
@ -116,30 +140,34 @@ const GridButton = ({
indicator, indicator,
], ],
}); });
return widget; return widget;
}; };
const Row = ({ buttons } = {}) => { const Row = ({ buttons } = {}) => {
const widget = Box({ const widget = Box({
vertical: true, vertical: true,
children: [ children: [
Box({ Box({
className: 'button-row', className: 'button-row',
hpack: 'center', hpack: 'center',
}), }),
Box(), Box(),
], ],
}); });
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) {
widget.children[0].add(buttons[i]); widget.children[0].add(buttons[i]);
widget.children[0].add(Separator(28));
} }
else { else {
widget.children[0].add(buttons[i]); widget.children[0].add(buttons[i]);
widget.children[0].add(Separator(SPACING));
} }
} }
return widget; return widget;
}; };
@ -148,24 +176,40 @@ const FirstRow = () => Row({
GridButton({ GridButton({
command: () => Network.toggleWifi(), command: () => Network.toggleWifi(),
secondaryCommand: () => { secondaryCommand: () => {
execAsync(['bash', '-c', 'nm-connection-editor']) execAsync(['bash', '-c', 'nm-connection-editor'])
.catch(print); .catch(print);
}, },
icon: [Network, icon => icon.icon = Network.wifi?.iconName],
indicator: [Network, self => self.label = Network.wifi?.ssid || Network.wired?.internet], icon: [Network, (icon) => {
icon.icon = Network.wifi?.iconName;
}],
indicator: [Network, (self) => {
self.label = Network.wifi?.ssid || Network.wired?.internet;
}],
menu: Box({ menu: Box({
className: 'menu', className: 'menu',
vertical: true, vertical: true,
connections: [[Network, box => box.children =
Network.wifi?.access_points.map(ap => EventBox({ connections: [[Network, (box) => {
box.children = Network.wifi
?.access_points.map((ap) => EventBox({
isButton: true, isButton: true,
on_clicked: () => execAsync(`nmcli device wifi connect ${ap.bssid}`).catch(print),
on_clicked: () => {
execAsync(`nmcli device wifi
connect ${ap.bssid}`).catch(print);
},
child: Box({ child: Box({
children: [ children: [
Icon(ap.iconName), Icon(ap.iconName),
Label(ap.ssid || ''), Label(ap.ssid || ''),
ap.active && Icon({ ap.active && Icon({
icon: 'object-select-symbolic', icon: 'object-select-symbolic',
hexpand: true, hexpand: true,
@ -173,8 +217,8 @@ const FirstRow = () => Row({
}), }),
], ],
}), }),
})), }));
]], }]],
}), }),
}), }),
@ -183,45 +227,54 @@ const FirstRow = () => Row({
execAsync(['bash', '-c', '$AGS_PATH/qs-toggles.sh blue-toggle']) execAsync(['bash', '-c', '$AGS_PATH/qs-toggles.sh blue-toggle'])
.catch(print); .catch(print);
}, },
secondaryCommand: () => { secondaryCommand: () => {
execAsync(['bash', '-c', 'blueberry']) execAsync(['bash', '-c', 'blueberry'])
.catch(print); .catch(print);
}, },
icon: [Bluetooth, self => {
icon: [Bluetooth, (self) => {
if (Bluetooth.enabled) { if (Bluetooth.enabled) {
self.icon = 'bluetooth-active-symbolic'; self.icon = 'bluetooth-active-symbolic';
execAsync(['bash', '-c', 'echo 󰂯 > $HOME/.config/.bluetooth']) execAsync(['bash', '-c',
.catch(print); 'echo 󰂯 > $HOME/.config/.bluetooth']).catch(print);
} }
else { else {
self.icon = 'bluetooth-disabled-symbolic'; self.icon = 'bluetooth-disabled-symbolic';
execAsync(['bash', '-c', 'echo 󰂲 > $HOME/.config/.bluetooth']) execAsync(['bash', '-c',
.catch(print); 'echo 󰂲 > $HOME/.config/.bluetooth']).catch(print);
} }
}, 'changed'], }, 'changed'],
indicator: [Bluetooth, self => {
if (Bluetooth.connectedDevices[0]) indicator: [Bluetooth, (self) => {
if (Bluetooth.connectedDevices[0]) {
self.label = String(Bluetooth.connectedDevices[0]); self.label = String(Bluetooth.connectedDevices[0]);
else }
else {
self.label = 'Disconnected'; self.label = 'Disconnected';
}
}, 'changed'], }, 'changed'],
}), }),
// TODO: replace with vpn // TODO: replace with vpn
GridButton({ GridButton({
command: () => { command: () => {
execAsync(['bash', '-c', '$AGS_PATH/qs-toggles.sh toggle-radio']) execAsync(['bash', '-c',
.catch(print); '$AGS_PATH/qs-toggles.sh toggle-radio']).catch(print);
}, },
secondaryCommand: () => { secondaryCommand: () => {
execAsync(['notify-send', 'set this up moron']) execAsync(['notify-send', 'set this up moron'])
.catch(print); .catch(print);
}, },
icon: [Network, self => {
if (Network.wifi.enabled) icon: [Network, (self) => {
if (Network.wifi.enabled) {
self.icon = 'airplane-mode-disabled-symbolic'; self.icon = 'airplane-mode-disabled-symbolic';
else }
else {
self.icon = 'airplane-mode-symbolic'; self.icon = 'airplane-mode-symbolic';
}
}, 'changed'], }, 'changed'],
}), }),
@ -230,11 +283,10 @@ const FirstRow = () => Row({
const SecondRow = () => Row({ const SecondRow = () => Row({
buttons: [ buttons: [
GridButton({ GridButton({
command: () => { command: () => {
execAsync(['pactl', 'set-sink-mute', '@DEFAULT_SINK@', 'toggle']) execAsync(['pactl', 'set-sink-mute',
.catch(print); '@DEFAULT_SINK@', 'toggle']).catch(print);
}, },
secondaryCommand: () => { secondaryCommand: () => {
@ -242,15 +294,15 @@ const SecondRow = () => Row({
.catch(print); .catch(print);
}, },
icon: [SpeakerIcon, self => { icon: [SpeakerIcon, (self) => {
self.icon = SpeakerIcon.value; self.icon = SpeakerIcon.value;
}], }],
}), }),
GridButton({ GridButton({
command: () => { command: () => {
execAsync(['pactl', 'set-source-mute', '@DEFAULT_SOURCE@', 'toggle']) execAsync(['pactl', 'set-source-mute',
.catch(print); '@DEFAULT_SOURCE@', 'toggle']).catch(print);
}, },
secondaryCommand: () => { secondaryCommand: () => {
@ -258,7 +310,7 @@ const SecondRow = () => Row({
.catch(print); .catch(print);
}, },
icon: [MicIcon, self => { icon: [MicIcon, (self) => {
self.icon = MicIcon.value; self.icon = MicIcon.value;
}], }],
}), }),
@ -271,7 +323,6 @@ const SecondRow = () => Row({
secondaryCommand: () => App.openWindow('powermenu'), secondaryCommand: () => App.openWindow('powermenu'),
icon: 'system-lock-screen-symbolic', icon: 'system-lock-screen-symbolic',
}), }),
], ],
}); });

View file

@ -48,9 +48,12 @@ const QuickSettingsWidget = () => {
}); });
}; };
const TOP_MARGIN = 6;
const RIGHT_MARGIN = 5;
export default () => PopupWindow({ export default () => PopupWindow({
name: 'quick-settings', name: 'quick-settings',
anchor: ['top', 'right'], anchor: ['top', 'right'],
margins: [6, 5, 0], margins: [TOP_MARGIN, RIGHT_MARGIN, 0, 0],
child: QuickSettingsWidget(), child: QuickSettingsWidget(),
}); });

View file

@ -1,6 +1,8 @@
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js'; import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import { Box, Slider, Icon } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Slider, Icon } from 'resource:///com/github/Aylur/ags/widget.js';
import Brightness from '../../services/brightness.js';
import { SpeakerIcon } from '../misc/audio-icons.js'; import { SpeakerIcon } from '../misc/audio-icons.js';
@ -14,6 +16,7 @@ export default () => Box({
className: 'slider', className: 'slider',
vpack: 'start', vpack: 'start',
hpack: 'center', hpack: 'center',
children: [ children: [
Icon({ Icon({
size: 26, size: 26,
@ -24,17 +27,25 @@ export default () => Box({
Slider({ Slider({
cursor: 'pointer', cursor: 'pointer',
vpack: 'center', vpack: 'center',
max: 0.999,
draw_value: false,
onChange: ({ value }) => {
Audio.speaker.volume = value;
},
connections: [ connections: [
[Audio, slider => { [Audio, (slider) => {
slider.value = Audio.speaker?.volume; slider.value = Audio.speaker?.volume;
}, 'speaker-changed'], }, 'speaker-changed'],
['button-press-event', s => { s.cursor = 'grabbing'; }], ['button-press-event', (s) => {
['button-release-event', s => { s.cursor = 'pointer'; }], s.cursor = 'grabbing';
}],
['button-release-event', (s) => {
s.cursor = 'pointer';
}],
], ],
onChange: ({ value }) => Audio.speaker.volume = value,
max: 0.999,
draw_value: false,
}), }),
], ],
}), }),
@ -43,6 +54,7 @@ export default () => Box({
className: 'slider', className: 'slider',
vpack: 'start', vpack: 'start',
hpack: 'center', hpack: 'center',
children: [ children: [
Icon({ Icon({
className: 'slider-label', className: 'slider-label',
@ -52,16 +64,24 @@ export default () => Box({
Slider({ Slider({
cursor: 'pointer', cursor: 'pointer',
vpack: 'center', vpack: 'center',
onChange: ({ value }) => Brightness.screen = value, draw_value: false,
onChange: ({ value }) => {
Brightness.screen = value;
},
connections: [ connections: [
[Brightness, slider => { [Brightness, (slider) => {
slider.value = Brightness.screen; slider.value = Brightness.screen;
}, 'screen'], }, 'screen'],
['button-press-event', s => { s.cursor = 'grabbing'; }], ['button-press-event', (s) => {
['button-release-event', s => { s.cursor = 'pointer'; }], s.cursor = 'grabbing';
}],
['button-release-event', (s) => {
s.cursor = 'pointer';
}],
], ],
draw_value: false,
}), }),
], ],
}), }),

View file

@ -1,12 +1,14 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js'; import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import { CenterBox, Icon, ToggleButton } from 'resource:///com/github/Aylur/ags/widget.js'; import { CenterBox, Icon, ToggleButton } from 'resource:///com/github/Aylur/ags/widget.js';
export default rev => CenterBox({ export default (rev) => CenterBox({
center_widget: ToggleButton({ center_widget: ToggleButton({
cursor: 'pointer', cursor: 'pointer',
setup: self => {
setup: (self) => {
// Open at startup if there are players // Open at startup if there are players
const id = Mpris.connect('changed', () => { const id = Mpris.connect('changed', () => {
self.set_active(Mpris.players.length > 0); self.set_active(Mpris.players.length > 0);
@ -14,7 +16,7 @@ export default rev => CenterBox({
}); });
}, },
connections: [['toggled', self => { connections: [['toggled', (self) => {
if (self.get_active()) { if (self.get_active()) {
self.get_children()[0] self.get_children()[0]
.setCss('-gtk-icon-transform: rotate(0deg);'); .setCss('-gtk-icon-transform: rotate(0deg);');
@ -28,7 +30,7 @@ export default rev => CenterBox({
}]], }]],
child: Icon({ child: Icon({
icon: App.configDir + '/icons/down-large.svg', icon: `${App.configDir }/icons/down-large.svg`,
className: 'arrow', className: 'arrow',
css: '-gtk-icon-transform: rotate(180deg);', css: '-gtk-icon-transform: rotate(180deg);',
}), }),

View file

@ -1,5 +1,6 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js'; import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
import Brightness from '../services/brightness.js'; import Brightness from '../services/brightness.js';
@ -28,27 +29,33 @@ export default () => {
name: 'oskOn', name: 'oskOn',
gesture: 'DU', gesture: 'DU',
edge: 'B', edge: 'B',
command: 'busctl call --user sm.puri.OSK0 /sm/puri/OSK0 sm.puri.OSK0 SetVisible b true', command: 'busctl call --user sm.puri.OSK0 /sm/puri/OSK0 sm.puri.OSK0 ' +
'SetVisible b true',
}); });
TouchGestures.addGesture({ TouchGestures.addGesture({
name: 'oskOff', name: 'oskOff',
gesture: 'UD', gesture: 'UD',
edge: 'B', edge: 'B',
command: 'busctl call --user sm.puri.OSK0 /sm/puri/OSK0 sm.puri.OSK0 SetVisible b false', command: 'busctl call --user sm.puri.OSK0 /sm/puri/OSK0 sm.puri.OSK0 ' +
'SetVisible b false',
}); });
TouchGestures.addGesture({ TouchGestures.addGesture({
name: 'swipeSpotify1', name: 'swipeSpotify1',
gesture: 'LR', gesture: 'LR',
edge: 'L', edge: 'L',
command: () => Hyprland.sendMessage('dispatch togglespecialworkspace spot'), command: () => Hyprland.sendMessage(
'dispatch togglespecialworkspace spot',
),
}); });
TouchGestures.addGesture({ TouchGestures.addGesture({
name: 'swipeSpotify2', name: 'swipeSpotify2',
gesture: 'RL', gesture: 'RL',
edge: 'L', edge: 'L',
command: () => Hyprland.sendMessage('dispatch togglespecialworkspace spot'), command: () => Hyprland.sendMessage(
'dispatch togglespecialworkspace spot',
),
}); });
}; };

View file

@ -9,5 +9,8 @@
"@typescript-eslint/parser": "^6.9.1", "@typescript-eslint/parser": "^6.9.1",
"eslint": "^8.52.0", "eslint": "^8.52.0",
"stylelint-config-standard-scss": "^11.0.0" "stylelint-config-standard-scss": "^11.0.0"
},
"devDependencies": {
"@stylistic/eslint-plugin": "^1.4.0"
} }
} }

View file

@ -4,39 +4,51 @@ import { exec, execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
const KBD = 'tpacpi::kbd_backlight'; const KBD = 'tpacpi::kbd_backlight';
const CAPS = 'input0::capslock'; const CAPS = 'input0::capslock';
const INTERVAL = 500;
class Brightness extends Service { class Brightness extends Service {
static { static {
Service.register(this, { Service.register(this, {
'screen': ['float'], screen: ['float'],
'kbd': ['float'], kbd: ['float'],
'caps': ['int'], caps: ['int'],
}); });
} }
_kbd = 0; #kbd = 0;
_screen = 0; #screen = 0;
_caps = 0; #caps = 0;
get kbd() { return this._kbd; } get kbd() {
get screen() { return this._screen; } return this.#kbd;
get caps() { return this._caps; } }
get screen() {
return this.#screen;
}
get caps() {
return this.#caps;
}
set kbd(value) { set kbd(value) {
this.#kbd = value;
// TODO // TODO
} }
set screen(percent) { set screen(percent) {
if (percent < 0) if (percent < 0) {
percent = 0; percent = 0;
}
if (percent > 1) if (percent > 1) {
percent = 1; percent = 1;
}
execAsync(`brightnessctl s ${percent * 100}% -q`) execAsync(`brightnessctl s ${percent * 100}% -q`)
.then(() => { .then(() => {
this._screen = percent; this.#screen = percent;
this.emit('screen', this._screen); this.emit('screen', this.#screen);
}) })
.catch(console.error); .catch(console.error);
} }
@ -44,29 +56,31 @@ class Brightness extends Service {
constructor() { constructor() {
super(); super();
try { try {
this.monitorKbdState(); this.#monitorKbdState();
this._caps = Number(exec(`brightnessctl -d ${CAPS} g`)); this.#caps = Number(exec(`brightnessctl -d ${CAPS} g`));
this._screen = Number(exec('brightnessctl g')) / Number(exec('brightnessctl m')); this.#screen = Number(exec('brightnessctl g')) /
} catch (error) { Number(exec('brightnessctl m'));
}
catch (error) {
console.error('missing dependancy: brightnessctl'); console.error('missing dependancy: brightnessctl');
} }
} }
fetchCapsState() { fetchCapsState() {
execAsync(`brightnessctl -d ${CAPS} g`) execAsync(`brightnessctl -d ${CAPS} g`)
.then(out => { .then((out) => {
this._caps = out; this.#caps = out;
this.emit('caps', this._caps); this.emit('caps', this.#caps);
}) })
.catch(logError); .catch(logError);
} }
monitorKbdState() { #monitorKbdState() {
Variable(0, { Variable(0, {
poll: [500, `brightnessctl -d ${KBD} g`, out => { poll: [INTERVAL, `brightnessctl -d ${KBD} g`, (out) => {
if (out !== this._kbd) { if (out !== this.#kbd) {
this._kbd = out; this.#kbd = out;
this.emit('kbd', this._kbd); this.emit('kbd', this.#kbd);
} }
}], }],
}); });
@ -74,4 +88,5 @@ class Brightness extends Service {
} }
const brightnessService = new Brightness(); const brightnessService = new Brightness();
export default brightnessService; export default brightnessService;

View file

@ -34,28 +34,36 @@ class Pointers extends Service {
}); });
} }
proc = undefined; #process;
output = ''; #lastLine = '';
devices = []; #pointers = [];
udevClient = GUdev.Client.new(['input']); #udevClient = GUdev.Client.new(['input']);
get process() { return this.proc; } get process() {
get lastLine() { return this.output; } return this.#process;
get pointers() { return this.devices; } }
get lastLine() {
return this.#lastLine;
}
get pointers() {
return this.#pointers;
}
constructor() { constructor() {
super(); super();
this.initUdevConnection(); this.#initUdevConnection();
this.initAppConnection(); this.#initAppConnection();
} }
// FIXME: logitech mouse screws everything up on disconnect // FIXME: logitech mouse screws everything up on disconnect
initUdevConnection() { #initUdevConnection() {
this.getDevices(); this.#getDevices();
this.udevClient.connect('uevent', (_, action) => { this.#udevClient.connect('uevent', (_, action) => {
if (action === 'add' || action === 'remove') { if (action === 'add' || action === 'remove') {
this.getDevices(); this.getDevices();
if (this.proc) { if (this.#process) {
this.killProc(); this.killProc();
this.startProc(); this.startProc();
} }
@ -63,15 +71,19 @@ class Pointers extends Service {
}); });
} }
getDevices() { #getDevices() {
this.devices = []; this.#pointers = [];
this.udevClient.query_by_subsystem('input').forEach(dev => { this.#udevClient.query_by_subsystem('input').forEach((dev) => {
const isPointer = UDEV_POINTERS.some(p => dev.has_property(p)); const isPointer = UDEV_POINTERS.some((p) => dev.has_property(p));
if (isPointer) { if (isPointer) {
const hasEventFile = dev.has_property('DEVNAME') && const hasEventFile = dev.has_property('DEVNAME') &&
dev.get_property('DEVNAME').includes('event'); dev.get_property('DEVNAME')
if (hasEventFile) .includes('event');
this.devices.push(dev.get_property('DEVNAME'));
if (hasEventFile) {
this.#pointers.push(dev.get_property('DEVNAME'));
}
} }
}); });
@ -79,51 +91,54 @@ class Pointers extends Service {
} }
startProc() { startProc() {
if (this.proc) if (this.#process) {
return; return;
}
const args = []; const args = [];
this.devices.forEach(dev => {
this.#pointers.forEach((dev) => {
args.push('--device'); args.push('--device');
args.push(dev); args.push(dev);
}); });
this.proc = subprocess( this.#process = subprocess(
['libinput', 'debug-events', ...args], ['libinput', 'debug-events', ...args],
output => { (output) => {
if (output.includes('cancelled')) if (output.includes('cancelled')) {
return; return;
}
if (ON_RELEASE_TRIGGERS.some(p => output.includes(p))) { if (ON_RELEASE_TRIGGERS.some((p) => output.includes(p))) {
this.output = output; this.#lastLine = output;
this.detectClickedOutside('released'); Pointers.detectClickedOutside('released');
this.emit('released', output); this.emit('released', output);
this.emit('new-line', output); this.emit('new-line', output);
} }
if (ON_CLICK_TRIGGERS.some(p => output.includes(p))) { if (ON_CLICK_TRIGGERS.some((p) => output.includes(p))) {
this.output = output; this.#lastLine = output;
this.detectClickedOutside('clicked'); Pointers.detectClickedOutside('clicked');
this.emit('clicked', output); this.emit('clicked', output);
this.emit('new-line', output); this.emit('new-line', output);
} }
}, },
err => logError(err), (err) => logError(err),
); );
this.emit('proc-started', true); this.emit('proc-started', true);
} }
killProc() { killProc() {
if (this.proc) { if (this.#process) {
this.proc.force_exit(); this.#process.force_exit();
this.proc = undefined; this.#process = null;
this.emit('proc-destroyed', true); this.emit('proc-destroyed', true);
} }
} }
initAppConnection() { #initAppConnection() {
App.connect('window-toggled', () => { App.connect('window-toggled', () => {
const anyVisibleAndClosable = Array.from(App.windows).some(w => { const anyVisibleAndClosable = Array.from(App.windows).some((w) => {
const closable = w[1].closeOnUnfocus && const closable = w[1].closeOnUnfocus &&
!(w[1].closeOnUnfocus === 'none' || !(w[1].closeOnUnfocus === 'none' ||
w[1].closeOnUnfocus === 'stay'); w[1].closeOnUnfocus === 'stay');
@ -131,51 +146,57 @@ class Pointers extends Service {
return w[1].visible && closable; return w[1].visible && closable;
}); });
if (anyVisibleAndClosable) if (anyVisibleAndClosable) {
this.startProc(); this.startProc();
}
else else {
this.killProc(); this.killProc();
}
}); });
} }
detectClickedOutside(clickStage) { static detectClickedOutside(clickStage) {
const toClose = Array.from(App.windows).some(w => { const toClose = Array.from(App.windows).some((w) => {
const closable = (w[1].closeOnUnfocus && const closable = (w[1].closeOnUnfocus &&
w[1].closeOnUnfocus === clickStage); w[1].closeOnUnfocus === clickStage);
return w[1].visible && closable; return w[1].visible && closable;
}); });
if (!toClose)
return;
Hyprland.sendMessage('j/layers').then(layers => { if (!toClose) {
return;
}
Hyprland.sendMessage('j/layers').then((layers) => {
layers = JSON.parse(layers); layers = JSON.parse(layers);
Hyprland.sendMessage('j/cursorpos').then(pos => { Hyprland.sendMessage('j/cursorpos').then((pos) => {
pos = JSON.parse(pos); pos = JSON.parse(pos);
Object.values(layers).forEach(key => { Object.values(layers).forEach((key) => {
const bar = key['levels']['3'] const bar = key.levels['3']
.find(n => n.namespace === 'bar'); .find((n) => n.namespace === 'bar');
const widgets = key['levels']['3'].filter(n => { const widgets = key.levels['3'].filter((n) => {
const window = App.getWindow(n.namespace); const window = App.getWindow(n.namespace);
return window.closeOnUnfocus &&
window.closeOnUnfocus === clickStage; return window?.closeOnUnfocus &&
window?.closeOnUnfocus === clickStage;
}); });
if (pos.x > bar.x && pos.x < bar.x + bar.w && if (pos.x > bar.x && pos.x < bar.x + bar.w &&
pos.y > bar.y && pos.y < bar.y + bar.h) { pos.y > bar.y && pos.y < bar.y + bar.h) {
// don't handle clicks when on bar // Don't handle clicks when on bar
// TODO: make this configurable // TODO: make this configurable
} }
else { else {
widgets.forEach(w => { widgets.forEach((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)) {
App.closeWindow(w.namespace); App.closeWindow(w.namespace);
}
}); });
} }
}); });
@ -184,5 +205,4 @@ class Pointers extends Service {
} }
} }
const pointersService = new Pointers(); export default new Pointers();
export default pointersService;

View file

@ -4,7 +4,7 @@ import TouchGestures from './touch-gestures.js';
import { execAsync, subprocess } from 'resource:///com/github/Aylur/ags/utils.js'; import { execAsync, subprocess } from 'resource:///com/github/Aylur/ags/utils.js';
import GUdev from 'gi://GUdev'; import GUdev from 'gi://GUdev';
const ROTATION_MAPPING = { const ROTATION_MAP = {
'normal': 0, 'normal': 0,
'right-up': 3, 'right-up': 3,
'bottom-up': 2, 'bottom-up': 2,
@ -28,42 +28,47 @@ class Tablet extends Service {
}); });
} }
tabletMode = false; #tabletMode = false;
autorotate = undefined; #oskState = false;
oskState = false; #autorotate;
blockedInputs = undefined; #blockedInputs;
udevClient = GUdev.Client.new(['input']); #udevClient = GUdev.Client.new(['input']);
get tabletMode() { return this.tabletMode; } get tabletMode() {
get oskState() { return this.oskState; } return this.#tabletMode;
}
get oskState() {
return this.#oskState;
}
constructor() { constructor() {
super(); super();
this.initUdevConnection(); this.#initUdevConnection();
this.listenOskState(); this.#listenOskState();
} }
blockInputs() { #blockInputs() {
if (this.blockedInputs) if (this.#blockedInputs) {
return; return;
}
this.blockedInputs = subprocess(['libinput', 'debug-events', '--grab', this.#blockedInputs = subprocess(['libinput', 'debug-events', '--grab',
'--device', '/dev/input/by-path/platform-i8042-serio-0-event-kbd', '--device', '/dev/input/by-path/platform-i8042-serio-0-event-kbd',
'--device', '/dev/input/by-path/platform-i8042-serio-1-event-mouse', '--device', '/dev/input/by-path/platform-i8042-serio-1-event-mouse',
'--device', '/dev/input/by-path/platform-AMDI0010:02-event-mouse', '--device', '/dev/input/by-path/platform-AMDI0010:02-event-mouse',
'--device', '/dev/input/by-path/platform-thinkpad_acpi-event', '--device', '/dev/input/by-path/platform-thinkpad_acpi-event',
'--device', '/dev/video-bus', '--device', '/dev/video-bus',
'--device', '/dev/touchpad', '--device', '/dev/touchpad'],
], () => { /**/ },
() => {}, (err) => logError(err));
err => logError(err));
this.emit('inputs-blocked', true); this.emit('inputs-blocked', true);
} }
unblockInputs() { #unblockInputs() {
if (this.blockedInputs) { if (this.#blockedInputs) {
this.blockedInputs.force_exit(); this.#blockedInputs.force_exit();
this.blockedInputs = undefined; this.#blockedInputs = null;
this.emit('inputs-unblocked', true); this.emit('inputs-unblocked', true);
} }
} }
@ -76,9 +81,9 @@ class Tablet extends Service {
.catch(print); .catch(print);
this.startAutorotate(); this.startAutorotate();
this.blockInputs(); this.#blockInputs();
this.tabletMode = true; this.#tabletMode = true;
this.emit('tablet-mode', true); this.emit('tablet-mode', true);
this.emit('mode-toggled', true); this.emit('mode-toggled', true);
} }
@ -91,38 +96,42 @@ class Tablet extends Service {
.catch(print); .catch(print);
this.killAutorotate(); this.killAutorotate();
this.unblockInputs(); this.#unblockInputs();
this.tabletMode = false; this.#tabletMode = false;
this.emit('laptop-mode', true); this.emit('laptop-mode', true);
this.emit('mode-toggled', true); this.emit('mode-toggled', true);
} }
toggleMode() { toggleMode() {
if (this.tabletMode) if (this.#tabletMode) {
this.setLaptopMode(); this.setLaptopMode();
else }
else {
this.setTabletMode(); this.setTabletMode();
}
this.emit('mode-toggled', true); this.emit('mode-toggled', true);
} }
initUdevConnection() { #initUdevConnection() {
this.getDevices(); this.#getDevices();
this.udevClient.connect('uevent', (_, action) => { this.#udevClient.connect('uevent', (_, action) => {
if (action === 'add' || action === 'remove') if (action === 'add' || action === 'remove') {
this.getDevices(); this.#getDevices();
}
}); });
} }
getDevices() { #getDevices() {
this.devices = []; this.devices = [];
Hyprland.sendMessage('j/devices').then(out => { Hyprland.sendMessage('j/devices').then((out) => {
const devices = JSON.parse(out); const devices = JSON.parse(out);
devices['touch'].forEach(dev => {
devices.touch.forEach((dev) => {
this.devices.push(dev.name); this.devices.push(dev.name);
}); });
devices['tablets'].forEach(dev => { devices.tablets.forEach((dev) => {
this.devices.push(dev.name); this.devices.push(dev.name);
}); });
}).catch(print); }).catch(print);
@ -131,20 +140,23 @@ class Tablet extends Service {
} }
startAutorotate() { startAutorotate() {
if (this.autorotate) if (this.#autorotate) {
return; return;
}
this.autorotate = subprocess( this.#autorotate = subprocess(
['monitor-sensor'], ['monitor-sensor'],
output => { (output) => {
if (output.includes('orientation changed')) { if (output.includes('orientation changed')) {
const orientation = ROTATION_MAPPING[output.split(' ').at(-1)]; const orientation = ROTATION_MAP[output.split(' ').at(-1)];
Hyprland.sendMessage(`keyword monitor ${SCREEN},transform,${orientation}`) Hyprland.sendMessage(
.catch(print); `keyword monitor ${SCREEN},transform,${orientation}`,
).catch(print);
const batchRotate = this.devices.map(dev => const batchRotate = this.devices.map((dev) =>
`keyword device:${dev}:transform ${orientation}; `); `keyword device:${dev}:transform ${orientation}; `);
Hyprland.sendMessage(`[[BATCH]] ${batchRotate.flat()}`); Hyprland.sendMessage(`[[BATCH]] ${batchRotate.flat()}`);
if (TouchGestures.gestureDaemon) { if (TouchGestures.gestureDaemon) {
@ -153,40 +165,40 @@ class Tablet extends Service {
} }
} }
}, },
err => logError(err), (err) => logError(err),
); );
this.emit('autorotate-started', true); this.emit('autorotate-started', true);
} }
killAutorotate() { killAutorotate() {
if (this.autorotate) { if (this.#autorotate) {
this.autorotate.force_exit(); this.#autorotate.force_exit();
this.autorotate = undefined; this.#autorotate = null;
this.emit('autorotate-destroyed', true); this.emit('autorotate-destroyed', true);
} }
} }
listenOskState() { #listenOskState() {
subprocess( subprocess(
['bash', '-c', 'busctl monitor --user sm.puri.OSK0'], ['bash', '-c', 'busctl monitor --user sm.puri.OSK0'],
output => { (output) => {
if (output.includes('BOOLEAN')) { if (output.includes('BOOLEAN')) {
this.oskState = output.match('true|false')[0] === 'true'; this.#oskState = output.match('true|false')[0] === 'true';
this.emit('osk-toggled', this.oskState); this.emit('osk-toggled', this.#oskState);
} }
}, },
err => logError(err), (err) => logError(err),
); );
} }
openOsk() { static openOsk() {
execAsync(['busctl', 'call', '--user', execAsync(['busctl', 'call', '--user',
'sm.puri.OSK0', '/sm/puri/OSK0', 'sm.puri.OSK0', 'sm.puri.OSK0', '/sm/puri/OSK0', 'sm.puri.OSK0',
'SetVisible', 'b', 'true']) 'SetVisible', 'b', 'true'])
.catch(print); .catch(print);
} }
closeOsk() { static closeOsk() {
execAsync(['busctl', 'call', '--user', execAsync(['busctl', 'call', '--user',
'sm.puri.OSK0', '/sm/puri/OSK0', 'sm.puri.OSK0', 'sm.puri.OSK0', '/sm/puri/OSK0', 'sm.puri.OSK0',
'SetVisible', 'b', 'false']) 'SetVisible', 'b', 'false'])
@ -194,12 +206,15 @@ class Tablet extends Service {
} }
toggleOsk() { toggleOsk() {
if (this.oskState) if (this.#oskState) {
this.closeOsk(); Tablet.closeOsk();
else }
this.openOsk(); else {
Tablet.openOsk();
}
} }
} }
const tabletService = new Tablet(); const tabletService = new Tablet();
export default tabletService; export default tabletService;

View file

@ -14,22 +14,22 @@ const GESTURE_VERIF = [
'ULDR', // Up to Left to Down to Right (counter-clockwise from Up) 'ULDR', // Up to Left to Down to Right (counter-clockwise from Up)
]; ];
const EDGE_VERIF = [ const EDGE_VERIF = [
'*', // any '*', // Any
'N', // none 'N', // None
'L', // left 'L', // Left
'R', // right 'R', // Right
'T', // top 'T', // Top
'B', // bottom 'B', // Bottom
'TL', // top left 'TL', // Top left
'TR', // top right 'TR', // Top right
'BL', // bottom left 'BL', // Bottom left
'BR', // bottom right 'BR', // Bottom right
]; ];
const DISTANCE_VERIF = [ const DISTANCE_VERIF = [
'*', // any '*', // Any
'S', // short 'S', // Short
'M', // medium 'M', // Medium
'L', // large 'L', // Large
]; ];
@ -43,13 +43,11 @@ class TouchGestures extends Service {
}); });
} }
gestures = new Map(); #gestures = new Map();
gestureDaemon = undefined; #gestureDaemon;
get gestures() { return this.gestures; } get gestures() {
return this.#gestures;
constructor() {
super();
} }
addGesture({ addGesture({
@ -63,18 +61,21 @@ class TouchGestures extends Service {
gesture = String(gesture).toUpperCase(); gesture = String(gesture).toUpperCase();
if (!GESTURE_VERIF.includes(gesture)) { if (!GESTURE_VERIF.includes(gesture)) {
logError('Wrong gesture id'); logError('Wrong gesture id');
return; return;
} }
edge = String(edge).toUpperCase(); edge = String(edge).toUpperCase();
if (!EDGE_VERIF.includes(edge)) { if (!EDGE_VERIF.includes(edge)) {
logError('Wrong edge id'); logError('Wrong edge id');
return; return;
} }
distance = String(distance).toUpperCase(); distance = String(distance).toUpperCase();
if (!DISTANCE_VERIF.includes(distance)) { if (!DISTANCE_VERIF.includes(distance)) {
logError('Wrong distance id'); logError('Wrong distance id');
return; return;
} }
@ -83,23 +84,25 @@ class TouchGestures extends Service {
command = `ags -r "${name}()"`; command = `ags -r "${name}()"`;
} }
this.gestures.set(name, [ this.#gestures.set(name, [
'-g', '-g',
`${nFingers},${gesture},${edge},${distance},${command}`, `${nFingers},${gesture},${edge},${distance},${command}`,
]); ]);
if (this.gestureDaemon) if (this.#gestureDaemon) {
this.killDaemon(); this.killDaemon();
}
this.startDaemon(); this.startDaemon();
} }
startDaemon() { startDaemon() {
if (this.gestureDaemon) if (this.#gestureDaemon) {
return; return;
}
var command = [ let command = [
'lisgd', '-d', SCREEN, 'lisgd', '-d', SCREEN,
// orientation // Orientation
'-o', '0', '-o', '0',
// Threshold of gesture recognized // Threshold of gesture recognized
'-t', '125', '-t', '125',
@ -109,26 +112,27 @@ class TouchGestures extends Service {
'-m', '3200', '-m', '3200',
]; ];
this.gestures.forEach(gesture => { this.#gestures.forEach((gesture) => {
command = command.concat(gesture); command = command.concat(gesture);
}); });
this.gestureDaemon = subprocess( this.#gestureDaemon = subprocess(
command, command,
() => {}, () => { /**/ },
err => logError(err), (err) => logError(err),
); );
this.emit('daemon-started', true); this.emit('daemon-started', true);
} }
killDaemon() { killDaemon() {
if (this.gestureDaemon) { if (this.#gestureDaemon) {
this.gestureDaemon.force_exit(); this.#gestureDaemon.force_exit();
this.gestureDaemon = undefined; this.#gestureDaemon = null;
this.emit('daemon-destroyed', true); this.emit('daemon-destroyed', true);
} }
} }
} }
const touchGesturesService = new TouchGestures(); const touchGesturesService = new TouchGestures();
export default touchGesturesService; export default touchGesturesService;