feat(ags4): implement lockscreen

This commit is contained in:
matt1432 2025-01-25 02:28:34 -05:00
parent 8bd0b732e7
commit 557e4c7a52
32 changed files with 281 additions and 12 deletions

View file

@ -0,0 +1,179 @@
import { property, register } from 'astal';
import { Gtk, hook } from 'astal/gtk4';
import { type Connectable, type Subscribable } from 'astal/binding';
import construct from './construct';
import setupControllers from './controller';
import {
type BindableProps,
childType,
type Cursor,
dummyBuilder,
type MixinParams,
noImplicitDestroy,
setChildren,
} from './generics';
export default <
C extends new (...props: MixinParams) => Gtk.Widget,
ConstructorProps,
>(
cls: C,
clsName = cls.name,
) => {
@register({ GTypeName: `RealClass_${clsName}` })
class Widget extends cls {
declare private _css: string | undefined;
declare private _provider: Gtk.CssProvider | undefined;
@property(String)
get css(): string | undefined {
return this._css;
}
set css(value: string) {
if (!this._provider) {
this._provider = new Gtk.CssProvider();
this.get_style_context().add_provider(
this._provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER,
);
}
this._css = value;
this._provider.load_from_string(value);
}
declare private [childType]: string;
@property(String)
get type(): string { return this[childType]; }
set type(value: string) { this[childType] = value; }
@property(Object)
get children(): Gtk.Widget[] { return this.getChildren(this); }
set children(value: Gtk.Widget[]) { this.setChildren(this, value); }
declare private [noImplicitDestroy]: boolean;
@property(String)
get noImplicitDestroy(): boolean { return this[noImplicitDestroy]; }
set noImplicitDestroy(value: boolean) { this[noImplicitDestroy] = value; }
protected getChildren(widget: Gtk.Widget): Gtk.Widget[] {
if ('get_child' in widget && typeof widget.get_child == 'function') {
return widget.get_child() ? [widget.get_child()] : [];
}
const children: Gtk.Widget[] = [];
let ch = widget.get_first_child();
while (ch !== null) {
children.push(ch);
ch = ch.get_next_sibling();
}
return children;
}
protected setChildren(widget: Gtk.Widget, children: Gtk.Widget[]) {
for (const child of children) {
widget.vfunc_add_child(
dummyBuilder,
child,
childType in widget ? widget[childType] as string : null,
);
}
}
[setChildren](children: Gtk.Widget[]) {
for (const child of (this.getChildren(this))) {
child?.unparent();
if (!children.includes(child) && noImplicitDestroy in this) {
child.run_dispose();
}
}
this.setChildren(this, children);
}
private _cursorName: Cursor = 'default';
@property(String)
get cursorName(): Cursor {
return this._cursorName;
}
set cursorName(val: Cursor) {
this._cursorName = val;
this.set_cursor_from_name(val);
}
hook(
object: Connectable,
signal: string,
callback: (self: this, ...args: unknown[]) => void,
): this;
hook(
object: Subscribable,
callback: (self: this, ...args: unknown[]) => void,
): this;
hook(
object: Connectable | Subscribable,
signalOrCallback: string | ((self: this, ...args: unknown[]) => void),
callback?: (self: this, ...args: unknown[]) => void,
) {
hook(this, object, signalOrCallback, callback);
return this;
}
constructor(...params: MixinParams) {
const props = params[0] || {};
super('cssName' in props ? { cssName: props.cssName } : {});
if ('cssName' in props) {
delete props.cssName;
}
if (props.noImplicitDestroy) {
this.noImplicitDestroy = true;
delete props.noImplicitDestroy;
}
if (props.type) {
this.type = props.type;
delete props.type;
}
construct(this, setupControllers(this, props));
}
}
type Constructor<Instance, Props> = new (...args: Props[]) => Instance;
type WidgetClass = Constructor<
Widget & Gtk.Widget & InstanceType<C>,
Partial<BindableProps<ConstructorProps>>
>;
// override the parameters of the `super` constructor
return Widget as unknown as WidgetClass;
};

View file

@ -0,0 +1,27 @@
import { Variable } from 'astal';
import { Binding } from 'astal/binding';
export const mergeBindings = <Value = unknown>(
array: (Value | Binding<Value> | Binding<Value[]>)[],
): Value[] | Binding<Value[]> => {
const getValues = (args: Value[]) => {
let i = 0;
return array.map((value) => value instanceof Binding ?
args[i++] :
value);
};
const bindings = array.filter((i) => i instanceof Binding);
if (bindings.length === 0) {
return array as Value[];
}
if (bindings.length === 1) {
return (bindings[0] as Binding<Value[]>).as(getValues);
}
return Variable.derive(bindings, getValues)();
};

View file

@ -0,0 +1,131 @@
import { execAsync } from 'astal';
import { type Gtk, type ConstructProps } from 'astal/gtk4';
import { Binding, kebabify, snakeify } from 'astal/binding';
import { mergeBindings } from './bindings';
import { type EventController } from './controller';
import { type AstalifyProps, type BindableProps, type GenericWidget, setChildren } from './generics';
export default <
Self extends GenericWidget,
Props extends Gtk.Widget.ConstructorProps,
>(
widget: Self,
props: Omit<
ConstructProps<Self, Props> & Partial<BindableProps<AstalifyProps>>,
keyof EventController<Self>
>,
) => {
type Key = keyof typeof props;
const keys = Object.keys(props) as Key[];
const entries = Object.entries(props) as [Key, unknown][];
const setProp = (prop: Key, value: Self[keyof Self]) => {
try {
const setter = `set_${snakeify(prop.toString())}` as keyof Self;
if (typeof widget[setter] === 'function') {
return widget[setter](value);
}
return (widget[prop as keyof Self] = value);
}
catch (error) {
console.error(`could not set property "${prop.toString()}" on ${widget}:`, error);
}
};
const children = props.children ?
props.children instanceof Binding ?
[props.children] as (Binding<Gtk.Widget[]> | Binding<Gtk.Widget> | Gtk.Widget)[] :
props.children as Gtk.Widget[] :
[];
if (props.child) {
children.unshift(props.child);
}
// remove undefined values
for (const [key, value] of entries) {
if (typeof value === 'undefined') {
delete props[key];
}
}
// collect bindings
const bindings: [Key, Binding<unknown>][] = [];
for (const key of keys) {
if (props[key] instanceof Binding) {
bindings.push([key, props[key]]);
delete props[key];
}
}
// collect signal handlers
const onHandlers: [string, string | (() => void)][] = [];
for (const key of keys) {
if (key.toString().startsWith('on')) {
const sig = kebabify(key.toString()).split('-').slice(1).join('-');
onHandlers.push([sig, props[key] as string | (() => void)]);
delete props[key];
}
}
// set children
const mergedChildren = mergeBindings<Gtk.Widget>(children.flat(Infinity));
if (mergedChildren instanceof Binding) {
widget[setChildren](mergedChildren.get());
widget.connect('destroy', mergedChildren.subscribe((v) => {
widget[setChildren](v);
}));
}
else if (mergedChildren.length > 0) {
widget[setChildren](mergedChildren);
}
// setup signal handlers
for (const [signal, callback] of onHandlers) {
const sig = signal.startsWith('notify') ?
signal.replace('-', '::') :
signal;
if (typeof callback === 'function') {
widget.connect(sig, callback);
}
else {
widget.connect(sig, () => execAsync(callback)
.then(print).catch(console.error));
}
}
// setup bindings handlers
for (const [prop, binding] of bindings) {
if (prop === 'child' || prop === 'children') {
widget.connect('destroy', (binding as Binding<Gtk.Widget[]>).subscribe((v: Gtk.Widget[]) => {
widget[setChildren](v);
}));
}
widget.connect('destroy', binding.subscribe((v: unknown) => {
setProp(prop, v as Self[keyof Self]);
}));
setProp(prop, binding.get() as Self[keyof Self]);
}
// filter undefined values
for (const [key, value] of entries) {
if (typeof value === 'undefined') {
delete props[key];
}
}
Object.assign(widget, props);
props.setup?.(widget);
return widget;
};

View file

@ -0,0 +1,120 @@
import { Gdk, Gtk } from 'astal/gtk4';
export interface EventController<Self extends Gtk.Widget> {
onFocusEnter?: (self: Self) => void
onFocusLeave?: (self: Self) => void
onKeyPressed?: (self: Self, keyval: number, keycode: number, state: Gdk.ModifierType) => void
onKeyReleased?: (self: Self, keyval: number, keycode: number, state: Gdk.ModifierType) => void
onKeyModifier?: (self: Self, state: Gdk.ModifierType) => void
onLegacy?: (self: Self, event: Gdk.Event) => void
onButtonPressed?: (self: Self, state: Gdk.ButtonEvent) => void
onButtonReleased?: (self: Self, state: Gdk.ButtonEvent) => void
onHoverEnter?: (self: Self, x: number, y: number) => void
onHoverLeave?: (self: Self) => void
onMotion?: (self: Self, x: number, y: number) => void
onScroll?: (self: Self, dx: number, dy: number) => void
onScrollDecelerate?: (self: Self, vel_x: number, vel_y: number) => void
}
export default <T>(widget: Gtk.Widget, {
onFocusEnter,
onFocusLeave,
onKeyPressed,
onKeyReleased,
onKeyModifier,
onLegacy,
onButtonPressed,
onButtonReleased,
onHoverEnter,
onHoverLeave,
onMotion,
onScroll,
onScrollDecelerate,
...props
}: EventController<Gtk.Widget> & T) => {
if (onFocusEnter || onFocusLeave) {
const focus = new Gtk.EventControllerFocus();
widget.add_controller(focus);
if (onFocusEnter) { focus.connect('focus-enter', () => onFocusEnter(widget)); }
if (onFocusLeave) { focus.connect('focus-leave', () => onFocusLeave(widget)); }
}
if (onKeyPressed || onKeyReleased || onKeyModifier) {
const key = new Gtk.EventControllerKey();
widget.add_controller(key);
if (onKeyPressed) {
key.connect('key-pressed', (_, val, code, state) => onKeyPressed(widget, val, code, state));
}
if (onKeyReleased) {
key.connect('key-released', (_, val, code, state) =>
onKeyReleased(widget, val, code, state));
}
if (onKeyModifier) {
key.connect('modifiers', (_, state) => onKeyModifier(widget, state));
}
}
if (onLegacy || onButtonPressed || onButtonReleased) {
const legacy = new Gtk.EventControllerLegacy();
widget.add_controller(legacy);
legacy.connect('event', (_, event) => {
if (event.get_event_type() === Gdk.EventType.BUTTON_PRESS) {
onButtonPressed?.(widget, event as Gdk.ButtonEvent);
}
if (event.get_event_type() === Gdk.EventType.BUTTON_RELEASE) {
onButtonReleased?.(widget, event as Gdk.ButtonEvent);
}
onLegacy?.(widget, event);
});
}
if (onMotion || onHoverEnter || onHoverLeave) {
const hover = new Gtk.EventControllerMotion();
widget.add_controller(hover);
if (onHoverEnter) {
hover.connect('enter', (_, x, y) => onHoverEnter(widget, x, y));
}
if (onHoverLeave) {
hover.connect('leave', () => onHoverLeave(widget));
}
if (onMotion) {
hover.connect('motion', (_, x, y) => onMotion(widget, x, y));
}
}
if (onScroll || onScrollDecelerate) {
const scroll = new Gtk.EventControllerScroll();
widget.add_controller(scroll);
if (onScroll) {
scroll.connect('scroll', (_, x, y) => onScroll(widget, x, y));
}
if (onScrollDecelerate) {
scroll.connect('decelerate', (_, x, y) => onScrollDecelerate(widget, x, y));
}
}
return props;
};

View file

@ -0,0 +1,91 @@
import { Gtk } from 'astal/gtk4';
import { Binding } from 'astal/binding';
import { EventController } from './controller';
// A mixin class must have a constructor with a single rest parameter of type 'any[]'
// eslint-disable-next-line "@typescript-eslint/no-explicit-any"
export type MixinParams = any[];
export type BindableChild = Gtk.Widget | Binding<Gtk.Widget>;
export type BindableProps<T> = {
[K in keyof T]: Binding<T[K]> | T[K];
};
export const noImplicitDestroy = Symbol('no no implicit destroy');
export const setChildren = Symbol('children setter method');
export const childType = Symbol('child type');
export const dummyBuilder = new Gtk.Builder();
export type GenericWidget = InstanceType<typeof Gtk.Widget> & {
[setChildren]: (children: Gtk.Widget[]) => void
};
export interface AstalifyProps {
css: string
child: Gtk.Widget
children: Gtk.Widget[]
cursorName: Cursor
}
type SigHandler<
W extends InstanceType<typeof Gtk.Widget>,
Args extends unknown[],
> = ((self: W, ...args: Args) => unknown) | string | string[];
export type ConstructProps<
Self extends InstanceType<typeof Gtk.Widget>,
Props extends Gtk.Widget.ConstructorProps,
Signals extends Record<`on${string}`, unknown[]> = Record<`on${string}`, unknown[]>,
> = Partial<{
// @ts-expect-error can't assign to unknown, but it works as expected though
[S in keyof Signals]: SigHandler<Self, Signals[S]>
}> & Partial<Record<`on${string}`, SigHandler<Self, unknown[]>>> & Partial<BindableProps<Omit<
Props,
'cssName' | 'css_name' | 'cursor'
>>> & {
noImplicitDestroy?: true
type?: string
cssName?: string
} & EventController<Self> & {
onDestroy?: (self: Self) => unknown
setup?: (self: Self) => void
};
export type Cursor =
| 'default'
| 'help'
| 'pointer'
| 'context-menu'
| 'progress'
| 'wait'
| 'cell'
| 'crosshair'
| 'text'
| 'vertical-text'
| 'alias'
| 'copy'
| 'no-drop'
| 'move'
| 'not-allowed'
| 'grab'
| 'grabbing'
| 'all-scroll'
| 'col-resize'
| 'row-resize'
| 'n-resize'
| 'e-resize'
| 's-resize'
| 'w-resize'
| 'ne-resize'
| 'nw-resize'
| 'sw-resize'
| 'se-resize'
| 'ew-resize'
| 'ns-resize'
| 'nesw-resize'
| 'nwse-resize'
| 'zoom-in'
| 'zoom-out';

View file

@ -0,0 +1,10 @@
import astalify from './astalify';
export default astalify;
export {
type AstalifyProps,
type BindableProps,
type ConstructProps,
childType,
} from './generics';

View file

@ -0,0 +1,49 @@
import { App, Astal, Gdk, Gtk } from 'astal/gtk4';
import { Variable } from 'astal';
import Kompass from 'gi://Kompass';
import { Box, Calendar, CenterBox, Label, MenuButton, Popover, Window } from './subclasses';
const { EXCLUSIVE } = Astal.Exclusivity;
const { TOP, LEFT, RIGHT } = Astal.WindowAnchor;
const { CENTER } = Gtk.Align;
const time = Variable(0);
setInterval(() => {
time.set(time.get() + 1);
}, 1000);
export default () => {
const styledBox = Box({
css: time().as((t) => `* { background: red; min-height: 10px; min-width: ${t}px; }`),
});
return Window({
visible: true,
cssClasses: ['Bar'],
exclusivity: EXCLUSIVE,
anchor: TOP | LEFT | RIGHT,
application: App,
child: CenterBox({
startWidget: new Kompass.Tray({
cursor: Gdk.Cursor.new_from_name('pointer', null),
}),
centerWidget: styledBox,
endWidget: MenuButton({
cursorName: 'pointer',
hexpand: true,
halign: CENTER,
children: [
Label({ label: time().as(String) }),
Popover({ child: Calendar() }),
],
}),
}),
});
};

View file

@ -0,0 +1,9 @@
window,
viewport {
all: unset;
}
.lock-clock {
font-size: 80pt;
font-family: 'Ubuntu Mono';
}

View file

@ -0,0 +1,253 @@
import { idle, timeout, Variable } from 'astal';
import { App, Astal, Gdk, Gtk } from 'astal/gtk4';
import { register } from 'astal/gobject';
import AstalAuth from 'gi://AstalAuth';
import Gtk4SessionLock from 'gi://Gtk4SessionLock';
import { Box, BoxClass, Entry, Label, Window } from '../subclasses';
import Separator from '../misc/separator';
import { get_hyprland_monitor_desc } from '../../lib';
// This file is generated by Nix
import Vars from './vars';
/* Types */
declare global {
function authFinger(): void;
}
@register()
class BlurredBox extends BoxClass {
geometry = {} as { w: number, h: number };
}
export default () => {
const windows = new Map<Gdk.Monitor, Gtk.Window>();
const blurBGs: BlurredBox[] = [];
const transition_duration = 1000;
const WINDOW_MARGINS = -2;
const ENTRY_SPACING = 20;
const CLOCK_SPACING = 60;
const bgCSS = ({ w = 1, h = 1 } = {}) => `* {
border: 2px solid rgba(189, 147, 249, 0.8);
background: rgba(0, 0, 0, 0.2);
min-height: ${h}px;
min-width: ${w}px;
transition: min-height ${transition_duration / 2}ms,
min-width ${transition_duration / 2}ms;
}`;
const lock = Gtk4SessionLock.get_singleton();
const unlock = () => {
blurBGs.forEach((b) => {
b.css = bgCSS({
w: b.geometry.w,
h: 1,
});
timeout(transition_duration / 2, () => {
b.css = bgCSS({
w: 1,
h: 1,
});
});
});
timeout(transition_duration, () => {
Gtk4SessionLock.unlock();
Gdk.Display.get_default()?.sync();
App.quit();
});
};
const Clock = () => {
const time = Variable<string>('').poll(1000, () => {
return (new Date().toLocaleString([], {
hour: 'numeric',
minute: 'numeric',
hour12: true,
}) ?? '')
.replace('a.m.', 'AM')
.replace('p.m.', 'PM');
});
return Label({
cssClasses: ['lock-clock'],
label: time(),
});
};
const PasswordPrompt = (monitor: Gdk.Monitor, visible: boolean) => {
const rev = new BlurredBox({ css: bgCSS() });
idle(() => {
rev.geometry = {
w: monitor.get_geometry().width,
h: monitor.get_geometry().height,
};
rev.css = bgCSS({
w: rev.geometry.w,
h: 1,
});
timeout(transition_duration / 2, () => {
rev.css = bgCSS({
w: rev.geometry.w,
h: rev.geometry.h,
});
});
});
blurBGs.push(rev);
Window({
name: `blur-bg-${monitor.get_model()}`,
namespace: `blur-bg-${monitor.get_model()}`,
gdkmonitor: monitor,
layer: Astal.Layer.OVERLAY,
visible: true,
anchor: Astal.WindowAnchor.TOP |
Astal.WindowAnchor.LEFT |
Astal.WindowAnchor.RIGHT |
Astal.WindowAnchor.BOTTOM,
margin: WINDOW_MARGINS,
exclusivity: Astal.Exclusivity.IGNORE,
child: Box({
halign: Gtk.Align.CENTER,
valign: Gtk.Align.CENTER,
children: [rev],
}),
});
const label = Label({ label: 'Enter password:' });
return new Gtk.Window({
child: visible ?
Box({
vertical: true,
halign: Gtk.Align.CENTER,
valign: Gtk.Align.CENTER,
spacing: 16,
children: [
Clock(),
Separator({
size: CLOCK_SPACING,
vertical: true,
}),
Box({
halign: Gtk.Align.CENTER,
cssClasses: ['avatar'],
}),
Box({
cssClasses: ['entry-box'],
vertical: true,
children: [
label,
Separator({
size: ENTRY_SPACING,
vertical: true,
}),
Entry({
halign: Gtk.Align.CENTER,
xalign: 0.5,
visibility: false,
placeholder_text: 'password',
onRealize: (self) => self.grab_focus(),
onActivate: (self) => {
self.set_sensitive(false);
AstalAuth.Pam.authenticate(self.get_text() ?? '', (_, task) => {
try {
AstalAuth.Pam.authenticate_finish(task);
unlock();
}
catch (e) {
self.set_text('');
label.set_label((e as Error).message);
self.set_sensitive(true);
}
});
},
}),
],
}),
],
}) :
Box(),
});
};
const createWindow = (monitor: Gdk.Monitor) => {
const hyprDesc = get_hyprland_monitor_desc(monitor);
const entryVisible = Vars.mainMonitor === hyprDesc || Vars.dupeLockscreen;
const win = PasswordPrompt(monitor, entryVisible);
windows.set(monitor, win);
};
const lock_screen = () => {
const display = Gdk.Display.get_default();
for (let m = 0; m < (display?.get_monitors().get_n_items() ?? 0); m++) {
const monitor = display?.get_monitors().get_item(m) as Gdk.Monitor;
if (monitor) {
createWindow(monitor);
}
}
display?.get_monitors()?.connect('items-changed', () => {
for (let m = 0; m < (display?.get_monitors().get_n_items() ?? 0); m++) {
const monitor = display?.get_monitors().get_item(m) as Gdk.Monitor;
if (monitor && !windows.has(monitor)) {
createWindow(monitor);
}
}
});
Gtk4SessionLock.lock();
windows.forEach((win, monitor) => {
Gtk4SessionLock.assign_window_to_monitor(win, monitor);
win.show();
});
};
const on_finished = () => {
Gdk.Display.get_default()?.sync();
App.quit();
};
lock.connect('finished', on_finished);
if (Vars.hasFprintd) {
globalThis.authFinger = () => AstalAuth.Pam.authenticate('', (_, task) => {
try {
AstalAuth.Pam.authenticate_finish(task);
unlock();
}
catch (e) {
console.error((e as Error).message);
}
});
globalThis.authFinger();
}
lock_screen();
};

View file

@ -0,0 +1,111 @@
import { App, Astal, Gtk } from 'astal/gtk4';
import { property, register } from 'astal/gobject';
import { Binding, idle } from 'astal';
import { WindowClass, WindowProps } from '../subclasses';
import { get_hyprland_monitor, hyprMessage } from '../../lib';
/* Types */
type CloseType = 'none' | 'stay' | 'released' | 'clicked';
type HyprTransition = 'slide' | 'slide top' | 'slide bottom' | 'slide left' |
'slide right' | 'popin' | 'fade';
type PopupCallback = (self?: WindowClass) => void;
export type PopupWindowProps = WindowProps & {
transition?: HyprTransition | Binding<HyprTransition>
close_on_unfocus?: CloseType | Binding<CloseType>
on_open?: PopupCallback
on_close?: PopupCallback
};
@register()
export class PopupWindow extends WindowClass {
@property(String)
declare transition: HyprTransition | Binding<HyprTransition>;
@property(String)
declare close_on_unfocus: CloseType | Binding<CloseType>;
on_open: PopupCallback;
on_close: PopupCallback;
constructor({
transition = 'slide top',
close_on_unfocus = 'released',
on_open = () => { /**/ },
on_close = () => { /**/ },
name,
visible = false,
layer = Astal.Layer.OVERLAY,
...rest
}: PopupWindowProps) {
super({
...rest,
name: `win-${name}`,
namespace: `win-${name}`,
visible: false,
layer,
setup: () => idle(() => {
// Add way to make window open on startup
if (visible) {
this.visible = true;
}
}),
} as WindowProps);
App.add_window(this);
const setTransition = (_: PopupWindow, t: HyprTransition | Binding<HyprTransition>) => {
hyprMessage(`keyword layerrule animation ${t}, ${this.name}`).catch(console.log);
};
this.connect('notify::transition', setTransition);
this.close_on_unfocus = close_on_unfocus;
this.transition = transition;
this.on_open = on_open;
this.on_close = on_close;
this.connect('notify::visible', () => {
// Make sure we have the right animation
setTransition(this, this.transition);
if (this.visible) {
this.on_open(this);
}
else {
this.on_close(this);
}
});
};
async set_x_pos(
alloc: Gtk.Allocation,
side = 'right' as 'left' | 'right',
) {
const monitor = this.gdkmonitor ?? this.get_current_monitor();
const transform = get_hyprland_monitor(monitor)?.get_transform();
let width: number;
if (transform && (transform === 1 || transform === 3)) {
width = monitor.get_geometry().height;
}
else {
width = monitor.get_geometry().width;
}
this.margin_right = side === 'right' ?
(width - alloc.x - alloc.width) :
this.margin_right;
this.margin_left = side === 'right' ?
this.margin_left :
(alloc.x - alloc.width);
}
}
export default PopupWindow;

View file

@ -0,0 +1,12 @@
import { Box, BoxProps } from '../subclasses';
export default ({
size,
vertical = false,
css = '',
...rest
}: { size: number } & BoxProps) => Box({
css: `* { ${vertical ? 'min-height' : 'min-width'}: ${size}px; ${css} }`,
...rest,
} as BoxProps);

View file

@ -0,0 +1,27 @@
import { register } from 'astal';
import { Astal, Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
export type BoxProps = ConstructProps<
BoxClass,
Astal.Box.ConstructorProps & AstalifyProps
>;
@register({ GTypeName: 'Box' })
export class BoxClass extends astalify(Astal.Box) {
constructor({ cssName = 'box', ...props }: BoxProps = {}) {
super({ cssName, ...props });
}
getChildren(self: BoxClass) {
return self.get_children();
}
setChildren(self: BoxClass, children: Gtk.Widget[]) {
return self.set_children(children);
}
}
export const Box = (props?: BoxProps) => new BoxClass(props);

View file

@ -0,0 +1,23 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
type ButtonSignals = Record<`on${string}`, unknown[]> & {
onClicked: []
};
export type ButtonProps = ConstructProps<
ButtonClass,
Gtk.Button.ConstructorProps & AstalifyProps,
ButtonSignals
>;
@register({ GTypeName: 'Button' })
export class ButtonClass extends astalify(Gtk.Button) {
constructor({ cssName = 'button', ...props }: ButtonProps = {}) {
super({ cssName, ...props });
}
}
export const Button = (props?: ButtonProps) => new ButtonClass(props);

View file

@ -0,0 +1,28 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
type CalendarSignals = Record<`on${string}`, unknown[]> & {
onDaySelected: []
onNextMonth: []
onNextYear: []
onPrevMonth: []
onPrevYear: []
};
export type CalendarProps = ConstructProps<
CalendarClass,
Gtk.Calendar.ConstructorProps & AstalifyProps,
CalendarSignals
>;
@register({ GTypeName: 'Calendar' })
export class CalendarClass extends astalify(Gtk.Calendar) {
constructor({ cssName = 'calendar', ...props }: CalendarProps = {}) {
super({ cssName, ...props });
}
}
export const Calendar = (props?: CalendarProps) => new CalendarClass(props);

View file

@ -0,0 +1,33 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
export type CenterBoxProps = ConstructProps<
CenterBoxClass,
Gtk.CenterBox.ConstructorProps & AstalifyProps
>;
@register({ GTypeName: 'CenterBox' })
export class CenterBoxClass extends astalify(Gtk.CenterBox) {
constructor({ cssName = 'centerbox', ...props }: CenterBoxProps = {}) {
super({ cssName, ...props });
}
getChildren(box: CenterBoxClass) {
return [box.startWidget, box.centerWidget, box.endWidget];
}
setChildren(box: CenterBoxClass, children: (Gtk.Widget | null)[]) {
if (children.length > 3) {
throw new Error('Cannot have more than 3 children in a CenterBox');
}
box.startWidget = children[0] || new Gtk.Box();
box.centerWidget = children[1] || new Gtk.Box();
box.endWidget = children[2] || new Gtk.Box();
}
}
export const CenterBox = (props?: CenterBoxProps) => new CenterBoxClass(props);

View file

@ -0,0 +1,26 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
type EntrySignals = Record<`on${string}`, unknown[]> & {
onActivate: []
onNotifyText: []
};
export type EntryProps = ConstructProps<
EntryClass,
Gtk.Entry.ConstructorProps & AstalifyProps,
EntrySignals
>;
@register({ GTypeName: 'Entry' })
export class EntryClass extends astalify(Gtk.Entry) {
constructor({ cssName = 'entry', ...props }: EntryProps = {}) {
super({ cssName, ...props });
}
getChildren() { return []; }
}
export const Entry = (props?: EntryProps) => new EntryClass(props);

View file

@ -0,0 +1,21 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
export type ImageProps = ConstructProps<
ImageClass,
Gtk.Image.ConstructorProps & AstalifyProps
>;
@register({ GTypeName: 'Image' })
export class ImageClass extends astalify(Gtk.Image) {
constructor({ cssName = 'image', ...props }: ImageProps = {}) {
super({ cssName, ...props });
}
getChildren() { return []; }
}
export const Image = (props?: ImageProps) => new ImageClass(props);

View file

@ -0,0 +1,16 @@
export * from './box';
export * from './button';
export * from './calendar';
export * from './centerbox';
export * from './entry';
export * from './image';
export * from './label';
export * from './levelbar';
export * from './menubutton';
export * from './overlay';
export * from './popover';
export * from './revealer';
export * from './slider';
export * from './stack';
export * from './switch';
export * from './window';

View file

@ -0,0 +1,21 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
export type LabelProps = ConstructProps<
LabelClass,
Gtk.Label.ConstructorProps & AstalifyProps
>;
@register({ GTypeName: 'Label' })
export class LabelClass extends astalify(Gtk.Label) {
constructor({ cssName = 'label', ...props }: LabelProps = {}) {
super({ cssName, ...props });
}
getChildren() { return []; }
}
export const Label = (props?: LabelProps) => new LabelClass(props);

View file

@ -0,0 +1,21 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
export type LevelBarProps = ConstructProps<
LevelBarClass,
Gtk.LevelBar.ConstructorProps & AstalifyProps
>;
@register({ GTypeName: 'LevelBar' })
export class LevelBarClass extends astalify(Gtk.LevelBar) {
constructor({ cssName = 'levelbar', ...props }: LevelBarProps = {}) {
super({ cssName, ...props });
}
getChildren() { return []; }
}
export const LevelBar = (props?: LevelBarProps) => new LevelBarClass(props);

View file

@ -0,0 +1,34 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
export type MenuButtonProps = ConstructProps<
MenuButtonClass,
Gtk.MenuButton.ConstructorProps & AstalifyProps
>;
@register({ GTypeName: 'MenuButton' })
export class MenuButtonClass extends astalify(Gtk.MenuButton) {
constructor({ cssName = 'menubutton', ...props }: MenuButtonProps = {}) {
super({ cssName, ...props });
}
getChildren(self: MenuButtonClass) {
return [self.popover, self.child];
}
setChildren(self: MenuButtonClass, children: Gtk.Widget[]) {
for (const child of children) {
if (child instanceof Gtk.Popover) {
self.set_popover(child);
}
else {
self.set_child(child);
}
}
}
}
export const MenuButton = (props?: MenuButtonProps) => new MenuButtonClass(props);

View file

@ -0,0 +1,49 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { childType, type AstalifyProps, type ConstructProps } from '../astalify';
export type OverlayProps = ConstructProps<
OverlayClass,
Gtk.Overlay.ConstructorProps & AstalifyProps
>;
@register({ GTypeName: 'Overlay' })
export class OverlayClass extends astalify(Gtk.Overlay) {
constructor({ cssName = 'overlay', ...props }: OverlayProps = {}) {
super({ cssName, ...props });
}
getChildren(self: OverlayClass) {
const children: Gtk.Widget[] = [];
let ch = self.get_first_child();
while (ch !== null) {
children.push(ch);
ch = ch.get_next_sibling();
}
return children.filter((child) => child !== self.child);
}
setChildren(self: OverlayClass, children: Gtk.Widget[]) {
for (const child of children) {
const types = childType in child ?
(child[childType] as string).split(/\s+/) :
[];
if (types.includes('overlay')) {
self.add_overlay(child);
}
else {
self.set_child(child);
}
self.set_measure_overlay(child, types.includes('measure'));
self.set_clip_overlay(child, types.includes('clip'));
}
}
}
export const Overlay = (props?: OverlayProps) => new OverlayClass(props);

View file

@ -0,0 +1,19 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
export type PopoverProps = ConstructProps<
PopoverClass,
Gtk.Popover.ConstructorProps & AstalifyProps
>;
@register({ GTypeName: 'Popover' })
export class PopoverClass extends astalify(Gtk.Popover) {
constructor({ cssName = 'popover', ...props }: PopoverProps = {}) {
super({ cssName, ...props });
}
}
export const Popover = (props?: PopoverProps) => new PopoverClass(props);

View file

@ -0,0 +1,19 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
export type RevealerProps = ConstructProps<
RevealerClass,
Gtk.Revealer.ConstructorProps & AstalifyProps
>;
@register({ GTypeName: 'Revealer' })
export class RevealerClass extends astalify(Gtk.Revealer) {
constructor({ cssName = 'revealer', ...props }: RevealerProps = {}) {
super({ cssName, ...props });
}
}
export const Revealer = (props?: RevealerProps) => new RevealerClass(props);

View file

@ -0,0 +1,25 @@
import { register } from 'astal';
import { Astal } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
type SliderSignals = Record<`on${string}`, unknown[]> & {
onClicked: []
};
export type SliderProps = ConstructProps<
SliderClass,
Astal.Slider.ConstructorProps & AstalifyProps,
SliderSignals
>;
@register({ GTypeName: 'Slider' })
export class SliderClass extends astalify(Astal.Slider) {
constructor({ cssName = 'slider', ...props }: SliderProps = {}) {
super({ cssName, ...props });
}
getChildren() { return []; }
}
export const Slider = (props?: SliderProps) => new SliderClass(props);

View file

@ -0,0 +1,30 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
export type StackProps = ConstructProps<
StackClass,
Gtk.Stack.ConstructorProps & AstalifyProps
>;
@register({ GTypeName: 'Stack' })
export class StackClass extends astalify(Gtk.Stack) {
constructor({ cssName = 'stack', ...props }: StackProps = {}) {
super({ cssName, ...props });
}
setChildren(self: StackClass, children: Gtk.Widget[]) {
for (const child of children) {
if (child.name !== '' && child.name !== null) {
self.add_named(child, child.name);
}
else {
self.add_child(child);
}
}
}
}
export const Stack = (props?: StackProps) => new StackClass(props);

View file

@ -0,0 +1,21 @@
import { register } from 'astal';
import { Gtk } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
export type SwitchProps = ConstructProps<
SwitchClass,
Gtk.Switch.ConstructorProps & AstalifyProps
>;
@register({ GTypeName: 'Switch' })
export class SwitchClass extends astalify(Gtk.Switch) {
constructor({ cssName = 'switch', ...props }: SwitchProps = {}) {
super({ cssName, ...props });
}
getChildren() { return []; }
}
export const Switch = (props?: SwitchProps) => new SwitchClass(props);

View file

@ -0,0 +1,19 @@
import { register } from 'astal';
import { Astal } from 'astal/gtk4';
import astalify, { type AstalifyProps, type ConstructProps } from '../astalify';
export type WindowProps = ConstructProps<
WindowClass,
Astal.Window.ConstructorProps & AstalifyProps
>;
@register({ GTypeName: 'Window' })
export class WindowClass extends astalify(Astal.Window) {
constructor({ cssName = 'window', ...props }: WindowProps = {}) {
super({ cssName, ...props });
}
}
export const Window = (props?: WindowProps) => new WindowClass(props);