parent
1ce40f2c1a
commit
6ca0d7248b
329 changed files with 178 additions and 139 deletions
modules/ags/config/widgets/on-screen-keyboard
109
modules/ags/config/widgets/on-screen-keyboard/_index.scss
Normal file
109
modules/ags/config/widgets/on-screen-keyboard/_index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
151
modules/ags/config/widgets/on-screen-keyboard/arcs.tsx
Normal file
151
modules/ags/config/widgets/on-screen-keyboard/arcs.tsx
Normal 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>
|
||||
);
|
223
modules/ags/config/widgets/on-screen-keyboard/gesture.ts
Normal file
223
modules/ags/config/widgets/on-screen-keyboard/gesture.ts
Normal 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;
|
||||
};
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
165
modules/ags/config/widgets/on-screen-keyboard/keyboard.tsx
Normal file
165
modules/ags/config/widgets/on-screen-keyboard/keyboard.tsx
Normal 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;
|
||||
};
|
243
modules/ags/config/widgets/on-screen-keyboard/keys.tsx
Normal file
243
modules/ags/config/widgets/on-screen-keyboard/keys.tsx
Normal 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);
|
27
modules/ags/config/widgets/on-screen-keyboard/main.tsx
Normal file
27
modules/ags/config/widgets/on-screen-keyboard/main.tsx
Normal 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);
|
||||
};
|
24
modules/ags/config/widgets/on-screen-keyboard/osk-window.tsx
Normal file
24
modules/ags/config/widgets/on-screen-keyboard/osk-window.tsx
Normal 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 });
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue