feat(ags): init migration of osk
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2024-11-25 12:09:03 -05:00
parent a1df7028f1
commit 524a2a6de9
17 changed files with 793 additions and 828 deletions

View file

@ -12,6 +12,7 @@ import Corners from '../widgets/corners/main';
import IconBrowser from '../widgets/icon-browser/main'; import IconBrowser from '../widgets/icon-browser/main';
import { NotifPopups, NotifCenter } from '../widgets/notifs/wim'; import { NotifPopups, NotifCenter } from '../widgets/notifs/wim';
import OSD from '../widgets/osd/main'; import OSD from '../widgets/osd/main';
import OSK from '../widgets/on-screen-keyboard/main';
import PowerMenu from '../widgets/powermenu/main'; import PowerMenu from '../widgets/powermenu/main';
import Screenshot from '../widgets/screenshot/main'; import Screenshot from '../widgets/screenshot/main';
@ -65,6 +66,7 @@ export default () => {
NotifPopups(); NotifPopups();
NotifCenter(); NotifCenter();
OSD(); OSD();
OSK();
PowerMenu(); PowerMenu();
Screenshot(); Screenshot();

View 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;
}
}

View file

@ -7,6 +7,7 @@
@use '../widgets/icon-browser'; @use '../widgets/icon-browser';
@use '../widgets/misc'; @use '../widgets/misc';
@use '../widgets/notifs'; @use '../widgets/notifs';
@use '../widgets/on-screen-keyboard';
@use '../widgets/osd'; @use '../widgets/osd';
@use '../widgets/powermenu'; @use '../widgets/powermenu';
@use '../widgets/screenshot'; @use '../widgets/screenshot';

View file

@ -1,23 +1,5 @@
.thingy { @use 'sass:color';
border-radius: 2rem 2rem 0 0; @use '../../style/colors';
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;
}
}
}
}
.osk { .osk {
padding-top: 4px; padding-top: 4px;
@ -26,12 +8,12 @@
.side { .side {
.key { .key {
&:active label { &:active label {
background-color: $contrast-bg; background-color: colors.$accent-color;
} }
label { label {
background-color: $bg; background-color: colors.$window_bg_color;
border: 0.08rem solid $darkbg; border: 0.08rem solid color.adjust(colors.$window_bg_color, $lightness: -3%);
border-radius: 0.7rem; border-radius: 0.7rem;
min-height: 3rem; min-height: 3rem;
@ -67,7 +49,7 @@
} }
&.active { &.active {
background-color: $darkbg; background-color: color.adjust(colors.$window_bg_color, $lightness: -3%);
} }
&.altgr { &.altgr {

View 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;
};

View file

@ -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;

View 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);

View 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);
};

View file

@ -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 });
}
}

View file

@ -54,15 +54,6 @@ export interface PlayerButtonType {
prop: string 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 // For ./ts/quick-settings
import { BluetoothDevice as BTDev } from 'types/service/bluetooth.ts'; import { BluetoothDevice as BTDev } from 'types/service/bluetooth.ts';
export interface APType { export interface APType {

View file

@ -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;

View file

@ -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;
};

View file

@ -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 }),
],
});
}),
}),
}),
],
});

View file

@ -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);

View file

@ -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);
};

View file

@ -48,10 +48,6 @@ in {
key = "edge:u:d"; key = "edge:u:d";
command = "ags request 'open win-applauncher'"; command = "ags request 'open win-applauncher'";
} }
{
key = "edge:d:u";
command = "ags request 'osk open'";
}
]; ];
hyprgrass-bindm = map mkBind [ hyprgrass-bindm = map mkBind [