refactor(ags): use aylur's lint settings

This commit is contained in:
matt1432 2023-10-20 23:11:21 -04:00
parent afe7f10128
commit 127974d0c2
46 changed files with 2732 additions and 2748 deletions

View file

@ -4,7 +4,7 @@ import Closer from './js/misc/closer.js';
import Powermenu from './js/powermenu.js'; import Powermenu from './js/powermenu.js';
import { Bar } from './js/bar/main.js'; import { Bar } from './js/bar/main.js';
import NotifCenter from './js/notifications/center.js'; import NotifCenter from './js/notifications/center.js';
import NotifPopups from './js/notifications/popup.js' import NotifPopups from './js/notifications/popup.js';
import Calendar from './js/date.js'; import Calendar from './js/date.js';
import QuickSettings from './js/quick-settings/main.js'; import QuickSettings from './js/quick-settings/main.js';
import Overview from './js/overview/main.js'; import Overview from './js/overview/main.js';
@ -21,28 +21,28 @@ Utils.execAsync(['bash', '-c', '$AGS_PATH/startup.sh']).catch(print);
export default { export default {
style: css, style: css,
notificationPopupTimeout: 5000, notificationPopupTimeout: 5000,
cacheNotificationActions: true, cacheNotificationActions: true,
closeWindowDelay: { closeWindowDelay: {
'quick-settings': 500, 'quick-settings': 500,
'notification-center': 500, 'notification-center': 500,
'calendar': 500, 'calendar': 500,
'powermenu': 500, 'powermenu': 500,
'overview': 500, 'overview': 500,
'applauncher': 500, 'applauncher': 500,
}, },
windows: [ windows: [
Powermenu(), Powermenu(),
Bar(), Bar(),
Closer(), Closer(),
NotifCenter(), NotifCenter(),
NotifPopups(), NotifPopups(),
Calendar(), Calendar(),
QuickSettings(), QuickSettings(),
Overview(), Overview(),
AppLauncher(), AppLauncher(),
Corners.Bottomleft(), Corners.Bottomleft(),
Corners.Bottomright(), Corners.Bottomright(),
], ],
}; };

View file

@ -5,118 +5,118 @@ import Separator from '../misc/separator.js';
import PopupWindow from '../misc/popup.js'; import PopupWindow from '../misc/popup.js';
const icons = { const icons = {
apps: { apps: {
apps: 'view-app-grid-symbolic', apps: 'view-app-grid-symbolic',
search: 'preferences-system-search-symbolic', search: 'preferences-system-search-symbolic',
} },
}; };
const AppItem = (app, window) => { const AppItem = (app, window) => {
if (app.app.get_string('Icon') == 'Nextcloud') if (app.app.get_string('Icon') == 'Nextcloud')
return; return;
return Button({ return Button({
className: 'app', className: 'app',
connections: [['clicked', () => { connections: [['clicked', () => {
App.closeWindow(window); App.closeWindow(window);
Utils.exec(`hyprctl dispatch exec ${app.executable}`); Utils.exec(`hyprctl dispatch exec ${app.executable}`);
// TODO: focus on new client. Is this only needed after launch? // TODO: focus on new client. Is this only needed after launch?
++app.frequency; ++app.frequency;
}]], }]],
child: Box({ child: Box({
children: [ children: [
Icon({ Icon({
icon: app.app.get_string('Icon'), icon: app.app.get_string('Icon'),
size: 42, size: 42,
}),
Box({
vertical: true,
children: [
Label({
className: 'title',
label: app.name,
xalign: 0,
valign: 'center',
ellipsize: 3,
}),
Label({
className: 'description',
label: app.description || '',
wrap: true,
xalign: 0,
justification: 'left',
valign: 'center',
}),
],
}),
],
}), }),
Box({ });
vertical: true,
children: [
Label({
className: 'title',
label: app.name,
xalign: 0,
valign: 'center',
ellipsize: 3,
}),
Label({
className: 'description',
label: app.description || '',
wrap: true,
xalign: 0,
justification: 'left',
valign: 'center',
}),
],
}),
],
}),
});
}; };
const Applauncher = ({ windowName = 'applauncher' } = {}) => { const Applauncher = ({ windowName = 'applauncher' } = {}) => {
const list = Box({ vertical: true }); const list = Box({ vertical: true });
const placeholder = Label({ const placeholder = Label({
label: " Couldn't find a match", label: " Couldn't find a match",
className: 'placeholder', className: 'placeholder',
}); });
const entry = Entry({ const entry = Entry({
hexpand: true, hexpand: true,
placeholderText: 'Search', placeholderText: 'Search',
onAccept: ({ text }) => { onAccept: ({ text }) => {
const list = Applications.query(text); const list = Applications.query(text);
if (list[0]) { if (list[0]) {
App.toggleWindow(windowName); App.toggleWindow(windowName);
list[0].launch(); list[0].launch();
} }
}, },
onChange: ({ text }) => { onChange: ({ text }) => {
list.children = Applications.query(text).map(app => [ list.children = Applications.query(text).map(app => [
Separator(4), Separator(4),
AppItem(app, windowName), AppItem(app, windowName),
]).flat(); ]).flat();
list.add(Separator(4)); list.add(Separator(4));
list.show_all(); list.show_all();
placeholder.visible = list.children.length === 1; placeholder.visible = list.children.length === 1;
}, },
}); });
return Box({ return Box({
className: 'applauncher', className: 'applauncher',
properties: [['list', list]], properties: [['list', list]],
vertical: true, vertical: true,
children: [
Box({
className: 'header',
children: [ children: [
Icon(icons.apps.search), Box({
entry, className: 'header',
children: [
Icon(icons.apps.search),
entry,
],
}),
Scrollable({
hscroll: 'never',
child: Box({
vertical: true,
children: [list, placeholder],
}),
}),
], ],
}), connections: [[App, (_b, name, visible) => {
Scrollable({ if (name !== windowName)
hscroll: 'never', return;
child: Box({
vertical: true,
children: [list, placeholder],
}),
}),
],
connections: [[App, (_b, name, visible) => {
if (name !== windowName)
return;
entry.set_text('-'); // force onChange entry.set_text('-'); // force onChange
entry.set_text(''); entry.set_text('');
if (visible) if (visible)
entry.grab_focus(); entry.grab_focus();
}]], }]],
}); });
}; };
export default () => PopupWindow({ export default () => PopupWindow({
name: 'applauncher', name: 'applauncher',
focusable: true, focusable: true,
child: Applauncher(), child: Applauncher(),
}); });

View file

@ -5,52 +5,52 @@ import Separator from '../misc/separator.js';
import EventBox from '../misc/cursorbox.js'; import EventBox from '../misc/cursorbox.js';
const items = { const items = {
101: 'audio-volume-overamplified-symbolic', 101: 'audio-volume-overamplified-symbolic',
67: 'audio-volume-high-symbolic', 67: 'audio-volume-high-symbolic',
34: 'audio-volume-medium-symbolic', 34: 'audio-volume-medium-symbolic',
1: 'audio-volume-low-symbolic', 1: 'audio-volume-low-symbolic',
0: 'audio-volume-muted-symbolic', 0: 'audio-volume-muted-symbolic',
}; };
const SpeakerIndicator = props => Icon({ const SpeakerIndicator = props => Icon({
...props, ...props,
icon: '', icon: '',
connections: [[Audio, self => { connections: [[Audio, self => {
if (!Audio.speaker) if (!Audio.speaker)
return; return;
if (Audio.speaker.stream.isMuted) { if (Audio.speaker.stream.isMuted) {
self.icon = items[0]; self.icon = items[0];
} }
else { else {
const vol = Audio.speaker.volume * 100; const vol = Audio.speaker.volume * 100;
for (const threshold of [-1, 0, 33, 66, 100]) { for (const threshold of [-1, 0, 33, 66, 100]) {
if (vol > threshold + 1) if (vol > threshold + 1)
self.icon = items[threshold + 1]; self.icon = items[threshold + 1];
} }
} }
}, 'speaker-changed']], }, 'speaker-changed']],
}); });
const SpeakerPercentLabel = props => Label({ const SpeakerPercentLabel = props => Label({
...props, ...props,
connections: [[Audio, label => { connections: [[Audio, label => {
if (Audio.speaker) if (Audio.speaker)
label.label = Math.round(Audio.speaker.volume * 100) + '%'; label.label = Math.round(Audio.speaker.volume * 100) + '%';
}, 'speaker-changed']], }, 'speaker-changed']],
}); });
export default () => EventBox({ export default () => EventBox({
onPrimaryClickRelease: 'pavucontrol', onPrimaryClickRelease: 'pavucontrol',
className: 'toggle-off', className: 'toggle-off',
child: Box({ child: Box({
className: 'audio', className: 'audio',
children: [ children: [
SpeakerIndicator(), SpeakerIndicator(),
Separator(5), Separator(5),
SpeakerPercentLabel(), SpeakerPercentLabel(),
], ],
}), }),
}); });

View file

@ -4,56 +4,56 @@ const { Label, Icon, Stack, Box } = Widget;
import Separator from '../misc/separator.js'; import Separator from '../misc/separator.js';
const icons = charging => ([ const icons = charging => ([
...Array.from({ length: 10 }, (_, i) => i * 10).map(i => ([ ...Array.from({ length: 10 }, (_, i) => i * 10).map(i => ([
`${i}`, Icon({ `${i}`, Icon({
className: `${i} ${charging ? 'charging' : 'discharging'}`, className: `${i} ${charging ? 'charging' : 'discharging'}`,
icon: `battery-level-${i}${charging ? '-charging' : ''}-symbolic`, icon: `battery-level-${i}${charging ? '-charging' : ''}-symbolic`,
}), }),
])), ])),
['100', Icon({ ['100', Icon({
className: `100 ${charging ? 'charging' : 'discharging'}`, className: `100 ${charging ? 'charging' : 'discharging'}`,
icon: `battery-level-100${charging ? '-charged' : ''}-symbolic`, icon: `battery-level-100${charging ? '-charged' : ''}-symbolic`,
})], })],
]); ]);
const Indicators = charging => Stack({ const Indicators = charging => Stack({
items: icons(charging), items: icons(charging),
connections: [[Battery, stack => { connections: [[Battery, stack => {
stack.shown = `${Math.floor(Battery.percent / 10) * 10}`; stack.shown = `${Math.floor(Battery.percent / 10) * 10}`;
}]], }]],
}); });
const Indicator = ({ const Indicator = ({
charging = Indicators(true), charging = Indicators(true),
discharging = Indicators(false), discharging = Indicators(false),
...props ...props
} = {}) => Stack({ } = {}) => Stack({
...props, ...props,
className: 'battery-indicator', className: 'battery-indicator',
items: [ items: [
['true', charging], ['true', charging],
['false', discharging], ['false', discharging],
], ],
connections: [[Battery, stack => { connections: [[Battery, stack => {
stack.shown = `${Battery.charging || Battery.charged}`; stack.shown = `${Battery.charging || Battery.charged}`;
stack.toggleClassName('charging', Battery.charging); stack.toggleClassName('charging', Battery.charging);
stack.toggleClassName('charged', Battery.charged); stack.toggleClassName('charged', Battery.charged);
stack.toggleClassName('low', Battery.percent < 20); stack.toggleClassName('low', Battery.percent < 20);
}]], }]],
}); });
const LevelLabel = props => Label({ const LevelLabel = props => Label({
...props, ...props,
className: 'label', className: 'label',
connections: [[Battery, self => self.label = `${Battery.percent}%`]], connections: [[Battery, self => self.label = `${Battery.percent}%`]],
}); });
export default () => Box({ export default () => Box({
className: 'toggle-off battery', className: 'toggle-off battery',
children: [ children: [
Indicator(), Indicator(),
Separator(5), Separator(5),
LevelLabel(), LevelLabel(),
], ],
}); });

View file

@ -6,30 +6,28 @@ import Heart from './heart.js';
export default () => Overlay({ export default () => Overlay({
tooltipText: 'Brightness', tooltipText: 'Brightness',
child: ProgressBar({ child: ProgressBar({
className: 'toggle-off brightness', className: 'toggle-off brightness',
connections: [ connections: [
[200, self => { [200, self => {
Utils.execAsync('brightnessctl get').then(out => { Utils.execAsync('brightnessctl get').then(out => {
let br = out / 255; const br = out / 255;
if (br > 0.33) { if (br > 0.33)
self.value = br; self.value = br;
} else
else { self.value = 0.33;
self.value = 0.33; }).catch(print);
} }],
}).catch(print); ],
}],
],
}),
overlays: [
Box({
style: 'color: #CBA6F7;',
children: [
Separator(25),
Heart(),
],
}), }),
], overlays: [
Box({
style: 'color: #CBA6F7;',
children: [
Separator(25),
Heart(),
],
}),
],
}); });

View file

@ -14,26 +14,25 @@ const ClockModule = ({
...props, ...props,
className: 'clock', className: 'clock',
connections: [ connections: [
[interval, self => { [interval, self => {
var time = DateTime.new_now_local(); var time = DateTime.new_now_local();
self.label = time.format('%a. ') + self.label = time.format('%a. ') +
time.get_day_of_month() + time.get_day_of_month() +
time.format(' %b. %H:%M'); time.format(' %b. %H:%M');
}], }],
], ],
}); });
export default () => EventBox({ export default () => EventBox({
className: 'toggle-off', className: 'toggle-off',
onPrimaryClickRelease: () => App.toggleWindow('calendar'), onPrimaryClickRelease: () => App.toggleWindow('calendar'),
connections: [ connections: [
[App, (self, windowName, visible) => { [App, (self, windowName, visible) => {
if (windowName == 'calendar') { if (windowName == 'calendar')
self.toggleClassName('toggle-on', visible); self.toggleClassName('toggle-on', visible);
} }],
}], ],
], child: Box({
child: Box({ child: ClockModule({}),
child: ClockModule({}), }),
}),
}); });

View file

@ -3,7 +3,7 @@ const { Label } = Widget;
export default () => Label({ export default () => Label({
style: 'color: #CBA6F7; font-size: 18px', style: 'color: #CBA6F7; font-size: 18px',
truncate: 'end', truncate: 'end',
binds: [['label', Hyprland.active.client, 'title']], binds: [['label', Hyprland.active.client, 'title']],
}); });

View file

@ -14,82 +14,82 @@ import Gesture from './gesture.js';
// with hyprctl if in fullscreen or not until fullscreen // with hyprctl if in fullscreen or not until fullscreen
// status changes again // status changes again
export default (props) => Overlay({ export default props => Overlay({
overlays: [ overlays: [
RoundedCorner('topleft', { className: 'corner' }), RoundedCorner('topleft', { className: 'corner' }),
RoundedCorner('topright', { className: 'corner' }), RoundedCorner('topright', { className: 'corner' }),
],
child: Box({
style: 'min-height: 1px',
hexpand: true,
vertical: true,
children: [
Widget.Revealer({
transition: 'slide_down',
setup: self => self.revealChild = true,
properties: [['timeouts', []]],
connections: [[Hyprland, self => {
Utils.execAsync('hyprctl activewindow -j').then(out => {
let client = JSON.parse(out);
if (client.fullscreen === Revealed.value)
return;
Revealed.value = client.fullscreen;
if (Revealed.value) {
setTimeout(() => {
if (Revealed.value)
self.revealChild = false
}, 2000);
}
else {
self.revealChild = true;
}
}).catch(print);
}]],
child: Gesture({
onHover: () => Hovering.value = true,
onHoverLost: self => {
Hovering.value = false;
if (Revealed.value) {
setTimeout(() => {
if (!Hovering.value) {
self.get_parent().get_parent().children[1].revealChild = true;
self.get_parent().revealChild = false;
}
}, 2000);
}
},
...props,
}),
}),
Widget.Revealer({
connections: [[Revealed, self => {
if (Revealed.value) {
setTimeout(() => {
if (Revealed.value)
self.revealChild = true;
}, 2000);
}
else {
self.revealChild = false;
}
}]],
child: EventBox({
onHover: self => {
Hovering.value = true;
self.get_parent().get_parent().children[0].revealChild = true;
self.get_parent().revealChild = false;
},
child: Box({
style: 'min-height: 50px;',
}),
}),
}),
], ],
}),
child: Box({
style: 'min-height: 1px',
hexpand: true,
vertical: true,
children: [
Widget.Revealer({
transition: 'slide_down',
setup: self => self.revealChild = true,
properties: [['timeouts', []]],
connections: [[Hyprland, self => {
Utils.execAsync('hyprctl activewindow -j').then(out => {
const client = JSON.parse(out);
if (client.fullscreen === Revealed.value)
return;
Revealed.value = client.fullscreen;
if (Revealed.value) {
Utils.timeout(2000, () => {
if (Revealed.value)
self.revealChild = false;
});
}
else {
self.revealChild = true;
}
}).catch(print);
}]],
child: Gesture({
onHover: () => Hovering.value = true,
onHoverLost: self => {
Hovering.value = false;
if (Revealed.value) {
Utils.timeout(2000, () => {
if (!Hovering.value) {
self.get_parent().get_parent().children[1].revealChild = true;
self.get_parent().revealChild = false;
}
});
}
},
...props,
}),
}),
Widget.Revealer({
connections: [[Revealed, self => {
if (Revealed.value) {
Utils.timeout(2000, () => {
if (Revealed.value)
self.revealChild = true;
});
}
else {
self.revealChild = false;
}
}]],
child: EventBox({
onHover: self => {
Hovering.value = true;
self.get_parent().get_parent().children[0].revealChild = true;
self.get_parent().revealChild = false;
},
child: Box({
style: 'min-height: 50px;',
}),
}),
}),
],
}),
}); });

View file

@ -5,24 +5,23 @@ import Gtk from 'gi://Gtk';
export default ({ export default ({
child, child,
...props ...props
}) => { }) => {
let widget = EventBox({ const widget = EventBox({
...props, ...props,
}); });
let gesture = Gtk.GestureSwipe.new(widget); const gesture = Gtk.GestureSwipe.new(widget);
widget.add(CenterBox({ widget.add(CenterBox({
children: [ child ], children: [child],
connections: [[gesture, () => { connections: [[gesture, () => {
const velocity = gesture.get_velocity()[1]; const velocity = gesture.get_velocity()[1];
if (velocity < -100) if (velocity < -100)
App.openWindow('quick-settings'); App.openWindow('quick-settings');
}, 'update']],
}));
}, 'update']], return widget;
}));
return widget;
}; };

View file

@ -6,24 +6,24 @@ import EventBox from '../misc/cursorbox.js';
export default () => EventBox({ export default () => EventBox({
halign: 'center', halign: 'center',
onPrimaryClickRelease: () => { onPrimaryClickRelease: () => {
execAsync(['bash', '-c', '$AGS_PATH/heart.sh toggle']).catch(print); execAsync(['bash', '-c', '$AGS_PATH/heart.sh toggle']).catch(print);
}, },
child: Box({ child: Box({
className: 'heart-toggle', className: 'heart-toggle',
vertical: false, vertical: false,
child: Label({ child: Label({
label: '', label: '',
setup: self => { setup: self => {
subprocess( subprocess(
['bash', '-c', 'tail -f /home/matt/.config/.heart'], ['bash', '-c', 'tail -f /home/matt/.config/.heart'],
(output) => self.label = ' ' + output, output => self.label = ' ' + output,
); );
}, },
}),
}), }),
}),
}); });

View file

@ -1,30 +1,30 @@
import { Hyprland, Utils, Widget } from '../../imports.js'; import { Hyprland, Utils, Widget } from '../../imports.js';
const { Label, Box, Icon } = Widget; const { Label, Box, Icon } = Widget;
const DEFAULT_KB = "at-translated-set-2-keyboard"; const DEFAULT_KB = 'at-translated-set-2-keyboard';
export default () => Box({ export default () => Box({
className: 'toggle-off', className: 'toggle-off',
children: [ children: [
Icon({ Icon({
icon: 'input-keyboard-symbolic', icon: 'input-keyboard-symbolic',
style: 'margin-right: 4px;', style: 'margin-right: 4px;',
}), }),
Label({ Label({
connections: [[Hyprland, (self, _n, layout) => { connections: [[Hyprland, (self, _n, layout) => {
if (!layout) { if (!layout) {
let obj = Utils.exec('hyprctl devices -j') const obj = Utils.exec('hyprctl devices -j');
let keyboards = JSON.parse(obj)['keyboards']; const keyboards = JSON.parse(obj)['keyboards'];
let kb = keyboards.find(val => val.name === DEFAULT_KB); const kb = keyboards.find(val => val.name === DEFAULT_KB);
layout = kb['active_keymap']; layout = kb['active_keymap'];
self.label = layout; self.label = layout;
} }
else { else {
self.label = layout; self.label = layout;
} }
}, 'keyboard-layout']], }, 'keyboard-layout']],
}), }),
], ],
}); });

View file

@ -18,70 +18,70 @@ import Revealer from './fullscreen.js';
export const Bar = () => Window({ export const Bar = () => Window({
name: 'bar', name: 'bar',
layer: 'overlay', layer: 'overlay',
anchor: [ 'top', 'left', 'right' ], anchor: ['top', 'left', 'right'],
exclusive: true, exclusive: true,
child: Revealer({ child: Revealer({
child: CenterBox({ child: CenterBox({
className: 'bar', className: 'bar',
vertical: false, vertical: false,
startWidget: Box({ startWidget: Box({
halign: 'start', halign: 'start',
children: [ children: [
OskToggle(), OskToggle(),
Separator(12), Separator(12),
TabletToggle(), TabletToggle(),
Separator(12), Separator(12),
SysTray(), SysTray(),
Audio(), Audio(),
Separator(12), Separator(12),
Brightness(), Brightness(),
Separator(12), Separator(12),
Workspaces(), Workspaces(),
], ],
}), }),
centerWidget: Box({ centerWidget: Box({
children: [ children: [
CurrentWindow(), CurrentWindow(),
], ],
}), }),
endWidget: Box({ endWidget: Box({
halign: 'end', halign: 'end',
children: [ children: [
Battery(), Battery(),
Separator(12), Separator(12),
//KeyboardLayout(), //KeyboardLayout(),
//Separator(12), //Separator(12),
Clock(), Clock(),
Separator(12), Separator(12),
NotifButton(), NotifButton(),
Separator(12), Separator(12),
QsToggle(), QsToggle(),
], ],
}), }),
}),
}), }),
}),
}); });

View file

@ -6,42 +6,40 @@ import EventBox from '../misc/cursorbox.js';
export default () => EventBox({ export default () => EventBox({
className: 'toggle-off', className: 'toggle-off',
onPrimaryClickRelease: () => App.toggleWindow('notification-center'), onPrimaryClickRelease: () => App.toggleWindow('notification-center'),
connections: [[App, (self, windowName, visible) => { connections: [[App, (self, windowName, visible) => {
if (windowName == 'notification-center') if (windowName == 'notification-center')
self.toggleClassName('toggle-on', visible); self.toggleClassName('toggle-on', visible);
}]], }]],
child: Box({ child: Box({
className: 'notif-panel', className: 'notif-panel',
vertical: false, vertical: false,
children: [ children: [
Separator(28), Separator(28),
Icon({ Icon({
connections: [[Notifications, self => { connections: [[Notifications, self => {
if (Notifications.dnd) { if (Notifications.dnd) {
self.icon = 'notification-disabled-symbolic' self.icon = 'notification-disabled-symbolic';
} }
else { else {
if (Notifications.notifications.length > 0) { if (Notifications.notifications.length > 0)
self.icon = 'notification-new-symbolic' self.icon = 'notification-new-symbolic';
} else
else { self.icon = 'notification-symbolic';
self.icon = 'notification-symbolic' }
} }]],
} }),
}]],
}),
Separator(8), Separator(8),
Label({
binds: [
['label', Notifications, 'notifications', n => String(n.length)],
],
}),
Label({
binds: [
['label', Notifications, 'notifications', n => String(n.length)],
], ],
}), }),
],
}),
}); });

View file

@ -6,29 +6,29 @@ import EventBox from '../misc/cursorbox.js';
export default () => EventBox({ export default () => EventBox({
className: 'toggle-off', className: 'toggle-off',
setup: self => { setup: self => {
subprocess( subprocess(
['bash', '-c', '$AGS_PATH/osk-toggle.sh getState'], ['bash', '-c', '$AGS_PATH/osk-toggle.sh getState'],
(output) => self.toggleClassName('toggle-on', output === 'Running'), output => self.toggleClassName('toggle-on', output === 'Running'),
); );
}, },
onPrimaryClickRelease: self => { onPrimaryClickRelease: self => {
subprocess( subprocess(
['bash', '-c', '$AGS_PATH/osk-toggle.sh toggle'], ['bash', '-c', '$AGS_PATH/osk-toggle.sh toggle'],
(output) => self.toggleClassName('toggle-on', output !== 'Running'), output => self.toggleClassName('toggle-on', output !== 'Running'),
); );
}, },
child: Box({ child: Box({
className: 'osk-toggle', className: 'osk-toggle',
vertical: false, vertical: false,
children: [ children: [
Label({ Label({
label: " 󰌌 ", label: ' 󰌌 ',
}), }),
], ],
}), }),
}); });

View file

@ -5,17 +5,17 @@ import EventBox from '../misc/cursorbox.js';
export default () => EventBox({ export default () => EventBox({
className: 'toggle-off', className: 'toggle-off',
onPrimaryClickRelease: () => App.toggleWindow('quick-settings'), onPrimaryClickRelease: () => App.toggleWindow('quick-settings'),
connections: [[App, (self, windowName, visible) => { connections: [[App, (self, windowName, visible) => {
if (windowName == 'quick-settings') if (windowName == 'quick-settings')
self.toggleClassName('toggle-on', visible); self.toggleClassName('toggle-on', visible);
}]], }]],
child: Box({ child: Box({
className: 'quick-settings-toggle', className: 'quick-settings-toggle',
vertical: false, vertical: false,
child: Label({ child: Label({
label: "  ", label: '  ',
}),
}), }),
}),
}); });

View file

@ -1,4 +1,4 @@
import { SystemTray, Widget } from '../../imports.js'; import { SystemTray, Utils, Widget } from '../../imports.js';
const { Box, Revealer, Icon, MenuItem } = Widget; const { Box, Revealer, Icon, MenuItem } = Widget;
import Gtk from 'gi://Gtk'; import Gtk from 'gi://Gtk';
@ -7,70 +7,70 @@ import Separator from '../misc/separator.js';
const SysTrayItem = item => MenuItem({ const SysTrayItem = item => MenuItem({
className: 'tray-item', className: 'tray-item',
child: Revealer({ child: Revealer({
transition: 'slide_right', transition: 'slide_right',
child: Icon({ child: Icon({
size: 24, size: 24,
}),
}), }),
}), submenu: item.menu,
submenu: item.menu, connections: [[item, btn => {
connections: [[item, btn => { btn.child.child.icon = item.icon;
btn.child.child.icon = item.icon; btn.tooltipMarkup = item.tooltipMarkup;
btn.tooltipMarkup = item.tooltipMarkup; }]],
}]]
}); });
const SysTray = () => { const SysTray = () => {
let widget = Gtk.MenuBar.new(); const widget = Gtk.MenuBar.new();
// Properties // Properties
widget._items = new Map(); widget._items = new Map();
widget._onAdded = (id) => { widget._onAdded = id => {
const item = SystemTray.getItem(id); const item = SystemTray.getItem(id);
if (widget._items.has(id) || !item) if (widget._items.has(id) || !item)
return; return;
const w = SysTrayItem(item); const w = SysTrayItem(item);
widget._items.set(id, w); widget._items.set(id, w);
widget.add(w); widget.add(w);
widget.show_all(); widget.show_all();
w.child.revealChild = true; w.child.revealChild = true;
}; };
widget._onRemoved = (id) => { widget._onRemoved = id => {
if (!widget._items.has(id)) if (!widget._items.has(id))
return; return;
widget._items.get(id).child.revealChild = false; widget._items.get(id).child.revealChild = false;
setTimeout(() => { Utils.timeout(400, () => {
widget._items.get(id).destroy(); widget._items.get(id).destroy();
widget._items.delete(id); widget._items.delete(id);
}, 400); });
}; };
// Connections // Connections
SystemTray.connect('added', (_, id) => widget._onAdded(id)); SystemTray.connect('added', (_, id) => widget._onAdded(id));
SystemTray.connect('removed', (_, id) => widget._onRemoved(id)); SystemTray.connect('removed', (_, id) => widget._onRemoved(id));
return widget; return widget;
} };
export default () => Revealer({ export default () => Revealer({
transition: 'slide_right', transition: 'slide_right',
connections: [[SystemTray, rev => { connections: [[SystemTray, rev => {
rev.revealChild = rev.child.children[0].get_children().length > 0; rev.revealChild = rev.child.children[0].get_children().length > 0;
}]], }]],
child: Box({ child: Box({
children: [
Box({
className: 'sys-tray',
children: [ children: [
SysTray(), Box({
className: 'sys-tray',
children: [
SysTray(),
],
}),
Separator(12),
], ],
}), }),
Separator(12),
],
}),
}); });

View file

@ -6,18 +6,18 @@ import EventBox from '../misc/cursorbox.js';
export default () => EventBox({ export default () => EventBox({
className: 'toggle-off', className: 'toggle-off',
onPrimaryClickRelease: self => { onPrimaryClickRelease: self => {
subprocess( subprocess(
['bash', '-c', '$AGS_PATH/tablet-toggle.sh toggle'], ['bash', '-c', '$AGS_PATH/tablet-toggle.sh toggle'],
(output) => self.toggleClassName('toggle-on', output == 'Tablet'), output => self.toggleClassName('toggle-on', output == 'Tablet'),
); );
}, },
child: Box({ child: Box({
className: 'tablet-toggle', className: 'tablet-toggle',
vertical: false, vertical: false,
child: Label({ child: Label({
label: " 󰦧 ", label: ' 󰦧 ',
}),
}), }),
}),
}); });

View file

@ -6,65 +6,64 @@ import EventBox from '../misc/cursorbox.js';
const Workspace = ({ i } = {}) => const Workspace = ({ i } = {}) =>
Revealer({ Revealer({
transition: "slide_right", transition: 'slide_right',
properties: [['id', i]], properties: [['id', i]],
child: EventBox({ child: EventBox({
tooltipText: `${i}`, tooltipText: `${i}`,
onPrimaryClickRelease: () => { onPrimaryClickRelease: () => {
execAsync(`hyprctl dispatch workspace ${i}`) execAsync(`hyprctl dispatch workspace ${i}`)
.catch(print); .catch(print);
}, },
child: Box({ child: Box({
className: 'button', className: 'button',
connections: [[Hyprland, self => { connections: [[Hyprland, self => {
const occupied = Hyprland.getWorkspace(i)?.windows > 0; const occupied = Hyprland.getWorkspace(i)?.windows > 0;
self.toggleClassName('active', Hyprland.active.workspace.id === i); self.toggleClassName('active', Hyprland.active.workspace.id === i);
self.toggleClassName('occupied', occupied); self.toggleClassName('occupied', occupied);
self.toggleClassName('empty', !occupied); self.toggleClassName('empty', !occupied);
}]], }]],
}), }),
}), }),
}); });
export default () => Box({ export default () => Box({
className: 'workspaces', className: 'workspaces',
children: [EventBox({ children: [EventBox({
child: Box({ child: Box({
properties: [ properties: [
['workspaces'], ['workspaces'],
['refresh', self => { ['refresh', self => {
self.children.forEach(rev => rev.reveal_child = false); self.children.forEach(rev => rev.reveal_child = false);
self._workspaces.forEach(ws => { self._workspaces.forEach(ws => {
ws.revealChild = true; ws.revealChild = true;
}); });
}], }],
['updateWs', self => { ['updateWs', self => {
Hyprland.workspaces.forEach(ws => { Hyprland.workspaces.forEach(ws => {
let currentWs = self.children.find(ch => ch._id == ws.id); const currentWs = self.children.find(ch => ch._id == ws.id);
if (!currentWs && ws.id > 0) { if (!currentWs && ws.id > 0)
self.add(Workspace({ i: ws.id})); self.add(Workspace({ i: ws.id }));
} });
}); self.show_all();
self.show_all();
// Make sure the order is correct // Make sure the order is correct
self._workspaces.forEach((workspace, i) => { self._workspaces.forEach((workspace, i) => {
workspace.get_parent().reorder_child(workspace, i); workspace.get_parent().reorder_child(workspace, i);
}); });
}], }],
], ],
connections: [[Hyprland, self => { connections: [[Hyprland, self => {
self._workspaces = self.children.filter(ch => { self._workspaces = self.children.filter(ch => {
return Hyprland.workspaces.find(ws => ws.id == ch._id) return Hyprland.workspaces.find(ws => ws.id == ch._id);
}).sort((a, b) => a._id - b._id); }).sort((a, b) => a._id - b._id);
self._updateWs(self); self._updateWs(self);
self._refresh(self); self._refresh(self);
}]], }]],
}), }),
})], })],
}); });

View file

@ -1,7 +1,7 @@
import { Widget } from '../imports.js'; import { Widget } from '../imports.js';
const { Box, Label } = Widget; const { Box, Label } = Widget;
import Gtk from 'gi://Gtk'; import Gtk from 'gi://Gtk';
import GLib from 'gi://GLib'; import GLib from 'gi://GLib';
const { DateTime } = GLib; const { DateTime } = GLib;
@ -9,80 +9,80 @@ import PopupWindow from './misc/popup.js';
const Divider = () => Box({ const Divider = () => Box({
className: 'divider', className: 'divider',
vertical: true, vertical: true,
}); });
const Time = () => Box({ const Time = () => Box({
className: 'timebox', className: 'timebox',
vertical: true, vertical: true,
children: [ children: [
Box({ Box({
className: 'time-container', className: 'time-container',
halign: 'center', halign: 'center',
valign: 'center', valign: 'center',
children: [ children: [
Label({ Label({
className: 'content', className: 'content',
label: 'hour', label: 'hour',
connections: [[1000, self => { connections: [[1000, self => {
self.label = DateTime.new_now_local().format('%H'); self.label = DateTime.new_now_local().format('%H');
}]], }]],
}),
Divider(),
Label({
className: 'content',
label: 'minute',
connections: [[1000, self => {
self.label = DateTime.new_now_local().format('%M');
}]],
}),
],
}), }),
Divider(), Box({
className: 'date-container',
Label({ halign: 'center',
className: 'content', child: Label({
label: 'minute', style: 'font-size: 20px',
connections: [[1000, self => { label: 'complete date',
self.label = DateTime.new_now_local().format('%M'); connections: [[1000, self => {
}]], var time = DateTime.new_now_local();
self.label = time.format('%A, %B ') +
time.get_day_of_month() +
time.format(', %Y');
}]],
}),
}), }),
], ],
}),
Box({
className: 'date-container',
halign: 'center',
child: Label({
style: 'font-size: 20px',
label: 'complete date',
connections: [[1000, self => {
var time = DateTime.new_now_local();
self.label = time.format("%A, %B ") +
time.get_day_of_month() +
time.format(", %Y");
}]],
}),
}),
],
}); });
const CalendarWidget = () => Box({ const CalendarWidget = () => Box({
className: 'cal-box', className: 'cal-box',
child: Widget({ child: Widget({
type: Gtk.Calendar, type: Gtk.Calendar,
showDayNames: true, showDayNames: true,
showHeading: true, showHeading: true,
className: 'cal', className: 'cal',
}), }),
}); });
export default () => PopupWindow({ export default () => PopupWindow({
anchor: [ 'top', 'right' ], anchor: ['top', 'right'],
margin: [ 8, 182, 0, 0], margin: [8, 182, 0, 0],
name: 'calendar', name: 'calendar',
child: Box({ child: Box({
className: 'date', className: 'date',
vertical: true, vertical: true,
children: [ children: [
Time(), Time(),
CalendarWidget(), CalendarWidget(),
], ],
}), }),
}); });

View file

@ -1,4 +1,4 @@
import { Widget } from '../../imports.js'; import { Utils, Widget } from '../../imports.js';
const { Box, Overlay, EventBox } = Widget; const { Box, Overlay, EventBox } = Widget;
import Gtk from 'gi://Gtk'; import Gtk from 'gi://Gtk';
@ -8,80 +8,84 @@ const OFFSCREEN = 500;
const TRANSITION = 'transition: margin 0.5s ease, opacity 3s ease;'; const TRANSITION = 'transition: margin 0.5s ease, opacity 3s ease;';
export default ({ properties, connections, props } = {}) => { export default ({
let widget = EventBox({}); properties,
let gesture = Gtk.GestureDrag.new(widget) connections,
props,
} = {}) => {
const widget = EventBox({});
const gesture = Gtk.GestureDrag.new(widget);
widget.add(Overlay({ widget.add(Overlay({
...props, ...props,
properties: [ properties: [
...properties, ...properties,
['dragging', false], ['dragging', false],
], ],
child: Box({className: 'player'}), child: Box({ className: 'player' }),
connections: [ connections: [
...connections, ...connections,
[gesture, overlay => { [gesture, overlay => {
if (overlay.list().length <= 1) if (overlay.list().length <= 1)
return; return;
overlay._dragging = true; overlay._dragging = true;
const offset = gesture.get_offset()[1]; const offset = gesture.get_offset()[1];
let playerBox = overlay.list().at(-1); const playerBox = overlay.list().at(-1);
if (offset >= 0) { if (offset >= 0) {
playerBox.setStyle(`margin-left: ${offset}px; playerBox.setStyle(`margin-left: ${offset}px;
margin-right: -${offset}px; margin-right: -${offset}px;
${playerBox._bgStyle}`); ${playerBox._bgStyle}`);
} }
else { else {
let newOffset = Math.abs(offset); const newOffset = Math.abs(offset);
playerBox.setStyle(`margin-left: -${newOffset}px; playerBox.setStyle(`margin-left: -${newOffset}px;
margin-right: ${newOffset}px; margin-right: ${newOffset}px;
${playerBox._bgStyle}`); ${playerBox._bgStyle}`);
} }
overlay._selected = playerBox; overlay._selected = playerBox;
}, 'drag-update'], }, 'drag-update'],
[gesture, overlay => { [gesture, overlay => {
if (overlay.list().length <= 1) if (overlay.list().length <= 1)
return; return;
overlay._dragging = false; overlay._dragging = false;
const offset = gesture.get_offset()[1]; const offset = gesture.get_offset()[1];
let playerBox = overlay.list().at(-1); const playerBox = overlay.list().at(-1);
if (Math.abs(offset) > MAX_OFFSET) { if (Math.abs(offset) > MAX_OFFSET) {
if (offset >= 0) { if (offset >= 0) {
playerBox.setStyle(`${TRANSITION} playerBox.setStyle(`${TRANSITION}
margin-left: ${OFFSCREEN}px; margin-left: ${OFFSCREEN}px;
margin-right: -${OFFSCREEN}px; margin-right: -${OFFSCREEN}px;
opacity: 0; opacity: 0;
${playerBox._bgStyle}`); ${playerBox._bgStyle}`);
} }
else { else {
playerBox.setStyle(`${TRANSITION} playerBox.setStyle(`${TRANSITION}
margin-left: -${OFFSCREEN}px; margin-left: -${OFFSCREEN}px;
margin-right: ${OFFSCREEN}px; margin-right: ${OFFSCREEN}px;
opacity: 0; opacity: 0;
${playerBox._bgStyle}`); ${playerBox._bgStyle}`);
} }
setTimeout(() => { Utils.timeout(500, () => {
overlay.reorder_overlay(playerBox, 0); overlay.reorder_overlay(playerBox, 0);
playerBox.setStyle(playerBox._bgStyle); playerBox.setStyle(playerBox._bgStyle);
overlay._selected = overlay.list().at(-1); overlay._selected = overlay.list().at(-1);
}, 500); });
} }
else else {
playerBox.setStyle(`${TRANSITION} ${playerBox._bgStyle}`); playerBox.setStyle(`${TRANSITION} ${playerBox._bgStyle}`);
}
}, 'drag-end'],
],
}));
widget.child.list = () => widget.child.get_children().filter(ch => ch._bgStyle !== undefined);
}, 'drag-end'], return widget;
],
}));
widget.child.list = () => widget.child.get_children().filter(ch => ch._bgStyle !== undefined);
return widget;
}; };

View file

@ -9,341 +9,343 @@ import Separator from '../misc/separator.js';
import EventBox from '../misc/cursorbox.js'; import EventBox from '../misc/cursorbox.js';
const icons = { const icons = {
mpris: { mpris: {
fallback: 'audio-x-generic-symbolic', fallback: 'audio-x-generic-symbolic',
shuffle: { shuffle: {
enabled: '󰒝', enabled: '󰒝',
disabled: '󰒞', disabled: '󰒞',
},
loop: {
none: '󰑗',
track: '󰑘',
playlist: '󰑖',
},
playing: ' ',
paused: ' ',
stopped: ' ',
prev: '󰒮',
next: '󰒭',
}, },
loop: { };
none: '󰑗',
track: '󰑘',
playlist: '󰑖',
},
playing: ' ',
paused: ' ',
stopped: ' ',
prev: '󰒮',
next: '󰒭',
},
}
export const CoverArt = (player, props) => CenterBox({ export const CoverArt = (player, props) => CenterBox({
...props, ...props,
vertical: true, vertical: true,
properties: [['bgStyle', '']], properties: [['bgStyle', '']],
connections: [[player, self => { connections: [[player, self => {
execAsync(['bash', '-c', `[[ -f "${player.coverPath}" ]] && execAsync(['bash', '-c', `[[ -f "${player.coverPath}" ]] &&
coloryou "${player.coverPath}" | grep -v Warning`]) coloryou "${player.coverPath}" | grep -v Warning`])
.then(out => { .then(out => {
if (!Mpris.players.find(p => player === p)) if (!Mpris.players.find(p => player === p))
return; return;
player.colors.value = JSON.parse(out); player.colors.value = JSON.parse(out);
self._bgStyle = `background: radial-gradient(circle, self._bgStyle = `background: radial-gradient(circle,
rgba(0, 0, 0, 0.4) 30%, rgba(0, 0, 0, 0.4) 30%,
${player.colors.value.imageAccent}), ${player.colors.value.imageAccent}),
url("${player.coverPath}"); url("${player.coverPath}");
background-size: cover; background-size: cover;
background-position: center;`; background-position: center;`;
if (!self.get_parent()._dragging) if (!self.get_parent()._dragging)
self.setStyle(self._bgStyle); self.setStyle(self._bgStyle);
}).catch(err => {
}).catch(err => {if (err !== "") print(err)}); if (err !== '')
}]], print(err);
});
}]],
}); });
export const TitleLabel = (player, props) => Label({ export const TitleLabel = (player, props) => Label({
...props, ...props,
xalign: 0, xalign: 0,
maxWidthChars: 40, maxWidthChars: 40,
truncate: 'end', truncate: 'end',
justification: 'left', justification: 'left',
className: 'title', className: 'title',
binds: [['label', player, 'track-title']], binds: [['label', player, 'track-title']],
}); });
export const ArtistLabel = (player, props) => Label({ export const ArtistLabel = (player, props) => Label({
...props, ...props,
xalign: 0, xalign: 0,
maxWidthChars: 40, maxWidthChars: 40,
truncate: 'end', truncate: 'end',
justification: 'left', justification: 'left',
className: 'artist', className: 'artist',
binds: [['label', player, 'track-artists', a => a.join(', ') || '']], binds: [['label', player, 'track-artists', a => a.join(', ') || '']],
}); });
export const PlayerIcon = (player, { symbolic = true, ...props } = {}) => { export const PlayerIcon = (player, { symbolic = true, ...props } = {}) => {
let MainIcon = Icon({ const MainIcon = Icon({
...props, ...props,
className: 'player-icon', className: 'player-icon',
size: 32, size: 32,
tooltipText: player.identity || '', tooltipText: player.identity || '',
connections: [[player, self => { connections: [[player, self => {
const name = `${player.entry}${symbolic ? '-symbolic' : ''}`; const name = `${player.entry}${symbolic ? '-symbolic' : ''}`;
lookUpIcon(name) ? self.icon = name lookUpIcon(name) ? self.icon = name
: self.icon = icons.mpris.fallback; : self.icon = icons.mpris.fallback;
}]], }]],
}); });
return Box({ return Box({
connections: [[Mpris, self => { connections: [[Mpris, self => {
let overlays = self.get_parent().get_parent() const overlays = self.get_parent().get_parent()
.get_parent().list(); .get_parent().list();
let player = overlays.find(overlay => { const player = overlays.find(overlay => {
overlay === self.get_parent().get_parent(); overlay === self.get_parent().get_parent();
}); });
let index = overlays.indexOf(player); const index = overlays.indexOf(player);
let children = []; const children = [];
for (let i = 0; i < overlays.length; ++i) { for (let i = 0; i < overlays.length; ++i) {
if (i === index) { if (i === index) {
children.push(MainIcon); children.push(MainIcon);
children.push(Separator(2)); children.push(Separator(2));
} }
else { else {
children.push(Box({className: 'position-indicator'})); children.push(Box({ className: 'position-indicator' }));
children.push(Separator(2)); children.push(Separator(2));
} }
} }
self.children = children; self.children = children;
}]], }]],
}); });
} };
// FIXME: get the cursors right or just don't display when disabled // FIXME: get the cursors right or just don't display when disabled
export const PositionSlider = (player, props) => EventBox({ export const PositionSlider = (player, props) => EventBox({
child: Slider({ child: Slider({
...props, ...props,
className: 'position-slider', className: 'position-slider',
hexpand: true, hexpand: true,
drawValue: false, drawValue: false,
onChange: ({ value }) => { onChange: ({ value }) => {
player.position = player.length * value; player.position = player.length * value;
}, },
properties: [['update', slider => { properties: [['update', slider => {
if (slider.dragging) { if (slider.dragging) {
slider.get_parent().window.set_cursor(Gdk.Cursor slider.get_parent().window.set_cursor(Gdk.Cursor
.new_from_name(display, 'grabbing')); .new_from_name(display, 'grabbing'));
} }
else { else {
if (slider.get_parent() && slider.get_parent().window) { if (slider.get_parent() && slider.get_parent().window) {
slider.get_parent().window.set_cursor(Gdk.Cursor slider.get_parent().window.set_cursor(Gdk.Cursor
.new_from_name(display, 'pointer')); .new_from_name(display, 'pointer'));
} }
slider.sensitive = player.length > 0; slider.sensitive = player.length > 0;
if (player.length > 0) { if (player.length > 0)
slider.value = player.position / player.length; slider.value = player.position / player.length;
} }
} }]],
}]], connections: [
connections: [ [player, s => s._update(s), 'position'],
[player, s => s._update(s), 'position'], [1000, s => s._update(s)],
[1000, s => s._update(s)], [player.colors, s => {
[player.colors, s => { const c = player.colors.value;
let c = player.colors.value; if (c) {
if (c) s.setCss(`highlight { background-color: ${c.buttonAccent}; }
s.setCss(`highlight { background-color: ${c.buttonAccent}; } slider { background-color: ${c.buttonAccent}; }
slider { background-color: ${c.buttonAccent}; } slider:hover { background-color: ${c.hoverAccent}; }
slider:hover { background-color: ${c.hoverAccent}; } trough { background-color: ${c.buttonText}; }`);
trough { background-color: ${c.buttonText}; }`); }
}], }],
], ],
}), }),
}); });
function lengthStr(length) { function lengthStr(length) {
const min = Math.floor(length / 60); const min = Math.floor(length / 60);
const sec0 = Math.floor(length % 60) < 10 ? '0' : ''; const sec0 = Math.floor(length % 60) < 10 ? '0' : '';
const sec = Math.floor(length % 60); const sec = Math.floor(length % 60);
return `${min}:${sec0}${sec}`; return `${min}:${sec0}${sec}`;
} }
export const PositionLabel = player => Label({ export const PositionLabel = player => Label({
properties: [['update', self => { properties: [['update', self => {
player.length > 0 ? self.label = lengthStr(player.position) player.length > 0 ? self.label = lengthStr(player.position)
: self.visible = !!player; : self.visible = !!player;
}]], }]],
connections: [ connections: [
[player, l => l._update(l), 'position'], [player, l => l._update(l), 'position'],
[1000, l => l._update(l)], [1000, l => l._update(l)],
], ],
}); });
export const LengthLabel = player => Label({ export const LengthLabel = player => Label({
connections: [[player, self => { connections: [[player, self => {
player.length > 0 ? self.label = lengthStr(player.length) player.length > 0 ? self.label = lengthStr(player.length)
: self.visible = !!player; : self.visible = !!player;
}]], }]],
}); });
export const Slash = player => Label({ export const Slash = player => Label({
label: '/', label: '/',
connections: [[player, self => { connections: [[player, self => {
self.visible = player.length > 0; self.visible = player.length > 0;
}]], }]],
}); });
// TODO: use label instead of stack to fix UI issues // TODO: use label instead of stack to fix UI issues
const PlayerButton = ({ player, items, onClick, prop }) => Button({ const PlayerButton = ({ player, items, onClick, prop }) => Button({
child: Stack({ items }), child: Stack({ items }),
onPrimaryClickRelease: () => player[onClick](), onPrimaryClickRelease: () => player[onClick](),
properties: [['hovered', false]], properties: [['hovered', false]],
onHover: self => { onHover: self => {
self._hovered = true; self._hovered = true;
if (! self.child.sensitive || ! self.sensitive) { if (! self.child.sensitive || ! self.sensitive)
self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'not-allowed')); self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'not-allowed'));
}
else {
self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'pointer'));
}
if (prop == 'playBackStatus') { else
items.forEach(item => { self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'pointer'));
item[1].setStyle(`background-color: ${player.colors.value.hoverAccent};
color: ${player.colors.value.buttonText};
min-height: 40px; min-width: 36px;
margin-bottom: 1px; margin-right: 1px;`);
});
}
},
onHoverLost: self => {
self._hovered = false;
self.window.set_cursor(null);
if (prop == 'playBackStatus') {
items.forEach(item => {
item[1].setStyle(`background-color: ${player.colors.value.buttonAccent};
color: ${player.colors.value.buttonText};
min-height: 42px; min-width: 38px;`);
});
}
},
connections: [
[player, button => {
button.child.shown = `${player[prop]}`;
}],
[player.colors, button => {
if (!Mpris.players.find(p => player === p))
return;
if (player.colors.value) {
if (prop == 'playBackStatus') { if (prop == 'playBackStatus') {
if (button._hovered) {
items.forEach(item => { items.forEach(item => {
item[1].setStyle(`background-color: ${player.colors.value.hoverAccent}; item[1].setStyle(`background-color: ${player.colors.value.hoverAccent};
color: ${player.colors.value.buttonText}; color: ${player.colors.value.buttonText};
min-height: 40px; min-width: 36px; min-height: 40px; min-width: 36px;
margin-bottom: 1px; margin-right: 1px;`); margin-bottom: 1px; margin-right: 1px;`);
}); });
} }
else { },
onHoverLost: self => {
self._hovered = false;
self.window.set_cursor(null);
if (prop == 'playBackStatus') {
items.forEach(item => { items.forEach(item => {
item[1].setStyle(`background-color: ${player.colors.value.buttonAccent}; item[1].setStyle(`background-color: ${player.colors.value.buttonAccent};
color: ${player.colors.value.buttonText}; color: ${player.colors.value.buttonText};
min-height: 42px; min-width: 38px;`); min-height: 42px; min-width: 38px;`);
}); });
}
} }
else { },
button.setCss(`* { color: ${player.colors.value.buttonAccent}; } connections: [
*:hover { color: ${player.colors.value.hoverAccent}; }`); [player, button => {
} button.child.shown = `${player[prop]}`;
} }],
}],
], [player.colors, button => {
if (!Mpris.players.find(p => player === p))
return;
if (player.colors.value) {
if (prop == 'playBackStatus') {
if (button._hovered) {
items.forEach(item => {
item[1].setStyle(`background-color: ${player.colors.value.hoverAccent};
color: ${player.colors.value.buttonText};
min-height: 40px; min-width: 36px;
margin-bottom: 1px; margin-right: 1px;`);
});
}
else {
items.forEach(item => {
item[1].setStyle(`background-color: ${player.colors.value.buttonAccent};
color: ${player.colors.value.buttonText};
min-height: 42px; min-width: 38px;`);
});
}
}
else {
button.setCss(`* { color: ${player.colors.value.buttonAccent}; }
*:hover { color: ${player.colors.value.hoverAccent}; }`);
}
}
}],
],
}); });
export const ShuffleButton = player => PlayerButton({ export const ShuffleButton = player => PlayerButton({
player, player,
items: [ items: [
['true', Label({ ['true', Label({
className: 'shuffle enabled', className: 'shuffle enabled',
label: icons.mpris.shuffle.enabled, label: icons.mpris.shuffle.enabled,
})], })],
['false', Label({ ['false', Label({
className: 'shuffle disabled', className: 'shuffle disabled',
label: icons.mpris.shuffle.disabled, label: icons.mpris.shuffle.disabled,
})], })],
], ],
onClick: 'shuffle', onClick: 'shuffle',
prop: 'shuffleStatus', prop: 'shuffleStatus',
}); });
export const LoopButton = player => PlayerButton({ export const LoopButton = player => PlayerButton({
player, player,
items: [ items: [
['None', Label({ ['None', Label({
className: 'loop none', className: 'loop none',
label: icons.mpris.loop.none, label: icons.mpris.loop.none,
})], })],
['Track', Label({ ['Track', Label({
className: 'loop track', className: 'loop track',
label: icons.mpris.loop.track, label: icons.mpris.loop.track,
})], })],
['Playlist', Label({ ['Playlist', Label({
className: 'loop playlist', className: 'loop playlist',
label: icons.mpris.loop.playlist, label: icons.mpris.loop.playlist,
})], })],
], ],
onClick: 'loop', onClick: 'loop',
prop: 'loopStatus', prop: 'loopStatus',
}); });
export const PlayPauseButton = player => PlayerButton({ export const PlayPauseButton = player => PlayerButton({
player, player,
items: [ items: [
['Playing', Label({ ['Playing', Label({
className: 'pausebutton playing', className: 'pausebutton playing',
label: icons.mpris.playing, label: icons.mpris.playing,
})], })],
['Paused', Label({ ['Paused', Label({
className: 'pausebutton paused', className: 'pausebutton paused',
label: icons.mpris.paused, label: icons.mpris.paused,
})], })],
['Stopped', Label({ ['Stopped', Label({
className: 'pausebutton stopped paused', className: 'pausebutton stopped paused',
label: icons.mpris.stopped, label: icons.mpris.stopped,
})], })],
], ],
onClick: 'playPause', onClick: 'playPause',
prop: 'playBackStatus', prop: 'playBackStatus',
}); });
export const PreviousButton = player => PlayerButton({ export const PreviousButton = player => PlayerButton({
player, player,
items: [ items: [
['true', Label({ ['true', Label({
className: 'previous', className: 'previous',
label: icons.mpris.prev, label: icons.mpris.prev,
})], })],
['false', Label({ ['false', Label({
className: 'previous', className: 'previous',
label: icons.mpris.prev, label: icons.mpris.prev,
})], })],
], ],
onClick: 'previous', onClick: 'previous',
prop: 'canGoPrev', prop: 'canGoPrev',
}); });
export const NextButton = player => PlayerButton({ export const NextButton = player => PlayerButton({
player, player,
items: [ items: [
['true', Label({ ['true', Label({
className: 'next', className: 'next',
label: icons.mpris.next, label: icons.mpris.next,
})], })],
['false', Label({ ['false', Label({
className: 'next', className: 'next',
label: icons.mpris.next, label: icons.mpris.next,
})], })],
], ],
onClick: 'next', onClick: 'next',
prop: 'canGoNext', prop: 'canGoNext',
}); });

View file

@ -1,5 +1,5 @@
import { Mpris, Variable, Widget } from '../../imports.js'; import { Mpris, Variable, Widget } from '../../imports.js';
const { Box, CenterBox, Label } = Widget; const { Box, CenterBox } = Widget;
import * as mpris from './mpris.js'; import * as mpris from './mpris.js';
import PlayerGesture from './gesture.js'; import PlayerGesture from './gesture.js';
@ -9,148 +9,144 @@ const FAVE_PLAYER = 'org.mpris.MediaPlayer2.spotify';
const Top = player => Box({ const Top = player => Box({
className: 'top', className: 'top',
halign: 'start', halign: 'start',
valign: 'start', valign: 'start',
children: [ children: [
mpris.PlayerIcon(player, { mpris.PlayerIcon(player, {
symbolic: false, symbolic: false,
}), }),
], ],
}); });
const Center = player => Box({ const Center = player => Box({
className: 'center', className: 'center',
children: [ children: [
CenterBox({ CenterBox({
vertical: true, vertical: true,
children: [ children: [
Box({ Box({
className: 'metadata', className: 'metadata',
vertical: true, vertical: true,
halign: 'start', halign: 'start',
valign: 'center', valign: 'center',
hexpand: true, hexpand: true,
children: [ children: [
mpris.TitleLabel(player), mpris.TitleLabel(player),
mpris.ArtistLabel(player), mpris.ArtistLabel(player),
], ],
}),
null,
null,
],
}), }),
null, CenterBox({
null, vertical: true,
], children: [
}), null,
mpris.PlayPauseButton(player),
null,
],
}),
CenterBox({ ],
vertical: true,
children: [
null,
mpris.PlayPauseButton(player),
null,
],
}),
],
}); });
const Bottom = player => Box({ const Bottom = player => Box({
className: 'bottom', className: 'bottom',
children: [ children: [
mpris.PreviousButton(player, { mpris.PreviousButton(player, {
valign: 'end', valign: 'end',
halign: 'start', halign: 'start',
}), }),
Separator(8), Separator(8),
mpris.PositionSlider(player), mpris.PositionSlider(player),
Separator(8), Separator(8),
mpris.NextButton(player), mpris.NextButton(player),
Separator(8), Separator(8),
mpris.ShuffleButton(player), mpris.ShuffleButton(player),
Separator(8), Separator(8),
mpris.LoopButton(player), mpris.LoopButton(player),
], ],
}); });
const PlayerBox = player => mpris.CoverArt(player, { const PlayerBox = player => mpris.CoverArt(player, {
className: `player ${player.name}`, className: `player ${player.name}`,
hexpand: true, hexpand: true,
children: [ children: [
Top(player), Top(player),
Center(player), Center(player),
Bottom(player), Bottom(player),
], ],
}); });
export default () => Box({ export default () => Box({
className: 'media', className: 'media',
child: PlayerGesture({ child: PlayerGesture({
properties: [ properties: [
['players', new Map()], ['players', new Map()],
['setup', false], ['setup', false],
['selected'], ['selected'],
], ],
connections: [ connections: [
[Mpris, (overlay, busName) => { [Mpris, (overlay, busName) => {
if (overlay._players.has(busName)) if (overlay._players.has(busName))
return; return;
if (!busName) { if (!busName) {
let player = Mpris.players.find(p => !overlay._players.has(p.busName)); const player = Mpris.players.find(p => !overlay._players.has(p.busName));
if (player) { if (player)
busName = player.busName; busName = player.busName;
} else
else { return;
return; }
}
}
const player = Mpris.getPlayer(busName); const player = Mpris.getPlayer(busName);
player.colors = Variable(); player.colors = Variable();
overlay._players.set(busName, PlayerBox(player)); overlay._players.set(busName, PlayerBox(player));
let result = []; const result = [];
overlay._players.forEach(widget => { overlay._players.forEach(widget => {
result.push(widget); result.push(widget);
}); });
overlay.overlays = result; overlay.overlays = result;
// Select favorite player at startup // Select favorite player at startup
if (!overlay._setup) { if (!overlay._setup) {
if (overlay._players.has(FAVE_PLAYER)) { if (overlay._players.has(FAVE_PLAYER))
overlay._selected = overlay._players.get(FAVE_PLAYER); overlay._selected = overlay._players.get(FAVE_PLAYER);
}
overlay._setup = true;
}
if (overlay._selected) overlay._setup = true;
overlay.reorder_overlay(overlay._selected, -1); }
}, 'player-added'], if (overlay._selected)
overlay.reorder_overlay(overlay._selected, -1);
}, 'player-added'],
[Mpris, (overlay, busName) => { [Mpris, (overlay, busName) => {
if (!busName || !overlay._players.has(busName)) if (!busName || !overlay._players.has(busName))
return; return;
overlay._players.delete(busName); overlay._players.delete(busName);
let result = []; const result = [];
overlay._players.forEach(widget => { overlay._players.forEach(widget => {
result.push(widget); result.push(widget);
}); });
overlay.overlays = result; overlay.overlays = result;
if (overlay._selected) if (overlay._selected)
overlay.reorder_overlay(overlay._selected, -1); overlay.reorder_overlay(overlay._selected, -1);
}, 'player-closed'],
}, 'player-closed'], ],
], }),
}),
}); });

View file

@ -4,80 +4,82 @@ const { Window, Revealer } = Widget;
import Pointers from '../../services/pointers.js'; import Pointers from '../../services/pointers.js';
const ALWAYS_OPEN = [ const ALWAYS_OPEN = [
'closer', 'closer',
'bar', 'bar',
'notifications', 'notifications',
'cornertl', 'cornertl',
'cornertr', 'cornertr',
'cornerbl', 'cornerbl',
'cornerbr' 'cornerbr',
]; ];
const closeAll = () => { const closeAll = () => {
App.windows.forEach(w => { App.windows.forEach(w => {
if (!ALWAYS_OPEN.some(window => window === w.name)) if (!ALWAYS_OPEN.some(window => window === w.name))
App.closeWindow(w.name) App.closeWindow(w.name);
}); });
App.closeWindow('closer'); App.closeWindow('closer');
}; };
globalThis.closeAll = closeAll; globalThis.closeAll = closeAll;
Pointers.connect('new-line', (_, out) => { Pointers.connect('new-line', (_, out) => {
if (out) { if (out) {
Utils.execAsync('hyprctl layers -j').then(layers => { Utils.execAsync('hyprctl layers -j').then(layers => {
layers = JSON.parse(layers); layers = JSON.parse(layers);
Utils.execAsync('hyprctl cursorpos -j').then(pos => { Utils.execAsync('hyprctl cursorpos -j').then(pos => {
pos = JSON.parse(pos); pos = JSON.parse(pos);
Object.values(layers).forEach(key => { Object.values(layers).forEach(key => {
let bar = key['levels']['3'] const bar = key['levels']['3']
.find(n => n.namespace === "bar"); .find(n => n.namespace === 'bar');
let widgets = key['levels']['3'] const widgets = key['levels']['3']
.filter(n => !ALWAYS_OPEN.includes(n.namespace)); .filter(n => !ALWAYS_OPEN.includes(n.namespace));
if (pos.x > bar.x && pos.x < bar.x + bar.w && if (pos.x > bar.x && pos.x < bar.x + bar.w &&
pos.y > bar.y && pos.y < bar.y + bar.h) { pos.y > bar.y && pos.y < bar.y + bar.h) {
// don't handle clicks when on bar // don't handle clicks when on bar
} }
else { else {
widgets.forEach(l => { widgets.forEach(l => {
if (!(pos.x > l.x && pos.x < l.x + l.w && if (!(pos.x > l.x && pos.x < l.x + l.w &&
pos.y > l.y && pos.y < l.y + l.h)) { pos.y > l.y && pos.y < l.y + l.h)) {
closeAll(); closeAll();
return return;
} }
}); });
} }
});
}); }).catch(print);
}).catch(print);
}).catch(print); }
}).catch(print); });
}
})
export default () => Window({ export default () => Window({
name: 'closer', name: 'closer',
popup: true, popup: true,
layer: 'top', layer: 'top',
child: Revealer({ child: Revealer({
connections: [[App, (_, windowName, visible) => { connections: [[App, (_, windowName, visible) => {
if (!Array.from(App.windows).some(w => w[1].visible && const anyVisibleAndClosable = Array.from(App.windows).some(w => {
!ALWAYS_OPEN.some(window => window === w[0]))) { const isAlwaysOpen = ALWAYS_OPEN.some(window => window === w[0]);
App.closeWindow('closer');
}
if (windowName === 'closer') { return w[1].visible && !isAlwaysOpen;
if (visible) });
Pointers.startProc();
else if (!anyVisibleAndClosable)
Pointers.killProc(); App.closeWindow('closer');
}
}]], if (windowName === 'closer') {
}), if (visible)
Pointers.startProc();
else
Pointers.killProc();
}
}]],
}),
}); });

View file

@ -5,42 +5,39 @@ const display = Gdk.Display.get_default();
export default ({ export default ({
type = "EventBox", type = 'EventBox',
reset = true, reset = true,
...props ...props
}) => { }) => {
if (type === "EventBox") { if (type === 'EventBox') {
return Widget.EventBox({ return Widget.EventBox({
...props, ...props,
onHover: self => { onHover: self => {
if (!self.child.sensitive || !self.sensitive) { if (!self.child.sensitive || !self.sensitive)
self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'not-allowed')); self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'not-allowed'));
}
else { else
self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'pointer')); self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'pointer'));
} },
}, onHoverLost: self => {
onHoverLost: self => { if (reset)
if (reset) self.window.set_cursor(null);
self.window.set_cursor(null); },
}, });
}); }
} else {
else { return Widget.Button({
return Widget.Button({ ...props,
...props, onHover: self => {
onHover: self => { if (!self.child.sensitive || !self.sensitive)
if (!self.child.sensitive || !self.sensitive) { self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'not-allowed'));
self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'not-allowed')); else
} self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'pointer'));
else { },
self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'pointer')); onHoverLost: self => {
} if (reset)
}, self.window.set_cursor(null);
onHoverLost: self => { },
if (reset) });
self.window.set_cursor(null); }
}, };
});
}
}

View file

@ -3,37 +3,37 @@ const { Revealer, Box, Window } = Widget;
export default ({ export default ({
name,
child,
transition = 'slide_down',
onOpen = rev => {},
...props
}) => {
let window = Window({
name, name,
popup: true, child,
visible: false, transition = 'slide_down',
layer: 'overlay', onOpen = () => {},
...props, ...props
}) => {
const window = Window({
name,
popup: true,
visible: false,
layer: 'overlay',
...props,
child: Box({ child: Box({
style: 'min-height:1px; min-width:1px', style: 'min-height:1px; min-width:1px',
child: Revealer({ child: Revealer({
transition, transition,
transitionDuration: 500, transitionDuration: 500,
connections: [[App, (rev, currentName, visible) => { connections: [[App, (rev, currentName, visible) => {
if (currentName === name) { if (currentName === name) {
rev.reveal_child = visible; rev.reveal_child = visible;
onOpen(child); onOpen(child);
if (visible && name !== 'overview') if (visible && name !== 'overview')
App.openWindow('closer'); App.openWindow('closer');
} }
}]], }]],
child: child, child: child,
}), }),
}), }),
}); });
window.getChild = () => child; window.getChild = () => child;
return window; return window;
} };

View file

@ -3,14 +3,14 @@ const { Box } = Widget;
export default (size, { vertical = false } = {}) => { export default (size, { vertical = false } = {}) => {
if (vertical) { if (vertical) {
return Box({ return Box({
style: `min-height: ${size}px;`, style: `min-height: ${size}px;`,
}); });
} }
else { else {
return Box({ return Box({
style: `min-width: ${size}px;`, style: `min-width: ${size}px;`,
}); });
} }
} };

View file

@ -5,190 +5,184 @@ const { Box, Icon, Label, Button } = Widget;
import GLib from 'gi://GLib'; import GLib from 'gi://GLib';
const setTime = time => { const setTime = time => {
return GLib.DateTime return GLib.DateTime
.new_from_unix_local(time) .new_from_unix_local(time)
.format('%H:%M'); .format('%H:%M');
}; };
const getDragState = box => box.get_parent().get_parent() const getDragState = box => box.get_parent().get_parent()
.get_parent().get_parent().get_parent()._dragging; .get_parent().get_parent().get_parent()._dragging;
import Gesture from './gesture.js'; import Gesture from './gesture.js';
import EventBox from '../misc/cursorbox.js' import EventBox from '../misc/cursorbox.js';
const NotificationIcon = notif => { const NotificationIcon = notif => {
let iconCmd = () => {}; let iconCmd = () => {};
if (Applications.query(notif.appEntry).length > 0) { if (Applications.query(notif.appEntry).length > 0) {
let app = Applications.query(notif.appEntry)[0]; const app = Applications.query(notif.appEntry)[0];
let wmClass = app.app.get_string('StartupWMClass'); let wmClass = app.app.get_string('StartupWMClass');
if (app.app.get_filename().includes('discord')) if (app.app.get_filename().includes('discord'))
wmClass = 'discord'; wmClass = 'discord';
if (wmClass != null) { if (wmClass != null) {
iconCmd = box => { iconCmd = box => {
if (!getDragState(box)) { if (!getDragState(box)) {
execAsync(['bash', '-c', execAsync(['bash', '-c',
`$AGS_PATH/launch-app.sh `$AGS_PATH/launch-app.sh
${wmClass} ${wmClass}
${app.app.get_string('Exec')}` ${app.app.get_string('Exec')}`,
]).catch(print); ]).catch(print);
globalThis.closeAll(); globalThis.closeAll();
}
};
} }
}
} }
}
if (notif.image) { if (notif.image) {
return EventBox({
onPrimaryClickRelease: iconCmd,
child: Box({
valign: 'start',
hexpand: false,
className: 'icon img',
style: `background-image: url("${notif.image}");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
min-width: 78px;
min-height: 78px;`,
}),
});
}
let icon = 'dialog-information-symbolic';
if (lookUpIcon(notif.appIcon))
icon = notif.appIcon;
if (lookUpIcon(notif.appEntry))
icon = notif.appEntry;
return EventBox({ return EventBox({
onPrimaryClickRelease: iconCmd, onPrimaryClickRelease: iconCmd,
child: Box({ child: Box({
valign: 'start', valign: 'start',
hexpand: false, hexpand: false,
className: 'icon img', className: 'icon',
style: ` style: `min-width: 78px;
background-image: url("${notif.image}"); min-height: 78px;`,
background-size: contain; children: [Icon({
background-repeat: no-repeat; icon, size: 58,
background-position: center; halign: 'center',
min-width: 78px; hexpand: true,
min-height: 78px; valign: 'center',
`, vexpand: true,
}), })],
}),
}); });
}
let icon = 'dialog-information-symbolic';
if (lookUpIcon(notif.appIcon)) {
icon = notif.appIcon;
}
if (lookUpIcon(notif.appEntry)) {
icon = notif.appEntry;
}
return EventBox({
onPrimaryClickRelease: iconCmd,
child: Box({
valign: 'start',
hexpand: false,
className: 'icon',
style: `
min-width: 78px;
min-height: 78px;
`,
children: [Icon({
icon, size: 58,
halign: 'center',
hexpand: true,
valign: 'center',
vexpand: true,
})],
}),
});
}; };
export default ({ notif, command = () => {}, } = {}) => { export default ({ notif, command = () => {} } = {}) => {
const BlockedApps = [ const BlockedApps = [
'Spotify', 'Spotify',
]; ];
if (BlockedApps.find(app => app == notif.appName)) { if (BlockedApps.find(app => app == notif.appName)) {
notif.close(); notif.close();
return; return;
} }
return Gesture({ return Gesture({
maxOffset: 200, maxOffset: 200,
command: () => command(), command: () => command(),
properties: [ properties: [
['hovered', false], ['hovered', false],
['id', notif.id], ['id', notif.id],
], ],
onHover: w => { onHover: w => {
if (!w._hovered) { if (!w._hovered)
w._hovered = true; w._hovered = true;
} },
}, onHoverLost: w => {
onHoverLost: w => { if (w._hovered)
if (w._hovered) { w._hovered = false;
w._hovered = false; },
}
},
child: Box({ child: Box({
className: `notification ${notif.urgency}`, className: `notification ${notif.urgency}`,
vexpand: false, vexpand: false,
// Notification // Notification
child: Box({ child: Box({
vertical: true,
children: [
// Content
Box({
children: [
NotificationIcon(notif),
Box({
hexpand: true,
vertical: true, vertical: true,
children: [ children: [
// Top of Content // Content
Box({ Box({
children: [ children: [
Label({ NotificationIcon(notif),
className: 'title', Box({
xalign: 0, hexpand: true,
justification: 'left', vertical: true,
hexpand: true, children: [
maxWidthChars: 24, // Top of Content
truncate: 'end', Box({
wrap: true, children: [
label: notif.summary, Label({
useMarkup: notif.summary.startsWith('<'), className: 'title',
}), xalign: 0,
Label({ justification: 'left',
className: 'time', hexpand: true,
valign: 'start', maxWidthChars: 24,
label: setTime(notif.time), truncate: 'end',
}), wrap: true,
EventBox({ label: notif.summary,
reset: false, useMarkup: notif.summary.startsWith('<'),
child: Button({ }),
className: 'close-button', Label({
valign: 'start', className: 'time',
onClicked: () => notif.close(), valign: 'start',
child: Icon('window-close-symbolic'), label: setTime(notif.time),
}), }),
}), EventBox({
], reset: false,
}), child: Button({
Label({ className: 'close-button',
className: 'description', valign: 'start',
hexpand: true, onClicked: () => notif.close(),
useMarkup: true, child: Icon('window-close-symbolic'),
xalign: 0, }),
justification: 'left', }),
label: notif.body, ],
wrap: true, }),
}), Label({
className: 'description',
hexpand: true,
useMarkup: true,
xalign: 0,
justification: 'left',
label: notif.body,
wrap: true,
}),
],
}),
],
}),
// Actions
Box({
className: 'actions',
children: notif.actions.map(action => Button({
className: 'action-button',
onClicked: () => notif.invoke(action.id),
hexpand: true,
child: Label(action.label),
})),
}),
], ],
}), }),
], }),
}), });
// Actions
Box({
className: 'actions',
children: notif.actions.map(action => Button({
className: 'action-button',
onClicked: () => notif.invoke(action.id),
hexpand: true,
child: Label(action.label),
})),
}),
],
}),
}),
});
}; };

View file

@ -8,148 +8,147 @@ import EventBox from '../misc/cursorbox.js';
const ClearButton = () => EventBox({ const ClearButton = () => EventBox({
child: Button({ child: Button({
onPrimaryClickRelease: button => { onPrimaryClickRelease: button => {
button._popups.children.forEach(ch => { button._popups.children.forEach(ch => {
ch.child.setStyle(ch.child._leftAnim1); ch.child.setStyle(ch.child._leftAnim1);
}); });
button._notifList.children.forEach(ch => { button._notifList.children.forEach(ch => {
if (ch.child) if (ch.child)
ch.child.setStyle(ch.child._rightAnim1); ch.child.setStyle(ch.child._rightAnim1);
timeout(500, () => { timeout(500, () => {
button._notifList.remove(ch); button._notifList.remove(ch);
Notifications.clear(); Notifications.clear();
}); });
}); });
}, },
properties: [ properties: [
['notifList'], ['notifList'],
['popups'], ['popups'],
], ],
connections: [[Notifications, button => { connections: [[Notifications, button => {
if (!button._notifList) if (!button._notifList)
button._notifList = NotificationList; button._notifList = NotificationList;
if (!button._popups) if (!button._popups)
button._popups = App.getWindow('notifications').child.children[0].child; button._popups = App.getWindow('notifications').child.children[0].child;
button.sensitive = Notifications.notifications.length > 0; button.sensitive = Notifications.notifications.length > 0;
}]], }]],
child: Box({ child: Box({
children: [ children: [
Label('Clear '), Label('Clear '),
Icon({ Icon({
connections: [[Notifications, self => { connections: [[Notifications, self => {
self.icon = Notifications.notifications.length > 0 self.icon = Notifications.notifications.length > 0
? 'user-trash-full-symbolic' : 'user-trash-symbolic'; ? 'user-trash-full-symbolic' : 'user-trash-symbolic';
}]], }]],
}),
],
}), }),
],
}), }),
}),
}); });
const Header = () => Box({ const Header = () => Box({
className: 'header', className: 'header',
children: [ children: [
Label({ Label({
label: 'Notifications', label: 'Notifications',
hexpand: true, hexpand: true,
xalign: 0, xalign: 0,
}), }),
ClearButton(), ClearButton(),
], ],
}); });
const NotificationList = Box({ const NotificationList = Box({
vertical: true, vertical: true,
vexpand: true, vexpand: true,
connections: [ connections: [
[Notifications, (box, id) => { [Notifications, (box, id) => {
if (box.children.length == 0) { if (box.children.length == 0) {
box.children = Notifications.notifications box.children = Notifications.notifications
.reverse() .reverse()
.map(n => Notification({ notif: n, command: () => n.close() })); .map(n => Notification({ notif: n, command: () => n.close() }));
} }
else if (id) { else if (id) {
let notif = Notifications.getNotification(id); const notif = Notifications.getNotification(id);
const NewNotif = Notification({ const NewNotif = Notification({
notif, notif,
command: () => notif.close(), command: () => notif.close(),
}); });
if (NewNotif) { if (NewNotif) {
box.add(NewNotif); box.add(NewNotif);
box.show_all(); box.show_all();
} }
} }
}, 'notified'],
}, 'notified'], [Notifications, (box, id) => {
for (const ch of box.children) {
if (ch._id == id) {
ch.child.setStyle(ch.child._rightAnim1);
timeout(500, () => box.remove(ch));
return;
}
}
}, 'closed'],
[Notifications, (box, id) => { [Notifications, box => box.visible = Notifications.notifications.length > 0],
for (const ch of box.children) { ],
if (ch._id == id) {
ch.child.setStyle(ch.child._rightAnim1);
timeout(500, () => box.remove(ch));
return;
}
}
}, 'closed'],
[Notifications, box => box.visible = Notifications.notifications.length > 0],
],
}); });
const Placeholder = () => Revealer({ const Placeholder = () => Revealer({
transition: 'crossfade', transition: 'crossfade',
connections: [[Notifications, box => { connections: [[Notifications, box => {
box.revealChild = Notifications.notifications.length === 0; box.revealChild = Notifications.notifications.length === 0;
}]], }]],
child: Box({ child: Box({
className: 'placeholder', className: 'placeholder',
vertical: true, vertical: true,
valign: 'center', valign: 'center',
halign: 'center', halign: 'center',
vexpand: true, vexpand: true,
hexpand: true, hexpand: true,
children: [ children: [
Icon('notification-disabled-symbolic'), Icon('notification-disabled-symbolic'),
Label('Your inbox is empty'), Label('Your inbox is empty'),
], ],
}), }),
}); });
const NotificationCenterWidget = () => Box({ const NotificationCenterWidget = () => Box({
className: 'notification-center', className: 'notification-center',
vertical: true, vertical: true,
children: [ children: [
Header(), Header(),
Box({ Box({
className: 'notification-wallpaper-box', className: 'notification-wallpaper-box',
children: [
Scrollable({
className: 'notification-list-box',
hscroll: 'never',
vscroll: 'automatic',
child: Box({
className: 'notification-list',
vertical: true,
children: [ children: [
NotificationList, Scrollable({
Placeholder(), className: 'notification-list-box',
hscroll: 'never',
vscroll: 'automatic',
child: Box({
className: 'notification-list',
vertical: true,
children: [
NotificationList,
Placeholder(),
],
}),
}),
], ],
}), }),
}) ],
],
}),
],
}); });
export default () => PopupWindow({ export default () => PopupWindow({
name: 'notification-center', name: 'notification-center',
anchor: [ 'top', 'right' ], anchor: ['top', 'right'],
margin: [ 8, 60, 0, 0 ], margin: [8, 60, 0, 0],
child: NotificationCenterWidget(), child: NotificationCenterWidget(),
}); });

View file

@ -1,4 +1,4 @@
import { Widget } from '../../imports.js'; import { Utils, Widget } from '../../imports.js';
const { Box, EventBox } = Widget; const { Box, EventBox } = Widget;
import Gtk from 'gi://Gtk'; import Gtk from 'gi://Gtk';
@ -7,136 +7,136 @@ const display = Gdk.Display.get_default();
export default ({ export default ({
maxOffset = 150, maxOffset = 150,
startMargin = 0, startMargin = 0,
endMargin = 300, endMargin = 300,
command = () => {}, command = () => {},
onHover = w => {}, onHover = () => {},
onHoverLost = w => {}, onHoverLost = () => {},
child = '', child = '',
children = [], children = [],
properties = [[]], properties = [[]],
...props ...props
}) => { }) => {
let widget = EventBox({ const widget = EventBox({
...props, ...props,
properties: [ properties: [
['dragging', false], ['dragging', false],
...properties, ...properties,
], ],
onHover: self => { onHover: self => {
self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab')); self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
onHover(self); onHover(self);
}, },
onHoverLost: self => { onHoverLost: self => {
self.window.set_cursor(null); self.window.set_cursor(null);
onHoverLost(self); onHoverLost(self);
}, },
}); });
let gesture = Gtk.GestureDrag.new(widget); const gesture = Gtk.GestureDrag.new(widget);
let leftAnim1 = `transition: margin 0.5s ease, opacity 0.5s ease; const leftAnim1 = `transition: margin 0.5s ease, opacity 0.5s ease;
margin-left: -${Number(maxOffset + endMargin)}px; margin-left: -${Number(maxOffset + endMargin)}px;
margin-right: ${Number(maxOffset + endMargin)}px; margin-right: ${Number(maxOffset + endMargin)}px;
opacity: 0;`; opacity: 0;`;
let leftAnim2 = `transition: margin 0.5s ease, opacity 0.5s ease; const leftAnim2 = `transition: margin 0.5s ease, opacity 0.5s ease;
margin-left: -${Number(maxOffset + endMargin)}px; margin-left: -${Number(maxOffset + endMargin)}px;
margin-right: ${Number(maxOffset + endMargin)}px; margin-right: ${Number(maxOffset + endMargin)}px;
margin-bottom: -70px; margin-top: -70px; opacity: 0;`; margin-bottom: -70px; margin-top: -70px; opacity: 0;`;
let rightAnim1 = `transition: margin 0.5s ease, opacity 0.5s ease; const rightAnim1 = `transition: margin 0.5s ease, opacity 0.5s ease;
margin-left: ${Number(maxOffset + endMargin)}px; margin-left: ${Number(maxOffset + endMargin)}px;
margin-right: -${Number(maxOffset + endMargin)}px; margin-right: -${Number(maxOffset + endMargin)}px;
opacity: 0;`; opacity: 0;`;
let rightAnim2 = `transition: margin 0.5s ease, opacity 0.5s ease; const rightAnim2 = `transition: margin 0.5s ease, opacity 0.5s ease;
margin-left: ${Number(maxOffset + endMargin)}px; margin-left: ${Number(maxOffset + endMargin)}px;
margin-right: -${Number(maxOffset + endMargin)}px; margin-right: -${Number(maxOffset + endMargin)}px;
margin-bottom: -70px; margin-top: -70px; opacity: 0;`; margin-bottom: -70px; margin-top: -70px; opacity: 0;`;
widget.add(Box({ widget.add(Box({
properties: [ properties: [
['leftAnim1', leftAnim1], ['leftAnim1', leftAnim1],
['leftAnim2', leftAnim2], ['leftAnim2', leftAnim2],
['rightAnim1', rightAnim1], ['rightAnim1', rightAnim1],
['rightAnim2', rightAnim2], ['rightAnim2', rightAnim2],
['ready', false] ['ready', false],
], ],
children: [ children: [
...children, ...children,
child, child,
], ],
style: leftAnim2, style: leftAnim2,
connections: [ connections: [
[gesture, self => { [gesture, self => {
var offset = gesture.get_offset()[1]; var offset = gesture.get_offset()[1];
if (offset >= 0) { if (offset >= 0) {
self.setStyle(`margin-left: ${Number(offset + startMargin)}px; self.setStyle(`margin-left: ${Number(offset + startMargin)}px;
margin-right: -${Number(offset + startMargin)}px;`); margin-right: -${Number(offset + startMargin)}px;`);
} }
else { else {
offset = Math.abs(offset); offset = Math.abs(offset);
self.setStyle(`margin-right: ${Number(offset + startMargin)}px; self.setStyle(`margin-right: ${Number(offset + startMargin)}px;
margin-left: -${Number(offset + startMargin)}px;`); margin-left: -${Number(offset + startMargin)}px;`);
} }
self.get_parent()._dragging = Math.abs(offset) > 10; self.get_parent()._dragging = Math.abs(offset) > 10;
if (widget.window) if (widget.window)
widget.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grabbing')); widget.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grabbing'));
}, 'drag-update'], }, 'drag-update'],
[gesture, self => { [gesture, self => {
if (!self._ready) { if (!self._ready) {
self.setStyle(`transition: margin 0.5s ease, opacity 0.5s ease; self.setStyle(`transition: margin 0.5s ease, opacity 0.5s ease;
margin-left: -${Number(maxOffset + endMargin)}px; margin-left: -${Number(maxOffset + endMargin)}px;
margin-right: ${Number(maxOffset + endMargin)}px; margin-right: ${Number(maxOffset + endMargin)}px;
margin-bottom: 0px; margin-top: 0px; opacity: 0;`); margin-bottom: 0px; margin-top: 0px; opacity: 0;`);
setTimeout(() => { Utils.timeout(500, () => {
self.setStyle(`transition: margin 0.5s ease, opacity 0.5s ease; self.setStyle(`transition: margin 0.5s ease, opacity 0.5s ease;
margin-left: ${startMargin}px; margin-left: ${startMargin}px;
margin-right: ${startMargin}px; margin-right: ${startMargin}px;
margin-bottom: unset; margin-top: unset; opacity: 1;`); margin-bottom: unset; margin-top: unset; opacity: 1;`);
}, 500); });
setTimeout(() => self._ready = true, 1000); Utils.timeout(1000, () => self._ready = true);
return; return;
} }
const offset = gesture.get_offset()[1]; const offset = gesture.get_offset()[1];
if (Math.abs(offset) > maxOffset) { if (Math.abs(offset) > maxOffset) {
if (offset > 0) { if (offset > 0) {
self.setStyle(rightAnim1); self.setStyle(rightAnim1);
setTimeout(() => self.setStyle(rightAnim2), 500); Utils.timeout(500, () => self.setStyle(rightAnim2));
} }
else { else {
self.setStyle(leftAnim1); self.setStyle(leftAnim1);
setTimeout(() => self.setStyle(leftAnim2), 500); Utils.timeout(500, () => self.setStyle(leftAnim2));
} }
setTimeout(() => { Utils.timeout(1000, () => {
command(); command();
self.destroy(); self.destroy();
}, 1000); });
} }
else { else {
self.setStyle(`transition: margin 0.5s ease, opacity 0.5s ease; self.setStyle(`transition: margin 0.5s ease, opacity 0.5s ease;
margin-left: ${startMargin}px; margin-left: ${startMargin}px;
margin-right: ${startMargin}px; margin-right: ${startMargin}px;
margin-bottom: unset; margin-top: unset; opacity: 1;`); margin-bottom: unset; margin-top: unset; opacity: 1;`);
if (widget.window) if (widget.window)
widget.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab')); widget.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
self.get_parent()._dragging = false; self.get_parent()._dragging = false;
} }
}, 'drag-end'], }, 'drag-end'],
], ],
})); }));
return widget; return widget;
}; };

View file

@ -1,87 +1,89 @@
import { Notifications, Widget } from '../../imports.js'; import { Notifications, Utils, Widget } from '../../imports.js';
const { Box, Revealer, Window } = Widget; const { Box, Revealer, Window } = Widget;
import GLib from 'gi://GLib';
import Notification from './base.js'; import Notification from './base.js';
const Popups = () => Box({ const Popups = () => Box({
vertical: true, vertical: true,
properties: [ properties: [
['map', new Map()], ['map', new Map()],
['dismiss', (box, id, force = false) => { ['dismiss', (box, id, force = false) => {
if (!id || !box._map.has(id) || if (!id || !box._map.has(id) ||
box._map.get(id)._hovered && !force) { box._map.get(id)._hovered && !force)
return; return;
}
if (box._map.size - 1 === 0)
box.get_parent().reveal_child = false;
setTimeout(() => { if (box._map.size - 1 === 0)
if (box._map.get(id)?.interval) { box.get_parent().reveal_child = false;
box._map.get(id).interval.destroy();
box._map.get(id).interval = undefined;
}
box._map.get(id)?.destroy();
box._map.delete(id);
}, 200);
}],
['notify', (box, id) => { Utils.timeout(200, () => {
if (!id || Notifications.dnd) if (box._map.get(id)?.interval) {
return; box._map.get(id).interval.destroy();
box._map.get(id).interval = undefined;
}
box._map.get(id)?.destroy();
box._map.delete(id);
});
}],
if (! Notifications.getNotification(id)) ['notify', (box, id) => {
return; if (!id || Notifications.dnd)
return;
box._map.delete(id); if (! Notifications.getNotification(id))
return;
let notif = Notifications.getNotification(id); box._map.delete(id);
box._map.set(id, Notification({
notif,
command: () => notif.dismiss(),
}));
box.children = Array.from(box._map.values()).reverse(); const notif = Notifications.getNotification(id);
box._map.set(id, Notification({
notif,
command: () => notif.dismiss(),
}));
setTimeout(() => { box.children = Array.from(box._map.values()).reverse();
box.get_parent().revealChild = true;
}, 10);
box._map.get(id).interval = setInterval(() => { Utils.timeout(10, () => {
if (!box._map.get(id)._hovered) { box.get_parent().revealChild = true;
box._map.get(id).child.setStyle(box._map.get(id).child._leftAnim1); });
if (box._map.get(id).interval) { box._map.get(id).interval = Utils.interval(4500, () => {
box._map.get(id).interval.destroy(); if (!box._map.get(id)._hovered) {
box._map.get(id).interval = undefined; box._map.get(id).child.setStyle(box._map.get(id).child._leftAnim1);
}
} if (box._map.get(id).interval) {
}, 4500); GLib.source_remove(box._map.get(id).interval);
}], box._map.get(id).interval = undefined;
], }
connections: [ }
[Notifications, (box, id) => box._notify(box, id), 'notified'], });
[Notifications, (box, id) => box._dismiss(box, id), 'dismissed'], }],
[Notifications, (box, id) => box._dismiss(box, id, true), 'closed'], ],
], connections: [
[Notifications, (box, id) => box._notify(box, id), 'notified'],
[Notifications, (box, id) => box._dismiss(box, id), 'dismissed'],
[Notifications, (box, id) => box._dismiss(box, id, true), 'closed'],
],
}); });
const PopupList = ({ transition = 'none' } = {}) => Box({ const PopupList = ({ transition = 'none' } = {}) => Box({
className: 'notifications-popup-list', className: 'notifications-popup-list',
style: 'padding: 1px', style: 'padding: 1px',
children: [ children: [
Revealer({ Revealer({
transition, transition,
child: Popups(), child: Popups(),
}), }),
], ],
}); });
export default () => Window({ export default () => Window({
name: `notifications`, name: 'notifications',
anchor: [ 'top', 'left' ], anchor: ['top', 'left'],
child: PopupList(), child: PopupList(),
}); });

View file

@ -6,11 +6,11 @@ import { WindowButton } from './dragndrop.js';
import * as VARS from './variables.js'; import * as VARS from './variables.js';
// Has to be a traditional function for 'this' scope // Has to be a traditional function for 'this' scope
Array.prototype.remove = function (el) { this.splice(this.indexOf(el), 1) }; Array.prototype.remove = function (el) { this.splice(this.indexOf(el), 1); };
const scale = size => size * VARS.SCALE - VARS.MARGIN; const scale = size => size * VARS.SCALE - VARS.MARGIN;
const getFontSize = client => Math.min(scale(client.size[0]), const getFontSize = client => Math.min(scale(client.size[0]),
scale(client.size[1])) * VARS.ICON_SCALE; scale(client.size[1])) * VARS.ICON_SCALE;
const IconStyle = client => `transition: font-size 0.2s linear; const IconStyle = client => `transition: font-size 0.2s linear;
min-width: ${scale(client.size[0])}px; min-width: ${scale(client.size[0])}px;
@ -19,127 +19,128 @@ const IconStyle = client => `transition: font-size 0.2s linear;
const Client = (client, active, clients) => { const Client = (client, active, clients) => {
let wsName = String(client.workspace.name).replace('special:', ''); const wsName = String(client.workspace.name).replace('special:', '');
let wsId = client.workspace.id; const wsId = client.workspace.id;
let addr = `address:${client.address}`; const addr = `address:${client.address}`;
// FIXME: special workspaces not closing when in one and clicking on normal client // FIXME: special workspaces not closing when in one and clicking on normal client
return Revealer({ return Revealer({
transition: 'crossfade', transition: 'crossfade',
setup: rev => rev.revealChild = true, setup: rev => rev.revealChild = true,
properties: [ properties: [
['address', client.address], ['address', client.address],
['toDestroy', false] ['toDestroy', false],
], ],
child: WindowButton({ child: WindowButton({
address: client.address, address: client.address,
onSecondaryClickRelease: () => { onSecondaryClickRelease: () => {
execAsync(`hyprctl dispatch closewindow ${addr}`) execAsync(`hyprctl dispatch closewindow ${addr}`)
.catch(print); .catch(print);
}, },
onPrimaryClickRelease: () => { onPrimaryClickRelease: () => {
if (wsId < 0) { if (wsId < 0) {
if (client.workspace.name === 'special') { if (client.workspace.name === 'special') {
execAsync(`hyprctl dispatch execAsync(`hyprctl dispatch
movetoworkspacesilent special:${wsId},${addr}`) movetoworkspacesilent special:${wsId},${addr}`)
.then( .then(
execAsync(`hyprctl dispatch togglespecialworkspace ${wsId}`).then( execAsync(`hyprctl dispatch togglespecialworkspace ${wsId}`).then(
() => App.closeWindow('overview') () => App.closeWindow('overview'),
).catch(print) ).catch(print),
).catch(print); ).catch(print);
} }
else { else {
execAsync(`hyprctl dispatch togglespecialworkspace ${wsName}`).then( execAsync(`hyprctl dispatch togglespecialworkspace ${wsName}`).then(
() => App.closeWindow('overview') () => App.closeWindow('overview'),
).catch(print); ).catch(print);
} }
} }
else { else {
// close special workspace if one is opened // close special workspace if one is opened
let activeAddress = Hyprland.active.client.address; const activeAddress = Hyprland.active.client.address;
let currentActive = clients.find(c => c.address === activeAddress) const currentActive = clients.find(c => c.address === activeAddress);
if (currentActive && currentActive.workspace.id < 0) { if (currentActive && currentActive.workspace.id < 0) {
execAsync(`hyprctl dispatch togglespecialworkspace ${wsName}`) execAsync(`hyprctl dispatch togglespecialworkspace ${wsName}`)
.catch(print); .catch(print);
} }
execAsync(`hyprctl dispatch focuswindow ${addr}`).then( execAsync(`hyprctl dispatch focuswindow ${addr}`).then(
() => App.closeWindow('overview') () => App.closeWindow('overview'),
).catch(print); ).catch(print);
} }
}, },
child: Icon({ child: Icon({
className: `window ${active}`, className: `window ${active}`,
style: IconStyle(client) + 'font-size: 10px;', style: IconStyle(client) + 'font-size: 10px;',
icon: client.class, icon: client.class,
}), }),
}), }),
}); });
}; };
export function updateClients(box) { export function updateClients(box) {
execAsync('hyprctl clients -j').then(out => { execAsync('hyprctl clients -j').then(out => {
let clients = JSON.parse(out).filter(client => client.class) const clients = JSON.parse(out).filter(client => client.class);
box._workspaces.forEach(workspace => { box._workspaces.forEach(workspace => {
let fixed = workspace.getFixed(); const fixed = workspace.getFixed();
let toRemove = fixed.get_children(); const toRemove = fixed.get_children();
clients.filter(client => client.workspace.id == workspace._id) clients.filter(client => client.workspace.id == workspace._id)
.forEach(client => { .forEach(client => {
let active = ''; let active = '';
if (client.address == Hyprland.active.client.address) { if (client.address == Hyprland.active.client.address)
active = 'active'; active = 'active';
}
// TODO: fix multi monitor issue. this is just a temp fix
client.at[1] -= 2920;
// Special workspaces that haven't been opened yet // TODO: fix multi monitor issue. this is just a temp fix
// return a size of 0. We need to set them to default client.at[1] -= 2920;
// values to show the workspace properly
if (client.size[0] === 0) {
client.size[0] = VARS.DEFAULT_SPECIAL.SIZE_X;
client.size[1] = VARS.DEFAULT_SPECIAL.SIZE_Y;
client.at[0] = VARS.DEFAULT_SPECIAL.POS_X;
client.at[1] = VARS.DEFAULT_SPECIAL.POS_Y;
}
let newClient = [ // Special workspaces that haven't been opened yet
fixed.get_children().find(ch => ch._address == client.address), // return a size of 0. We need to set them to default
client.at[0] * VARS.SCALE, // values to show the workspace properly
client.at[1] * VARS.SCALE, if (client.size[0] === 0) {
]; client.size[0] = VARS.DEFAULT_SPECIAL.SIZE_X;
client.size[1] = VARS.DEFAULT_SPECIAL.SIZE_Y;
client.at[0] = VARS.DEFAULT_SPECIAL.POS_X;
client.at[1] = VARS.DEFAULT_SPECIAL.POS_Y;
}
if (newClient[0]) { const newClient = [
toRemove.remove(newClient[0]); fixed.get_children().find(ch => ch._address == client.address),
fixed.move(...newClient); client.at[0] * VARS.SCALE,
} client.at[1] * VARS.SCALE,
else { ];
newClient[0] = Client(client, active, clients);
fixed.put(...newClient);
}
// Set a timeout here to have an animation when the icon first appears if (newClient[0]) {
setTimeout(() => { toRemove.remove(newClient[0]);
newClient[0].child.child.className = `window ${active}`; fixed.move(...newClient);
newClient[0].child.child.style = IconStyle(client); }
}, 1); else {
}); newClient[0] = Client(client, active, clients);
fixed.show_all(); fixed.put(...newClient);
toRemove.forEach(ch => { }
if (ch._toDestroy) {
ch.destroy(); // Set a timeout here to have an animation when the icon first appears
} Utils.timeout(1, () => {
else { newClient[0].child.child.className = `window ${active}`;
ch.revealChild = false; newClient[0].child.child.style = IconStyle(client);
ch._toDestroy = true; });
} });
});
}); fixed.show_all();
}).catch(print); toRemove.forEach(ch => {
}; if (ch._toDestroy) {
ch.destroy();
}
else {
ch.revealChild = false;
ch._toDestroy = true;
}
});
});
}).catch(print);
}

View file

@ -13,61 +13,61 @@ const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
function createSurfaceFromWidget(widget) { function createSurfaceFromWidget(widget) {
const alloc = widget.get_allocation(); const alloc = widget.get_allocation();
const surface = new Cairo.ImageSurface( const surface = new Cairo.ImageSurface(
Cairo.Format.ARGB32, Cairo.Format.ARGB32,
alloc.width, alloc.width,
alloc.height, alloc.height,
); );
const cr = new Cairo.Context(surface); const cr = new Cairo.Context(surface);
cr.setSourceRGBA(255, 255, 255, 0); cr.setSourceRGBA(255, 255, 255, 0);
cr.rectangle(0, 0, alloc.width, alloc.height); cr.rectangle(0, 0, alloc.width, alloc.height);
cr.fill(); cr.fill();
widget.draw(cr); widget.draw(cr);
return surface; return surface;
}; }
let hidden = 0; let hidden = 0;
export const WorkspaceDrop = props => EventBox({ export const WorkspaceDrop = props => EventBox({
...props, ...props,
connections: [['drag-data-received', (self, _c, _x, _y, data) => { connections: [['drag-data-received', (self, _c, _x, _y, data) => {
let id = self.get_parent()._id; let id = self.get_parent()._id;
if (id < -1) { if (id < -1)
id = self.get_parent()._name; id = self.get_parent()._name;
}
else if (id === -1) { else if (id === -1)
id = `special:${++hidden}`; id = `special:${++hidden}`;
}
else if (id === 1000) { else if (id === 1000)
id = "empty"; id = 'empty';
}
execAsync(`hyprctl dispatch movetoworkspacesilent ${id},address:${data.get_text()}`) execAsync(`hyprctl dispatch movetoworkspacesilent ${id},address:${data.get_text()}`)
.catch(print); .catch(print);
}]], }]],
setup: self => { setup: self => {
self.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY); self.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
}, },
}); });
export const WindowButton = ({address, ...props} = {}) => Button({ export const WindowButton = ({ address, ...props } = {}) => Button({
type: "Button", type: 'Button',
...props, ...props,
setup: self => { setup: self => {
self.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.COPY); self.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.COPY);
self.connect('drag-data-get', (_w, _c, data) => { self.connect('drag-data-get', (_w, _c, data) => {
data.set_text(address, address.length); data.set_text(address, address.length);
}); });
self.connect('drag-begin', (_, context) => { self.connect('drag-begin', (_, context) => {
Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(self)); Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(self));
self.get_parent().revealChild = false; self.get_parent().revealChild = false;
}); });
self.connect('drag-end', () => { self.connect('drag-end', () => {
self.get_parent().destroy(); self.get_parent().destroy();
let mainBox = App.getWindow('overview').getChild(); const mainBox = App.getWindow('overview').getChild();
updateClients(mainBox); updateClients(mainBox);
}); });
}, },
}); });

View file

@ -7,42 +7,42 @@ import { updateClients } from './clients.js';
function update(box) { function update(box) {
getWorkspaces(box); getWorkspaces(box);
updateWorkspaces(box); updateWorkspaces(box);
updateClients(box); updateClients(box);
} }
export default () => PopupWindow({ export default () => PopupWindow({
name: 'overview', name: 'overview',
transition: 'crossfade', transition: 'crossfade',
onOpen: child => update(child), onOpen: child => update(child),
child: Box({ child: Box({
className: 'overview', className: 'overview',
vertical: true,
children: [
Box({
vertical: true, vertical: true,
children: [ children: [
WorkspaceRow('normal', 0), Box({
vertical: true,
children: [
WorkspaceRow('normal', 0),
],
}),
Box({
vertical: true,
children: [
WorkspaceRow('special', 0),
],
}),
], ],
}), connections: [[Hyprland, self => {
Box({ if (!App.getWindow('overview').visible)
vertical: true, return;
children: [
WorkspaceRow('special', 0),
],
}),
],
connections: [[Hyprland, self => {
if (!App.getWindow('overview').visible)
return;
update(self); update(self);
}]], }]],
properties: [ properties: [
['workspaces'], ['workspaces'],
], ],
}), }),
}); });

View file

@ -2,13 +2,13 @@ export const SCALE = 0.11;
export const ICON_SCALE = 0.8; export const ICON_SCALE = 0.8;
export const MARGIN = 8; export const MARGIN = 8;
export const DEFAULT_SPECIAL = { export const DEFAULT_SPECIAL = {
SIZE_X: 1524, SIZE_X: 1524,
SIZE_Y: 908, SIZE_Y: 908,
POS_X: 197, POS_X: 197,
POS_Y: 170, POS_Y: 170,
}; };
export const WORKSPACE_PER_ROW = 6; export const WORKSPACE_PER_ROW = 6;
export const SCREEN = { export const SCREEN = {
X: 1920, X: 1920,
Y: 1200, Y: 1200,
}; };

View file

@ -1,7 +1,8 @@
import { Hyprland, Widget } from '../../imports.js'; import { Hyprland, Utils, Widget } from '../../imports.js';
const { Revealer, CenterBox, Box, EventBox, Label, Overlay } = Widget; const { Revealer, CenterBox, Box, EventBox, Label, Overlay } = Widget;
import Gtk from 'gi://Gtk'; import Gtk from 'gi://Gtk';
import GLib from 'gi://GLib';
import { WorkspaceDrop } from './dragndrop.js'; import { WorkspaceDrop } from './dragndrop.js';
import * as VARS from './variables.js'; import * as VARS from './variables.js';
@ -11,203 +12,201 @@ const DEFAULT_STYLE = `min-width: ${VARS.SCREEN.X * VARS.SCALE}px;
export function getWorkspaces(box) { export function getWorkspaces(box) {
let children = []; const children = [];
box.children.forEach(type => { box.children.forEach(type => {
type.children.forEach(row => { type.children.forEach(row => {
row.child.centerWidget.child.children.forEach(ch => { row.child.centerWidget.child.children.forEach(ch => {
children.push(ch); children.push(ch);
}); });
});
}); });
}); box._workspaces = children.sort((a, b) => a._id - b._id);
box._workspaces = children.sort((a, b) => a._id - b._id); }
};
export const WorkspaceRow = (className, i) => Revealer({ export const WorkspaceRow = (className, i) => Revealer({
transition: 'slide_down', transition: 'slide_down',
connections: [[Hyprland, rev => { connections: [[Hyprland, rev => {
let minId = i * VARS.WORKSPACE_PER_ROW; const minId = i * VARS.WORKSPACE_PER_ROW;
let activeId = Hyprland.active.workspace.id; const activeId = Hyprland.active.workspace.id;
rev.revealChild = Hyprland.workspaces.some(ws => ws.id > minId && rev.revealChild = Hyprland.workspaces.some(ws => ws.id > minId &&
(ws.windows > 0 ||
ws.id === activeId));
}]],
child: CenterBox({
children: [null, EventBox({
properties: [['box']],
setup: eventbox => eventbox._box = eventbox.child.children[0],
connections: [[Hyprland, eventbox => {
const maxId = i * VARS.WORKSPACE_PER_ROW + VARS.WORKSPACE_PER_ROW;
const activeId = Hyprland.active.workspace.id;
eventbox._box.revealChild = className === 'special' ||
!Hyprland.workspaces.some(ws => ws.id > maxId &&
(ws.windows > 0 || (ws.windows > 0 ||
ws.id === activeId)); ws.id === activeId));
}]], }]],
child: CenterBox({ child: Box({
children: [null, EventBox({ className: className,
properties: [['box']], children: [
setup: eventbox => eventbox._box = eventbox.child.children[0], Workspace(className === 'special' ? -1 : 1000,
connections: [[Hyprland, eventbox => { className === 'special' ? 'special' : '',
let maxId = i * VARS.WORKSPACE_PER_ROW + VARS.WORKSPACE_PER_ROW; true),
let activeId = Hyprland.active.workspace.id; ],
}),
eventbox._box.revealChild = className === 'special' || }), null],
!Hyprland.workspaces.some(ws => ws.id > maxId && }),
(ws.windows > 0 ||
ws.id === activeId));
}]],
child: Box({
className: className,
children: [
Workspace(className === 'special' ? -1 : 1000,
className === 'special' ? 'special' : '',
true),
],
}),
}), null],
}),
}); });
// TODO: please make this readable for the love of god // TODO: please make this readable for the love of god
const Workspace = (id, name, extra = false) => { const Workspace = (id, name, extra = false) => {
let workspace; let workspace;
let fixed = Gtk.Fixed.new(); const fixed = Gtk.Fixed.new();
if (!extra) { if (!extra) {
workspace = Revealer({ workspace = Revealer({
transition: 'slide_right', transition: 'slide_right',
transitionDuration: 500, transitionDuration: 500,
properties: [ properties: [
['id', id], ['id', id],
['name', name], ['name', name],
['timeouts', []], ['timeouts', []],
['wasActive', false], ['wasActive', false],
], ],
connections: [[Hyprland, box => { connections: [[Hyprland, box => {
box._timeouts.forEach(timer => timer.destroy()); box._timeouts.forEach(timer => GLib.source_remove(timer));
let activeId = Hyprland.active.workspace.id; const activeId = Hyprland.active.workspace.id;
let active = activeId === box._id; const active = activeId === box._id;
let rev = box.child.child.get_children()[1]; const rev = box.child.child.get_children()[1];
let n = activeId > box._id; const n = activeId > box._id;
if (Hyprland.getWorkspace(box._id)?.windows > 0 || active) { if (Hyprland.getWorkspace(box._id)?.windows > 0 || active) {
rev.setStyle(DEFAULT_STYLE); rev.setStyle(DEFAULT_STYLE);
box._timeouts.push(setTimeout(() => { box._timeouts.push(Utils.timeout(100, () => {
box.revealChild = true; box.revealChild = true;
}, 100)); }));
}
else if (!Hyprland.getWorkspace(box._id)?.windows > 0) {
rev.setStyle(DEFAULT_STYLE);
box._timeouts.push(Utils.timeout(100, () => {
box.revealChild = false;
}));
return;
}
} if (active) {
else if (!Hyprland.getWorkspace(box._id)?.windows > 0) { rev.setStyle(`${DEFAULT_STYLE}
rev.setStyle(DEFAULT_STYLE); transition: margin 0.5s ease-in-out;
box._timeouts.push(setTimeout(() => { opacity: 1;`);
box.revealChild = false; box._wasActive = true;
}, 100)); }
return; else if (box._wasActive) {
} box._timeouts.push(Utils.timeout(120, () => {
rev.setStyle(`${DEFAULT_STYLE}
if (active) { transition: margin 0.5s ease-in-out;
rev.setStyle(`${DEFAULT_STYLE} opacity: 1; margin-left: ${n ? '' : '-'}300px;
transition: margin 0.5s ease-in-out; margin-right: ${n ? '-' : ''}300px;`);
opacity: 1;`); box._wasActive = false;
box._wasActive = true; }));
} box._timeouts.push(Utils.timeout(500, () => {
else if (box._wasActive) { rev.setStyle(`${DEFAULT_STYLE} opacity: 0;
box._timeouts.push(setTimeout(() => { margin-left: ${n ? '' : '-'}300px;
rev.setStyle(`${DEFAULT_STYLE} margin-right: ${n ? '-' : ''}300px;`);
transition: margin 0.5s ease-in-out; }));
opacity: 1; margin-left: ${n ? '' : '-'}300px; }
margin-right: ${n ? '-' : ''}300px;`); else {
box._wasActive = false; rev.setStyle(`${DEFAULT_STYLE} opacity: 0;
}, 120)); margin-left: ${n ? '' : '-'}300px;
box._timeouts.push(setTimeout(() => { margin-right: ${n ? '-' : ''}300px;`);
rev.setStyle(`${DEFAULT_STYLE} opacity: 0; }
margin-left: ${n ? '' : '-'}300px; }]],
margin-right: ${n ? '-' : ''}300px;`); child: WorkspaceDrop({
}, 500)); child: Overlay({
} child: Box({
else { className: 'workspace active',
rev.setStyle(`${DEFAULT_STYLE} opacity: 0; style: `${DEFAULT_STYLE} opacity: 0;`,
margin-left: ${n ? '' : '-'}300px; }),
margin-right: ${n ? '-' : ''}300px;`); overlays: [
} Box({
}]], className: 'workspace active',
child: WorkspaceDrop({ style: `${DEFAULT_STYLE} opacity: 0;`,
child: Overlay({ }),
child: Box({ Box({
className: 'workspace active', className: 'workspace',
style: `${DEFAULT_STYLE} opacity: 0;`, style: DEFAULT_STYLE,
}), child: fixed,
overlays: [ }),
Box({ ],
className: 'workspace active',
style: `${DEFAULT_STYLE} opacity: 0;`,
}),
Box({
className: 'workspace',
style: DEFAULT_STYLE,
child: fixed,
})
],
}),
}),
});
}
else {
workspace = Revealer({
transition: 'slide_right',
properties: [
['id', id],
['name', name],
],
child: WorkspaceDrop({
child: Overlay({
child: Box({
className: 'workspace',
style: DEFAULT_STYLE,
}),
overlays: [
Box({
className: 'workspace active',
style: `${DEFAULT_STYLE} opacity: 0;`,
}),
Box({
style: DEFAULT_STYLE,
children: [
fixed,
Label({
label: ' +',
style: 'font-size: 40px;',
}), }),
], }),
}) });
], }
}), else {
}), workspace = Revealer({
}); transition: 'slide_right',
} properties: [
['id', id],
['name', name],
],
child: WorkspaceDrop({
child: Overlay({
child: Box({
className: 'workspace',
style: DEFAULT_STYLE,
}),
overlays: [
Box({
className: 'workspace active',
style: `${DEFAULT_STYLE} opacity: 0;`,
}),
Box({
style: DEFAULT_STYLE,
children: [
fixed,
Label({
label: ' +',
style: 'font-size: 40px;',
}),
],
}),
],
}),
}),
});
}
workspace.getFixed = () => fixed; workspace.getFixed = () => fixed;
return workspace; return workspace;
}; };
export function updateWorkspaces(box) { export function updateWorkspaces(box) {
Hyprland.workspaces.forEach(ws => { Hyprland.workspaces.forEach(ws => {
let currentWs = box._workspaces.find(ch => ch._id == ws.id); const currentWs = box._workspaces.find(ch => ch._id == ws.id);
if (!currentWs) { if (!currentWs) {
var type = 0; var type = 0;
var rowNo = 0; var rowNo = 0;
if (ws.id < 0) { if (ws.id < 0) {
// This means it's a special workspace // This means it's a special workspace
type = 1; type = 1;
} }
else { else {
rowNo = Math.floor((ws.id - 1) / VARS.WORKSPACE_PER_ROW); rowNo = Math.floor((ws.id - 1) / VARS.WORKSPACE_PER_ROW);
if (rowNo >= box.children[type].children.length) { if (rowNo >= box.children[type].children.length) {
for (let i = box.children[type].children.length; i <= rowNo; ++i) { for (let i = box.children[type].children.length; i <= rowNo; ++i)
box.children[type].add(WorkspaceRow(type ? 'special' : 'normal', i)); box.children[type].add(WorkspaceRow(type ? 'special' : 'normal', i));
} }
}
var row = box.children[type].children[rowNo].child.centerWidget.child;
row.add(Workspace(ws.id, type ? ws.name : ''));
} }
} });
var row = box.children[type].children[rowNo].child.centerWidget.child; box.show_all();
row.add(Workspace(ws.id, type ? ws.name : ''));
}
});
box.show_all();
// Make sure the order is correct // Make sure the order is correct
box._workspaces.forEach((workspace, i) => { box._workspaces.forEach((workspace, i) => {
workspace.get_parent().reorder_child(workspace, i) workspace.get_parent().reorder_child(workspace, i);
}); });
} }

View file

@ -2,46 +2,46 @@ import { Widget } from '../imports.js';
const { CenterBox, Label } = Widget; const { CenterBox, Label } = Widget;
import PopupWindow from './misc/popup.js'; import PopupWindow from './misc/popup.js';
import Button from './misc/cursorbox.js' import Button from './misc/cursorbox.js';
const PowermenuWidget = () => CenterBox({ const PowermenuWidget = () => CenterBox({
className: 'powermenu', className: 'powermenu',
vertical: false, vertical: false,
startWidget: Button({ startWidget: Button({
type: "Button", type: 'Button',
className: 'shutdown', className: 'shutdown',
onPrimaryClickRelease: 'systemctl poweroff', onPrimaryClickRelease: 'systemctl poweroff',
child: Label({ child: Label({
label: '襤', label: '襤',
}),
}), }),
}),
centerWidget: Button({ centerWidget: Button({
type: "Button", type: 'Button',
className: 'reboot', className: 'reboot',
onPrimaryClickRelease: 'systemctl reboot', onPrimaryClickRelease: 'systemctl reboot',
child: Label({ child: Label({
label: '勒', label: '勒',
}),
}), }),
}),
endWidget: Button({ endWidget: Button({
type: "Button", type: 'Button',
className: 'logout', className: 'logout',
onPrimaryClickRelease: 'hyprctl dispatch exit', onPrimaryClickRelease: 'hyprctl dispatch exit',
child: Label({ child: Label({
label: '', label: '',
}),
}), }),
}),
}); });
export default () => PopupWindow({ export default () => PopupWindow({
name: 'powermenu', name: 'powermenu',
transition: 'crossfade', transition: 'crossfade',
child: PowermenuWidget(), child: PowermenuWidget(),
}); });

View file

@ -6,240 +6,237 @@ import EventBox from '../misc/cursorbox.js';
const GridButton = ({ const GridButton = ({
command = () => {}, command = () => {},
secondaryCommand = () => {}, secondaryCommand = () => {},
icon icon,
} = {}) => Box({ } = {}) => Box({
className: 'grid-button', className: 'grid-button',
children: [ children: [
EventBox({ EventBox({
className: 'left-part', className: 'left-part',
onPrimaryClickRelease: () => command(), onPrimaryClickRelease: () => command(),
child: icon, child: icon,
}), }),
EventBox({ EventBox({
className: 'right-part', className: 'right-part',
onPrimaryClickRelease: () => secondaryCommand(), onPrimaryClickRelease: () => secondaryCommand(),
child: Label({ child: Label({
label: " ", label: ' ',
className: 'grid-chev', className: 'grid-chev',
}), }),
}), }),
], ],
}); });
const FirstRow = () => Box({ const FirstRow = () => Box({
className: 'button-row', className: 'button-row',
halign: 'center', halign: 'center',
style: 'margin-top: 15px; margin-bottom: 7px;', style: 'margin-top: 15px; margin-bottom: 7px;',
children: [ children: [
GridButton({ GridButton({
command: () => Network.toggleWifi(), command: () => Network.toggleWifi(),
secondaryCommand: () => { secondaryCommand: () => {
execAsync(['bash', '-c', 'nm-connection-editor']) execAsync(['bash', '-c', 'nm-connection-editor'])
.catch(print); .catch(print);
}, },
icon: Icon({ icon: Icon({
className: 'grid-label', className: 'grid-label',
connections: [[Network, icon => { connections: [[Network, icon => {
if (Network.wifi.enabled) { if (Network.wifi.enabled)
icon.icon = 'network-wireless-connected-symbolic'; icon.icon = 'network-wireless-connected-symbolic';
}
else {
icon.icon = 'network-wireless-offline-symbolic';
}
}, 'changed']],
}),
}),
GridButton({ else
command: () => { icon.icon = 'network-wireless-offline-symbolic';
execAsync(['bash', '-c', '$AGS_PATH/qs-toggles.sh blue-toggle']) }, 'changed']],
.catch(print); }),
}, }),
secondaryCommand: () => {
execAsync(['bash', '-c', 'blueberry'])
.catch(print)
},
icon: Icon({
className: 'grid-label',
connections: [[Bluetooth, self => {
if (Bluetooth.enabled) {
self.icon = 'bluetooth-active-symbolic';
execAsync(['bash', '-c', 'echo 󰂯 > $HOME/.config/.bluetooth'])
.catch(print);
}
else {
self.icon = 'bluetooth-disabled-symbolic';
execAsync(['bash', '-c', 'echo 󰂲 > $HOME/.config/.bluetooth'])
.catch(print);
}
}, 'changed']],
})
}),
GridButton({ GridButton({
command: () => { command: () => {
execAsync(['bash', '-c', '$AGS_PATH/qs-toggles.sh toggle-radio']) execAsync(['bash', '-c', '$AGS_PATH/qs-toggles.sh blue-toggle'])
.catch(print); .catch(print);
}, },
secondaryCommand: () => { secondaryCommand: () => {
execAsync(['notify-send', 'set this up moron']) execAsync(['bash', '-c', 'blueberry'])
.catch(print); .catch(print);
}, },
icon: Icon({ icon: Icon({
className: 'grid-label', className: 'grid-label',
connections: [[Network, self => { connections: [[Bluetooth, self => {
if (Network.wifi.enabled) if (Bluetooth.enabled) {
self.icon = 'airplane-mode-disabled-symbolic'; self.icon = 'bluetooth-active-symbolic';
else execAsync(['bash', '-c', 'echo 󰂯 > $HOME/.config/.bluetooth'])
self.icon = 'airplane-mode-symbolic'; .catch(print);
}, 'changed']], }
}), else {
}), self.icon = 'bluetooth-disabled-symbolic';
execAsync(['bash', '-c', 'echo 󰂲 > $HOME/.config/.bluetooth'])
.catch(print);
}
}, 'changed']],
}),
}),
], GridButton({
command: () => {
execAsync(['bash', '-c', '$AGS_PATH/qs-toggles.sh toggle-radio'])
.catch(print);
},
secondaryCommand: () => {
execAsync(['notify-send', 'set this up moron'])
.catch(print);
},
icon: Icon({
className: 'grid-label',
connections: [[Network, self => {
if (Network.wifi.enabled)
self.icon = 'airplane-mode-disabled-symbolic';
else
self.icon = 'airplane-mode-symbolic';
}, 'changed']],
}),
}),
],
}); });
const SubRow = () => CenterBox({ const SubRow = () => CenterBox({
halign: 'start', halign: 'start',
children: [ children: [
Label({ Label({
className: 'sub-label', className: 'sub-label',
truncate: 'end', truncate: 'end',
maxWidthChars: 12, maxWidthChars: 12,
connections: [[Network, self => { connections: [[Network, self => {
// TODO: handle ethernet too // TODO: handle ethernet too
self.label = Network.wifi.ssid; self.label = Network.wifi.ssid;
}, 'changed']], }, 'changed']],
}), }),
Label({ Label({
className: 'sub-label', className: 'sub-label',
truncate: 'end', truncate: 'end',
maxWidthChars: 12, maxWidthChars: 12,
connections: [[Bluetooth, self => { connections: [[Bluetooth, self => {
if (Bluetooth.connectedDevices[0]) if (Bluetooth.connectedDevices[0])
self.label = String(Bluetooth.connectedDevices[0]) self.label = String(Bluetooth.connectedDevices[0]);
else else
self.label = 'Disconnected'; self.label = 'Disconnected';
}, 'changed']], }, 'changed']],
}), }),
null, null,
], ],
}); });
const items = { const items = {
101: 'audio-volume-overamplified-symbolic', 101: 'audio-volume-overamplified-symbolic',
67: 'audio-volume-high-symbolic', 67: 'audio-volume-high-symbolic',
34: 'audio-volume-medium-symbolic', 34: 'audio-volume-medium-symbolic',
1: 'audio-volume-low-symbolic', 1: 'audio-volume-low-symbolic',
0: 'audio-volume-muted-symbolic', 0: 'audio-volume-muted-symbolic',
}; };
const itemsMic = { const itemsMic = {
2: 'audio-input-microphone-high-symbolic', 2: 'audio-input-microphone-high-symbolic',
1: 'audio-input-microphone-muted-symbolic', 1: 'audio-input-microphone-muted-symbolic',
0: 'audio-input-microphone-muted-symbolic', 0: 'audio-input-microphone-muted-symbolic',
}; };
const SecondRow = () => Box({ const SecondRow = () => Box({
className: 'button-row', className: 'button-row',
halign: 'center', halign: 'center',
style: 'margin-top: 7px; margin-bottom: 15px;', style: 'margin-top: 7px; margin-bottom: 15px;',
children: [ children: [
GridButton({ GridButton({
command: () => { command: () => {
execAsync(['swayosd-client', '--output-volume', 'mute-toggle']) execAsync(['swayosd-client', '--output-volume', 'mute-toggle'])
.catch(print); .catch(print);
}, },
secondaryCommand: () => { secondaryCommand: () => {
execAsync(['bash', '-c', 'pavucontrol']) execAsync(['bash', '-c', 'pavucontrol'])
.catch(print); .catch(print);
}, },
icon: Icon({ icon: Icon({
className: 'grid-label', className: 'grid-label',
connections: [[Audio, icon => { connections: [[Audio, icon => {
if (Audio.speaker) { if (Audio.speaker) {
if (Audio.speaker.stream.isMuted) { if (Audio.speaker.stream.isMuted) {
icon.icon = items[0]; icon.icon = items[0];
} }
else { else {
const vol = Audio.speaker.volume * 100; const vol = Audio.speaker.volume * 100;
for (const threshold of [-1, 0, 33, 66, 100]) { for (const threshold of [-1, 0, 33, 66, 100]) {
if (vol > threshold + 1) { if (vol > threshold + 1)
icon.icon = items[threshold + 1]; icon.icon = items[threshold + 1];
} }
} }
} }
} }, 'speaker-changed']],
}, 'speaker-changed']], }),
}), }),
}),
GridButton({ GridButton({
command: () => { command: () => {
execAsync(['swayosd-client', '--input-volume', 'mute-toggle']) execAsync(['swayosd-client', '--input-volume', 'mute-toggle'])
.catch(print); .catch(print);
}, },
secondaryCommand: () => { secondaryCommand: () => {
execAsync(['bash', '-c', 'pavucontrol']) execAsync(['bash', '-c', 'pavucontrol'])
.catch(print); .catch(print);
}, },
icon: Icon({ icon: Icon({
className: 'grid-label', className: 'grid-label',
connections: [[Audio, icon => { connections: [[Audio, icon => {
if (Audio.microphone) { if (Audio.microphone) {
if (Audio.microphone.stream.isMuted) { if (Audio.microphone.stream.isMuted) {
icon.icon = itemsMic[0]; icon.icon = itemsMic[0];
} }
else { else {
const vol = Audio.microphone.volume * 100; const vol = Audio.microphone.volume * 100;
for (const threshold of [-1, 0, 1]) { for (const threshold of [-1, 0, 1]) {
if (vol > threshold + 1) { if (vol > threshold + 1)
icon.icon = itemsMic[threshold + 1]; icon.icon = itemsMic[threshold + 1];
} }
} }
} }
} }, 'microphone-changed']],
}, 'microphone-changed']], }),
}) }),
}),
GridButton({ GridButton({
command: () => { command: () => {
execAsync(['bash', '-c', '$LOCK_PATH/lock.sh']) execAsync(['bash', '-c', '$LOCK_PATH/lock.sh'])
.catch(print); .catch(print);
}, },
secondaryCommand: () => App.openWindow('powermenu'), secondaryCommand: () => App.openWindow('powermenu'),
icon: Label({ icon: Label({
className: 'grid-label', className: 'grid-label',
label: " 󰌾 ", label: ' 󰌾 ',
}), }),
}), }),
], ],
}); });
export default () => Box({ export default () => Box({
className: 'button-grid', className: 'button-grid',
vertical: true, vertical: true,
halign: 'center', halign: 'center',
children: [ children: [
FirstRow(), FirstRow(),
SubRow(), SubRow(),
SecondRow(), SecondRow(),
], ],
}); });

View file

@ -9,42 +9,42 @@ import ToggleButton from './toggle-button.js';
const QuickSettingsWidget = () => Box({ const QuickSettingsWidget = () => Box({
className: 'qs-container', className: 'qs-container',
vertical: true, vertical: true,
children: [ children: [
Box({ Box({
className: 'quick-settings', className: 'quick-settings',
vertical: true, vertical: true,
children: [ children: [
Label({ Label({
label: 'Control Center', label: 'Control Center',
className: 'title', className: 'title',
halign: 'start', halign: 'start',
style: 'margin-left: 20px' style: 'margin-left: 20px',
}),
ButtonGrid(),
SliderBox(),
ToggleButton(),
],
}), }),
ButtonGrid(), Revealer({
transition: 'slide_down',
child: Player(),
}),
SliderBox(), ],
ToggleButton(),
],
}),
Revealer({
transition: 'slide_down',
child: Player(),
})
],
}); });
export default () => PopupWindow({ export default () => PopupWindow({
name: 'quick-settings', name: 'quick-settings',
anchor: [ 'top', 'right' ], anchor: ['top', 'right'],
margin: [ 8, 5, 0, ], margin: [8, 5, 0],
child: QuickSettingsWidget(), child: QuickSettingsWidget(),
}); });

View file

@ -3,93 +3,91 @@ const { Box, Slider, Icon, EventBox } = Widget;
const { execAsync } = Utils; const { execAsync } = Utils;
const items = { const items = {
101: 'audio-volume-overamplified-symbolic', 101: 'audio-volume-overamplified-symbolic',
67: 'audio-volume-high-symbolic', 67: 'audio-volume-high-symbolic',
34: 'audio-volume-medium-symbolic', 34: 'audio-volume-medium-symbolic',
1: 'audio-volume-low-symbolic', 1: 'audio-volume-low-symbolic',
0: 'audio-volume-muted-symbolic', 0: 'audio-volume-muted-symbolic',
}; };
export default () => Box({ export default () => Box({
className: 'slider-box', className: 'slider-box',
vertical: true, vertical: true,
halign: 'center', halign: 'center',
children: [ children: [
Box({ Box({
className: 'slider', className: 'slider',
valign: 'start', valign: 'start',
halign: 'center', halign: 'center',
children: [ children: [
Icon({ Icon({
size: 26, size: 26,
className: 'slider-label', className: 'slider-label',
connections: [[Audio, icon => { connections: [[Audio, icon => {
if (Audio.speaker) { if (Audio.speaker) {
if (Audio.speaker.stream.isMuted) { if (Audio.speaker.stream.isMuted) {
icon.icon = items[0]; icon.icon = items[0];
} }
else { else {
const vol = Audio.speaker.volume * 100; const vol = Audio.speaker.volume * 100;
for (const threshold of [-1, 0, 33, 66, 100]) { for (const threshold of [-1, 0, 33, 66, 100]) {
if (vol > threshold + 1) { if (vol > threshold + 1)
icon.icon = items[threshold + 1]; icon.icon = items[threshold + 1];
} }
} }
} }
} }, 'speaker-changed']],
}, 'speaker-changed']], }),
}),
Slider({ Slider({
connections: [[Audio, slider => { connections: [[Audio, slider => {
if (Audio.speaker) { if (Audio.speaker)
slider.value = Audio.speaker.volume; slider.value = Audio.speaker.volume;
} }, 'speaker-changed']],
}, 'speaker-changed']], onChange: ({ value }) => Audio.speaker.volume = value,
onChange: ({ value }) => Audio.speaker.volume = value, max: 0.999,
max: 0.999, draw_value: false,
draw_value: false, }),
}),
],
}),
Box({
className: 'slider',
valign: 'start',
halign: 'center',
children: [
Icon({
className: 'slider-label',
icon: 'display-brightness-symbolic',
}),
EventBox({
onHover: box => box.child._canChange = false,
onHoverLost: box => box.child._canChange = true,
child: Slider({
properties: [
['canChange', true],
], ],
onChange: ({ value }) => {
execAsync(`brightnessctl set ${value}`)
.catch(print);
},
connections: [[1000, slider => {
if (slider._canChange) {
execAsync('brightnessctl get')
.then(out => slider.value = out)
.catch(print);
}
}]],
min: 0,
max: 255,
draw_value: false,
}),
}), }),
],
}),
], Box({
className: 'slider',
valign: 'start',
halign: 'center',
children: [
Icon({
className: 'slider-label',
icon: 'display-brightness-symbolic',
}),
EventBox({
onHover: box => box.child._canChange = false,
onHoverLost: box => box.child._canChange = true,
child: Slider({
properties: [
['canChange', true],
],
onChange: ({ value }) => {
execAsync(`brightnessctl set ${value}`)
.catch(print);
},
connections: [[1000, slider => {
if (slider._canChange) {
execAsync('brightnessctl get')
.then(out => slider.value = out)
.catch(print);
}
}]],
min: 0,
max: 255,
draw_value: false,
}),
}),
],
}),
],
}); });

View file

@ -5,38 +5,38 @@ import Gtk from 'gi://Gtk';
import EventBox from '../misc/cursorbox.js'; import EventBox from '../misc/cursorbox.js';
export default () => { export default () => {
let widget = EventBox({}); const widget = EventBox({});
let toggleButton = Gtk.ToggleButton.new(); const toggleButton = Gtk.ToggleButton.new();
toggleButton.add(Icon({ toggleButton.add(Icon({
icon: 'go-down-symbolic', icon: 'go-down-symbolic',
className: 'arrow', className: 'arrow',
style: `-gtk-icon-transform: rotate(180deg);`, style: '-gtk-icon-transform: rotate(180deg);',
})); }));
// Setup // Setup
const id = Mpris.connect('changed', () => { const id = Mpris.connect('changed', () => {
toggleButton.set_active(Mpris.players.length > 0); toggleButton.set_active(Mpris.players.length > 0);
Mpris.disconnect(id); Mpris.disconnect(id);
}); });
// Connections // Connections
toggleButton.connect('toggled', () => { toggleButton.connect('toggled', () => {
let rev = toggleButton.get_parent().get_parent().get_parent().children[1]; const rev = toggleButton.get_parent().get_parent().get_parent().children[1];
if (toggleButton.get_active()) { if (toggleButton.get_active()) {
toggleButton.get_children()[0] toggleButton.get_children()[0]
.setStyle("-gtk-icon-transform: rotate(0deg);"); .setStyle('-gtk-icon-transform: rotate(0deg);');
rev.revealChild = true; rev.revealChild = true;
} }
else { else {
toggleButton.get_children()[0] toggleButton.get_children()[0]
.setStyle('-gtk-icon-transform: rotate(180deg);'); .setStyle('-gtk-icon-transform: rotate(180deg);');
rev.revealChild = false; rev.revealChild = false;
} }
}); });
widget.add(toggleButton); widget.add(toggleButton);
return widget; return widget;
} };

View file

@ -9,22 +9,22 @@ export const RoundedCorner = (place, props) => Widget({
valign: place.includes('top') ? 'start' : 'end', valign: place.includes('top') ? 'start' : 'end',
setup: widget => { setup: widget => {
const r = widget.get_style_context() const r = widget.get_style_context()
.get_property('border-radius', Gtk.StateFlags.NORMAL); .get_property('border-radius', Gtk.StateFlags.NORMAL);
widget.set_size_request(r, r); widget.set_size_request(r, r);
widget.connect('draw', Lang.bind(widget, (widget, cr) => { widget.connect('draw', Lang.bind(widget, (widget, cr) => {
const c = widget.get_style_context() const c = widget.get_style_context()
.get_property('background-color', Gtk.StateFlags.NORMAL); .get_property('background-color', Gtk.StateFlags.NORMAL);
const r = widget.get_style_context() const r = widget.get_style_context()
.get_property('border-radius', Gtk.StateFlags.NORMAL); .get_property('border-radius', Gtk.StateFlags.NORMAL);
const borderColor = widget.get_style_context() const borderColor = widget.get_style_context()
.get_property('color', Gtk.StateFlags.NORMAL); .get_property('color', Gtk.StateFlags.NORMAL);
// ur going to write border-width: something anyway // ur going to write border-width: something anyway
const borderWidth = widget.get_style_context() const borderWidth = widget.get_style_context()
.get_border(Gtk.StateFlags.NORMAL).left; .get_border(Gtk.StateFlags.NORMAL).left;
widget.set_size_request(r, r); widget.set_size_request(r, r);
switch (place) { switch (place) {
@ -54,9 +54,9 @@ export const RoundedCorner = (place, props) => Widget({
cr.fill(); cr.fill();
cr.setLineWidth(borderWidth); cr.setLineWidth(borderWidth);
cr.setSourceRGBA(borderColor.red, cr.setSourceRGBA(borderColor.red,
borderColor.green, borderColor.green,
borderColor.blue, borderColor.blue,
borderColor.alpha); borderColor.alpha);
cr.stroke(); cr.stroke();
})); }));
}, },
@ -68,7 +68,7 @@ export const Topleft = () => Widget.Window({
anchor: ['top', 'left'], anchor: ['top', 'left'],
exclusive: false, exclusive: false,
visible: true, visible: true,
child: RoundedCorner('topleft', { className: 'corner', }), child: RoundedCorner('topleft', { className: 'corner' }),
}); });
export const Topright = () => Widget.Window({ export const Topright = () => Widget.Window({
name: 'cornertr', name: 'cornertr',
@ -76,7 +76,7 @@ export const Topright = () => Widget.Window({
anchor: ['top', 'right'], anchor: ['top', 'right'],
exclusive: false, exclusive: false,
visible: true, visible: true,
child: RoundedCorner('topright', { className: 'corner', }), child: RoundedCorner('topright', { className: 'corner' }),
}); });
export const Bottomleft = () => Widget.Window({ export const Bottomleft = () => Widget.Window({
name: 'cornerbl', name: 'cornerbl',
@ -84,7 +84,7 @@ export const Bottomleft = () => Widget.Window({
anchor: ['bottom', 'left'], anchor: ['bottom', 'left'],
exclusive: false, exclusive: false,
visible: true, visible: true,
child: RoundedCorner('bottomleft', { className: 'corner', }), child: RoundedCorner('bottomleft', { className: 'corner' }),
}); });
export const Bottomright = () => Widget.Window({ export const Bottomright = () => Widget.Window({
name: 'cornerbr', name: 'cornerbr',
@ -92,5 +92,5 @@ export const Bottomright = () => Widget.Window({
anchor: ['bottom', 'right'], anchor: ['bottom', 'right'],
exclusive: false, exclusive: false,
visible: true, visible: true,
child: RoundedCorner('bottomright', { className: 'corner', }), child: RoundedCorner('bottomright', { className: 'corner' }),
}); });

View file

@ -1,6 +1,7 @@
{ {
"dependencies": { "dependencies": {
"@girs/gtk-3.0": "^3.24.39-3.2.2", "@girs/gtk-3.0": "^3.24.39-3.2.2",
"eslint": "^8.52.0" "eslint": "^8.52.0",
"stylelint-config-standard-scss": "^11.0.0"
} }
} }

View file

@ -1,5 +1,5 @@
.corner { .corner {
background-color: black; background-color: black;
border-radius: 18px; //Hard code because Hyprland rounding is in px border-radius: 18px; //Hard code because Hyprland rounding is in px
border-width: 0.068rem; border-width: 0.068rem;
} }

View file

@ -21,26 +21,26 @@
} }
button { button {
all: unset; all: unset;
transition: 200ms; transition: 200ms;
border-radius: 9px; border-radius: 9px;
color: #eee; color: #eee;
background-color: #664C90; background-color: #664C90;
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
padding: 4.5px 9px; padding: 4.5px 9px;
&:hover { &:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: rgba(238, 238, 238, 0.154); background-color: rgba(238, 238, 238, 0.154);
color: #f1f1f1; color: #f1f1f1;
} }
&:disabled { &:disabled {
box-shadow: none; box-shadow: none;
background-color: rgba(#664C90, 0.3); background-color: rgba(#664C90, 0.3);
color: rgba(238, 238, 238, 0.3); color: rgba(238, 238, 238, 0.3);
} }
label { label {
font-size: 1.2em; font-size: 1.2em;
} }
} }
} }
@ -50,51 +50,51 @@
border-radius: 30px; border-radius: 30px;
border-top: 2px solid $contrastbg; border-top: 2px solid $contrastbg;
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5); box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.5);
scrollbar { scrollbar {
all: unset; all: unset;
border-radius: 8px; border-radius: 8px;
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
* { * {
all: unset; all: unset;
} }
&:hover { &:hover {
border-radius: 8px; border-radius: 8px;
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
} }
scrollbar.vertical { scrollbar.vertical {
transition: 200ms; transition: 200ms;
background-color: rgba(23, 23, 23, 0.3); background-color: rgba(23, 23, 23, 0.3);
&:hover { &:hover {
background-color: rgba(23, 23, 23, 0.7); background-color: rgba(23, 23, 23, 0.7);
slider { slider {
background-color: rgba(238, 238, 238, 0.7); background-color: rgba(238, 238, 238, 0.7);
min-width: .6em; min-width: .6em;
} }
} }
slider { slider {
background-color: rgba(238, 238, 238, 0.5); background-color: rgba(238, 238, 238, 0.5);
border-radius: 9px; border-radius: 9px;
min-width: .4em; min-width: .4em;
min-height: 2em; min-height: 2em;
transition: 200ms; transition: 200ms;
} }
} }
} }
.placeholder { .placeholder {
color: white; color: white;
image { image {
font-size: 7em; font-size: 7em;
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6); text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6);
-gtk-icon-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6); -gtk-icon-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6);
} }
label { label {
font-size: 1.2em; font-size: 1.2em;
text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6); text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6);
-gtk-icon-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6); -gtk-icon-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6);
} }
} }
} }

View file

@ -10,170 +10,170 @@ $background-color_9: rgba(238, 238, 238, 0.7);
$background-color_10: rgba(238, 238, 238, 0.5); $background-color_10: rgba(238, 238, 238, 0.5);
.notification.critical { .notification.critical {
>box { >box {
box-shadow: inset 0 0 0.5em 0 #e67090; box-shadow: inset 0 0 0.5em 0 #e67090;
} }
} }
.notification { .notification {
>box { >box {
all: unset; all: unset;
box-shadow: 0 0 4.5px 0 rgba(0, 0, 0, 0.4); box-shadow: 0 0 4.5px 0 rgba(0, 0, 0, 0.4);
margin: 9px 9px 0 9px; margin: 9px 9px 0 9px;
border: 2px solid $contrastbg; border: 2px solid $contrastbg;
border-radius: 15px; border-radius: 15px;
background-color: $bg; background-color: $bg;
padding: 16.2px; padding: 16.2px;
* { * {
font-size: 16px; font-size: 16px;
} }
} }
&:hover { &:hover {
.close-button { .close-button {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: $background-color_1; background-color: $background-color_1;
background-color: $background-color_2; background-color: $background-color_2;
} }
} }
.title { .title {
margin-right: 9px; margin-right: 9px;
font-size: 1.1em; font-size: 1.1em;
} }
.description { .description {
font-size: .9em; font-size: .9em;
min-width: 350px; min-width: 350px;
} }
.icon { .icon {
border-radius: 7.2px; border-radius: 7.2px;
margin-right: 9px; margin-right: 9px;
} }
.icon.img { .icon.img {
border: 1px solid rgba(238, 238, 238, 0.03); border: 1px solid rgba(238, 238, 238, 0.03);
} }
.actions { .actions {
button { button {
all: unset; all: unset;
transition: all 500ms; transition: all 500ms;
border-radius: 9px; border-radius: 9px;
background-color: $background-color_3; background-color: $background-color_3;
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
border-radius: 7.2px; border-radius: 7.2px;
font-size: 1.2em; font-size: 1.2em;
padding: 4.5px 9px; padding: 4.5px 9px;
margin: 9px 4.5px 0; margin: 9px 4.5px 0;
* { * {
font-size: 16px; font-size: 16px;
} }
&:focus { &:focus {
box-shadow: inset 0 0 0 1px #51a4e7; box-shadow: inset 0 0 0 1px #51a4e7;
background-color: $background-color_1; background-color: $background-color_1;
} }
&:hover { &:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: $background-color_1; background-color: $background-color_1;
} }
&:active { &:active {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb); background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4; background-color: $background-color_4;
&:hover { &:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
} }
} }
&:checked { &:checked {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb); background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4; background-color: $background-color_4;
&:hover { &:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
} }
} }
&:disabled { &:disabled {
box-shadow: none; box-shadow: none;
background-color: $background-color_5; background-color: $background-color_5;
} }
&:first-child { &:first-child {
margin-left: 0; margin-left: 0;
} }
&:last-child { &:last-child {
margin-right: 0; margin-right: 0;
} }
} }
button.on { button.on {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb); background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4; background-color: $background-color_4;
&:hover { &:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
} }
} }
button.active { button.active {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb); background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4; background-color: $background-color_4;
&:hover { &:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
} }
} }
} }
button.close-button { button.close-button {
all: unset; all: unset;
transition: all 500ms; transition: all 500ms;
border-radius: 9px; border-radius: 9px;
background-color: $background-color_5; background-color: $background-color_5;
background-image: none; background-image: none;
box-shadow: none; box-shadow: none;
margin-left: 9px; margin-left: 9px;
border-radius: 7.2px; border-radius: 7.2px;
min-width: 1.2em; min-width: 1.2em;
min-height: 1.2em; min-height: 1.2em;
* { * {
font-size: 16px; font-size: 16px;
} }
&:focus { &:focus {
box-shadow: inset 0 0 0 1px #51a4e7; box-shadow: inset 0 0 0 1px #51a4e7;
background-color: $background-color_1; background-color: $background-color_1;
} }
&:hover { &:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-color: $background-color_1; background-color: $background-color_1;
background-color: $background-color_2; background-color: $background-color_2;
} }
&:active { &:active {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb); background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4; background-color: $background-color_4;
background-image: linear-gradient(#e67090, #e67090); background-image: linear-gradient(#e67090, #e67090);
&:hover { &:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
} }
} }
&:checked { &:checked {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb); background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4; background-color: $background-color_4;
&:hover { &:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
} }
} }
&:disabled { &:disabled {
box-shadow: none; box-shadow: none;
background-color: $background-color_5; background-color: $background-color_5;
} }
} }
button.close-button.on { button.close-button.on {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb); background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4; background-color: $background-color_4;
&:hover { &:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
} }
} }
button.close-button.active { button.close-button.active {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03);
background-image: linear-gradient(to right, #51a4e7, #6cb2eb); background-image: linear-gradient(to right, #51a4e7, #6cb2eb);
background-color: $background-color_4; background-color: $background-color_4;
&:hover { &:hover {
box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154); box-shadow: inset 0 0 0 1px rgba(238, 238, 238, 0.03), inset 0 0 0 99px rgba(238, 238, 238, 0.154);
} }
} }
} }

View file

@ -4,88 +4,87 @@ import { Service, Utils } from '../imports.js';
class Pointers extends Service { class Pointers extends Service {
static { static {
Service.register(this, { Service.register(this, {
'proc-started': ['boolean'], 'proc-started': ['boolean'],
'proc-destroyed': ['boolean'], 'proc-destroyed': ['boolean'],
'device-fetched': ['boolean'], 'device-fetched': ['boolean'],
'new-line': ['string'], 'new-line': ['string'],
}); });
} }
proc = undefined; proc = undefined;
output = ""; output = '';
devices = new Map(); devices = new Map();
get proc() { return this.proc; } get process() { return this.proc; }
get output() { return this.output; } get lastLine() { return this.output; }
get devices() { return this.devices; } get pointers() { return this.devices; }
constructor() { constructor() {
super(); super();
this.parseDevices(); this.parseDevices();
} }
parseDevices() { parseDevices() {
Utils.execAsync(['libinput', 'list-devices']).then(out => { Utils.execAsync(['libinput', 'list-devices']).then(out => {
let lines = out.split('\n'); const lines = out.split('\n');
let device = null; let device = null;
let devices = new Map(); const devices = new Map();
lines.forEach(line => { lines.forEach(line => {
let parts = line.split(':'); const parts = line.split(':');
if (parts[0] === 'Device') { if (parts[0] === 'Device') {
device = {}; device = {};
devices.set(parts[1].trim(), device); devices.set(parts[1].trim(), device);
} }
else if (device && parts[1]) { else if (device && parts[1]) {
let key = parts[0].trim(); const key = parts[0].trim();
let value = parts[1].trim(); const value = parts[1].trim();
device[key] = value; device[key] = value;
} }
}); });
this.devices = Array.from(devices).filter(dev => dev.Capabilities && this.devices = Array.from(devices).filter(dev => dev.Capabilities &&
dev.Capabilities.includes('pointer')); dev.Capabilities.includes('pointer'));
this.emit('device-fetched', true); this.emit('device-fetched', true);
}).catch(print); }).catch(print);
} }
startProc() { startProc() {
if (this.proc) if (this.proc)
return; return;
let args = []; const args = [];
this.devices.forEach(dev => { this.devices.forEach(dev => {
if (dev.Kernel) { if (dev.Kernel) {
args.push('--device'); args.push('--device');
args.push(dev.Kernel); args.push(dev.Kernel);
} }
}); });
this.proc = Utils.subprocess( this.proc = Utils.subprocess(
['libinput', 'debug-events', ...args], ['libinput', 'debug-events', ...args],
(output) => { output => {
if (output.includes('BTN') && output.includes('released') || if (output.includes('BTN') && output.includes('released') ||
output.includes('TOUCH_UP') || output.includes('TOUCH_UP') ||
output.includes('HOLD_END') && !output.includes('cancelled')) { output.includes('HOLD_END') && !output.includes('cancelled')) {
this.output = output;
this.output = output; this.emit('new-line', output);
this.emit('new-line', output); }
} },
}, err => logError(err),
(err) => logError(err) );
); this.emit('proc-started', true);
this.emit('proc-started', true); }
}
killProc() {
killProc() { if (this.proc) {
if (this.proc) { this.proc.force_exit();
this.proc.force_exit(); this.proc = undefined;
this.proc = undefined; this.emit('proc-destroyed', true);
this.emit('proc-destroyed', true); }
} }
}
} }
const pointersService = new Pointers(); const pointersService = new Pointers();