feat(ags): add bluetooth stuff and some refactor

This commit is contained in:
matt1432 2023-12-05 11:35:40 -05:00
parent 5dba231074
commit a76d920a57
19 changed files with 328 additions and 278 deletions

View file

@ -1,14 +0,0 @@
final: prev: {
blueberry = prev.blueberry.overrideAttrs (o: {
patches =
(o.patches or [])
++ [
./wayland.patch
];
buildInputs =
(o.buildInputs or [])
++ [
prev.libappindicator
];
});
}

View file

@ -1,95 +0,0 @@
# https://github.com/linuxmint/blueberry/issues/120
--- /usr/lib/blueberry/blueberry-tray.py.org 2021-12-13 01:02:56.923349069 -0800
+++ /usr/lib/blueberry/blueberry-tray.py 2021-12-13 02:21:23.253300141 -0800
@@ -5,8 +5,8 @@
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GnomeBluetooth', '1.0')
-gi.require_version('XApp', '1.0')
-from gi.repository import Gtk, Gdk, GnomeBluetooth, Gio, XApp
+gi.require_version('AppIndicator3', '0.1')
+from gi.repository import AppIndicator3, Gtk, Gdk, GnomeBluetooth, Gio
import rfkillMagic
import setproctitle
import subprocess
@@ -53,12 +53,16 @@
self.model.connect('row-deleted', self.update_icon_callback)
self.model.connect('row-inserted', self.update_icon_callback)
- self.icon = XApp.StatusIcon()
- self.icon.set_name("blueberry")
- self.icon.set_tooltip_text(_("Bluetooth"))
- self.icon.connect("activate", self.on_statusicon_activated)
- self.icon.connect("button-release-event", self.on_statusicon_released)
-
+ self.paired_devices = {}
+
+ self.icon = AppIndicator3.Indicator.new(
+ 'BlueBerry',
+ 'blueberry',
+ AppIndicator3.IndicatorCategory.SYSTEM_SERVICES
+ )
+ self.icon.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
+ self.icon.set_menu(self.build_menu())
+
self.update_icon_callback(None, None, None)
def on_settings_changed_cb(self, setting, key, data=None):
@@ -71,21 +75,23 @@
return
if self.rfkill.hard_block or self.rfkill.soft_block:
- self.icon.set_icon_name(self.tray_disabled_icon)
- self.icon.set_tooltip_text(_("Bluetooth is disabled"))
+ self.icon.set_title(_("Bluetooth is disabled"))
+ self.icon.set_icon(self.tray_disabled_icon)
+ self.icon.set_menu(self.build_menu())
else:
- self.icon.set_icon_name(self.tray_icon)
+ self.icon.set_icon(self.tray_icon)
+ self.icon.set_menu(self.build_menu())
self.update_connected_state()
def update_connected_state(self):
self.get_devices()
if len(self.connected_devices) > 0:
- self.icon.set_icon_name(self.tray_active_icon)
- self.icon.set_tooltip_text(_("Bluetooth: Connected to %s") % (", ".join(self.connected_devices)))
+ self.icon.set_title(_("Bluetooth: Connected to %s") % (", ".join(self.connected_devices)))
+ self.icon.set_icon(self.tray_active_icon)
else:
- self.icon.set_icon_name(self.tray_icon)
- self.icon.set_tooltip_text(_("Bluetooth"))
+ self.icon.set_title(_("Bluetooth"))
+ self.icon.set_icon(self.tray_icon)
def get_devices(self):
self.connected_devices = []
@@ -117,13 +118,11 @@
iter = self.model.iter_next(iter)
- def on_statusicon_activated(self, icon, button, time):
- if button == Gdk.BUTTON_PRIMARY:
- subprocess.Popen(["blueberry"])
+ def start_blueberry(self, _ignored):
+ subprocess.Popen(["blueberry"])
- def on_statusicon_released(self, icon, x, y, button, time, position):
- if button == 3:
+ def build_menu(self):
menu = Gtk.Menu()
if not self.rfkill.hard_block:
if self.rfkill.soft_block:
@@ -168,7 +170,7 @@
menu.append(item)
menu.show_all()
- icon.popup_menu(menu, x, y, button, time, position)
+ return menu
def toggle_connect_cb(self, item, data = None):
proxy = self.paired_devices[data]

View file

@ -9,7 +9,6 @@
]; ];
nixpkgs.overlays = [ nixpkgs.overlays = [
(import ./blueberry)
(import ./spotifywm) (import ./spotifywm)
(import ./squeekboard) (import ./squeekboard)

View file

@ -1,33 +0,0 @@
#!/usr/bin/env bash
radio_status () {
radio_status=$(nmcli radio wifi)
if [[ $radio_status == "enabled" ]]; then
echo "on"
else
echo "off"
fi
}
if [[ $1 == "toggle-radio" ]]; then
stat=$(radio_status)
if [[ $stat == "on" ]]; then
nmcli radio wifi off
else
nmcli radio wifi on
fi
fi
FILE='/home/matt/.config/.bluetooth'
get_state() {
if [[ "$(rfkill list | grep -A 1 hci0 | grep -o no)" == "no" ]]; then
echo " 󰂯 " > "$FILE"
else
echo " 󰂲 " > "$FILE"
fi
}
if [[ "$1" == "blue-toggle" ]]; then
rfkill toggle bluetooth
get_state
fi

View file

@ -1,10 +0,0 @@
#!/usr/bin/env bash
## Make bluetooth status persistent between reboots
if [[ ! -f "$HOME/.config/.bluetooth" ]]; then
echo 󰂲 > "$FILE"
fi
if grep -q 󰂲 "$HOME/.config/.bluetooth"; then
rfkill block bluetooth
fi

View file

@ -1,37 +1,24 @@
import Battery from 'resource:///com/github/Aylur/ags/service/battery.js'; import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
import { Box, EventBox, Icon, Label, Overlay, Revealer } from 'resource:///com/github/Aylur/ags/widget.js'; import { Label, Icon, Box } from 'resource:///com/github/Aylur/ags/widget.js';
import Separator from '../../misc/separator.js'; import Separator from '../../misc/separator.js';
const LOW_BATT = 20; const LOW_BATT = 20;
const NumOverlay = () => Label({ const Indicator = () => Icon({
className: 'bg-text', className: 'battery-indicator',
hpack: 'center',
vpack: 'center', binds: [['icon', Battery, 'icon-name']],
connections: [[Battery, (self) => { connections: [[Battery, (self) => {
self.label = `${Math.floor(Battery.percent / 10)}`; self.toggleClassName('charging', Battery.charging);
self.visible = !Battery.charging; self.toggleClassName('charged', Battery.charged);
self.toggleClassName('low', Battery.percent < LOW_BATT);
}]], }]],
}); });
const Indicator = (overlay) => Overlay({
child: Icon({
className: 'battery-indicator',
binds: [['icon', Battery, 'icon-name']],
connections: [[Battery, (self) => {
self.toggleClassName('charging', Battery.charging);
self.toggleClassName('charged', Battery.charged);
self.toggleClassName('low', Battery.percent < LOW_BATT);
}]],
}),
overlays: [overlay],
});
const LevelLabel = (props) => Label({ const LevelLabel = (props) => Label({
...props, ...props,
className: 'label', className: 'label',
@ -43,44 +30,12 @@ const LevelLabel = (props) => Label({
const SPACING = 5; const SPACING = 5;
export default () => { export default () => Box({
const rev1 = NumOverlay(); className: 'toggle-off battery',
const rev = Revealer({
transition: 'slide_right',
child: Box({
children: [
Separator(SPACING),
LevelLabel(),
],
}),
});
const widget = EventBox({ children: [
onHover: () => { Indicator(),
rev.revealChild = true; Separator(SPACING),
LevelLabel(),
if (!Battery.charging) { ],
rev1.visible = false; });
}
},
onHoverLost: () => {
rev.revealChild = false;
if (!Battery.charging) {
rev1.visible = true;
}
},
child: Box({
className: 'battery',
children: [
Indicator(rev1),
rev,
],
}),
});
widget.rev = rev;
return widget;
};

View file

@ -0,0 +1,64 @@
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
import { Label, Box, EventBox, Icon, Revealer } from 'resource:///com/github/Aylur/ags/widget.js';
import Separator from '../../misc/separator.js';
const Indicator = (props) => Icon({
...props,
connections: [[Bluetooth, (self) => {
if (Bluetooth.enabled) {
self.icon = Bluetooth.connectedDevices[0] ?
Bluetooth.connectedDevices[0].iconName :
'bluetooth-active-symbolic';
}
else {
self.icon = 'bluetooth-disabled-symbolic';
}
}]],
});
const ConnectedLabel = (props) => Label({
...props,
connections: [[Bluetooth, (self) => {
self.label = Bluetooth.connectedDevices[0] ?
`${Bluetooth.connectedDevices[0]}` :
'Disconnected';
}, 'notify::connected-devices']],
});
const SPACING = 5;
export default () => {
const rev = Revealer({
transition: 'slide_right',
child: Box({
children: [
Separator(SPACING),
ConnectedLabel(),
],
}),
});
const widget = EventBox({
onHover: () => {
rev.revealChild = true;
},
onHoverLost: () => {
rev.revealChild = false;
},
child: Box({
className: 'bluetooth',
children: [
Indicator(),
rev,
],
}),
});
widget.rev = rev;
return widget;
};

View file

@ -3,7 +3,7 @@ import App from 'resource:///com/github/Aylur/ags/app.js';
import { Box, Label } from 'resource:///com/github/Aylur/ags/widget.js'; import { Box, Label } from 'resource:///com/github/Aylur/ags/widget.js';
import Audio from './audio.js'; import Audio from './audio.js';
import Battery from './battery.js'; import Bluetooth from './bluetooth.js';
import KeyboardLayout from './keyboard-layout.js'; import KeyboardLayout from './keyboard-layout.js';
import Network from './network.js'; import Network from './network.js';
@ -36,7 +36,7 @@ export default () => EventBox({
Audio(), Audio(),
Battery(), Bluetooth(),
Network(), Network(),

View file

@ -2,6 +2,7 @@ import { Window, CenterBox, Box } from 'resource:///com/github/Aylur/ags/widget.
import Separator from '../misc/separator.js'; import Separator from '../misc/separator.js';
import Battery from './buttons/battery.js';
import Brightness from './buttons/brightness.js'; import Brightness from './buttons/brightness.js';
import Clock from './buttons/clock.js'; import Clock from './buttons/clock.js';
import CurrentWindow from './buttons/current-window.js'; import CurrentWindow from './buttons/current-window.js';
@ -41,11 +42,11 @@ export default () => Window({
SysTray(), SysTray(),
Brightness(), Workspaces(),
Separator(SPACING), Separator(SPACING),
Workspaces(), CurrentWindow(),
], ],
}), }),
@ -54,7 +55,7 @@ export default () => Window({
children: [ children: [
Separator(SPACING), Separator(SPACING),
CurrentWindow(), Clock(),
Separator(SPACING), Separator(SPACING),
], ],
@ -63,7 +64,11 @@ export default () => Window({
endWidget: Box({ endWidget: Box({
hpack: 'end', hpack: 'end',
children: [ children: [
Clock(), Brightness(),
Separator(SPACING),
Battery(),
Separator(SPACING), Separator(SPACING),

View file

@ -72,11 +72,10 @@ const CalendarWidget = () => Box({
}); });
const TOP_MARGIN = 6; const TOP_MARGIN = 6;
const RIGHT_MARGIN = 182;
export default () => PopupWindow({ export default () => PopupWindow({
anchor: ['top', 'right'], anchor: ['top'],
margins: [TOP_MARGIN, RIGHT_MARGIN, 0, 0], margins: [TOP_MARGIN, 0, 0, 0],
name: 'calendar', name: 'calendar',
child: Box({ child: Box({
className: 'date', className: 'date',

View file

@ -0,0 +1,192 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
import { Box, Icon, Label, ListBox, Overlay, Revealer, Scrollable } from 'resource:///com/github/Aylur/ags/widget.js';
import EventBox from '../misc/cursorbox.js';
const SCROLL_THRESHOLD_H = 200;
const SCROLL_THRESHOLD_N = 7;
const BluetoothDevice = (dev) => {
const widget = Box({
className: 'menu-item',
});
const child = Box({
hexpand: true,
children: [
Icon({
binds: [['icon', dev, 'icon-name']],
}),
Label({
binds: [['label', dev, 'name']],
}),
Icon({
icon: 'object-select-symbolic',
hexpand: true,
hpack: 'end',
connections: [[dev, (self) => {
self.setCss(`opacity: ${dev.paired ? '1' : '0'};`);
}]],
}),
],
});
widget.dev = dev;
widget.add(Revealer({
revealChild: true,
transition: 'slide_down',
child: EventBox({
onPrimaryClickRelease: () => dev.setConnection(true),
child,
}),
}));
return widget;
};
export const BluetoothMenu = () => {
const DevList = new Map();
const topArrow = Revealer({
transition: 'slide_down',
child: Icon({
icon: `${App.configDir }/icons/down-large.svg`,
className: 'scrolled-indicator',
size: 16,
css: '-gtk-icon-transform: rotate(180deg);',
}),
});
const bottomArrow = Revealer({
transition: 'slide_up',
child: Icon({
icon: `${App.configDir }/icons/down-large.svg`,
className: 'scrolled-indicator',
size: 16,
}),
});
return Overlay({
pass_through: true,
overlays: [
Box({
vpack: 'start',
hpack: 'center',
css: 'margin-top: 12px',
children: [topArrow],
}),
Box({
vpack: 'end',
hpack: 'center',
css: 'margin-bottom: 12px',
children: [bottomArrow],
}),
],
child: Box({
className: 'menu',
child: Scrollable({
hscroll: 'never',
vscroll: 'never',
connections: [['edge-reached', (_, pos) => {
// Manage scroll indicators
if (pos === 2) {
topArrow.revealChild = false;
bottomArrow.revealChild = true;
}
else if (pos === 3) {
topArrow.revealChild = true;
bottomArrow.revealChild = false;
}
}]],
child: ListBox({
setup: (self) => {
self.set_sort_func((a, b) => {
return b.get_children()[0].dev.paired -
a.get_children()[0].dev.paired;
});
},
connections: [[Bluetooth, (box) => {
// Get all devices
const Devices = [].concat(
Bluetooth.devices,
Bluetooth.connectedDevices,
);
// Add missing devices
Devices.forEach((dev) => {
if (!DevList.has(dev) && dev.name) {
DevList.set(dev, BluetoothDevice(dev));
box.add(DevList.get(dev));
box.show_all();
}
});
// Delete ones that don't exist anymore
const difference = Array.from(DevList.keys())
.filter((dev) => !Devices
.find((d) => dev === d) &&
dev.name);
difference.forEach((dev) => {
const devWidget = DevList.get(dev);
if (devWidget) {
if (devWidget.toDestroy) {
devWidget.get_parent().destroy();
DevList.delete(dev);
}
else {
devWidget.children[0].revealChild = false;
devWidget.toDestroy = true;
}
}
});
// Start scrolling after a specified height
// is reached by the children
const height = Math.max(
box.get_parent().get_allocated_height(),
SCROLL_THRESHOLD_H,
);
const scroll = box.get_parent().get_parent();
if (box.get_children().length > SCROLL_THRESHOLD_N) {
scroll.vscroll = 'always';
scroll.setCss(`min-height: ${height}px;`);
// Make bottom scroll indicator appear only
// when first getting overflowing children
if (!(bottomArrow.revealChild === true ||
topArrow.revealChild === true)) {
bottomArrow.revealChild = true;
}
}
else {
scroll.vscroll = 'never';
scroll.setCss('');
topArrow.revealChild = false;
bottomArrow.revealChild = false;
}
// Trigger sort_func
box.get_children().forEach((ch) => {
ch.changed();
});
}]],
}),
}),
}),
});
};

View file

@ -11,6 +11,7 @@ import EventBox from '../misc/cursorbox.js';
import Separator from '../misc/separator.js'; import Separator from '../misc/separator.js';
import { NetworkMenu } from './network.js'; import { NetworkMenu } from './network.js';
import { BluetoothMenu } from './bluetooth.js';
const SPACING = 28; const SPACING = 28;
@ -19,6 +20,7 @@ const ButtonStates = [];
const GridButton = ({ const GridButton = ({
command = () => { /**/ }, command = () => { /**/ },
secondaryCommand = () => { /**/ }, secondaryCommand = () => { /**/ },
onOpen = () => { /**/ },
icon, icon,
indicator, indicator,
menu, menu,
@ -129,6 +131,7 @@ const GridButton = ({
if (Activated.value) { if (Activated.value) {
deg = menu ? 360 : 450; deg = menu ? 360 : 450;
onOpen(menu);
} }
self.setCss(` self.setCss(`
-gtk-icon-transform: rotate(${deg}deg); -gtk-icon-transform: rotate(${deg}deg);
@ -156,7 +159,7 @@ const Row = ({ buttons } = {}) => {
hpack: 'center', hpack: 'center',
}), }),
Box(), Box({ vertical: true }),
], ],
}); });
@ -180,8 +183,7 @@ const FirstRow = () => Row({
command: () => Network.toggleWifi(), command: () => Network.toggleWifi(),
secondaryCommand: () => { secondaryCommand: () => {
execAsync(['bash', '-c', 'nm-connection-editor']) // TODO: connection editor
.catch(print);
}, },
icon: [Network, (icon) => { icon: [Network, (icon) => {
@ -193,62 +195,52 @@ const FirstRow = () => Row({
}], }],
menu: NetworkMenu(), menu: NetworkMenu(),
onOpen: () => Network.wifi.scan(),
}), }),
// TODO: do vpn
GridButton({ GridButton({
command: () => { command: () => {
execAsync(['bash', '-c', '$AGS_PATH/qs-toggles.sh blue-toggle']) //
.catch(print);
}, },
secondaryCommand: () => { secondaryCommand: () => {
execAsync(['bash', '-c', 'blueberry']) //
.catch(print); },
icon: 'airplane-mode-disabled-symbolic',
}),
GridButton({
command: () => Bluetooth.toggle(),
secondaryCommand: () => {
// TODO: bluetooth connection editor
}, },
icon: [Bluetooth, (self) => { icon: [Bluetooth, (self) => {
if (Bluetooth.enabled) { if (Bluetooth.enabled) {
self.icon = 'bluetooth-active-symbolic'; self.icon = Bluetooth.connectedDevices[0] ?
execAsync(['bash', '-c', Bluetooth.connectedDevices[0].iconName :
'echo 󰂯 > $HOME/.config/.bluetooth']).catch(print); 'bluetooth-active-symbolic';
} }
else { else {
self.icon = 'bluetooth-disabled-symbolic'; self.icon = 'bluetooth-disabled-symbolic';
execAsync(['bash', '-c',
'echo 󰂲 > $HOME/.config/.bluetooth']).catch(print);
} }
}, 'changed'], }],
indicator: [Bluetooth, (self) => { indicator: [Bluetooth, (self) => {
if (Bluetooth.connectedDevices[0]) { self.label = Bluetooth.connectedDevices[0] ?
self.label = String(Bluetooth.connectedDevices[0]); `${Bluetooth.connectedDevices[0]}` :
} 'Disconnected';
else { }, 'notify::connected-devices'],
self.label = 'Disconnected';
}
}, 'changed'],
}),
// TODO: replace with vpn menu: BluetoothMenu(),
GridButton({ onOpen: (menu) => {
command: () => { execAsync(`bluetoothctl scan ${menu.revealChild ?
execAsync(['bash', '-c', 'on' :
'$AGS_PATH/qs-toggles.sh toggle-radio']).catch(print); 'off'}`);
}, },
secondaryCommand: () => {
execAsync(['notify-send', 'set this up moron'])
.catch(print);
},
icon: [Network, (self) => {
if (Network.wifi.enabled) {
self.icon = 'airplane-mode-disabled-symbolic';
}
else {
self.icon = 'airplane-mode-symbolic';
}
}, 'changed'],
}), }),
], ],

View file

@ -13,7 +13,7 @@ const SCROLL_THRESHOLD_N = 7;
const AccessPoint = (ap) => { const AccessPoint = (ap) => {
const widget = Box({ const widget = Box({
className: 'ap', className: 'menu-item',
}); });
widget.ap = Variable(ap); widget.ap = Variable(ap);
@ -38,7 +38,11 @@ const AccessPoint = (ap) => {
hexpand: true, hexpand: true,
hpack: 'end', hpack: 'end',
connections: [[Network, (self) => { connections: [[Network, (self) => {
self.visible = widget.ap.value.ssid === Network.wifi.ssid; self.setCss(`opacity: ${
widget.ap.value.ssid === Network.wifi.ssid ?
'1' :
'0'
};`);
}]], }]],
}), }),
], ],
@ -55,7 +59,6 @@ const AccessPoint = (ap) => {
child, child,
}), }),
})); }));
widget.show_all();
return widget; return widget;
}; };

View file

@ -1,8 +1,6 @@
import App from 'resource:///com/github/Aylur/ags/app.js'; import App from 'resource:///com/github/Aylur/ags/app.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js'; import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js';
import Brightness from '../services/brightness.js'; import Brightness from '../services/brightness.js';
import Pointers from '../services/pointers.js'; import Pointers from '../services/pointers.js';
import Tablet from '../services/tablet.js'; import Tablet from '../services/tablet.js';
@ -16,8 +14,6 @@ export default () => {
globalThis.Tablet = Tablet; globalThis.Tablet = Tablet;
globalThis.closeAll = closeAll; globalThis.closeAll = closeAll;
execAsync(['bash', '-c', '$AGS_PATH/startup.sh']).catch(print);
TouchGestures.addGesture({ TouchGestures.addGesture({
name: 'openAppLauncher', name: 'openAppLauncher',
gesture: 'UD', gesture: 'UD',

View file

@ -2,7 +2,6 @@
background-color: $bg; background-color: $bg;
color: $fg; color: $fg;
border-radius: 30px; border-radius: 30px;
border-top-right-radius: 0;
border: 2px solid $contrast-bg; border: 2px solid $contrast-bg;
} }

View file

@ -40,7 +40,7 @@
margin: 0; margin: 0;
} }
.ap { .menu-item {
margin: 5px; margin: 5px;
label { label {

View file

@ -53,7 +53,8 @@
padding: 0 15px; padding: 0 15px;
} }
.audio { .audio,
.bluetooth {
padding: 0 10px; padding: 0 10px;
font-size: 20px; font-size: 20px;
margin-right: -10px; margin-right: -10px;
@ -72,7 +73,6 @@
.battery { .battery {
padding: 0 10px; padding: 0 10px;
font-size: 20px; font-size: 20px;
margin-right: -10px;
.battery-indicator { .battery-indicator {
&.charging { &.charging {

View file

@ -16,8 +16,7 @@ plugin {
} }
# Autostart programs # Autostart programs
exec-once = sleep 4; blueberry-tray exec-once = sleep 3; nextcloud --background
exec-once = sleep 6; nextcloud --background
exec-once = squeekboard exec-once = squeekboard
exec-once = ags exec-once = ags
exec-once = sleep 3; ags -t applauncher exec-once = sleep 3; ags -t applauncher

View file

@ -42,7 +42,6 @@ in {
lisgd lisgd
squeekboard squeekboard
ydotool ydotool
blueberry
])); ]));
}) })
]; ];