refactor: rename some flake attr directories

This commit is contained in:
matt1432 2024-12-16 15:51:41 -05:00
parent 1ce40f2c1a
commit 6ca0d7248b
329 changed files with 178 additions and 139 deletions

View file

@ -0,0 +1,109 @@
@use 'sass:color';
@use '../../style/colors';
.settings {
padding: 0.5rem;
.button {
background-color: colors.$window_bg_color;
border: 0.1rem solid color.adjust(colors.$window_bg_color, $lightness: -3%);
border-radius: 0.7rem;
padding: 0.3rem;
&.toggled {
background-color: colors.$accent-color;
}
}
}
.osk {
padding-top: 4px;
&.hidden {
opacity: 0;
}
.side {
.key {
&:active label {
background-color: colors.$accent-color;
}
label {
background-color: colors.$window_bg_color;
border-radius: 6px;
min-height: 3rem;
transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out;
box-shadow: 2px 1px 2px colors.$accent-color;
padding: 4px;
margin: 4px 4px 4px 4px;
&.active {
box-shadow: 0 0 0 white;
margin: 6px 4px 2px 4px;
background-color: color.adjust(colors.$window_bg_color, $lightness: -3%);
}
&.normal,
&.Super {
min-width: 3rem;
}
&.Tab,
&.Backspace {
min-width: 7rem;
}
&.Enter,
&.Caps {
min-width: 8rem;
}
&.Shift {
min-width: 9rem;
}
&.Space {
min-width: 20rem;
}
&.PrtSc,
&.AltGr {
min-width: 3.2rem;
}
&.altgr {
border: 0.08rem solid blue;
}
}
}
&.right-side {
border-radius: 0 10px 0 0;
.key .mod {
&.Ctrl {
min-width: 2.4rem;
}
}
}
&.left-side {
border-radius: 10px 0 0 0;
.key .mod {
&.Alt {
min-width: 3rem;
}
&.Ctrl {
min-width: 4rem;
}
}
}
}
}

View file

@ -0,0 +1,151 @@
import { Gdk, Gtk } from 'astal/gtk3';
import Cairo from 'cairo';
import { Bezier, BezierPoints, Point } from '../../lib';
/* Types */
type Side = 'top' | 'right' | 'bottom' | 'left';
const SIDES: Side[] = ['top', 'right', 'bottom', 'left'];
export default ({
css = 'background-color: black;',
allocation = { width: 0, height: 0 },
}) => (
<box>
<drawingarea
css={css}
setup={(widget) => {
widget.set_size_request(allocation.width, allocation.height);
widget.connect('draw', (_, cairoContext: Cairo.Context) => {
widget.set_size_request(allocation.width, allocation.height);
const styleContext = widget.get_style_context();
const bgColor = styleContext.get_background_color(Gtk.StateFlags.NORMAL);
const borderWidth = styleContext.get_border(Gtk.StateFlags.NORMAL);
const borderColor = Object.fromEntries(SIDES.map((side) => [
side,
styleContext.get_property(
`border-${side}-color`, Gtk.StateFlags.NORMAL,
) as Gdk.RGBA,
])) as Record<Side, Gdk.RGBA>;
/*
* Draws a cubic bezier curve from the current point
* of the cairo context.
*
* @param dest the destination point of the curve
* @param curve the cubic bezier's control points. this
* is the same as a CSS easing function
*/
const drawBezier = (dest: Point, curve: BezierPoints): void => {
const start = new Point(cairoContext.getCurrentPoint());
// Converts the ratios to absolute coordinates
curve[0] = curve[0] * (dest.x - start.x) + start.x;
curve[1] = curve[1] * (dest.y - start.y) + start.y;
curve[2] = curve[2] * (dest.x - start.x) + start.x;
curve[3] = curve[3] * (dest.y - start.y) + start.y;
cairoContext.curveTo(...curve, dest.x, dest.y);
};
const colorBorder = (
side: Side,
start: Point,
dest: Point,
curve?: BezierPoints,
) => {
cairoContext.moveTo(...start.values);
if (curve) {
drawBezier(dest, curve);
}
else {
cairoContext.lineTo(...dest.values);
}
cairoContext.setLineWidth(borderWidth[side]);
cairoContext.setSourceRGBA(
borderColor[side].red,
borderColor[side].green,
borderColor[side].blue,
borderColor[side].alpha,
);
cairoContext.stroke();
};
const bottomLeft = new Point({
x: 0,
y: allocation.height,
});
const topLeft = new Point({
x: 0,
y: 0,
});
const middleLeft = new Point({
x: allocation.width / 3,
y: allocation.height * 0.8,
});
const middleRight = new Point({
x: (allocation.width / 3) * 2,
y: allocation.height * 0.8,
});
const topRight = new Point({
x: allocation.width,
y: 0,
});
const bottomRight = new Point({
x: allocation.width,
y: allocation.height,
});
const bezier = new Bezier(0.76, 0, 0.24, 1);
// bottom left to top left
cairoContext.moveTo(...bottomLeft.values);
cairoContext.lineTo(...topLeft.values);
// top left to middle left
drawBezier(middleLeft, bezier.points);
// middle left to middle right
cairoContext.lineTo(...middleRight.values);
// middle right to top right
drawBezier(topRight, bezier.points);
// top right to bottom right
cairoContext.lineTo(...bottomRight.values);
// bottom right to bottom left
cairoContext.closePath();
// Add color
cairoContext.setSourceRGBA(bgColor.red, bgColor.green, bgColor.blue, bgColor.alpha);
cairoContext.fill();
colorBorder('left', bottomLeft, topLeft);
colorBorder('top', topLeft, middleLeft, bezier.points);
colorBorder('top', middleLeft, middleRight);
colorBorder('top', middleRight, topRight, bezier.points);
colorBorder('right', topRight, bottomRight);
colorBorder('bottom', bottomLeft, bottomRight);
});
}}
/>
</box>
);

View file

@ -0,0 +1,223 @@
import { execAsync, idle } from 'astal';
import { Astal, Gtk } from 'astal/gtk3';
import Tablet from '../../services/tablet';
import { hyprMessage } from '../../lib';
import OskWindow from './osk-window';
import { Keys } from './keys';
const KEY_N = 249;
const KEYCODES = Array.from(Array(KEY_N).keys()).map((keycode) => `${keycode}:0`);
const ANIM_DURATION = 700;
const releaseAllKeys = () => {
execAsync([
'ydotool', 'key',
...KEYCODES,
]).catch(print);
};
export default (window: OskWindow) => {
const tablet = Tablet.get_default();
let signals = [] as number[];
let calculatedHeight = 0;
idle(() => {
calculatedHeight = window.get_allocated_height();
tablet.oskState = false;
setTimeout(() => {
window.get_grandchildren()[0].toggleClassName('hidden', false);
window.set_exclusivity(Astal.Exclusivity.EXCLUSIVE);
}, ANIM_DURATION * 3);
});
const gesture = Gtk.GestureDrag.new(window);
window.hook(tablet, 'notify::osk-state', () => {
if (tablet.oskState) {
window.setSlideDown();
window.get_child().css = `
transition: margin-bottom ${ANIM_DURATION}ms cubic-bezier(0.36, 0, 0.66, -0.56);
margin-bottom: 0px;
`;
}
else {
releaseAllKeys();
window.setSlideUp();
window.get_child().css = `
transition: margin-bottom ${ANIM_DURATION}ms cubic-bezier(0.36, 0, 0.66, -0.56);
margin-bottom: -${calculatedHeight}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: -${calculatedHeight}px;
`;
return;
}
if (offset > calculatedHeight) {
window.get_child().css = `
margin-bottom: 0px;
`;
}
else {
window.get_child().css = `
margin-bottom: ${offset - calculatedHeight}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 > calculatedHeight) {
window.get_child().css = `
transition: margin-bottom 0.5s ease-in-out;
margin-bottom: 0px;
`;
tablet.oskState = true;
}
else {
window.get_child().css = `
transition: margin-bottom 0.5s ease-in-out;
margin-bottom: -${calculatedHeight}px;
`;
}
window.startY = null;
});
}),
);
};
window.setSlideDown = () => {
window.killGestureSigs();
// Begin drag
signals.push(
gesture.connect('drag-begin', () => {
hyprMessage('j/cursorpos').then((out) => {
const hasActiveKey = Keys.get().map((v) => v.get()).includes(true);
if (!hasActiveKey) {
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 < -(calculatedHeight * 2 / 3)) {
window.get_child().css = `
transition: margin-bottom 0.5s ease-in-out;
margin-bottom: -${calculatedHeight}px;
`;
tablet.oskState = false;
}
else {
window.get_child().css = `
transition: margin-bottom 0.5s ease-in-out;
margin-bottom: 0px;
`;
}
window.startY = null;
});
}),
);
};
return window;
};

View file

@ -0,0 +1,551 @@
// TODO: right Ctrl https://handwiki.org/wiki/images/4/41/KB_Canadian_Multilingual_Standard.svg
export const defaultOskLayout = 'qwerty_custom';
export const oskLayouts = {
qwerty_custom: {
name: 'QWERTY - Custom',
name_short: 'CSA',
comment: 'Like physical keyboard',
// A normal key looks like this: {label: "a", labelShift: "A", shape: "normal", keycode: 30, type: "normal"}
// A modkey looks like this: {label: "Ctrl", shape: "control", keycode: 29, type: "modkey"}
// key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand
keys: [
[
{
keytype: 'normal',
label: 'Esc',
shape: 'fn',
keycode: 1,
},
{
keytype: 'normal',
label: 'F1',
shape: 'fn',
keycode: 59,
},
{
keytype: 'normal',
label: 'F2',
shape: 'fn',
keycode: 60,
},
{
keytype: 'normal',
label: 'F3',
shape: 'fn',
keycode: 61,
},
{
keytype: 'normal',
label: 'F4',
shape: 'fn',
keycode: 62,
},
{
keytype: 'normal',
label: 'F5',
shape: 'fn',
keycode: 63,
},
{
keytype: 'normal',
label: 'F6',
shape: 'fn',
keycode: 64,
},
{
keytype: 'normal',
label: 'F7',
shape: 'fn',
keycode: 65,
},
{
keytype: 'normal',
label: 'F8',
shape: 'fn',
keycode: 66,
},
{
keytype: 'normal',
label: 'F9',
shape: 'fn',
keycode: 67,
},
{
keytype: 'normal',
label: 'F10',
shape: 'fn',
keycode: 68,
},
{
keytype: 'normal',
label: 'F11',
shape: 'fn',
keycode: 87,
},
{
keytype: 'normal',
label: 'F12',
shape: 'fn',
keycode: 88,
},
{
keytype: 'normal',
label: 'Home',
shape: 'fn',
keycode: 110,
},
{
keytype: 'normal',
label: 'End',
shape: 'fn',
keycode: 115,
},
{
keytype: 'normal',
label: 'Del',
shape: 'fn',
keycode: 111,
},
],
[
{
keytype: 'normal',
label: '/',
labelShift: '\\',
labelAltGr: '|',
shape: 'normal',
keycode: 41,
},
{
keytype: 'normal',
label: '1',
labelShift: '!',
shape: 'normal',
keycode: 2,
},
{
keytype: 'normal',
label: '2',
labelShift: '@',
shape: 'normal',
keycode: 3,
},
{
keytype: 'normal',
label: '3',
labelShift: '#',
labelAltGr: '¤',
shape: 'normal',
keycode: 4,
},
{
keytype: 'normal',
label: '4',
labelShift: '$',
shape: 'normal',
keycode: 5,
},
{
keytype: 'normal',
label: '5',
labelShift: '%',
shape: 'normal',
keycode: 6,
},
{
keytype: 'normal',
label: '6',
labelShift: '?',
shape: 'normal',
keycode: 7,
},
{
keytype: 'normal',
label: '7',
labelShift: '&',
labelAltGr: '{',
shape: 'normal',
keycode: 8,
},
{
keytype: 'normal',
label: '8',
labelShift: '*',
labelAltGr: '}',
shape: 'normal',
keycode: 9,
},
{
keytype: 'normal',
label: '9',
labelShift: '(',
labelAltGr: '[',
shape: 'normal',
keycode: 10,
},
{
keytype: 'normal',
label: '0',
labelShift: ')',
labelAltGr: ']',
shape: 'normal',
keycode: 11,
},
{
keytype: 'normal',
label: '-',
labelShift: '_',
shape: 'normal',
keycode: 12,
},
{
keytype: 'normal',
label: '=',
labelShift: '+',
labelAltGr: '¬',
shape: 'normal',
keycode: 13,
},
{
keytype: 'normal',
label: 'Backspace',
shape: 'expand',
keycode: 14,
},
],
[
{
keytype: 'normal',
label: 'Tab',
shape: 'tab',
keycode: 15,
},
{
keytype: 'normal',
label: 'q',
labelShift: 'Q',
shape: 'normal',
keycode: 16,
},
{
keytype: 'normal',
label: 'w',
labelShift: 'W',
shape: 'normal',
keycode: 17,
},
{
keytype: 'normal',
label: 'e',
labelShift: 'E',
labelAltGr: '€',
shape: 'normal',
keycode: 18,
},
{
keytype: 'normal',
label: 'r',
labelShift: 'R',
shape: 'normal',
keycode: 19,
},
{
keytype: 'normal',
label: 't',
labelShift: 'T',
shape: 'normal',
keycode: 20,
},
{
keytype: 'normal',
label: 'y',
labelShift: 'Y',
shape: 'normal',
keycode: 21,
},
{
keytype: 'normal',
label: 'u',
labelShift: 'U',
shape: 'normal',
keycode: 22,
},
{
keytype: 'normal',
label: 'i',
labelShift: 'I',
shape: 'normal',
keycode: 23,
},
{
keytype: 'normal',
label: 'o',
labelShift: 'O',
shape: 'normal',
keycode: 24,
},
{
keytype: 'normal',
label: 'p',
labelShift: 'P',
shape: 'normal',
keycode: 25,
},
{
keytype: 'normal',
label: '^',
labelShift: '"',
labelAltGr: '`',
shape: 'normal',
keycode: 26,
},
{
keytype: 'normal',
label: 'ç',
labelShift: 'Ç',
labelAltGr: '~',
shape: 'normal',
keycode: 27,
},
{
keytype: 'normal',
label: 'à',
labelShift: 'À',
shape: 'expand',
keycode: 43,
},
],
[
{
keytype: 'normal',
label: 'Caps',
shape: 'caps',
keycode: 58,
},
{
keytype: 'normal',
label: 'a',
labelShift: 'A',
shape: 'normal',
keycode: 30,
},
{
keytype: 'normal',
label: 's',
labelShift: 'S',
shape: 'normal',
keycode: 31,
},
{
keytype: 'normal',
label: 'd',
labelShift: 'D',
shape: 'normal',
keycode: 32,
},
{
keytype: 'normal',
label: 'f',
labelShift: 'F',
shape: 'normal',
keycode: 33,
},
{
keytype: 'normal',
label: 'g',
labelShift: 'G',
shape: 'normal',
keycode: 34,
},
{
keytype: 'normal',
label: 'h',
labelShift: 'H',
shape: 'normal',
keycode: 35,
},
{
keytype: 'normal',
label: 'j',
labelShift: 'J',
shape: 'normal',
keycode: 36,
},
{
keytype: 'normal',
label: 'k',
labelShift: 'K',
shape: 'normal',
keycode: 37,
},
{
keytype: 'normal',
label: 'l',
labelShift: 'L',
shape: 'normal',
keycode: 38,
},
{
keytype: 'normal',
label: ';',
labelShift: ':',
labelAltGr: '°',
shape: 'normal',
keycode: 39,
},
{
keytype: 'normal',
label: 'è',
labelShift: 'È',
shape: 'normal',
keycode: 40,
},
{
keytype: 'normal',
label: 'Enter',
shape: 'expand',
keycode: 28,
},
],
[
{
keytype: 'modkey',
label: 'Shift',
shape: 'shift',
keycode: 42,
},
{
keytype: 'normal',
label: 'z',
labelShift: 'Z',
labelAltGr: '«',
shape: 'normal',
keycode: 44,
},
{
keytype: 'normal',
label: 'x',
labelShift: 'X',
labelAltGr: '»',
shape: 'normal',
keycode: 45,
},
{
keytype: 'normal',
label: 'c',
labelShift: 'C',
shape: 'normal',
keycode: 46,
},
{
keytype: 'normal',
label: 'v',
labelShift: 'V',
shape: 'normal',
keycode: 47,
},
{
keytype: 'normal',
label: 'b',
labelShift: 'B',
shape: 'normal',
keycode: 48,
},
{
keytype: 'normal',
label: 'n',
labelShift: 'N',
shape: 'normal',
keycode: 49,
},
{
keytype: 'normal',
label: 'm',
labelShift: 'M',
shape: 'normal',
keycode: 50,
},
{
keytype: 'normal',
label: ',',
labelShift: "'",
labelAltGr: '<',
shape: 'normal',
keycode: 51,
},
{
keytype: 'normal',
label: '.',
labelShift: '"',
labelAltGr: '>',
shape: 'normal',
keycode: 52,
},
{
keytype: 'normal',
label: 'é',
labelShift: 'É',
shape: 'normal',
keycode: 53,
},
{
keytype: 'modkey',
label: 'Shift',
shape: 'expand',
keycode: 54,
},
],
[
{
keytype: 'modkey',
label: 'Ctrl',
shape: 'control',
keycode: 29,
},
{
keytype: 'modkey',
label: 'Super',
shape: 'normal',
keycode: 125,
},
{
keytype: 'modkey',
label: 'Alt',
shape: 'normal',
keycode: 56,
},
{
keytype: 'normal',
label: 'Space',
shape: 'space',
keycode: 57,
},
{
keytype: 'normal',
label: 'Space',
shape: 'space',
keycode: 57,
},
{
keytype: 'modkey',
label: 'AltGr',
shape: 'normal',
keycode: 100,
},
{
keytype: 'normal',
label: 'PrtSc',
shape: 'fn',
keycode: 99,
},
{
keytype: 'modkey',
label: 'Ctrl',
shape: 'control',
keycode: 97,
},
],
],
},
};

View file

@ -0,0 +1,165 @@
import { bind, idle, Variable } from 'astal';
import { Astal, Gtk, Widget } from 'astal/gtk3';
import { ToggleButton } from '../misc/subclasses';
import Separator from '../misc/separator';
import Arc from './arcs';
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];
const SPACING = 4;
const COLOR = 'rgba(0, 0, 0, 0.5)';
export default () => {
const ThirdWidth = Variable(0);
return (
<box
vertical
onRealize={(self) => idle(() => {
ThirdWidth.set(self.get_allocated_width() / 3);
})}
>
<centerbox
className="osk hidden"
hexpand
>
{/* LEFT */}
<box
widthRequest={bind(ThirdWidth)}
css={`background: ${COLOR};`}
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>
{/* MIDDLE */}
<box
widthRequest={bind(ThirdWidth)}
halign={Gtk.Align.CENTER}
valign={Gtk.Align.FILL}
vertical
>
<box
valign={Gtk.Align.START}
>
{bind(ThirdWidth).as((width) => (
<Arc
allocation={{ width, height: 160 }}
css={`background: ${COLOR};`}
/>
))}
</box>
<box
halign={Gtk.Align.FILL}
hexpand
vexpand
css={`background: ${COLOR};`}
className="settings"
>
<centerbox
halign={Gtk.Align.FILL}
hexpand
vexpand
>
<box />
<ToggleButton
className="button"
cursor="pointer"
active
valign={Gtk.Align.CENTER}
halign={Gtk.Align.CENTER}
onToggled={(self) => {
self.toggleClassName(
'toggled',
self.get_active(),
);
if (self.get_toplevel() === self) {
return;
}
(self.get_toplevel() as Widget.Window | undefined)
?.set_exclusivity(
self.get_active() ?
Astal.Exclusivity.EXCLUSIVE :
Astal.Exclusivity.NORMAL,
);
}}
>
Exclusive
</ToggleButton>
<box />
</centerbox>
</box>
</box>
{/* RIGHT */}
<box
widthRequest={bind(ThirdWidth)}
css={`background: ${COLOR};`}
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
className="row"
halign={Gtk.Align.END}
>
{...keys}
</box>
<Separator size={SPACING} vertical />
</box>
);
})}
</box>
</centerbox>
</box>
) as Widget.Box;
};

View file

@ -0,0 +1,243 @@
import { bind, execAsync, interval, Variable } from 'astal';
import { Gtk, Widget } from 'astal/gtk3';
import Brightness from '../../services/brightness';
import Separator from '../misc/separator';
/* Types */
import AstalIO from 'gi://AstalIO';
interface Key {
keytype: string
label: string
labelShift?: string
labelAltGr?: string
shape: string
keycode: number
}
const brightness = Brightness.get_default();
const SPACING = 4;
const LSHIFT_CODE = 42;
const LALT_CODE = 56;
const LCTRL_CODE = 29;
export const Keys = Variable<Variable<boolean>[]>([]);
// 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;
}
Keys.set([...Keys.get(), Mod!]);
const label = (
<label
className={`mod ${key.label}`}
label={key.label}
/>
) as Widget.Label;
const button = (
<eventbox
className="key"
cursor="pointer"
onButtonPressEvent={() => {
execAsync(`ydotool key ${key.keycode}:${Mod.get() ? 0 : 1}`);
label.toggleClassName('active', !Mod.get());
Mod.set(!Mod.get());
}}
>
{label}
</eventbox>
);
return (
<box>
{button}
<Separator size={SPACING} />
</box>
) as Widget.Box;
};
const RegularKey = (key: Key) => {
const IsActive = Variable(false);
const IsLongPressing = Variable(false);
Keys.set([...Keys.get(), IsActive]);
const widget = (
<eventbox
className="key"
cursor="pointer"
onButtonReleaseEvent={() => {
IsLongPressing.set(false);
IsActive.set(false);
}}
>
<label
className={bind(IsActive).as((v) => [
'normal',
key.label,
(v ? 'active' : ''),
].join(' '))}
label={key.label}
setup={(self) => {
self
.hook(Shift, () => {
if (key.labelShift) {
self.label = Shift.get() ?
key.labelShift :
key.label;
}
})
.hook(Caps, () => {
if (key.label === 'Caps') {
IsActive.set(Caps.get());
return;
}
if (key.labelShift && key.label.match(/[A-Za-z]/)) {
self.label = Caps.get() ?
key.labelShift :
key.label;
}
})
.hook(AltGr, () => {
if (key.labelAltGr) {
self.toggleClassName('altgr', AltGr.get());
self.label = AltGr.get() ?
key.labelAltGr :
key.label;
}
});
}}
/>
</eventbox>
) as Widget.EventBox;
const gesture = Gtk.GestureLongPress.new(widget);
gesture.delay_factor = 1.0;
const onClick = (callback: () => void) => {
const pointer = gesture.get_point(null);
const x = pointer[1];
const y = pointer[2];
if ((!x || !y) || (x === 0 && y === 0)) {
return;
}
callback();
};
widget.hook(gesture, 'begin', () => {
IsActive.set(true);
});
widget.hook(gesture, 'cancelled', () => {
onClick(() => {
execAsync(`ydotool key ${key.keycode}:1`);
execAsync(`ydotool key ${key.keycode}:0`);
IsActive.set(false);
});
});
// Long Press
widget.hook(gesture, 'pressed', () => {
onClick(() => {
IsLongPressing.set(true);
});
});
let spamClick: AstalIO.Time | undefined;
IsLongPressing.subscribe((v) => {
if (v) {
spamClick = interval(100, () => {
execAsync(`ydotool key ${key.keycode}:1`);
execAsync(`ydotool key ${key.keycode}:0`);
});
}
else {
spamClick?.cancel();
}
});
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,27 @@
import { execAsync } from 'astal';
import { Astal } from 'astal/gtk3';
import OskWindow from './osk-window';
import Gesture from './gesture';
import Keyboard from './keyboard';
export default () => {
execAsync('ydotoold').catch(() => { /**/ });
return Gesture((
<OskWindow
name="osk"
namespace="noanim-osk"
layer={Astal.Layer.OVERLAY}
anchor={
Astal.WindowAnchor.BOTTOM |
Astal.WindowAnchor.LEFT |
Astal.WindowAnchor.RIGHT
}
>
<Keyboard />
</OskWindow>
) as OskWindow);
};

View file

@ -0,0 +1,24 @@
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 killGestureSigs: () => void;
declare public setSlideUp: () => void;
declare public setSlideDown: () => void;
get_child(): Widget.Box {
return super.get_child() as Widget.Box;
}
get_grandchildren(): (Widget.Box | Widget.CenterBox)[] {
return this.get_child().get_children() as (Widget.Box | Widget.CenterBox)[];
}
constructor({ ...rest }: Widget.WindowProps) {
super({ application: App, ...rest });
}
}