feat(ags): init migration of osk
All checks were successful
Discord / discord commits (push) Has been skipped
All checks were successful
Discord / discord commits (push) Has been skipped
This commit is contained in:
parent
a1df7028f1
commit
524a2a6de9
17 changed files with 793 additions and 828 deletions
|
@ -12,6 +12,7 @@ import Corners from '../widgets/corners/main';
|
|||
import IconBrowser from '../widgets/icon-browser/main';
|
||||
import { NotifPopups, NotifCenter } from '../widgets/notifs/wim';
|
||||
import OSD from '../widgets/osd/main';
|
||||
import OSK from '../widgets/on-screen-keyboard/main';
|
||||
import PowerMenu from '../widgets/powermenu/main';
|
||||
import Screenshot from '../widgets/screenshot/main';
|
||||
|
||||
|
@ -65,6 +66,7 @@ export default () => {
|
|||
NotifPopups();
|
||||
NotifCenter();
|
||||
OSD();
|
||||
OSK();
|
||||
PowerMenu();
|
||||
Screenshot();
|
||||
|
||||
|
|
176
nixosModules/ags/config/services/tablet.ts
Normal file
176
nixosModules/ags/config/services/tablet.ts
Normal file
|
@ -0,0 +1,176 @@
|
|||
import { execAsync, subprocess } from 'astal';
|
||||
import GObject, { register, property, signal } from 'astal/gobject';
|
||||
|
||||
import { hyprMessage } from '../lib';
|
||||
|
||||
/* Types */
|
||||
import AstalIO from 'gi://AstalIO';
|
||||
type RotationName = 'normal' | 'right-up' | 'bottom-up' | 'left-up';
|
||||
|
||||
|
||||
const ROTATION_MAP: Record<RotationName, 0 | 1 | 2 | 3> = {
|
||||
'normal': 0,
|
||||
'right-up': 3,
|
||||
'bottom-up': 2,
|
||||
'left-up': 1,
|
||||
};
|
||||
|
||||
const SCREEN = 'desc:BOE 0x0964';
|
||||
|
||||
const DEVICES = [
|
||||
'wacom-hid-52eb-finger',
|
||||
'wacom-hid-52eb-pen',
|
||||
];
|
||||
|
||||
@register()
|
||||
export default class Tablet extends GObject.Object {
|
||||
@signal(Boolean)
|
||||
declare autorotateChanged: (running: boolean) => void;
|
||||
|
||||
@signal(Boolean)
|
||||
declare inputsChanged: (blocked: boolean) => void;
|
||||
|
||||
|
||||
private _currentMode = 'laptop';
|
||||
|
||||
@property(String)
|
||||
get currentMode() {
|
||||
return this._currentMode;
|
||||
}
|
||||
|
||||
set currentMode(val) {
|
||||
this._currentMode = val;
|
||||
|
||||
if (this._currentMode === 'tablet') {
|
||||
execAsync(['brightnessctl', '-d', 'tpacpi::kbd_backlight', 's', '0'])
|
||||
.catch(print);
|
||||
|
||||
this.startAutorotate();
|
||||
this._blockInputs();
|
||||
}
|
||||
else if (this._currentMode === 'laptop') {
|
||||
execAsync(['brightnessctl', '-d', 'tpacpi::kbd_backlight', 's', '2'])
|
||||
.catch(print);
|
||||
|
||||
this.killAutorotate();
|
||||
this._unblockInputs();
|
||||
}
|
||||
|
||||
this.notify('current-mode');
|
||||
}
|
||||
|
||||
|
||||
private _oskState = false;
|
||||
|
||||
@property(Boolean)
|
||||
get oskState() {
|
||||
return this._oskState;
|
||||
}
|
||||
|
||||
set oskState(val) {
|
||||
this._oskState = val;
|
||||
this.notify('osk-state');
|
||||
}
|
||||
|
||||
public toggleOsk() {
|
||||
this.oskState = !this.oskState;
|
||||
}
|
||||
|
||||
|
||||
private _autorotate = null as AstalIO.Process | null;
|
||||
|
||||
get autorotateState() {
|
||||
return this._autorotate !== null;
|
||||
}
|
||||
|
||||
|
||||
private _blockedInputs = null as AstalIO.Process | null;
|
||||
|
||||
private _blockInputs() {
|
||||
if (this._blockedInputs) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._blockedInputs = subprocess(['libinput', 'debug-events', '--grab',
|
||||
'--device', '/dev/input/by-path/platform-i8042-serio-0-event-kbd',
|
||||
'--device', '/dev/input/by-path/platform-i8042-serio-1-event-mouse',
|
||||
'--device', '/dev/input/by-path/platform-AMDI0010:02-event-mouse',
|
||||
'--device', '/dev/input/by-path/platform-thinkpad_acpi-event',
|
||||
'--device', '/dev/video-bus'],
|
||||
() => { /**/ });
|
||||
|
||||
this.emit('inputs-changed', true);
|
||||
}
|
||||
|
||||
private _unblockInputs() {
|
||||
if (this._blockedInputs) {
|
||||
this._blockedInputs.kill();
|
||||
this._blockedInputs = null;
|
||||
|
||||
this.emit('inputs-changed', false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public toggleMode() {
|
||||
if (this.currentMode === 'laptop') {
|
||||
this.currentMode = 'tablet';
|
||||
}
|
||||
else if (this.currentMode === 'tablet') {
|
||||
this.currentMode = 'laptop';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public startAutorotate() {
|
||||
if (this._autorotate) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._autorotate = subprocess(
|
||||
['monitor-sensor'],
|
||||
(output) => {
|
||||
if (output.includes('orientation changed')) {
|
||||
const index = output.split(' ').at(-1) as RotationName | undefined;
|
||||
|
||||
if (!index) {
|
||||
return;
|
||||
}
|
||||
|
||||
const orientation = ROTATION_MAP[index];
|
||||
|
||||
hyprMessage(
|
||||
`keyword monitor ${SCREEN},transform,${orientation}`,
|
||||
).catch(print);
|
||||
|
||||
const batchRotate = DEVICES.map((dev) =>
|
||||
`keyword device:${dev}:transform ${orientation}; `);
|
||||
|
||||
hyprMessage(`[[BATCH]] ${batchRotate.flat()}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
this.emit('autorotate-changed', true);
|
||||
}
|
||||
|
||||
public killAutorotate() {
|
||||
if (this._autorotate) {
|
||||
this._autorotate.kill();
|
||||
this._autorotate = null;
|
||||
|
||||
this.emit('autorotate-changed', false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static _default: InstanceType<typeof Tablet> | undefined;
|
||||
|
||||
public static get_default() {
|
||||
if (!Tablet._default) {
|
||||
Tablet._default = new Tablet();
|
||||
}
|
||||
|
||||
return Tablet._default;
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
@use '../widgets/icon-browser';
|
||||
@use '../widgets/misc';
|
||||
@use '../widgets/notifs';
|
||||
@use '../widgets/on-screen-keyboard';
|
||||
@use '../widgets/osd';
|
||||
@use '../widgets/powermenu';
|
||||
@use '../widgets/screenshot';
|
||||
|
|
|
@ -1,23 +1,5 @@
|
|||
.thingy {
|
||||
border-radius: 2rem 2rem 0 0;
|
||||
min-height: 2.7rem;
|
||||
min-width: 20rem;
|
||||
|
||||
.settings {
|
||||
padding: 0.5rem;
|
||||
|
||||
.button {
|
||||
background-color: $bgfull;
|
||||
border: 0.1rem solid $darkbg;
|
||||
border-radius: 0.7rem;
|
||||
padding: 0.3rem;
|
||||
|
||||
&.toggled {
|
||||
background-color: $contrast-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@use 'sass:color';
|
||||
@use '../../style/colors';
|
||||
|
||||
.osk {
|
||||
padding-top: 4px;
|
||||
|
@ -26,12 +8,12 @@
|
|||
.side {
|
||||
.key {
|
||||
&:active label {
|
||||
background-color: $contrast-bg;
|
||||
background-color: colors.$accent-color;
|
||||
}
|
||||
|
||||
label {
|
||||
background-color: $bg;
|
||||
border: 0.08rem solid $darkbg;
|
||||
background-color: colors.$window_bg_color;
|
||||
border: 0.08rem solid color.adjust(colors.$window_bg_color, $lightness: -3%);
|
||||
border-radius: 0.7rem;
|
||||
min-height: 3rem;
|
||||
|
||||
|
@ -67,7 +49,7 @@
|
|||
}
|
||||
|
||||
&.active {
|
||||
background-color: $darkbg;
|
||||
background-color: color.adjust(colors.$window_bg_color, $lightness: -3%);
|
||||
}
|
||||
|
||||
&.altgr {
|
194
nixosModules/ags/config/widgets/on-screen-keyboard/gesture.ts
Normal file
194
nixosModules/ags/config/widgets/on-screen-keyboard/gesture.ts
Normal file
|
@ -0,0 +1,194 @@
|
|||
import { execAsync } from 'astal';
|
||||
import { Gtk } from 'astal/gtk3';
|
||||
|
||||
import { hyprMessage } from '../../lib';
|
||||
|
||||
import OskWindow from './osk-window';
|
||||
|
||||
|
||||
const KEY_N = 249;
|
||||
const HIDDEN_MARGIN = 340;
|
||||
|
||||
const releaseAllKeys = () => {
|
||||
const keycodes = Array.from(Array(KEY_N).keys());
|
||||
|
||||
execAsync([
|
||||
'ydotool', 'key',
|
||||
...keycodes.map((keycode) => `${keycode}:0`),
|
||||
]).catch(print);
|
||||
};
|
||||
|
||||
export default (window: OskWindow) => {
|
||||
const gesture = Gtk.GestureDrag.new(window);
|
||||
|
||||
window.get_child().css = `margin-bottom: -${HIDDEN_MARGIN}px;`;
|
||||
|
||||
let signals = [] as number[];
|
||||
|
||||
window.setVisible = (state: boolean) => {
|
||||
if (state) {
|
||||
window.setSlideDown();
|
||||
|
||||
window.get_child().css = `
|
||||
transition: margin-bottom 0.7s cubic-bezier(0.36, 0, 0.66, -0.56);
|
||||
margin-bottom: 0px;
|
||||
`;
|
||||
}
|
||||
else {
|
||||
releaseAllKeys();
|
||||
window.setSlideUp();
|
||||
|
||||
window.get_child().css = `
|
||||
transition: margin-bottom 0.7s cubic-bezier(0.36, 0, 0.66, -0.56);
|
||||
margin-bottom: -${HIDDEN_MARGIN}px;
|
||||
`;
|
||||
}
|
||||
};
|
||||
|
||||
window.killGestureSigs = () => {
|
||||
signals.forEach((id) => {
|
||||
gesture.disconnect(id);
|
||||
});
|
||||
signals = [];
|
||||
window.startY = null;
|
||||
};
|
||||
|
||||
window.setSlideUp = () => {
|
||||
window.killGestureSigs();
|
||||
|
||||
// Begin drag
|
||||
signals.push(
|
||||
gesture.connect('drag-begin', () => {
|
||||
hyprMessage('j/cursorpos').then((out) => {
|
||||
window.startY = JSON.parse(out).y;
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// Update drag
|
||||
signals.push(
|
||||
gesture.connect('drag-update', () => {
|
||||
hyprMessage('j/cursorpos').then((out) => {
|
||||
if (!window.startY) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentY = JSON.parse(out).y;
|
||||
const offset = window.startY - currentY;
|
||||
|
||||
if (offset < 0) {
|
||||
window.get_child().css = `
|
||||
transition: margin-bottom 0.5s ease-in-out;
|
||||
margin-bottom: -${HIDDEN_MARGIN}px;
|
||||
`;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
window.get_child().css = `
|
||||
margin-bottom: ${offset - HIDDEN_MARGIN}px;
|
||||
`;
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// End drag
|
||||
signals.push(
|
||||
gesture.connect('drag-end', () => {
|
||||
hyprMessage('j/cursorpos').then((out) => {
|
||||
if (!window.startY) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentY = JSON.parse(out).y;
|
||||
const offset = window.startY - currentY;
|
||||
|
||||
if (offset > HIDDEN_MARGIN) {
|
||||
window.get_child().css = `
|
||||
transition: margin-bottom 0.5s ease-in-out;
|
||||
margin-bottom: 0px;
|
||||
`;
|
||||
window.setVisible(true);
|
||||
}
|
||||
else {
|
||||
window.get_child().css = `
|
||||
transition: margin-bottom 0.5s ease-in-out;
|
||||
margin-bottom: -${HIDDEN_MARGIN}px;
|
||||
`;
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
window.setSlideDown = () => {
|
||||
window.killGestureSigs();
|
||||
|
||||
// Begin drag
|
||||
signals.push(
|
||||
gesture.connect('drag-begin', () => {
|
||||
hyprMessage('j/cursorpos').then((out) => {
|
||||
window.startY = JSON.parse(out).y;
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// Update drag
|
||||
signals.push(
|
||||
gesture.connect('drag-update', () => {
|
||||
hyprMessage('j/cursorpos').then((out) => {
|
||||
if (!window.startY) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentY = JSON.parse(out).y;
|
||||
const offset = window.startY - currentY;
|
||||
|
||||
if (offset > 0) {
|
||||
window.get_child().css = `
|
||||
transition: margin-bottom 0.5s ease-in-out;
|
||||
margin-bottom: 0px;
|
||||
`;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
window.get_child().css = `
|
||||
margin-bottom: ${offset}px;
|
||||
`;
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// End drag
|
||||
signals.push(
|
||||
gesture.connect('drag-end', () => {
|
||||
hyprMessage('j/cursorpos').then((out) => {
|
||||
if (!window.startY) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentY = JSON.parse(out).y;
|
||||
const offset = window.startY - currentY;
|
||||
|
||||
if (offset < -(HIDDEN_MARGIN * 2 / 3)) {
|
||||
window.get_child().css = `
|
||||
transition: margin-bottom 0.5s ease-in-out;
|
||||
margin-bottom: -${HIDDEN_MARGIN}px;
|
||||
`;
|
||||
|
||||
window.setVisible(false);
|
||||
}
|
||||
else {
|
||||
window.get_child().css = `
|
||||
transition: margin-bottom 0.5s ease-in-out;
|
||||
margin-bottom: 0px;
|
||||
`;
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return window;
|
||||
};
|
|
@ -0,0 +1,89 @@
|
|||
import { Gtk, Widget } from 'astal/gtk3';
|
||||
|
||||
import Separator from '../misc/separator';
|
||||
|
||||
import Key from './keys';
|
||||
|
||||
import { defaultOskLayout, oskLayouts } from './keyboard-layouts';
|
||||
|
||||
const keyboardLayout = defaultOskLayout;
|
||||
const keyboardJson = oskLayouts[keyboardLayout];
|
||||
|
||||
|
||||
const L_KEY_PER_ROW = [8, 7, 6, 6, 6, 4]; // eslint-disable-line
|
||||
const COLOR = 'rgba(0, 0, 0, 0.3)';
|
||||
const SPACING = 4;
|
||||
|
||||
export default (): Widget.Box => (
|
||||
<box vertical>
|
||||
<centerbox
|
||||
css={`background: ${COLOR};`}
|
||||
className="osk"
|
||||
hexpand
|
||||
>
|
||||
<box
|
||||
className="left-side side"
|
||||
halign={Gtk.Align.START}
|
||||
vertical
|
||||
>
|
||||
{...keyboardJson.keys.map((row, rowIndex) => {
|
||||
const keys = [] as Widget.Box[];
|
||||
|
||||
row.forEach((key, keyIndex) => {
|
||||
if (keyIndex < L_KEY_PER_ROW[rowIndex]) {
|
||||
keys.push(Key(key));
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<box vertical>
|
||||
<box className="row">
|
||||
<Separator size={SPACING} />
|
||||
|
||||
{...keys}
|
||||
</box>
|
||||
|
||||
<Separator size={SPACING} vertical />
|
||||
</box>
|
||||
);
|
||||
})}
|
||||
</box>
|
||||
|
||||
<box
|
||||
halign={Gtk.Align.CENTER}
|
||||
valign={Gtk.Align.CENTER}
|
||||
>
|
||||
</box>
|
||||
|
||||
<box
|
||||
className="right-side side"
|
||||
halign={Gtk.Align.END}
|
||||
vertical
|
||||
>
|
||||
{...keyboardJson.keys.map((row, rowIndex) => {
|
||||
const keys = [] as Widget.Box[];
|
||||
|
||||
row.forEach((key, keyIndex) => {
|
||||
if (keyIndex >= L_KEY_PER_ROW[rowIndex]) {
|
||||
keys.push(Key(key));
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<box vertical>
|
||||
|
||||
<box
|
||||
halign={Gtk.Align.END}
|
||||
className="row"
|
||||
>
|
||||
{...keys}
|
||||
</box>
|
||||
|
||||
<Separator size={SPACING} vertical />
|
||||
</box>
|
||||
);
|
||||
})}
|
||||
</box>
|
||||
</centerbox>
|
||||
</box>
|
||||
) as Widget.Box;
|
261
nixosModules/ags/config/widgets/on-screen-keyboard/keys.tsx
Normal file
261
nixosModules/ags/config/widgets/on-screen-keyboard/keys.tsx
Normal file
|
@ -0,0 +1,261 @@
|
|||
import { execAsync, Variable } from 'astal';
|
||||
import { Gdk, Gtk, Widget } from 'astal/gtk3';
|
||||
|
||||
import Brightness from '../../services/brightness';
|
||||
|
||||
import Separator from '../misc/separator';
|
||||
|
||||
/* Types */
|
||||
interface Key {
|
||||
keytype: string
|
||||
label: string
|
||||
labelShift?: string
|
||||
labelAltGr?: string
|
||||
shape: string
|
||||
keycode: number
|
||||
}
|
||||
|
||||
|
||||
const display = Gdk.Display.get_default();
|
||||
const brightness = Brightness.get_default();
|
||||
|
||||
const SPACING = 4;
|
||||
const LSHIFT_CODE = 42;
|
||||
const LALT_CODE = 56;
|
||||
const LCTRL_CODE = 29;
|
||||
|
||||
// Keep track of when a non modifier key
|
||||
// is clicked to release all modifiers
|
||||
const NormalClick = Variable(false);
|
||||
|
||||
// Keep track of modifier statuses
|
||||
const Super = Variable(false);
|
||||
const LAlt = Variable(false);
|
||||
const LCtrl = Variable(false);
|
||||
const AltGr = Variable(false);
|
||||
const RCtrl = Variable(false);
|
||||
|
||||
const Caps = Variable(false);
|
||||
|
||||
brightness.connect('notify::caps-level', (_, state) => {
|
||||
Caps.set(state);
|
||||
});
|
||||
|
||||
// Assume both shifts are the same for key.labelShift
|
||||
const LShift = Variable(false);
|
||||
const RShift = Variable(false);
|
||||
|
||||
const Shift = Variable(false);
|
||||
|
||||
LShift.subscribe(() => {
|
||||
Shift.set(LShift.get() || RShift.get());
|
||||
});
|
||||
RShift.subscribe(() => {
|
||||
Shift.set(LShift.get() || RShift.get());
|
||||
});
|
||||
|
||||
|
||||
const ModKey = (key: Key) => {
|
||||
let Mod: Variable<boolean>;
|
||||
|
||||
if (key.label === 'Super') {
|
||||
Mod = Super;
|
||||
}
|
||||
|
||||
// Differentiate left and right mods
|
||||
else if (key.label === 'Shift' && key.keycode === LSHIFT_CODE) {
|
||||
Mod = LShift;
|
||||
}
|
||||
|
||||
else if (key.label === 'Alt' && key.keycode === LALT_CODE) {
|
||||
Mod = LAlt;
|
||||
}
|
||||
|
||||
else if (key.label === 'Ctrl' && key.keycode === LCTRL_CODE) {
|
||||
Mod = LCtrl;
|
||||
}
|
||||
|
||||
else if (key.label === 'Shift') {
|
||||
Mod = RShift;
|
||||
}
|
||||
|
||||
else if (key.label === 'AltGr') {
|
||||
Mod = AltGr;
|
||||
}
|
||||
|
||||
else if (key.label === 'Ctrl') {
|
||||
Mod = RCtrl;
|
||||
}
|
||||
|
||||
const label = (
|
||||
<label
|
||||
className={`mod ${key.label}`}
|
||||
label={key.label}
|
||||
/>
|
||||
) as Widget.Label;
|
||||
|
||||
const button = (
|
||||
<eventbox
|
||||
className="key"
|
||||
|
||||
onButtonReleaseEvent={() => {
|
||||
console.log('mod toggled');
|
||||
|
||||
execAsync(`ydotool key ${key.keycode}:${Mod.get() ? 0 : 1}`);
|
||||
|
||||
label.toggleClassName('active', !Mod.get());
|
||||
Mod.set(!Mod.get());
|
||||
}}
|
||||
|
||||
setup={(self) => {
|
||||
self.hook(NormalClick, () => {
|
||||
Mod.set(false);
|
||||
|
||||
label.toggleClassName('active', false);
|
||||
execAsync(`ydotool key ${key.keycode}:0`);
|
||||
});
|
||||
|
||||
// OnHover
|
||||
self.connect('enter-notify-event', () => {
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
self.window.set_cursor(Gdk.Cursor.new_from_name(
|
||||
display,
|
||||
'pointer',
|
||||
));
|
||||
self.toggleClassName('hover', true);
|
||||
});
|
||||
|
||||
// OnHoverLost
|
||||
self.connect('leave-notify-event', () => {
|
||||
self.window.set_cursor(null);
|
||||
self.toggleClassName('hover', false);
|
||||
});
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</eventbox>
|
||||
);
|
||||
|
||||
return (
|
||||
<box>
|
||||
{button}
|
||||
<Separator size={SPACING} />
|
||||
</box>
|
||||
) as Widget.Box;
|
||||
};
|
||||
|
||||
const RegularKey = (key: Key) => {
|
||||
const widget = (
|
||||
<eventbox
|
||||
className="key"
|
||||
>
|
||||
<label
|
||||
className={`normal ${key.label}`}
|
||||
label={key.label}
|
||||
|
||||
setup={(self) => {
|
||||
self
|
||||
.hook(Shift, () => {
|
||||
if (!key.labelShift) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.label = Shift.get() ? key.labelShift : key.label;
|
||||
})
|
||||
.hook(Caps, () => {
|
||||
if (key.label === 'Caps') {
|
||||
self.toggleClassName('active', Caps.get());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!key.labelShift) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.label.match(/[A-Za-z]/)) {
|
||||
self.label = Caps.get() ?
|
||||
key.labelShift :
|
||||
key.label;
|
||||
}
|
||||
})
|
||||
.hook(AltGr, () => {
|
||||
if (!key.labelAltGr) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.toggleClassName('altgr', AltGr.get());
|
||||
self.label = AltGr.get() ? key.labelAltGr : key.label;
|
||||
});
|
||||
|
||||
// OnHover
|
||||
self.connect('enter-notify-event', () => {
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
self.window.set_cursor(Gdk.Cursor.new_from_name(
|
||||
display,
|
||||
'pointer',
|
||||
));
|
||||
self.toggleClassName('hover', true);
|
||||
});
|
||||
|
||||
// OnHoverLost
|
||||
self.connect('leave-notify-event', () => {
|
||||
self.window.set_cursor(null);
|
||||
self.toggleClassName('hover', false);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</eventbox>
|
||||
) as Widget.EventBox;
|
||||
|
||||
const gesture = Gtk.GestureLongPress.new(widget);
|
||||
|
||||
gesture.delay_factor = 1.0;
|
||||
|
||||
// Long press
|
||||
widget.hook(gesture, 'pressed', () => {
|
||||
const pointer = gesture.get_point(null);
|
||||
const x = pointer[1];
|
||||
const y = pointer[2];
|
||||
|
||||
if ((!x || !y) || (x === 0 && y === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Not implemented yet');
|
||||
|
||||
// TODO: popup menu for accents
|
||||
});
|
||||
|
||||
// OnPrimaryClickRelease
|
||||
widget.hook(gesture, 'cancelled', () => {
|
||||
const pointer = gesture.get_point(null);
|
||||
const x = pointer[1];
|
||||
const y = pointer[2];
|
||||
|
||||
if ((!x || !y) || (x === 0 && y === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('key clicked');
|
||||
|
||||
execAsync(`ydotool key ${key.keycode}:1`);
|
||||
execAsync(`ydotool key ${key.keycode}:0`);
|
||||
NormalClick.set(true);
|
||||
});
|
||||
|
||||
return (
|
||||
<box>
|
||||
{widget}
|
||||
<Separator size={SPACING} />
|
||||
</box>
|
||||
) as Widget.Box;
|
||||
};
|
||||
|
||||
export default (key: Key): Widget.Box => key.keytype === 'normal' ?
|
||||
RegularKey(key) :
|
||||
ModKey(key);
|
43
nixosModules/ags/config/widgets/on-screen-keyboard/main.tsx
Normal file
43
nixosModules/ags/config/widgets/on-screen-keyboard/main.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { execAsync, idle } from 'astal';
|
||||
import { Astal } from 'astal/gtk3';
|
||||
|
||||
import Tablet from '../../services/tablet';
|
||||
|
||||
import OskWindow from './osk-window';
|
||||
import Gesture from './gesture';
|
||||
import Keyboard from './keyboard';
|
||||
|
||||
|
||||
export default () => {
|
||||
// Start ydotool daemon
|
||||
execAsync('ydotoold').catch(print);
|
||||
|
||||
const tablet = Tablet.get_default();
|
||||
|
||||
const window = (
|
||||
<OskWindow
|
||||
name="osk"
|
||||
namespace="noanim-osk"
|
||||
|
||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||
anchor={
|
||||
Astal.WindowAnchor.BOTTOM |
|
||||
Astal.WindowAnchor.LEFT |
|
||||
Astal.WindowAnchor.RIGHT
|
||||
}
|
||||
layer={Astal.Layer.OVERLAY}
|
||||
>
|
||||
<Keyboard />
|
||||
</OskWindow>
|
||||
) as OskWindow;
|
||||
|
||||
window.hook(tablet, 'notify::osk-state', (self, state) => {
|
||||
self.setVisible(state);
|
||||
});
|
||||
|
||||
idle(() => {
|
||||
window.setVisible(false);
|
||||
});
|
||||
|
||||
return Gesture(window);
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
import { App, Widget } from 'astal/gtk3';
|
||||
import { register } from 'astal/gobject';
|
||||
|
||||
|
||||
@register()
|
||||
export default class OskWindow extends Widget.Window {
|
||||
public startY: number | null = null;
|
||||
|
||||
declare public setVisible: (state: boolean) => void;
|
||||
declare public killGestureSigs: () => void;
|
||||
declare public setSlideUp: () => void;
|
||||
declare public setSlideDown: () => void;
|
||||
|
||||
get_child(): Widget.Box {
|
||||
return super.get_child() as Widget.Box;
|
||||
}
|
||||
|
||||
constructor({ ...rest }: Widget.WindowProps) {
|
||||
super({ application: App, ...rest });
|
||||
}
|
||||
}
|
9
nixosModules/ags/v1/config/global-types.d.ts
vendored
9
nixosModules/ags/v1/config/global-types.d.ts
vendored
|
@ -54,15 +54,6 @@ export interface PlayerButtonType {
|
|||
prop: string
|
||||
}
|
||||
|
||||
// For ./ts/on-screen-keyboard
|
||||
export type OskWindow = Window<BoxGeneric, {
|
||||
startY: null | number
|
||||
setVisible: (state: boolean) => void
|
||||
killGestureSigs: () => void
|
||||
setSlideUp: () => void
|
||||
setSlideDown: () => void
|
||||
}>;
|
||||
|
||||
// For ./ts/quick-settings
|
||||
import { BluetoothDevice as BTDev } from 'types/service/bluetooth.ts';
|
||||
export interface APType {
|
||||
|
|
|
@ -1,172 +0,0 @@
|
|||
const Hyprland = await Service.import('hyprland');
|
||||
const { execAsync, subprocess } = Utils;
|
||||
|
||||
import TouchGestures from './touch-gestures.ts';
|
||||
|
||||
const ROTATION_MAP = {
|
||||
'normal': 0,
|
||||
'right-up': 3,
|
||||
'bottom-up': 2,
|
||||
'left-up': 1,
|
||||
};
|
||||
const SCREEN = 'desc:BOE 0x0964';
|
||||
const DEVICES = [
|
||||
'wacom-hid-52eb-finger',
|
||||
'wacom-hid-52eb-pen',
|
||||
];
|
||||
|
||||
// Types
|
||||
import { Subprocess } from 'types/@girs/gio-2.0/gio-2.0.cjs';
|
||||
|
||||
|
||||
class Tablet extends Service {
|
||||
static {
|
||||
Service.register(this, {
|
||||
'device-fetched': ['boolean'],
|
||||
'autorotate-started': ['boolean'],
|
||||
'autorotate-destroyed': ['boolean'],
|
||||
'autorotate-toggled': ['boolean'],
|
||||
'inputs-blocked': ['boolean'],
|
||||
'inputs-unblocked': ['boolean'],
|
||||
'laptop-mode': ['boolean'],
|
||||
'tablet-mode': ['boolean'],
|
||||
'mode-toggled': ['boolean'],
|
||||
'osk-toggled': ['boolean'],
|
||||
});
|
||||
}
|
||||
|
||||
#tabletMode = false;
|
||||
#oskState = false;
|
||||
#autorotate = null as Subprocess | null;
|
||||
#blockedInputs = null as Subprocess | null;
|
||||
|
||||
get tabletMode() {
|
||||
return this.#tabletMode;
|
||||
}
|
||||
|
||||
get autorotateState() {
|
||||
return this.#autorotate !== null;
|
||||
}
|
||||
|
||||
get oskState() {
|
||||
return this.#oskState;
|
||||
}
|
||||
|
||||
set oskState(value: boolean) {
|
||||
this.#oskState = value;
|
||||
this.emit('osk-toggled', this.#oskState);
|
||||
}
|
||||
|
||||
#blockInputs() {
|
||||
if (this.#blockedInputs) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#blockedInputs = subprocess(['libinput', 'debug-events', '--grab',
|
||||
'--device', '/dev/input/by-path/platform-i8042-serio-0-event-kbd',
|
||||
'--device', '/dev/input/by-path/platform-i8042-serio-1-event-mouse',
|
||||
'--device', '/dev/input/by-path/platform-AMDI0010:02-event-mouse',
|
||||
'--device', '/dev/input/by-path/platform-thinkpad_acpi-event',
|
||||
'--device', '/dev/video-bus'],
|
||||
() => { /**/ });
|
||||
this.emit('inputs-blocked', true);
|
||||
}
|
||||
|
||||
#unblockInputs() {
|
||||
if (this.#blockedInputs) {
|
||||
this.#blockedInputs.force_exit();
|
||||
this.#blockedInputs = null;
|
||||
this.emit('inputs-unblocked', true);
|
||||
}
|
||||
}
|
||||
|
||||
setTabletMode() {
|
||||
execAsync(['brightnessctl', '-d', 'tpacpi::kbd_backlight', 's', '0'])
|
||||
.catch(print);
|
||||
|
||||
this.startAutorotate();
|
||||
this.#blockInputs();
|
||||
|
||||
this.#tabletMode = true;
|
||||
this.emit('tablet-mode', true);
|
||||
this.emit('mode-toggled', true);
|
||||
}
|
||||
|
||||
setLaptopMode() {
|
||||
execAsync(['brightnessctl', '-d', 'tpacpi::kbd_backlight', 's', '2'])
|
||||
.catch(print);
|
||||
|
||||
this.killAutorotate();
|
||||
this.#unblockInputs();
|
||||
|
||||
this.#tabletMode = false;
|
||||
this.emit('laptop-mode', true);
|
||||
this.emit('mode-toggled', true);
|
||||
}
|
||||
|
||||
toggleMode() {
|
||||
if (this.#tabletMode) {
|
||||
this.setLaptopMode();
|
||||
}
|
||||
else {
|
||||
this.setTabletMode();
|
||||
}
|
||||
|
||||
this.emit('mode-toggled', true);
|
||||
}
|
||||
|
||||
startAutorotate() {
|
||||
if (this.#autorotate) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#autorotate = subprocess(
|
||||
['monitor-sensor'],
|
||||
(output) => {
|
||||
if (output.includes('orientation changed')) {
|
||||
const index = output.split(' ').at(-1);
|
||||
|
||||
if (!index) {
|
||||
return;
|
||||
}
|
||||
|
||||
const orientation = ROTATION_MAP[index];
|
||||
|
||||
Hyprland.messageAsync(
|
||||
`keyword monitor ${SCREEN},transform,${orientation}`,
|
||||
).catch(print);
|
||||
|
||||
const batchRotate = DEVICES.map((dev) =>
|
||||
`keyword device:${dev}:transform ${orientation}; `);
|
||||
|
||||
Hyprland.messageAsync(`[[BATCH]] ${batchRotate.flat()}`);
|
||||
|
||||
if (TouchGestures.gestureDaemon) {
|
||||
TouchGestures.killDaemon();
|
||||
TouchGestures.startDaemon();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
this.emit('autorotate-started', true);
|
||||
this.emit('autorotate-toggled', true);
|
||||
}
|
||||
|
||||
killAutorotate() {
|
||||
if (this.#autorotate) {
|
||||
this.#autorotate.force_exit();
|
||||
this.#autorotate = null;
|
||||
this.emit('autorotate-destroyed', true);
|
||||
this.emit('autorotate-toggled', false);
|
||||
}
|
||||
}
|
||||
|
||||
toggleOsk() {
|
||||
this.#oskState = !this.#oskState;
|
||||
this.emit('osk-toggled', this.#oskState);
|
||||
}
|
||||
}
|
||||
|
||||
const tabletService = new Tablet();
|
||||
|
||||
export default tabletService;
|
|
@ -1,164 +0,0 @@
|
|||
const Hyprland = await Service.import('hyprland');
|
||||
|
||||
const { execAsync, timeout } = Utils;
|
||||
|
||||
const { Gtk } = imports.gi;
|
||||
|
||||
import Tablet from '../../services/tablet.ts';
|
||||
|
||||
const KEY_N = 249;
|
||||
const HIDDEN_MARGIN = 340;
|
||||
const ANIM_DURATION = 700;
|
||||
|
||||
// Types
|
||||
import { OskWindow } from 'global-types';
|
||||
|
||||
|
||||
const releaseAllKeys = () => {
|
||||
const keycodes = Array.from(Array(KEY_N).keys());
|
||||
|
||||
execAsync([
|
||||
'ydotool', 'key',
|
||||
...keycodes.map((keycode) => `${keycode}:0`),
|
||||
]).catch(print);
|
||||
};
|
||||
|
||||
export default (window: OskWindow) => {
|
||||
const gesture = Gtk.GestureDrag.new(window);
|
||||
|
||||
window.child.setCss(`margin-bottom: -${HIDDEN_MARGIN}px;`);
|
||||
|
||||
let signals = [] as number[];
|
||||
|
||||
window.attribute = {
|
||||
startY: null,
|
||||
|
||||
setVisible: (state: boolean) => {
|
||||
if (state) {
|
||||
window.visible = true;
|
||||
window.attribute.setSlideDown();
|
||||
|
||||
window.child.setCss(`
|
||||
transition: margin-bottom 0.7s
|
||||
cubic-bezier(0.36, 0, 0.66, -0.56);
|
||||
margin-bottom: 0px;
|
||||
`);
|
||||
}
|
||||
else {
|
||||
timeout(ANIM_DURATION + 100 + 100, () => {
|
||||
if (!Tablet.tabletMode) {
|
||||
window.visible = false;
|
||||
}
|
||||
});
|
||||
releaseAllKeys();
|
||||
window.attribute.setSlideUp();
|
||||
|
||||
window.child.setCss(`
|
||||
transition: margin-bottom 0.7s
|
||||
cubic-bezier(0.36, 0, 0.66, -0.56);
|
||||
margin-bottom: -${HIDDEN_MARGIN}px;
|
||||
`);
|
||||
}
|
||||
},
|
||||
|
||||
killGestureSigs: () => {
|
||||
signals.forEach((id) => {
|
||||
gesture.disconnect(id);
|
||||
});
|
||||
signals = [];
|
||||
window.attribute.startY = null;
|
||||
},
|
||||
|
||||
setSlideUp: () => {
|
||||
window.attribute.killGestureSigs();
|
||||
|
||||
// Begin drag
|
||||
signals.push(
|
||||
gesture.connect('drag-begin', () => {
|
||||
Hyprland.messageAsync('j/cursorpos').then((out) => {
|
||||
window.attribute.startY = JSON.parse(out).y;
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// Update drag
|
||||
signals.push(
|
||||
gesture.connect('drag-update', () => {
|
||||
Hyprland.messageAsync('j/cursorpos').then((out) => {
|
||||
if (!window.attribute.startY) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentY = JSON.parse(out).y;
|
||||
const offset = window.attribute.startY - currentY;
|
||||
|
||||
if (offset < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.child.setCss(`
|
||||
margin-bottom: ${offset - HIDDEN_MARGIN}px;
|
||||
`);
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// End drag
|
||||
signals.push(
|
||||
gesture.connect('drag-end', () => {
|
||||
window.child.setCss(`
|
||||
transition: margin-bottom 0.5s ease-in-out;
|
||||
margin-bottom: -${HIDDEN_MARGIN}px;
|
||||
`);
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
setSlideDown: () => {
|
||||
window.attribute.killGestureSigs();
|
||||
|
||||
// Begin drag
|
||||
signals.push(
|
||||
gesture.connect('drag-begin', () => {
|
||||
Hyprland.messageAsync('j/cursorpos').then((out) => {
|
||||
window.attribute.startY = JSON.parse(out).y;
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// Update drag
|
||||
signals.push(
|
||||
gesture.connect('drag-update', () => {
|
||||
Hyprland.messageAsync('j/cursorpos').then((out) => {
|
||||
if (!window.attribute.startY) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentY = JSON.parse(out).y;
|
||||
const offset = window.attribute.startY - currentY;
|
||||
|
||||
if (offset > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.child.setCss(`
|
||||
margin-bottom: ${offset}px;
|
||||
`);
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// End drag
|
||||
signals.push(
|
||||
gesture.connect('drag-end', () => {
|
||||
window.child.setCss(`
|
||||
transition: margin-bottom 0.5s ease-in-out;
|
||||
margin-bottom: 0px;
|
||||
`);
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
return window;
|
||||
};
|
|
@ -1,169 +0,0 @@
|
|||
const { Box, CenterBox, Label, ToggleButton } = Widget;
|
||||
|
||||
const { Gdk } = imports.gi;
|
||||
const display = Gdk.Display.get_default();
|
||||
|
||||
import Separator from '../misc/separator.ts';
|
||||
import RoundedCorner from '../corners/screen-corners.ts';
|
||||
import Key from './keys.ts';
|
||||
|
||||
import { defaultOskLayout, oskLayouts } from './keyboard-layouts.ts';
|
||||
const keyboardLayout = defaultOskLayout;
|
||||
const keyboardJson = oskLayouts[keyboardLayout];
|
||||
|
||||
const L_KEY_PER_ROW = [8, 7, 6, 6, 6, 4]; // eslint-disable-line
|
||||
const COLOR = 'rgba(0, 0, 0, 0.3)';
|
||||
const SPACING = 4;
|
||||
|
||||
// Types
|
||||
import { BoxGeneric, OskWindow } from 'global-types';
|
||||
|
||||
|
||||
export default (window: OskWindow) => Box({
|
||||
vertical: true,
|
||||
children: [
|
||||
CenterBox({
|
||||
hpack: 'center',
|
||||
|
||||
start_widget: RoundedCorner('bottomright', `
|
||||
background-color: ${COLOR};
|
||||
`),
|
||||
|
||||
center_widget: CenterBox({
|
||||
class_name: 'thingy',
|
||||
css: `background: ${COLOR};`,
|
||||
|
||||
center_widget: Box({
|
||||
hpack: 'center',
|
||||
class_name: 'settings',
|
||||
|
||||
children: [
|
||||
ToggleButton({
|
||||
class_name: 'button',
|
||||
active: true,
|
||||
vpack: 'center',
|
||||
|
||||
setup: (self) => {
|
||||
self
|
||||
.on('toggled', () => {
|
||||
self.toggleClassName(
|
||||
'toggled',
|
||||
self.get_active(),
|
||||
);
|
||||
window.exclusivity = self.get_active() ?
|
||||
'exclusive' :
|
||||
'normal';
|
||||
})
|
||||
|
||||
// OnHover
|
||||
.on('enter-notify-event', () => {
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
self.window.set_cursor(
|
||||
Gdk.Cursor.new_from_name(
|
||||
display,
|
||||
'pointer',
|
||||
),
|
||||
);
|
||||
self.toggleClassName('hover', true);
|
||||
})
|
||||
|
||||
// OnHoverLost
|
||||
.on('leave-notify-event', () => {
|
||||
self.window.set_cursor(null);
|
||||
self.toggleClassName('hover', false);
|
||||
});
|
||||
},
|
||||
|
||||
child: Label('Exclusive'),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
|
||||
end_widget: RoundedCorner('bottomleft', `
|
||||
background-color: ${COLOR};
|
||||
`),
|
||||
}),
|
||||
|
||||
CenterBox({
|
||||
css: `background: ${COLOR};`,
|
||||
class_name: 'osk',
|
||||
hexpand: true,
|
||||
|
||||
start_widget: Box({
|
||||
class_name: 'left-side side',
|
||||
hpack: 'start',
|
||||
vertical: true,
|
||||
|
||||
children: keyboardJson.keys.map((row, rowIndex) => {
|
||||
const keys = [] as BoxGeneric[];
|
||||
|
||||
row.forEach((key, keyIndex) => {
|
||||
if (keyIndex < L_KEY_PER_ROW[rowIndex]) {
|
||||
keys.push(Key(key));
|
||||
}
|
||||
});
|
||||
|
||||
return Box({
|
||||
vertical: true,
|
||||
|
||||
children: [
|
||||
Box({
|
||||
class_name: 'row',
|
||||
|
||||
children: [
|
||||
Separator(SPACING),
|
||||
|
||||
...keys,
|
||||
],
|
||||
}),
|
||||
|
||||
Separator(SPACING, { vertical: true }),
|
||||
],
|
||||
});
|
||||
}),
|
||||
}),
|
||||
|
||||
center_widget: Box({
|
||||
hpack: 'center',
|
||||
vpack: 'center',
|
||||
|
||||
children: [
|
||||
],
|
||||
}),
|
||||
|
||||
end_widget: Box({
|
||||
class_name: 'right-side side',
|
||||
hpack: 'end',
|
||||
vertical: true,
|
||||
|
||||
children: keyboardJson.keys.map((row, rowIndex) => {
|
||||
const keys = [] as BoxGeneric[];
|
||||
|
||||
row.forEach((key, keyIndex) => {
|
||||
if (keyIndex >= L_KEY_PER_ROW[rowIndex]) {
|
||||
keys.push(Key(key));
|
||||
}
|
||||
});
|
||||
|
||||
return Box({
|
||||
vertical: true,
|
||||
|
||||
children: [
|
||||
Box({
|
||||
hpack: 'end',
|
||||
class_name: 'row',
|
||||
|
||||
children: keys,
|
||||
}),
|
||||
|
||||
Separator(SPACING, { vertical: true }),
|
||||
],
|
||||
});
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
|
@ -1,254 +0,0 @@
|
|||
import Brightness from '../../services/brightness.ts';
|
||||
|
||||
const { Box, EventBox, Label } = Widget;
|
||||
const { execAsync } = Utils;
|
||||
|
||||
const { Gdk, Gtk } = imports.gi;
|
||||
const display = Gdk.Display.get_default();
|
||||
|
||||
import Separator from '../misc/separator.ts';
|
||||
|
||||
// Keep track of when a non modifier key
|
||||
// is clicked to release all modifiers
|
||||
const NormalClick = Variable(false);
|
||||
|
||||
// Keep track of modifier statuses
|
||||
const Super = Variable(false);
|
||||
const LAlt = Variable(false);
|
||||
const LCtrl = Variable(false);
|
||||
const AltGr = Variable(false);
|
||||
const RCtrl = Variable(false);
|
||||
|
||||
const Caps = Variable(false);
|
||||
|
||||
Brightness.connect('caps', (_, state) => {
|
||||
Caps.setValue(state);
|
||||
});
|
||||
|
||||
// Assume both shifts are the same for key.labelShift
|
||||
const LShift = Variable(false);
|
||||
const RShift = Variable(false);
|
||||
|
||||
const Shift = Variable(false);
|
||||
|
||||
LShift.connect('changed', () => {
|
||||
Shift.setValue(LShift.value || RShift.value);
|
||||
});
|
||||
RShift.connect('changed', () => {
|
||||
Shift.setValue(LShift.value || RShift.value);
|
||||
});
|
||||
|
||||
const SPACING = 4;
|
||||
const LSHIFT_CODE = 42;
|
||||
const LALT_CODE = 56;
|
||||
const LCTRL_CODE = 29;
|
||||
|
||||
// Types
|
||||
import { Variable as Var } from 'types/variable.ts';
|
||||
interface Key {
|
||||
keytype: string
|
||||
label: string
|
||||
labelShift?: string
|
||||
labelAltGr?: string
|
||||
shape: string
|
||||
keycode: number
|
||||
}
|
||||
|
||||
|
||||
const ModKey = (key: Key) => {
|
||||
let Mod: Var<boolean>;
|
||||
|
||||
if (key.label === 'Super') {
|
||||
Mod = Super;
|
||||
}
|
||||
|
||||
// Differentiate left and right mods
|
||||
else if (key.label === 'Shift' && key.keycode === LSHIFT_CODE) {
|
||||
Mod = LShift;
|
||||
}
|
||||
|
||||
else if (key.label === 'Alt' && key.keycode === LALT_CODE) {
|
||||
Mod = LAlt;
|
||||
}
|
||||
|
||||
else if (key.label === 'Ctrl' && key.keycode === LCTRL_CODE) {
|
||||
Mod = LCtrl;
|
||||
}
|
||||
|
||||
else if (key.label === 'Shift') {
|
||||
Mod = RShift;
|
||||
}
|
||||
|
||||
else if (key.label === 'AltGr') {
|
||||
Mod = AltGr;
|
||||
}
|
||||
|
||||
else if (key.label === 'Ctrl') {
|
||||
Mod = RCtrl;
|
||||
}
|
||||
const label = Label({
|
||||
class_name: `mod ${key.label}`,
|
||||
label: key.label,
|
||||
});
|
||||
|
||||
const button = EventBox({
|
||||
class_name: 'key',
|
||||
|
||||
on_primary_click_release: () => {
|
||||
console.log('mod toggled');
|
||||
|
||||
execAsync(`ydotool key ${key.keycode}:${Mod.value ? 0 : 1}`);
|
||||
|
||||
label.toggleClassName('active', !Mod.value);
|
||||
Mod.setValue(!Mod.value);
|
||||
},
|
||||
|
||||
setup: (self) => {
|
||||
self
|
||||
.hook(NormalClick, () => {
|
||||
Mod.setValue(false);
|
||||
|
||||
label.toggleClassName('active', false);
|
||||
execAsync(`ydotool key ${key.keycode}:0`);
|
||||
})
|
||||
|
||||
// OnHover
|
||||
.on('enter-notify-event', () => {
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
self.window.set_cursor(Gdk.Cursor.new_from_name(
|
||||
display,
|
||||
'pointer',
|
||||
));
|
||||
self.toggleClassName('hover', true);
|
||||
})
|
||||
|
||||
// OnHoverLost
|
||||
.on('leave-notify-event', () => {
|
||||
self.window.set_cursor(null);
|
||||
self.toggleClassName('hover', false);
|
||||
});
|
||||
},
|
||||
child: label,
|
||||
});
|
||||
|
||||
return Box({
|
||||
children: [
|
||||
button,
|
||||
Separator(SPACING),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const RegularKey = (key: Key) => {
|
||||
const widget = EventBox({
|
||||
class_name: 'key',
|
||||
|
||||
child: Label({
|
||||
class_name: `normal ${key.label}`,
|
||||
label: key.label,
|
||||
|
||||
setup: (self) => {
|
||||
self
|
||||
.hook(Shift, () => {
|
||||
if (!key.labelShift) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.label = Shift.value ? key.labelShift : key.label;
|
||||
})
|
||||
.hook(Caps, () => {
|
||||
if (key.label === 'Caps') {
|
||||
self.toggleClassName('active', Caps.value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!key.labelShift) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.label.match(/[A-Za-z]/)) {
|
||||
self.label = Caps.value ?
|
||||
key.labelShift :
|
||||
key.label;
|
||||
}
|
||||
})
|
||||
.hook(AltGr, () => {
|
||||
if (!key.labelAltGr) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.toggleClassName('altgr', AltGr.value);
|
||||
self.label = AltGr.value ? key.labelAltGr : key.label;
|
||||
})
|
||||
|
||||
// OnHover
|
||||
.on('enter-notify-event', () => {
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
self.window.set_cursor(Gdk.Cursor.new_from_name(
|
||||
display,
|
||||
'pointer',
|
||||
));
|
||||
self.toggleClassName('hover', true);
|
||||
})
|
||||
|
||||
// OnHoverLost
|
||||
.on('leave-notify-event', () => {
|
||||
self.window.set_cursor(null);
|
||||
self.toggleClassName('hover', false);
|
||||
});
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const gesture = Gtk.GestureLongPress.new(widget);
|
||||
|
||||
gesture.delay_factor = 1.0;
|
||||
|
||||
// Long press
|
||||
widget.hook(gesture, () => {
|
||||
const pointer = gesture.get_point(null);
|
||||
const x = pointer[1];
|
||||
const y = pointer[2];
|
||||
|
||||
if ((!x || !y) || (x === 0 && y === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Not implemented yet');
|
||||
|
||||
// TODO: popup menu for accents
|
||||
}, 'pressed');
|
||||
|
||||
// OnPrimaryClickRelease
|
||||
widget.hook(gesture, () => {
|
||||
const pointer = gesture.get_point(null);
|
||||
const x = pointer[1];
|
||||
const y = pointer[2];
|
||||
|
||||
if ((!x || !y) || (x === 0 && y === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('key clicked');
|
||||
|
||||
execAsync(`ydotool key ${key.keycode}:1`);
|
||||
execAsync(`ydotool key ${key.keycode}:0`);
|
||||
NormalClick.setValue(true);
|
||||
}, 'cancelled');
|
||||
|
||||
return Box({
|
||||
children: [
|
||||
widget,
|
||||
Separator(SPACING),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
export default (key: Key) => key.keytype === 'normal' ?
|
||||
RegularKey(key) :
|
||||
ModKey(key);
|
|
@ -1,32 +0,0 @@
|
|||
const { Window } = Widget;
|
||||
const { execAsync } = Utils;
|
||||
|
||||
import Tablet from '../../services/tablet.ts';
|
||||
import Gesture from './gesture.ts';
|
||||
import Keyboard from './keyboard.ts';
|
||||
|
||||
/* Types */
|
||||
import { OskWindow } from 'global-types';
|
||||
|
||||
// Start ydotool daemon
|
||||
execAsync('ydotoold').catch(print);
|
||||
|
||||
// Window
|
||||
export default () => {
|
||||
const window = Window({
|
||||
name: 'osk',
|
||||
layer: 'overlay',
|
||||
anchor: ['left', 'bottom', 'right'],
|
||||
})
|
||||
.hook(Tablet, (self: OskWindow, state) => {
|
||||
self.attribute.setVisible(state);
|
||||
}, 'osk-toggled')
|
||||
|
||||
.hook(Tablet, () => {
|
||||
window.visible = !(!Tablet.tabletMode && !Tablet.oskState);
|
||||
}, 'mode-toggled');
|
||||
|
||||
window.child = Keyboard(window);
|
||||
|
||||
return Gesture(window);
|
||||
};
|
|
@ -48,10 +48,6 @@ in {
|
|||
key = "edge:u:d";
|
||||
command = "ags request 'open win-applauncher'";
|
||||
}
|
||||
{
|
||||
key = "edge:d:u";
|
||||
command = "ags request 'osk open'";
|
||||
}
|
||||
];
|
||||
|
||||
hyprgrass-bindm = map mkBind [
|
||||
|
|
Loading…
Reference in a new issue