diff --git a/modules/ags/config/widgets/bar/_index.scss b/modules/ags/config/widgets/bar/_index.scss
index 7bdbcd34..7eaec5e6 100644
--- a/modules/ags/config/widgets/bar/_index.scss
+++ b/modules/ags/config/widgets/bar/_index.scss
@@ -20,6 +20,8 @@
}
&.bluetooth icon,
+ &.heart-toggle label,
+ &.keyboard icon,
&.network icon,
&.tablet-mode icon {
min-width: 30px;
diff --git a/modules/ags/config/widgets/bar/items/heart.tsx b/modules/ags/config/widgets/bar/items/heart.tsx
new file mode 100644
index 00000000..92c2d52b
--- /dev/null
+++ b/modules/ags/config/widgets/bar/items/heart.tsx
@@ -0,0 +1,29 @@
+import { Variable } from 'astal';
+
+import Persist from '../../misc/persist';
+
+const HeartState = Variable('');
+
+Persist({
+ name: 'heart',
+ variable: HeartState,
+ condition: '',
+ whenFalse: '',
+});
+
+
+export default () => (
+
+);
diff --git a/modules/ags/config/widgets/bar/items/keyboard-layout.tsx b/modules/ags/config/widgets/bar/items/keyboard-layout.tsx
new file mode 100644
index 00000000..3b71f009
--- /dev/null
+++ b/modules/ags/config/widgets/bar/items/keyboard-layout.tsx
@@ -0,0 +1,87 @@
+import { Variable } from 'astal';
+import { Label } from 'astal/gtk3/widget';
+
+import AstalHyprland from 'gi://AstalHyprland';
+
+import { hyprMessage } from '../../../lib';
+import { Gtk } from 'astal/gtk3';
+
+/* Types */
+interface Keyboard {
+ address: string
+ name: string
+ rules: string
+ model: string
+ layout: string
+ variant: string
+ options: string
+ active_keymap: string
+ main: boolean
+}
+
+
+const DEFAULT_KB = 'at-translated-set-2-keyboard';
+
+export default () => {
+ const Hovered = Variable(false);
+
+ const hyprland = AstalHyprland.get_default();
+
+ const getKbdLayout = (self: Label, _: string, layout?: string) => {
+ if (layout) {
+ if (layout === 'error') {
+ return;
+ }
+
+ const shortName = layout.match(/\(([A-Za-z]+)\)/);
+
+ self.label = shortName ? shortName[1] : layout;
+ }
+ else {
+ // At launch, kb layout is undefined
+ hyprMessage('j/devices').then((obj) => {
+ const keyboards = Array.from(JSON.parse(obj)
+ .keyboards) as Keyboard[];
+ const kb = keyboards.find((v) => v.name === DEFAULT_KB);
+
+ if (kb) {
+ layout = kb.active_keymap;
+
+ const shortName = layout
+ .match(/\(([A-Za-z]+)\)/);
+
+ self.label = shortName ? shortName[1] : layout;
+ }
+ else {
+ self.label = 'None';
+ }
+ }).catch(print);
+ }
+ };
+
+ return (
+
+ );
+};
diff --git a/modules/ags/config/widgets/bar/wim.tsx b/modules/ags/config/widgets/bar/wim.tsx
index e6846c0f..65c9c95a 100644
--- a/modules/ags/config/widgets/bar/wim.tsx
+++ b/modules/ags/config/widgets/bar/wim.tsx
@@ -7,6 +7,8 @@ import Bluetooth from './items/bluetooth';
import Brightness from './items/brightness';
import Clock from './items/clock';
import CurrentClient from './items/current-client';
+import Heart from './items/heart';
+import Keyboard from './items/keyboard-layout';
import Network from './items/network';
import NotifButton from './items/notif-button';
import SysTray from './items/tray';
@@ -52,6 +54,10 @@ export default () => (
/>
+
+
+
+
@@ -70,6 +76,10 @@ export default () => (
+
+
+
+
diff --git a/modules/ags/config/widgets/misc/persist.ts b/modules/ags/config/widgets/misc/persist.ts
new file mode 100644
index 00000000..dd22f703
--- /dev/null
+++ b/modules/ags/config/widgets/misc/persist.ts
@@ -0,0 +1,49 @@
+import { execAsync, readFileAsync, timeout, GLib, type Variable } from 'astal';
+
+const { get_home_dir } = GLib;
+
+
+export default ({
+ name,
+ variable,
+ condition = true,
+ whenTrue = condition,
+ whenFalse = false,
+}: {
+ name: string
+ variable: Variable
+ condition?: boolean | string
+ whenTrue?: boolean | string
+ whenFalse?: boolean | string
+}) => {
+ const cacheFile = `${get_home_dir()}/.cache/ags/.${name}`;
+
+ const stateCmd = () => ['bash', '-c',
+ `echo ${variable.get() === condition} > ${cacheFile}`];
+
+ const monitorState = () => {
+ variable.subscribe(() => {
+ execAsync(stateCmd()).catch(print);
+ });
+ };
+
+ readFileAsync(cacheFile)
+ .then((content) => {
+ // JSON.parse was the only way I found to reliably
+ // convert a string of 'true' or 'false' into a bool
+ const value = (JSON.parse(content) ? whenTrue : whenFalse) as T;
+
+ variable.set(value);
+
+ timeout(1000, () => {
+ monitorState();
+ });
+ })
+ .catch(() => {
+ execAsync(stateCmd())
+ .then(() => {
+ monitorState();
+ })
+ .catch(print);
+ });
+};
diff --git a/modules/ags/v1/config/scss/quick-settings.scss b/modules/ags/v1/config/scss/quick-settings.scss
deleted file mode 100644
index 22a7386b..00000000
--- a/modules/ags/v1/config/scss/quick-settings.scss
+++ /dev/null
@@ -1,184 +0,0 @@
-.quick-settings {
- font-size: 30px;
- min-width: 500px;
- padding: 0;
- background-color: $bg;
- border-radius: 30px 0 30px 30px;
- border: 2px solid $contrast-bg;
-}
-
-.title {
- font-size: 22px;
- margin-top: 30px;
-}
-
-.grid-label {
- font-size: 30px;
- margin-left: 15px;
- margin-right: 10px;
- min-width: 50px;
-}
-
-.scrolled-indicator {
- margin: 5px 0;
-}
-
-.menu {
- margin: 10px;
- padding: 0;
- border: 1.5px solid $contrast-bg;
- border-radius: 10px;
- font-size: 12px;
-
- scrolledwindow {
- padding: 3px;
- }
-
- row {
- padding: 0;
- margin: 0;
- }
-
- .menu-item {
- margin: 5px;
-
- label {
- font-size: 16px;
- margin-left: 5px;
- }
-
- image {
- font-size: 20px;
- }
- }
-}
-
-.sub-label {
- font-size: 14px;
- padding: 3px;
- border: 2px solid $contrast-bg;
- border-radius: 10px 20px 20px 10px;
- min-width: 106px;
- background: #1b1b1b;
- margin-top: 5px;
-}
-
-.grid-chev {
- margin-left: 10px;
- margin-right: 12px;
- font-size: 25px;
-
- transition: -gtk-icon-transform 0.3s ease-in-out;
-}
-
-.button-grid {
- font-size: 10px;
- min-width: 440px;
- background-color: $bgfull;
- border-top: 2px solid $contrast-bg;
- border-bottom: 2px solid $contrast-bg;
- border-radius: 15px;
- padding: 10px 15px;
-}
-
-.grid-button {
- min-height: 65px;
- min-width: 70px;
-}
-
-.left-part {
- background: #1b1b1b;
- border-top-left-radius: 15px;
- border-bottom-left-radius: 15px;
- border-left: 2px solid $contrast-bg;
- border-top: 2px solid $contrast-bg;
- border-bottom: 2px solid $contrast-bg;
- transition: all 0.5s ease-in-out;
-}
-
-.right-part {
- background: #1b1b1b;
- border-top-right-radius: 30px;
- border-bottom-right-radius: 30px;
- border-right: 2px solid $contrast-bg;
- border-top: 2px solid $contrast-bg;
- border-bottom: 2px solid $contrast-bg;
- transition: all 0.5s ease-in-out;
-}
-
-.right-part:hover,
-.right-part:active {
- color: $contrast-bg;
- border: 2px solid $contrast-bg;
- border-top-left-radius: 7px;
- border-bottom-left-radius: 7px;
- transition: all 0.5s ease-in-out;
-}
-
-.left-part:hover,
-.left-part:active {
- color: $contrast-bg;
- border: 2px solid $contrast-bg;
- border-top-right-radius: 7px;
- border-bottom-right-radius: 7px;
- transition: all 0.5s ease-in-out;
-}
-
-.player {
- margin-top: 6px;
- min-height: 220px;
- opacity: 0;
-}
-
-.slider-box {
- min-height: 100px;
- min-width: 470px;
- background-color: $bgfull;
- border-top: 2px solid $contrast-bg;
- border-bottom: 2px solid $contrast-bg;
- border-radius: 15px;
- margin-top: 30px;
- margin-bottom: 20px;
-
- .slider-label {
- font-size: 30px;
- min-width: 40px;
- margin-right: -20px;
- }
-
- .slider {
- min-height: 55px;
- margin-right: -15px;
-
- scale {
- min-width: 400px;
- margin-left: 18px;
- margin-right: 20px;
-
- highlight {
- margin: 0;
- background-color: #79659f;
- border-radius: 2em;
- }
-
- trough {
- background-color: #363847;
- border-radius: 2em;
- }
-
- slider {
- margin: -4px;
- min-width: 20px;
- min-height: 20px;
- background: #3e4153;
- border-radius: 100%;
- transition: background-color 0.5s ease-in-out;
- }
-
- slider:hover {
- background-color: #303240;
- transition: background-color 0.5s ease-in-out;
- }
- }
- }
-}
diff --git a/modules/ags/v1/config/ts/bar/heart.ts b/modules/ags/v1/config/ts/bar/heart.ts
deleted file mode 100644
index d942b5ab..00000000
--- a/modules/ags/v1/config/ts/bar/heart.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-const { Label } = Widget;
-
-import CursorBox from '../../misc/cursorbox.ts';
-import Persist from '../../misc/persist.ts';
-
-const HeartState = Variable('');
-
-Persist({
- name: 'heart',
- gobject: HeartState,
- prop: 'value',
- condition: '',
- whenFalse: '',
-});
-
-
-export default () => CursorBox({
- on_primary_click_release: () => {
- HeartState.setValue(HeartState.value === '' ? '' : '');
- },
-
- child: Label({
- class_name: 'heart-toggle',
- label: HeartState.bind(),
- }),
-});
diff --git a/modules/ags/v1/config/ts/bar/keyboard-layout.ts b/modules/ags/v1/config/ts/bar/keyboard-layout.ts
deleted file mode 100644
index 4faa2abc..00000000
--- a/modules/ags/v1/config/ts/bar/keyboard-layout.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-const Hyprland = await Service.import('hyprland');
-const { Icon, Label } = Widget;
-
-import HoverRevealer from './hover-revealer.ts';
-
-const DEFAULT_KB = 'at-translated-set-2-keyboard';
-
-// Types
-import { Keyboard, LabelGeneric } from 'global-types';
-
-
-const getKbdLayout = (self: LabelGeneric, _: string, layout: string) => {
- if (layout) {
- if (layout === 'error') {
- return;
- }
-
- const shortName = layout.match(/\(([A-Za-z]+)\)/);
-
- self.label = shortName ? shortName[1] : layout;
- }
- else {
- // At launch, kb layout is undefined
- Hyprland.messageAsync('j/devices').then((obj) => {
- const keyboards = Array.from(JSON.parse(obj)
- .keyboards) as Keyboard[];
- const kb = keyboards.find((v) => v.name === DEFAULT_KB);
-
- if (kb) {
- layout = kb.active_keymap;
-
- const shortName = layout
- .match(/\(([A-Za-z]+)\)/);
-
- self.label = shortName ? shortName[1] : layout;
- }
- else {
- self.label = 'None';
- }
- }).catch(print);
- }
-};
-
-export default () => HoverRevealer({
- class_name: 'keyboard',
- spacing: 4,
-
- icon: Icon({
- icon: 'input-keyboard-symbolic',
- size: 20,
- }),
- label: Label({ css: 'font-size: 20px;' })
- .hook(Hyprland, getKbdLayout, 'keyboard-layout'),
-});