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",
"sourceType": "module"
},
"plugins": ["@stylistic"],
"rules": {
"arrow-parens": ["error", "as-needed"],
"comma-dangle": ["error", "always-multiline"],
"comma-spacing": ["error", { "before": false, "after": true }],
"comma-style": ["error", "last"],
"curly": ["error", "multi-or-nest", "consistent"],
"dot-location": ["error", "property"],
"eol-last": 0,
"indent": ["error", 4, { "SwitchCase": 1 }],
"keyword-spacing": ["error", { "before": true }],
"lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }],
"padded-blocks": ["error", "never", { "allowSingleLineBlocks": false }],
"prefer-const": "error",
"quotes": ["error", "single", { "avoidEscape": true }],
"semi": ["error", "always"],
"nonblock-statement-body-position": ["error", "below"],
"no-trailing-spaces": ["error"],
"no-dupe-class-members": 0,
"array-bracket-spacing": ["error", "never"],
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],
"object-curly-spacing": ["error", "always"],
"no-useless-escape": ["off"]
"array-callback-return": ["error", {
"allowImplicit": true,
"checkForEach": true
}],
"no-constructor-return": ["error"],
"no-unreachable-loop": ["error", { "ignore": [
"ForInStatement", "ForOfStatement"
]}],
"no-use-before-define": ["error", {
"functions": false
}],
"block-scoped-var": ["error"],
"capitalized-comments": ["warn", "always", {
"ignoreConsecutiveComments": true
}],
"class-methods-use-this": ["error"],
"curly": ["warn"],
"default-case-last": ["warn"],
"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": {
"Tablet": "readonly",
"Pointers": "readonly",
"Brightness": "readonly",
"ARGV": "readonly",
"imports": "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 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 Separator from '../misc/separator.js';
export default app => {
const title = Widget.Label({
export default (app) => {
const title = Label({
class_name: 'title',
label: app.name,
xalign: 0,
@ -15,7 +16,7 @@ export default app => {
truncate: 'end',
});
const description = Widget.Label({
const description = Label({
class_name: 'description',
label: app.description || '',
wrap: true,
@ -24,28 +25,42 @@ export default app => {
vpack: 'center',
});
const icon = Widget.Icon({
icon: lookUpIcon(app.icon_name) ? app.icon_name :
app.app.get_string('Icon') !== 'nix-snowflake' ? app.app.get_string('Icon') : '',
const icon = Icon({
icon: lookUpIcon(app.icon_name) ?
app.icon_name :
app.app.get_string('Icon') === 'nix-snowflake' ?
'' :
app.app.get_string('Icon'),
size: 42,
});
const textBox = Widget.Box({
const textBox = Box({
vertical: true,
vpack: 'center',
children: [title, description],
});
return Widget.Button({
const ENTRY_SPACING = 16;
return Button({
class_name: 'app',
setup: self => self.app = app,
setup: (self) => {
self.app = app;
},
on_clicked: () => {
App.closeWindow('applauncher');
Hyprland.sendMessage(`dispatch exec sh -c ${app.executable}`);
++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 ICON_SEPARATION = 4;
const children = () => [
...Applications.query('').flatMap(app => {
...Applications.query('').flatMap((app) => {
const item = AppItem(app);
return [
Separator(4, {
Separator(ICON_SEPARATION, {
binds: [['visible', item, 'visible']],
}),
item,
];
}),
Separator(4),
Separator(ICON_SEPARATION),
];
const list = Box({
@ -28,43 +31,46 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => {
children: children(),
});
const entry = Entry({
hexpand: true,
placeholder_text: 'Search',
const placeholder = Label({
label: " Couldn't find a match",
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: '-',
hexpand: true,
on_accept: ({ text }) => {
const list = Applications.query(text || '');
if (list[0]) {
const appList = Applications.query(text || '');
if (appList[0]) {
App.toggleWindow(window_name);
Hyprland.sendMessage(`dispatch exec sh -c ${list[0]}`);
++list[0].frequency;
Hyprland.sendMessage(`dispatch exec sh -c ${appList[0]}`);
++appList[0].frequency;
}
},
on_change: ({ text }) => {
let visibleApps = 0;
list.children.map(item => {
list.children.forEach((item) => {
if (item.app) {
item.visible = item.app.match(text);
if (item.app.match(text))
if (item.app.match(text)) {
++visibleApps;
}
}
});
placeholder.visible = visibleApps <= 0;
},
});
const placeholder = Label({
label: " Couldn't find a match",
className: 'placeholder',
});
return Box({
className: 'applauncher',
vertical: true,
children: [
Box({
className: 'header',
@ -82,15 +88,20 @@ const Applauncher = ({ window_name = 'applauncher' } = {}) => {
}),
}),
],
connections: [[App, (_, name, visible) => {
if (name !== window_name)
if (name !== window_name) {
return;
}
entry.text = '';
if (visible)
if (visible) {
entry.grab_focus();
else
}
else {
list.children = children();
}
}]],
});
};

View file

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

View file

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

View file

@ -1,22 +1,30 @@
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 Heart from './heart.js';
const SPACING = 25;
const BAR_CUTOFF = 0.33;
export default () => Overlay({
tooltipText: 'Brightness',
child: ProgressBar({
className: 'toggle-off brightness',
connections: [[Brightness, self => {
self.value = Brightness.screen > 0.33 ? Brightness.screen : 0.33;
connections: [[Brightness, (self) => {
self.value = Brightness.screen > BAR_CUTOFF ?
Brightness.screen :
BAR_CUTOFF;
}, 'screen']],
}),
overlays: [
Box({
css: 'color: #CBA6F7;',
children: [
Separator(25),
Separator(SPACING),
Heart(),
],
}),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,24 +1,33 @@
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { timeout } from 'resource:///com/github/Aylur/ags/utils.js';
import { Box, Overlay, Revealer } from 'resource:///com/github/Aylur/ags/widget.js';
import EventBox from '../../misc/cursorbox.js';
const URGENT_DURATION = 1000;
const Workspace = ({ i } = {}) =>
Revealer({
const Workspace = ({ i } = {}) => {
return Revealer({
transition: 'slide_right',
properties: [['id', i]],
child: EventBox({
tooltipText: `${i}`,
onPrimaryClickRelease: () => Hyprland.sendMessage(`dispatch workspace ${i}`),
onPrimaryClickRelease: () => {
Hyprland.sendMessage(`dispatch workspace ${i}`);
},
child: Box({
vpack: 'center',
className: 'button',
setup: self => {
self.update = addr => {
setup: (self) => {
self.update = (addr) => {
const occupied = Hyprland.getWorkspace(i)?.windows > 0;
self.toggleClassName('occupied', occupied);
self.toggleClassName('empty', !occupied);
@ -27,36 +36,47 @@ const Workspace = ({ i } = {}) =>
self.toggleClassName('urgent', true);
// Only show for a sec when urgent is current workspace
if (Hyprland.active.workspace.id === i)
timeout(1000, () => self.toggleClassName('urgent', false));
if (Hyprland.active.workspace.id === i) {
timeout(URGENT_DURATION, () => {
self.toggleClassName('urgent', false);
});
}
}
};
},
connections: [
[Hyprland, self => self.update()],
[Hyprland, (self) => self.update()],
// Deal with urgent windows
[Hyprland, (self, addr) => self.update(addr), 'urgent-window'],
[Hyprland.active.workspace, self => {
if (Hyprland.active.workspace.id === i)
[Hyprland, (self, a) => self.update(a), 'urgent-window'],
[Hyprland.active.workspace, (self) => {
if (Hyprland.active.workspace.id === i) {
self.toggleClassName('urgent', false);
}
}],
],
}),
}),
});
};
export default () => {
const updateHighlight = () => {
const L_PADDING = 16;
const WS_WIDTH = 30;
const updateHighlight = (self) => {
const currentId = Hyprland.active.workspace.id;
const indicators = highlight.get_parent().get_children()[0].child.children;
const currentIndex = indicators.findIndex(w => w._id == currentId);
const indicators = self.get_parent().get_children()[0].child.children;
const currentIndex = indicators.findIndex((w) => w._id === currentId);
if (currentIndex < 0)
if (currentIndex < 0) {
return;
}
highlight.setCss(`margin-left: ${16 + currentIndex * 30}px`);
self.setCss(`margin-left: ${L_PADDING + (currentIndex * WS_WIDTH)}px`);
};
const highlight = Box({
vpack: 'center',
hpack: 'start',
@ -70,21 +90,28 @@ export default () => {
child: EventBox({
child: Box({
className: 'workspaces',
properties: [
['workspaces'],
['refresh', self => {
self.children.forEach(rev => rev.reveal_child = false);
self._workspaces.forEach(ws => {
['refresh', (self) => {
self.children.forEach((rev) => {
rev.reveal_child = false;
});
self._workspaces.forEach((ws) => {
ws.revealChild = true;
});
}],
['updateWorkspaces', self => {
Hyprland.workspaces.forEach(ws => {
const currentWs = self.children.find(ch => ch._id == ws.id);
if (!currentWs && ws.id > 0)
['updateWorkspaces', (self) => {
Hyprland.workspaces.forEach((ws) => {
const currentWs = self.children.find((ch) => {
return ch._id === ws.id;
});
if (!currentWs && ws.id > 0) {
self.add(Workspace({ i: ws.id }));
}
});
self.show_all();
@ -94,16 +121,21 @@ export default () => {
});
}],
],
connections: [[Hyprland, self => {
self._workspaces = self.children.filter(ch => {
return Hyprland.workspaces.find(ws => ws.id == ch._id);
connections: [[Hyprland, (self) => {
self._workspaces = self.children.filter((ch) => {
return Hyprland.workspaces.find((ws) => {
return ws.id === ch._id;
});
}).sort((a, b) => a._id - b._id);
self._updateWorkspaces(self);
self._refresh(self);
// 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';
const BarCloser = variable => Window({
const BarCloser = (variable) => Window({
name: 'bar-closer',
visible: false,
anchor: ['top', 'bottom', 'left', 'right'],
layer: 'overlay',
child: EventBox({
onHover: self => {
onHover: (self) => {
variable.value = false;
self.get_parent().visible = false;
},
child: Box({
css: 'padding: 1px',
}),
}),
});
export default props => {
export default (props) => {
const Revealed = Variable(true);
const barCloser = BarCloser(Revealed);
@ -28,28 +30,39 @@ export default props => {
css: 'min-height: 1px',
hexpand: true,
vertical: true,
connections: [
[Hyprland.active, () => {
const workspace = Hyprland.getWorkspace(Hyprland.active.workspace.id);
const workspace = Hyprland.getWorkspace(
Hyprland.active.workspace.id,
);
Revealed.value = !workspace?.hasfullscreen;
}],
[Hyprland, (_, fullscreen) => Revealed.value = !fullscreen, 'fullscreen'],
[Hyprland, (_, fullscreen) => {
Revealed.value = !fullscreen;
}, 'fullscreen'],
],
children: [
Revealer({
...props,
transition: 'slide_down',
revealChild: true,
binds: [['revealChild', Revealed, 'value']],
...props,
}),
Revealer({
binds: [['revealChild', Revealed, 'value', v => !v]],
binds: [['revealChild', Revealed, 'value', (v) => !v]],
child: EventBox({
onHover: () => {
barCloser.visible = true;
Revealed.value = true;
},
child: Box({
css: 'min-height: 5px;',
}),

View file

@ -1,21 +1,24 @@
import { Window, CenterBox, Box } from 'resource:///com/github/Aylur/ags/widget.js';
import Separator from '../misc/separator.js';
import CurrentWindow from './buttons/current-window.js';
import Workspaces from './buttons/workspaces.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 Audio from './buttons/audio.js';
import Battery from './buttons/battery.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 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';
const SPACING = 12;
export default () => Window({
name: 'bar',
@ -33,21 +36,21 @@ export default () => Window({
OskToggle(),
Separator(12),
Separator(SPACING),
TabletToggle(),
Separator(12),
Separator(SPACING),
SysTray(),
Audio(),
Separator(12),
Separator(SPACING),
Brightness(),
Separator(12),
Separator(SPACING),
Workspaces(),
@ -56,11 +59,11 @@ export default () => Window({
centerWidget: Box({
children: [
Separator(12),
Separator(SPACING),
CurrentWindow(),
Separator(12),
Separator(SPACING),
],
}),
@ -69,19 +72,19 @@ export default () => Window({
children: [
Battery(),
Separator(12),
Separator(SPACING),
KeyboardLayout(),
Separator(12),
Separator(SPACING),
Clock(),
Separator(12),
Separator(SPACING),
NotifButton(),
Separator(12),
Separator(SPACING),
QsToggle(),
],

View file

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

View file

@ -1,4 +1,5 @@
import { Box, DrawingArea } from 'resource:///com/github/Aylur/ags/widget.js';
import Gtk from 'gi://Gtk';
const Lang = imports.lang;
@ -19,25 +20,27 @@ export default (
css: `
border-radius: 18px;
border-width: 0.068rem;
` + css,
setup: widget => {
const r = widget.get_style_context()
${css}
`,
setup: (widget) => {
let r = widget.get_style_context()
.get_property('border-radius', Gtk.StateFlags.NORMAL);
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()
.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);
const borderColor = widget.get_style_context()
.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()
.get_border(Gtk.StateFlags.NORMAL).left;
widget.set_size_request(r, r);
switch (place) {

View file

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

View file

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

View file

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

View file

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

View file

@ -19,8 +19,9 @@ const micIcons = {
export const SpeakerIcon = Variable();
Audio.connect('speaker-changed', () => {
if (!Audio.speaker)
if (!Audio.speaker) {
return;
}
if (Audio.speaker.stream.isMuted) {
SpeakerIcon.value = speakerIcons[0];
@ -29,16 +30,18 @@ Audio.connect('speaker-changed', () => {
const vol = Audio.speaker.volume * 100;
for (const threshold of [-1, 0, 33, 66, 100]) {
if (vol > threshold + 1)
if (vol > threshold + 1) {
SpeakerIcon.value = speakerIcons[threshold + 1];
}
}
}
});
export const MicIcon = Variable();
Audio.connect('microphone-changed', () => {
if (!Audio.microphone)
if (!Audio.microphone) {
return;
}
if (Audio.microphone.stream.isMuted) {
MicIcon.value = micIcons[0];
@ -47,8 +50,9 @@ Audio.connect('microphone-changed', () => {
const vol = Audio.microphone.volume * 100;
for (const threshold of [-1, 0, 33, 66]) {
if (vol > threshold + 1)
if (vol > 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 () => {
Array.from(App.windows)
.filter(w => w[1].closeOnUnfocus && w[1].closeOnUnfocus !== 'stay')
.forEach(w => {
.filter((w) => w[1].closeOnUnfocus && w[1].closeOnUnfocus !== 'stay')
.forEach((w) => {
App.closeWindow(w[0]);
});
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
import { Box } from 'resource:///com/github/Aylur/ags/widget.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 PopupWindow from '../misc/popup.js';
const DELAY = 2000;
const addPopup = (box, id) => {
if (!id)
if (!id) {
return;
}
const notif = Notifications.getNotification(id);
@ -20,32 +24,32 @@ const addPopup = (box, id) => {
});
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.show_all();
}
};
const handleDismiss = (box, id, force = false) => {
const notif = box.children.find(ch => ch._id === id);
if (!notif)
const notif = box.children.find((ch) => ch._id === id);
if (!notif) {
return;
}
// If notif isn't hovered or was closed, slide away
if (!notif._hovered || force) {
notif.slideAway('Left');
return;
}
// If notif is hovered, delay close
else if (notif._hovered) {
notif.interval = interval(2000, () => {
notif.interval = interval(DELAY, () => {
if (!notif._hovered && notif.interval) {
notif.slideAway('Left');
GLib.source_remove(notif.interval);
notif.interval = undefined;
return;
notif.interval = null;
}
});
}
@ -57,12 +61,14 @@ export default () => PopupWindow({
visible: true,
transition: 'none',
closeOnUnfocus: 'stay',
child: Box({
vertical: true,
connections: [
[Notifications, (box, id) => addPopup(box, id), 'notified'],
[Notifications, (box, id) => handleDismiss(box, id), 'dismissed'],
[Notifications, (box, id) => handleDismiss(box, id, true), 'closed'],
[Notifications, (s, id) => addPopup(s, id), 'notified'],
[Notifications, (s, id) => handleDismiss(s, id), 'dismissed'],
[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 { execAsync, timeout } from 'resource:///com/github/Aylur/ags/utils.js';
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 keycodes = Array.from(Array(249).keys());
const keycodes = Array.from(Array(KEY_N).keys());
execAsync([
'ydotool', 'key',
...keycodes.map(keycode => `${keycode}:0`),
...keycodes.map((keycode) => `${keycode}:0`),
]).catch(print);
};
const hidden = 340;
export default window => {
window.child.setCss(`margin-bottom: -${hidden}px;`);
export default (window) => {
window.child.setCss(`margin-bottom: -${HIDDEN_MARGIN}px;`);
const gesture = Gtk.GestureDrag.new(window);
window.setVisible = state => {
window.setVisible = (state) => {
if (state) {
window.visible = true;
window.setSlideDown();
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;
`);
}
else {
timeout(710, () => {
if (!Tablet.tabletMode)
timeout(ANIM_DURATION + 10, () => {
if (!Tablet.tabletMode) {
window.visible = false;
}
});
releaseAllKeys();
window.setSlideUp();
window.child.setCss(`
transition: margin-bottom 0.7s cubic-bezier(0.36, 0, 0.66, -0.56);
margin-bottom: -${hidden}px;
transition: margin-bottom 0.7s
cubic-bezier(0.36, 0, 0.66, -0.56);
margin-bottom: -${HIDDEN_MARGIN}px;
`);
}
};
gesture.signals = [];
window.killGestureSigs = () => {
gesture.signals.forEach(id => gesture.disconnect(id));
gesture.signals.forEach((id) => {
gesture.disconnect(id);
});
gesture.signals = [];
};
@ -53,7 +64,7 @@ export default window => {
// Begin drag
gesture.signals.push(
gesture.connect('drag-begin', () => {
Hyprland.sendMessage('j/cursorpos').then(out => {
Hyprland.sendMessage('j/cursorpos').then((out) => {
gesture.startY = JSON.parse(out).y;
});
}),
@ -62,15 +73,16 @@ export default window => {
// Update drag
gesture.signals.push(
gesture.connect('drag-update', () => {
Hyprland.sendMessage('j/cursorpos').then(out => {
Hyprland.sendMessage('j/cursorpos').then((out) => {
const currentY = JSON.parse(out).y;
const offset = gesture.startY - currentY;
if (offset < 0)
if (offset < 0) {
return;
}
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', () => {
window.child.setCss(`
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
gesture.signals.push(
gesture.connect('drag-begin', () => {
Hyprland.sendMessage('j/cursorpos').then(out => {
Hyprland.sendMessage('j/cursorpos').then((out) => {
gesture.startY = JSON.parse(out).y;
});
}),
@ -102,12 +114,13 @@ export default window => {
// Update drag
gesture.signals.push(
gesture.connect('drag-update', () => {
Hyprland.sendMessage('j/cursorpos').then(out => {
Hyprland.sendMessage('j/cursorpos').then((out) => {
const currentY = JSON.parse(out).y;
const offset = gesture.startY - currentY;
if (offset > 0)
if (offset > 0) {
return;
}
window.child.setCss(`
margin-bottom: ${offset}px;

View file

@ -8,48 +8,59 @@ import { defaultOskLayout, oskLayouts } from './keyboard-layouts.js';
const keyboardLayout = defaultOskLayout;
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,
children: [
CenterBox({
hpack: 'center',
start_widget: RoundedCorner('bottomright', `
background-color: ${color};
background-color: ${COLOR};
`),
center_widget: Box({
class_name: 'thingy',
css: `background: ${color};`,
css: `background: ${COLOR};`,
}),
end_widget: RoundedCorner('bottomleft', `
background-color: ${color};
background-color: ${COLOR};
`),
}),
CenterBox({
css: `background: ${color};`,
css: `background: ${COLOR};`,
class_name: 'osk',
hexpand: true,
start_widget: Box({
class_name: 'left-side side',
hpack: 'start',
vertical: true,
children: keyboardJson.keys.map((row, rowIndex) => Box({
vertical: true,
children: [
Box({
class_name: 'row',
children: [
Separator(4),
Separator(SPACING),
...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({
hpack: 'center',
vpack: 'center',
children: [
Box({
class_name: 'settings',
children: [
ToggleButton({
cursor: 'pointer',
class_name: 'button',
active: true,
vpack: 'center',
connections: [['toggled', self => {
self.toggleClassName('toggled', self.get_active());
window.exclusivity = self.get_active() ? 'exclusive' : 'normal';
connections: [['toggled', (self) => {
self.toggleClassName(
'toggled',
self.get_active(),
);
window.exclusivity = self.get_active() ?
'exclusive' :
'normal';
}]],
child: Label('Exclusive'),
}),
],
@ -81,17 +101,23 @@ export default window => Box({
class_name: 'right-side side',
hpack: 'end',
vertical: true,
children: keyboardJson.keys.map((row, rowIndex) => Box({
vertical: true,
children: [
Box({
hpack: 'end',
class_name: 'row',
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 Brightness from '../../services/brightness.js';
import { Box, EventBox, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
@ -19,59 +20,73 @@ const AltGr = Variable(false);
const RCtrl = 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
const LShift = Variable(false);
const RShift = 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 => {
if (key.keytype === 'normal')
return RegularKey(key);
else
return ModKey(key);
};
const ModKey = key => {
const ModKey = (key) => {
let Mod;
if (key.label === 'Super')
if (key.label === 'Super') {
Mod = Super;
}
// Differentiate left and right mods
else if (key.label === 'Shift' && key.keycode === 42)
else if (key.label === 'Shift' && key.keycode === LSHIFT_CODE) {
Mod = LShift;
}
else if (key.label === 'Alt' && key.keycode === 56)
else if (key.label === 'Alt' && key.keycode === LALT_CODE) {
Mod = LAlt;
}
else if (key.label === 'Ctrl' && key.keycode === 29)
else if (key.label === 'Ctrl' && key.keycode === LCTRL_CODE) {
Mod = LCtrl;
}
else if (key.label === 'Shift')
else if (key.label === 'Shift') {
Mod = RShift;
}
else if (key.label === 'AltGr')
else if (key.label === 'AltGr') {
Mod = AltGr;
}
else if (key.label === 'Ctrl')
else if (key.label === 'Ctrl') {
Mod = RCtrl;
}
const button = EventBox({
cursor: 'pointer',
class_name: 'key',
onPrimaryClickRelease: self => {
onPrimaryClickRelease: (self) => {
console.log('mod toggled');
execAsync(`ydotool key ${key.keycode}:${Mod.value ? 0 : 1}`);
self.child.toggleClassName('active', !Mod.value);
Mod.value = !Mod.value;
},
connections: [[NormalClick, self => {
connections: [[NormalClick, (self) => {
Mod.value = false;
self.child.toggleClassName('active', false);
execAsync(`ydotool key ${key.keycode}:0`);
@ -85,42 +100,49 @@ const ModKey = key => {
return Box({
children: [
button,
Separator(4),
Separator(SPACING),
],
});
};
const RegularKey = key => {
const RegularKey = (key) => {
const widget = EventBox({
cursor: 'pointer',
class_name: 'key',
child: Label({
class_name: `normal ${key.label}`,
label: key.label,
connections: [
[Shift, self => {
if (!key.labelShift)
[Shift, (self) => {
if (!key.labelShift) {
return;
}
self.label = Shift.value ? key.labelShift : key.label;
}],
[Caps, self => {
[Caps, (self) => {
if (key.label === 'Caps') {
self.toggleClassName('active', Caps.value);
return;
}
if (!key.labelShift)
if (!key.labelShift) {
return;
}
if (key.label.match(/[A-Za-z]/))
if (key.label.match(/[A-Za-z]/)) {
self.label = Caps.value ? key.labelShift : key.label;
}
}],
[AltGr, self => {
if (!key.labelAltGr)
[AltGr, (self) => {
if (!key.labelAltGr) {
return;
}
self.toggleClassName('altgr', AltGr.value);
self.label = AltGr.value ? key.labelAltGr : key.label;
@ -130,6 +152,7 @@ const RegularKey = key => {
});
const gesture = Gtk.GestureLongPress.new(widget);
gesture.delay_factor = 1.0;
// Long press
@ -138,22 +161,24 @@ const RegularKey = key => {
const x = pointer[1];
const y = pointer[2];
if ((!x || !y) || x === 0 && y === 0)
if ((!x || !y) || (x === 0 && y === 0)) {
return;
}
console.log('Not implemented yet');
// TODO: popup menu for accents
}, 'pressed');
// onPrimaryClickRelease
// OnPrimaryClickRelease
widget.connectTo(gesture, () => {
const pointer = gesture.get_point(null);
const x = pointer[1];
const y = pointer[2];
if ((!x || !y) || x === 0 && y === 0)
if ((!x || !y) || (x === 0 && y === 0)) {
return;
}
console.log('key clicked');
@ -165,7 +190,11 @@ const RegularKey = key => {
return Box({
children: [
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 { execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
import Tablet from '../../services/tablet.js';
import Gesture from './gesture.js';
import Keyboard from './keyboard.js';
// start ydotool daemon
// Start ydotool daemon
execAsync('ydotoold').catch(print);
// Window
@ -14,17 +15,20 @@ export default () => {
name: 'osk',
visible: false,
anchor: ['left', 'bottom', 'right'],
connections: [
[Tablet, (self, state) => {
self.setVisible(state);
}, 'osk-toggled'],
[Tablet, () => {
if (!Tablet.tabletMode && !Tablet.oskState)
if (!Tablet.tabletMode && !Tablet.oskState) {
window.visible = false;
}
}, 'mode-toggled'],
],
});
window.child = Keyboard(window);
return Gesture(window);

View file

@ -1,8 +1,11 @@
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import { Box, Icon, ProgressBar } from 'resource:///com/github/Aylur/ags/widget.js';
import { SpeakerIcon } from '../misc/audio-icons.js';
const AUDIO_MAX = 1.5;
export default () => Box({
className: 'osd',
@ -14,14 +17,20 @@ export default () => Box({
ProgressBar({
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;
const stack = self.get_parent().get_parent();
stack.shown = 'audio';
stack.resetTimer();
}, 'speaker-changed']],

View file

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

View file

@ -1,5 +1,7 @@
import { Box, Icon, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import Brightness from '../../services/brightness.js';
export default () => Box({
className: 'osd',
@ -7,10 +9,14 @@ export default () => Box({
Icon({
hpack: 'start',
icon: 'caps-lock-symbolic',
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();
stack.shown = 'caps';
stack.resetTimer();
}, 'caps']],

View file

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

View file

@ -1,4 +1,5 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import { timeout } from 'resource:///com/github/Aylur/ags/utils.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 Microphone from './mic.js';
const HIDE_DELAY = 2000;
export default () => {
let setup = 0;
@ -37,20 +40,23 @@ export default () => {
});
let timer;
stack.resetTimer = () => {
// Each osd calls resetTimer once at startup
if (setup <= stack.items.length + 1) {
++setup;
return;
}
App.openWindow('osd');
if (timer)
if (timer) {
GLib.source_remove(timer);
}
timer = timeout(2000, () => {
timer = timeout(HIDE_DELAY, () => {
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 { Box, Icon, ProgressBar } from 'resource:///com/github/Aylur/ags/widget.js';
import { MicIcon } from '../misc/audio-icons.js';
@ -6,6 +7,7 @@ import { MicIcon } from '../misc/audio-icons.js';
export default () => Box({
className: 'osd',
children: [
Icon({
hpack: 'start',
@ -14,14 +16,17 @@ export default () => Box({
ProgressBar({
vpack: 'center',
connections: [[Audio, self => {
if (!Audio.microphone)
connections: [[Audio, (self) => {
if (!Audio.microphone) {
return;
}
self.value = Audio.microphone ? Audio.microphone.volume : 0;
self.sensitive = !Audio.microphone?.stream.isMuted;
const stack = self.get_parent().get_parent();
stack.shown = 'mic';
stack.resetTimer();
}, 'microphone-changed']],

View file

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

View file

@ -1,10 +1,13 @@
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { Box } from 'resource:///com/github/Aylur/ags/widget.js';
import * as VARS from './variables.js';
const PADDING = 34;
const MARGIN = 9;
const DEFAULT_STYLE = `
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;
`;
@ -21,14 +24,16 @@ export const updateCurrentWorkspace = (main, highlighter) => {
const row = Math.floor((currentId - 1) / VARS.WORKSPACE_PER_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(`
${DEFAULT_STYLE}
margin-left: ${9 + currentIndex * (VARS.SCREEN.X * VARS.SCALE + 34)}px;
margin-top: ${9 + height}px;
margin-left: ${MARGIN + left}px;
margin-top: ${MARGIN + height}px;
`);
};

View file

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

View file

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

View file

@ -1,91 +1,65 @@
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 { WorkspaceDrop } from './dragndrop.js';
import * as VARS from './variables.js';
const DEFAULT_STYLE = `min-width: ${VARS.SCREEN.X * VARS.SCALE}px;
min-height: ${VARS.SCREEN.Y * VARS.SCALE}px;`;
const DEFAULT_STYLE = `
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 = [];
box.children.forEach(type => {
type.children.forEach(row => {
row.child.centerWidget.child.children.forEach(ch => {
box.children.forEach((type) => {
type.children.forEach((row) => {
row.child.centerWidget.child.children.forEach((ch) => {
children.push(ch);
});
});
});
box._workspaces = children.sort((a, b) => a._id - b._id);
}
};
export const WorkspaceRow = (className, i) => Revealer({
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 Workspace = (id, name, normal = true) => {
const fixed = Fixed();
if (!extra) {
workspace = Revealer({
const workspace = Revealer({
transition: 'slide_right',
transitionDuration: 500,
connections: [[Hyprland, box => {
connections: normal ?
[[Hyprland, (box) => {
const activeId = Hyprland.active.workspace.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: Box({
className: 'workspace',
css: DEFAULT_STYLE,
child: fixed,
}),
}),
});
}
// 'add' workspace
else {
workspace = Revealer({
transition: 'slide_right',
child: WorkspaceDrop({
child: Box({
css: `min-width: ${VARS.SCREEN.X * VARS.SCALE / 2}px;
min-height: ${VARS.SCREEN.Y * VARS.SCALE}px;`,
children: [
css: normal ?
DEFAULT_STYLE :
`
min-width: ${VARS.SCREEN.X * VARS.SCALE / 2}px;
min-height: ${VARS.SCREEN.Y * VARS.SCALE}px;
`,
children: normal ?
[fixed] :
[
fixed,
Label({
label: ' +',
@ -95,20 +69,74 @@ const Workspace = (id, name, extra = false) => {
}),
}),
});
}
workspace._id = id;
workspace._name = name;
workspace.getFixed = () => fixed;
return workspace;
};
export function updateWorkspaces(box) {
Hyprland.workspaces.forEach(ws => {
const currentWs = box._workspaces.find(ch => ch._id == ws.id);
export const WorkspaceRow = (className, i) => {
const addWorkspace = Workspace(
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) {
var type = 0;
var rowNo = 0;
let type = 0;
let rowNo = 0;
if (ws.id < 0) {
// This means it's a special workspace
@ -116,12 +144,19 @@ export function updateWorkspaces(box) {
}
else {
rowNo = Math.floor((ws.id - 1) / VARS.WORKSPACE_PER_ROW);
if (rowNo >= 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));
const wsQty = box.children[type].children.length;
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 : ''));
}
});
@ -131,4 +166,4 @@ export function updateWorkspaces(box) {
workspace.get_parent().reorder_child(workspace, i);
});
box.show_all();
}
};

View file

@ -1,4 +1,5 @@
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { CenterBox, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
@ -13,7 +14,8 @@ const PowermenuWidget = () => CenterBox({
startWidget: Button({
isButton: true,
className: 'shutdown',
onPrimaryClickRelease: () => execAsync(['systemctl', 'poweroff']).catch(print),
onPrimaryClickRelease: () => execAsync(['systemctl', 'poweroff'])
.catch(print),
child: Label({
label: '襤',
@ -23,7 +25,8 @@ const PowermenuWidget = () => CenterBox({
centerWidget: Button({
isButton: true,
className: 'reboot',
onPrimaryClickRelease: () => execAsync(['systemctl', 'reboot']).catch(print),
onPrimaryClickRelease: () => execAsync(['systemctl', 'reboot'])
.catch(print),
child: Label({
label: '勒',
@ -33,7 +36,8 @@ const PowermenuWidget = () => CenterBox({
endWidget: Button({
isButton: true,
className: 'logout',
onPrimaryClickRelease: () => Hyprland.sendMessage('dispatch exit').catch(print),
onPrimaryClickRelease: () => Hyprland.sendMessage('dispatch exit')
.catch(print),
child: 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 Network from 'resource:///com/github/Aylur/ags/service/network.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 { 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 Separator from '../misc/separator.js';
const SPACING = 28;
const ButtonStates = [];
const GridButton = ({
command = () => {},
secondaryCommand = () => {},
command = () => { /**/ },
secondaryCommand = () => { /**/ },
icon,
indicator,
menu,
} = {}) => {
const Activated = Variable(false);
ButtonStates.push(Activated);
// allow setting icon dynamically or statically
// Allow setting icon dynamically or statically
if (typeof icon === 'string') {
icon = Icon({
className: 'grid-label',
icon: icon,
connections: [[Activated, self => {
self.setCss(`color: ${Activated.value ? 'rgba(189, 147, 249, 0.8)' : 'unset'};`);
icon,
connections: [[Activated, (self) => {
self.setCss(`color: ${Activated.value ?
'rgba(189, 147, 249, 0.8)' :
'unset'};`);
}]],
});
}
@ -36,8 +42,10 @@ const GridButton = ({
className: 'grid-label',
connections: [
icon,
[Activated, self => {
self.setCss(`color: ${Activated.value ? 'rgba(189, 147, 249, 0.8)' : 'unset'};`);
[Activated, (self) => {
self.setCss(`color: ${Activated.value ?
'rgba(189, 147, 249, 0.8)' :
'unset'};`);
}],
],
});
@ -70,44 +78,60 @@ const GridButton = ({
EventBox({
className: 'left-part',
onPrimaryClickRelease: () => {
if (!Activated.value)
command();
else
if (Activated.value) {
secondaryCommand();
}
else {
command();
}
},
child: icon,
}),
EventBox({
className: 'right-part',
onPrimaryClickRelease: () => {
ButtonStates.forEach(state => {
if (state !== Activated)
ButtonStates.forEach((state) => {
if (state !== Activated) {
state.value = false;
}
});
Activated.value = !Activated.value;
},
onHover: self => {
onHover: (self) => {
if (menu) {
const rowMenu = self.get_parent().get_parent()
.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.show_all();
}
}
},
child: Icon({
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);`);
}]],
icon: `${App.configDir }/icons/down-large.svg`,
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,
],
});
return widget;
};
const Row = ({ buttons } = {}) => {
const widget = Box({
vertical: true,
children: [
Box({
className: 'button-row',
hpack: 'center',
}),
Box(),
],
});
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(Separator(28));
}
else {
widget.children[0].add(buttons[i]);
widget.children[0].add(Separator(SPACING));
}
}
return widget;
};
@ -148,24 +176,40 @@ const FirstRow = () => Row({
GridButton({
command: () => Network.toggleWifi(),
secondaryCommand: () => {
execAsync(['bash', '-c', 'nm-connection-editor'])
.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({
className: 'menu',
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,
on_clicked: () => execAsync(`nmcli device wifi connect ${ap.bssid}`).catch(print),
on_clicked: () => {
execAsync(`nmcli device wifi
connect ${ap.bssid}`).catch(print);
},
child: Box({
children: [
Icon(ap.iconName),
Label(ap.ssid || ''),
ap.active && Icon({
icon: 'object-select-symbolic',
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'])
.catch(print);
},
secondaryCommand: () => {
execAsync(['bash', '-c', 'blueberry'])
.catch(print);
},
icon: [Bluetooth, self => {
icon: [Bluetooth, (self) => {
if (Bluetooth.enabled) {
self.icon = 'bluetooth-active-symbolic';
execAsync(['bash', '-c', 'echo 󰂯 > $HOME/.config/.bluetooth'])
.catch(print);
execAsync(['bash', '-c',
'echo 󰂯 > $HOME/.config/.bluetooth']).catch(print);
}
else {
self.icon = 'bluetooth-disabled-symbolic';
execAsync(['bash', '-c', 'echo 󰂲 > $HOME/.config/.bluetooth'])
.catch(print);
execAsync(['bash', '-c',
'echo 󰂲 > $HOME/.config/.bluetooth']).catch(print);
}
}, 'changed'],
indicator: [Bluetooth, self => {
if (Bluetooth.connectedDevices[0])
indicator: [Bluetooth, (self) => {
if (Bluetooth.connectedDevices[0]) {
self.label = String(Bluetooth.connectedDevices[0]);
else
}
else {
self.label = 'Disconnected';
}
}, 'changed'],
}),
// TODO: replace with vpn
GridButton({
command: () => {
execAsync(['bash', '-c', '$AGS_PATH/qs-toggles.sh toggle-radio'])
.catch(print);
execAsync(['bash', '-c',
'$AGS_PATH/qs-toggles.sh toggle-radio']).catch(print);
},
secondaryCommand: () => {
execAsync(['notify-send', 'set this up moron'])
.catch(print);
},
icon: [Network, self => {
if (Network.wifi.enabled)
icon: [Network, (self) => {
if (Network.wifi.enabled) {
self.icon = 'airplane-mode-disabled-symbolic';
else
}
else {
self.icon = 'airplane-mode-symbolic';
}
}, 'changed'],
}),
@ -230,11 +283,10 @@ const FirstRow = () => Row({
const SecondRow = () => Row({
buttons: [
GridButton({
command: () => {
execAsync(['pactl', 'set-sink-mute', '@DEFAULT_SINK@', 'toggle'])
.catch(print);
execAsync(['pactl', 'set-sink-mute',
'@DEFAULT_SINK@', 'toggle']).catch(print);
},
secondaryCommand: () => {
@ -242,15 +294,15 @@ const SecondRow = () => Row({
.catch(print);
},
icon: [SpeakerIcon, self => {
icon: [SpeakerIcon, (self) => {
self.icon = SpeakerIcon.value;
}],
}),
GridButton({
command: () => {
execAsync(['pactl', 'set-source-mute', '@DEFAULT_SOURCE@', 'toggle'])
.catch(print);
execAsync(['pactl', 'set-source-mute',
'@DEFAULT_SOURCE@', 'toggle']).catch(print);
},
secondaryCommand: () => {
@ -258,7 +310,7 @@ const SecondRow = () => Row({
.catch(print);
},
icon: [MicIcon, self => {
icon: [MicIcon, (self) => {
self.icon = MicIcon.value;
}],
}),
@ -271,7 +323,6 @@ const SecondRow = () => Row({
secondaryCommand: () => App.openWindow('powermenu'),
icon: 'system-lock-screen-symbolic',
}),
],
});

View file

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

View file

@ -1,6 +1,8 @@
import Audio from 'resource:///com/github/Aylur/ags/service/audio.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';
@ -14,6 +16,7 @@ export default () => Box({
className: 'slider',
vpack: 'start',
hpack: 'center',
children: [
Icon({
size: 26,
@ -24,17 +27,25 @@ export default () => Box({
Slider({
cursor: 'pointer',
vpack: 'center',
max: 0.999,
draw_value: false,
onChange: ({ value }) => {
Audio.speaker.volume = value;
},
connections: [
[Audio, slider => {
[Audio, (slider) => {
slider.value = Audio.speaker?.volume;
}, 'speaker-changed'],
['button-press-event', s => { s.cursor = 'grabbing'; }],
['button-release-event', s => { s.cursor = 'pointer'; }],
['button-press-event', (s) => {
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',
vpack: 'start',
hpack: 'center',
children: [
Icon({
className: 'slider-label',
@ -52,16 +64,24 @@ export default () => Box({
Slider({
cursor: 'pointer',
vpack: 'center',
onChange: ({ value }) => Brightness.screen = value,
draw_value: false,
onChange: ({ value }) => {
Brightness.screen = value;
},
connections: [
[Brightness, slider => {
[Brightness, (slider) => {
slider.value = Brightness.screen;
}, 'screen'],
['button-press-event', s => { s.cursor = 'grabbing'; }],
['button-release-event', s => { s.cursor = 'pointer'; }],
['button-press-event', (s) => {
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 Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import { CenterBox, Icon, ToggleButton } from 'resource:///com/github/Aylur/ags/widget.js';
export default rev => CenterBox({
export default (rev) => CenterBox({
center_widget: ToggleButton({
cursor: 'pointer',
setup: self => {
setup: (self) => {
// Open at startup if there are players
const id = Mpris.connect('changed', () => {
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()) {
self.get_children()[0]
.setCss('-gtk-icon-transform: rotate(0deg);');
@ -28,7 +30,7 @@ export default rev => CenterBox({
}]],
child: Icon({
icon: App.configDir + '/icons/down-large.svg',
icon: `${App.configDir }/icons/down-large.svg`,
className: 'arrow',
css: '-gtk-icon-transform: rotate(180deg);',
}),

View file

@ -1,5 +1,6 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
import Brightness from '../services/brightness.js';
@ -28,27 +29,33 @@ export default () => {
name: 'oskOn',
gesture: 'DU',
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({
name: 'oskOff',
gesture: 'UD',
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({
name: 'swipeSpotify1',
gesture: 'LR',
edge: 'L',
command: () => Hyprland.sendMessage('dispatch togglespecialworkspace spot'),
command: () => Hyprland.sendMessage(
'dispatch togglespecialworkspace spot',
),
});
TouchGestures.addGesture({
name: 'swipeSpotify2',
gesture: 'RL',
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",
"eslint": "^8.52.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 CAPS = 'input0::capslock';
const INTERVAL = 500;
class Brightness extends Service {
static {
Service.register(this, {
'screen': ['float'],
'kbd': ['float'],
'caps': ['int'],
screen: ['float'],
kbd: ['float'],
caps: ['int'],
});
}
_kbd = 0;
_screen = 0;
_caps = 0;
#kbd = 0;
#screen = 0;
#caps = 0;
get kbd() { return this._kbd; }
get screen() { return this._screen; }
get caps() { return this._caps; }
get kbd() {
return this.#kbd;
}
get screen() {
return this.#screen;
}
get caps() {
return this.#caps;
}
set kbd(value) {
this.#kbd = value;
// TODO
}
set screen(percent) {
if (percent < 0)
if (percent < 0) {
percent = 0;
}
if (percent > 1)
if (percent > 1) {
percent = 1;
}
execAsync(`brightnessctl s ${percent * 100}% -q`)
.then(() => {
this._screen = percent;
this.emit('screen', this._screen);
this.#screen = percent;
this.emit('screen', this.#screen);
})
.catch(console.error);
}
@ -44,29 +56,31 @@ class Brightness extends Service {
constructor() {
super();
try {
this.monitorKbdState();
this._caps = Number(exec(`brightnessctl -d ${CAPS} g`));
this._screen = Number(exec('brightnessctl g')) / Number(exec('brightnessctl m'));
} catch (error) {
this.#monitorKbdState();
this.#caps = Number(exec(`brightnessctl -d ${CAPS} g`));
this.#screen = Number(exec('brightnessctl g')) /
Number(exec('brightnessctl m'));
}
catch (error) {
console.error('missing dependancy: brightnessctl');
}
}
fetchCapsState() {
execAsync(`brightnessctl -d ${CAPS} g`)
.then(out => {
this._caps = out;
this.emit('caps', this._caps);
.then((out) => {
this.#caps = out;
this.emit('caps', this.#caps);
})
.catch(logError);
}
monitorKbdState() {
#monitorKbdState() {
Variable(0, {
poll: [500, `brightnessctl -d ${KBD} g`, out => {
if (out !== this._kbd) {
this._kbd = out;
this.emit('kbd', this._kbd);
poll: [INTERVAL, `brightnessctl -d ${KBD} g`, (out) => {
if (out !== this.#kbd) {
this.#kbd = out;
this.emit('kbd', this.#kbd);
}
}],
});
@ -74,4 +88,5 @@ class Brightness extends Service {
}
const brightnessService = new Brightness();
export default brightnessService;

View file

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

View file

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

View file

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