Skip to content

Commit

Permalink
Merge branch 'main' into feat/child-second-parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
Aylur authored Jan 31, 2024
2 parents dea491f + db37e5f commit 0d35fba
Show file tree
Hide file tree
Showing 18 changed files with 426 additions and 11 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
- add: Window.gdkmonitor
- export modules globally
- make Audio.microphone and Audio.speaker always
- feat: greetd service (#282)
- feat(pam): Utils.authenticate (#273)
- feat: child property as second parameter

## Breaking change
Expand Down
11 changes: 7 additions & 4 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

- services
- [x] power profiles [#218](https://github.com/Aylur/ags/pull/218)
- [ ] greetd - this would allow to create login managers
- [x] greetd [#282](https://github.com/Aylur/ags/pull/282)
- [ ] evolution data server - allows for to sync with calendars, todos and contact lists
- [ ] improve Network service - its currently very barebones, and state changes are not properly signaled

- [ ] utility gobject based library in c
- to be able to use wayland protocols especially for lockscreens in the form of a PAM util function for authentication and input inhibitor
- utility gobject based library in c
- [x] pam module [#273](https://github.com/Aylur/ags/pull/273)
- [ ] ext-session-lock

- [x] fetch util function [#187](https://github.com/Aylur/ags/pull/187)
- [x] toJSON overridies [#203](https://github.com/Aylur/ags/pull/203)
Expand All @@ -22,8 +23,10 @@
- [ ] NixOS module
- [x] binary cache [#212](https://github.com/Aylur/ags/pull/212)

- [ ] ~~github action to package types~~
- package generated types and @gir types
- [ ] ~~github action to package types~~
- [x] install them at ~~/etc/ags~~ pkgdatadir/share with meson
- [x] `--init` cli flag

- Wiki
- [x] update to use `bind` `hook` `on` `poll`
Expand Down
7 changes: 7 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ subproject('gvc',
]
)

subproject('gutils',
default_options: [
'pkgdatadir=' + pkgdatadir,
'pkglibdir=' + libdir,
]
)

subdir('src')

meson.add_install_script(
Expand Down
2 changes: 2 additions & 0 deletions nix/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
, gvfs
, libsoup_3
, libnotify
, pam
, extraPackages ? [ ]
, version ? "git"
, buildTypes ? false
Expand Down Expand Up @@ -88,6 +89,7 @@ stdenv.mkDerivation rec {
gvfs
libsoup_3
libnotify
pam
] ++ extraPackages;

meta = with lib; {
Expand Down
2 changes: 2 additions & 0 deletions src/com.github.Aylur.ags.src.gresource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<file>service/audio.js</file>
<file>service/battery.js</file>
<file>service/bluetooth.js</file>
<file>service/greetd.js</file>
<file>service/hyprland.js</file>
<file>service/mpris.js</file>
<file>service/network.js</file>
Expand All @@ -59,6 +60,7 @@
<file>utils/gobject.js</file>
<file>utils/init.js</file>
<file>utils/notify.js</file>
<file>utils/pam.js</file>
<file>utils/timeout.js</file>
</gresource>
</gresources>
1 change: 1 addition & 0 deletions src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ interface Services {
notifications: typeof import('./service/notifications.js').default
powerprofiles: typeof import('./service/powerprofiles.js').default
systemtray: typeof import('./service/systemtray.js').default
greetd: typeof import('./service/greetd.js').default
}

export default class Service extends GObject.Object {
Expand Down
115 changes: 115 additions & 0 deletions src/service/greetd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import App from '../app.js';
import Service from '../service.js';
import GLib from 'gi://GLib';
import Gio from 'gi://Gio';

Gio._promisify(Gio.InputStream.prototype, 'read_bytes_async');
const SOCK = GLib.getenv('GREETD_SOCK');

type Request = {
create_session: {
username: string
}
post_auth_message_response: {
response?: string
}
start_session: {
cmd: string[]
env: string[]
}
cancel_session: Record<never, never>
}

type Response = {
type: 'success'
} | {
type: 'error'
error_type: 'auth_error' | 'error'
description: string
} | {
type: 'auth_message'
auth_message_type: 'visible' | 'secret' | 'info' | 'error'
auth_message: string
}

export class Greetd extends Service {
static { Service.register(this); }

private _decoder = new TextDecoder;

async login(
username: string,
password: string,
cmd: string[] | string,
env: string[] = [],
) {
const session = await this.createSession(username);
if (session.type !== 'auth_message') {
this.cancelSession();
throw session;
}

const auth = await this.postAuth(password);
if (auth.type !== 'success') {
this.cancelSession();
throw auth;
}

const start = await this.startSession(cmd, env);
if (start.type !== 'success') {
this.cancelSession();
throw start;
}

App.quit();
}

createSession(username: string) {
return this._send('create_session', { username });
}

postAuth(response?: string) {
return this._send('post_auth_message_response', { response });
}

startSession(cmd: string[] | string, env: string[] = []) {
const cmdv = Array.isArray(cmd)
? cmd
: GLib.shell_parse_argv(cmd)[1];

return this._send('start_session', { cmd: cmdv, env });
}

cancelSession() {
return this._send('cancel_session', {});
}

private async _send<R extends keyof Request>(req: R, payload: Request[R]): Promise<Response> {
const connection = new Gio.SocketClient()
.connect(new Gio.UnixSocketAddress({ path: SOCK }), null);

try {
const json = JSON.stringify({ type: req, ...payload });
const ostream = new Gio.DataOutputStream({
close_base_stream: true,
base_stream: connection.get_output_stream(),
byte_order: Gio.DataStreamByteOrder.HOST_ENDIAN,
});

const istream = connection.get_input_stream();

ostream.put_int32(json.length, null);
ostream.put_string(json, null);

const data = await istream.read_bytes_async(4, GLib.PRIORITY_DEFAULT, null);
const length = new Uint32Array(data.get_data()?.buffer || [0])[0];
const res = await istream.read_bytes_async(length, GLib.PRIORITY_DEFAULT, null);
return JSON.parse(this._decoder.decode(res.get_data()!)) as Response;
} finally {
connection.close(null);
}
}
}

export const greetd = new Greetd;
export default greetd;
9 changes: 9 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as Etc from './utils/etc.js';
import * as Timeout from './utils/timeout.js';
import * as Fetch from './utils/fetch.js';
import * as Notify from './utils/notify.js';
import * as Pam from './utils/pam.js';

export const USER = GLib.get_user_name();
export const HOME = GLib.get_home_dir();
Expand Down Expand Up @@ -37,6 +38,11 @@ export const {
lookUpIcon,
} = Etc;

export const {
authenticate,
authenticateUser,
} = Pam;

export const { fetch } = Fetch;
export const { notify } = Notify;

Expand Down Expand Up @@ -66,4 +72,7 @@ export default {

fetch,
notify,

authenticate,
authenticateUser,
};
15 changes: 15 additions & 0 deletions src/utils/gobject.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import Gtk from 'gi://Gtk?version=3.0';
import GObject from 'gi://GObject';

type Camel<S extends string> = S extends `${infer P1}_${infer P2}${infer P3}`
? `${Lowercase<P1>}${Uppercase<P2>}${Camel<P3>}` : S

type Kebab<S extends string> = S extends `${infer Head}_${infer Tail}`
? `${Head}-${Kebab<Tail>}` : S

/**
* turns a snake_cased Record into
* snake & camel & kebab keyed Record
*/
export type CtorProps<T> =
T &
{ [K in keyof T as Camel<string & K>]: T[K] } &
{ [K in keyof T as Kebab<string & K>]: T[K] }

export const kebabify = (str: string) => str
.replace(/([a-z])([A-Z])/g, '$1-$2')
.replaceAll('_', '-')
Expand Down
30 changes: 30 additions & 0 deletions src/utils/pam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//@ts-expect-error missing types
import GUtils from 'gi://GUtils';
import Gio from 'gi://Gio';

export function authenticate(password: string) {
return new Promise((resolve, reject) => {
GUtils.authenticate(password, 0, null, (_: unknown, res: Gio.AsyncResult) => {
try {
resolve(GUtils.authenticate_finish(res));
}
catch (e) {
reject(e);
}
});
});
}

export function authenticateUser(username: string, password: string) {
return new Promise((resolve, reject) => {
GUtils.authenticate_user(
username, password, 0, null, (_: unknown, res: Gio.AsyncResult) => {
try {
resolve(GUtils.authenticate_finish(res));
}
catch (e) {
reject(e);
}
});
});
}
7 changes: 5 additions & 2 deletions src/widgets/centerbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type CenterBoxProps<
Self = CenterBox<StartWidget, CenterWidget, EndWidget, Attr>,
> = BaseProps<Self, Gtk.Box.ConstructorProperties & {
vertical?: boolean
children?: [StartWidget?, CenterWidget?, EndWidget?],
start_widget?: StartWidget
center_widget?: CenterWidget
end_widget?: EndWidget
Expand All @@ -26,6 +27,7 @@ export class CenterBox<
register(this, {
properties: {
'vertical': ['boolean', 'rw'],
'children': ['boolean', 'rw'],
'start-widget': ['widget', 'rw'],
'center-widget': ['widget', 'rw'],
'end-widget': ['widget', 'rw'],
Expand All @@ -51,11 +53,12 @@ export class CenterBox<
super(props as Gtk.Widget.ConstructorProperties);
}

set children(children: [StartWidget, CenterWidget, EndWidget]) {
get children() { return [this.start_widget, this.center_widget, this.end_widget]; }
set children(children: [StartWidget | null, CenterWidget | null, EndWidget | null]) {
const newChildren = children || [];

newChildren.filter(ch => !newChildren?.includes(ch))
.forEach(ch => ch.destroy());
.forEach(ch => ch && ch.destroy());

if (children[0])
this.start_widget = children[0];
Expand Down
9 changes: 5 additions & 4 deletions src/widgets/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import GLib from 'gi://GLib?version=2.0';
import Gdk from 'gi://Gdk?version=3.0';
import Cairo from 'gi://cairo?version=1.0';
import Service, { Props, BindableProps, Binding } from '../service.js';
import { registerGObject, kebabify } from '../utils/gobject.js';
import { registerGObject, kebabify, type CtorProps } from '../utils/gobject.js';
import { interval, idle } from '../utils.js';
import { Variable } from '../variable.js';
import { App } from '../app.js';
Expand Down Expand Up @@ -79,7 +79,7 @@ type Bind = [
transform?: (value: any) => any,
];

export interface CommonProps<Attr> {
interface CommonProps<Attr> {
class_name?: string
class_names?: Array<string>
click_through?: boolean
Expand All @@ -92,9 +92,10 @@ export interface CommonProps<Attr> {

export type BaseProps<Self, Props, Attr = unknown> = {
setup?: (self: Self) => void
} & BindableProps<Props & CommonProps<Attr>>
} & BindableProps<CtorProps<Props & CommonProps<Attr>>>

export interface Widget<Attr> extends CommonProps<Attr> {
type Requierd<T> = { [K in keyof T]-?: T[K] };
export interface Widget<Attr> extends Requierd<CommonProps<Attr>> {
hook<
Gobject extends GObject.Object,
>(
Expand Down
4 changes: 4 additions & 0 deletions subprojects/gutils/ags
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# PAM configuration file for ags. By default, it includes
# the 'login' configuration file (see /etc/pam.d/login)

auth include login
40 changes: 40 additions & 0 deletions subprojects/gutils/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
project('pam', 'c')

pkglibdir = get_option('pkglibdir')
pkgdatadir = get_option('pkgdatadir')

cc = meson.get_compiler('c')

libsources = ['pam.c', 'pam.h']

deps = [
dependency('gobject-2.0'),
dependency('gio-2.0'),
dependency('pam')
]

girlib = shared_library(
'gutils',
sources: libsources,
dependencies: deps,
install: true
)

gnome = import('gnome')
gnome.generate_gir(
girlib,
sources: libsources,
nsversion: '1.0',
namespace: 'GUtils',
symbol_prefix: 'gutils',
identifier_prefix: 'GUtils',
includes: ['GObject-2.0', 'Gio-2.0'],
install_dir_gir: pkgdatadir,
install_dir_typelib: pkglibdir,
install: true
)

install_data(
'ags',
install_dir: get_option('sysconfdir') / 'pam.d'
)
Loading

0 comments on commit 0d35fba

Please sign in to comment.