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 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();
|
||||||
|
|
||||||
|
|
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/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';
|
||||||
|
|
|
@ -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 {
|
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
|
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 {
|
||||||
|
|
|
@ -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";
|
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 [
|
||||||
|
|
Loading…
Reference in a new issue