├── lib ├── apps │ ├── gir.py │ ├── version │ ├── meson_options.txt │ ├── default.nix │ └── src │ │ ├── config.vala.in │ │ ├── cli.vala │ │ └── fuzzy.vala ├── auth │ ├── version │ ├── pam │ │ └── astal-auth │ ├── default.nix │ ├── meson_options.txt │ ├── include │ │ ├── meson.build │ │ └── astal-auth.h.in │ ├── meson.build │ └── src │ │ └── meson.build ├── battery │ ├── gir.py │ ├── version │ ├── meson_options.txt │ ├── default.nix │ └── src │ │ ├── config.vala.in │ │ ├── cli.vala │ │ └── ifaces.vala ├── cava │ ├── version │ ├── .gitignore │ ├── subprojects │ │ └── cava.wrap │ ├── meson_options.txt │ ├── default.nix │ └── meson.build ├── greet │ ├── gir.py │ ├── version │ ├── meson_options.txt │ ├── default.nix │ └── src │ │ ├── config.vala.in │ │ └── cli.vala ├── hyprland │ ├── gir.py │ ├── version │ ├── meson_options.txt │ ├── default.nix │ └── src │ │ ├── config.vala.in │ │ ├── cli.vala │ │ ├── workspace.vala │ │ └── structs.vala ├── mpris │ ├── gir.py │ ├── version │ ├── meson.options │ ├── default.nix │ └── src │ │ └── config.vala.in ├── network │ ├── gir.py │ ├── version │ ├── src │ │ ├── vpn.vala │ │ └── config.vala.in │ └── default.nix ├── notifd │ ├── gir.py │ ├── version │ ├── meson_options.txt │ ├── default.nix │ └── src │ │ ├── config.vala.in │ │ ├── enums.vala │ │ ├── gschema.xml │ │ └── action.vala ├── river │ ├── version │ ├── subprojects │ │ └── wayland-glib │ ├── meson_options.txt │ ├── include │ │ ├── meson.build │ │ └── river-private.h │ ├── default.nix │ ├── meson.build │ ├── protocols │ │ └── meson.build │ └── src │ │ ├── astal-river.c │ │ └── meson.build ├── tray │ ├── gir.py │ ├── version │ ├── meson_options.txt │ ├── src │ │ ├── config.vala.in │ │ └── cli.vala │ └── default.nix ├── astal │ ├── gtk3 │ │ ├── gir.py │ │ ├── version │ │ ├── src │ │ │ ├── config.vala.in │ │ │ ├── widget │ │ │ │ ├── levelbar.vala │ │ │ │ ├── label.vala │ │ │ │ ├── stack.vala │ │ │ │ ├── scrollable.vala │ │ │ │ ├── box.vala │ │ │ │ ├── overlay.vala │ │ │ │ └── centerbox.vala │ │ │ ├── vapi │ │ │ │ └── AstalInhibitManager.vapi │ │ │ └── idle-inhibit.h │ │ ├── default.nix │ │ └── meson.build │ ├── gtk4 │ │ ├── gir.py │ │ ├── version │ │ ├── src │ │ │ ├── config.vala.in │ │ │ └── widget │ │ │ │ ├── bin.vala │ │ │ │ ├── box.vala │ │ │ │ └── slider.vala │ │ ├── default.nix │ │ └── meson.build │ └── io │ │ ├── gir.py │ │ ├── version │ │ ├── default.nix │ │ └── config.vala.in ├── bluetooth │ ├── gir.py │ ├── version │ ├── default.nix │ └── src │ │ ├── config.vala.in │ │ ├── utils.vala │ │ ├── battery.vala │ │ └── interfaces.vala ├── powerprofiles │ ├── gir.py │ ├── version │ ├── meson_options.txt │ ├── default.nix │ └── src │ │ └── config.vala.in ├── wireplumber │ ├── version │ ├── meson_options.txt │ ├── include │ │ ├── astal │ │ │ └── wireplumber │ │ │ │ ├── meson.build │ │ │ │ ├── channel.h │ │ │ │ ├── profile.h │ │ │ │ ├── route.h │ │ │ │ ├── stream.h │ │ │ │ ├── endpoint.h │ │ │ │ ├── video.h │ │ │ │ ├── audio.h │ │ │ │ ├── wp.h │ │ │ │ ├── device.h │ │ │ │ └── node.h │ │ ├── private │ │ │ ├── channel-private.h │ │ │ ├── wp-private.h │ │ │ └── node-private.h │ │ ├── meson.build │ │ └── astal-wp.h.in │ ├── default.nix │ ├── meson.build │ └── src │ │ └── meson.build └── wayland-glib │ ├── version │ ├── meson.build │ └── wl-source.vala ├── examples ├── gtk3 │ └── simple-bar │ │ ├── py │ │ ├── __init__.py │ │ ├── widget │ │ │ └── __init__.py │ │ ├── README.md │ │ └── app.py │ │ └── vala │ │ ├── README.md │ │ ├── flake.nix │ │ ├── meson.build │ │ └── app.in.vala ├── gtk4 │ └── simple-bar │ │ ├── py │ │ ├── src │ │ │ ├── app │ │ │ │ ├── __init__.py │ │ │ │ └── App.py │ │ │ ├── bar │ │ │ │ ├── __init__.py │ │ │ │ ├── Bar.scss │ │ │ │ └── Bar.blp │ │ │ ├── style.scss │ │ │ ├── gresource.xml │ │ │ ├── main.in.py │ │ │ └── meson.build │ │ ├── meson.build │ │ ├── README.md │ │ └── flake.nix │ │ ├── js │ │ ├── src │ │ │ ├── style.scss │ │ │ ├── main.in.sh │ │ │ ├── gresource.xml │ │ │ ├── main.in.js │ │ │ ├── bar │ │ │ │ ├── Bar.scss │ │ │ │ └── Bar.blp │ │ │ ├── App.ts │ │ │ └── meson.build │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── meson.build │ │ ├── flake.nix │ │ └── README.md │ │ └── vala │ │ ├── src │ │ ├── style.scss │ │ ├── gresource.xml │ │ ├── bar │ │ │ ├── Bar.scss │ │ │ └── Bar.blp │ │ ├── meson.build │ │ └── App.vala │ │ ├── meson.build │ │ ├── README.md │ │ ├── flake.nix │ │ └── flake.lock ├── .gitignore └── README.md ├── lang ├── gjs │ ├── index.ts │ ├── .gitignore │ ├── src │ │ ├── gtk3 │ │ │ ├── app.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── gtk4 │ │ │ ├── index.ts │ │ │ ├── app.ts │ │ │ └── jsx-runtime.ts │ │ ├── time.ts │ │ ├── package.json │ │ ├── file.ts │ │ └── process.ts │ ├── default.nix │ ├── tsconfig.json │ ├── eslint.config.mjs │ ├── meson.build │ └── package.json ├── lua │ ├── stylua.toml │ ├── astal │ │ ├── gtk3 │ │ │ └── init.lua │ │ ├── time.lua │ │ ├── init.lua │ │ ├── file.lua │ │ ├── process.lua │ │ └── binding.lua │ └── astal-dev-1.rockspec └── README.md ├── CHANGELOG.md ├── docs ├── .vitepress │ ├── config.ts │ └── theme │ │ └── index.ts ├── public │ └── showcase │ │ ├── aylur.webp │ │ ├── ezerinz.webp │ │ ├── tokyob0t.webp │ │ ├── delta-shell.webp │ │ ├── hyprpanel_showcase.webp │ │ ├── kotontrion-kompass.webp │ │ └── retrozinndev-colorshell.webp ├── showcases │ ├── index.md │ ├── Showcases.vue │ └── showcases.ts ├── .gitignore ├── default.nix ├── package.json ├── README.md └── guide │ ├── installation.md │ └── libraries │ ├── network.md │ ├── river.md │ ├── battery.md │ ├── hyprland.md │ ├── powerprofiles.md │ ├── cava.md │ ├── tray.md │ └── bluetooth.md ├── .gitignore ├── .gitattributes ├── README.md ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── question.md │ └── feature_request.md ├── FUNDING.yml └── workflows │ ├── vitepress.yml │ └── gi-docs.yml ├── flake.lock ├── nix ├── gi-docgen.patch └── devshell.nix ├── CONTRIBUTING.md └── flake.nix /lib/apps/gir.py: -------------------------------------------------------------------------------- 1 | ../gir.py -------------------------------------------------------------------------------- /lib/apps/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/auth/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/battery/gir.py: -------------------------------------------------------------------------------- 1 | ../gir.py -------------------------------------------------------------------------------- /lib/cava/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/greet/gir.py: -------------------------------------------------------------------------------- 1 | ../gir.py -------------------------------------------------------------------------------- /lib/greet/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/hyprland/gir.py: -------------------------------------------------------------------------------- 1 | ../gir.py -------------------------------------------------------------------------------- /lib/mpris/gir.py: -------------------------------------------------------------------------------- 1 | ../gir.py -------------------------------------------------------------------------------- /lib/mpris/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/network/gir.py: -------------------------------------------------------------------------------- 1 | ../gir.py -------------------------------------------------------------------------------- /lib/notifd/gir.py: -------------------------------------------------------------------------------- 1 | ../gir.py -------------------------------------------------------------------------------- /lib/notifd/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/river/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/tray/gir.py: -------------------------------------------------------------------------------- 1 | ../gir.py -------------------------------------------------------------------------------- /lib/tray/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/astal/gtk3/gir.py: -------------------------------------------------------------------------------- 1 | ../../gir.py -------------------------------------------------------------------------------- /lib/astal/gtk3/version: -------------------------------------------------------------------------------- 1 | 3.0.0 2 | -------------------------------------------------------------------------------- /lib/astal/gtk4/gir.py: -------------------------------------------------------------------------------- 1 | ../../gir.py -------------------------------------------------------------------------------- /lib/astal/gtk4/version: -------------------------------------------------------------------------------- 1 | 4.0.0 2 | -------------------------------------------------------------------------------- /lib/astal/io/gir.py: -------------------------------------------------------------------------------- 1 | ../../gir.py -------------------------------------------------------------------------------- /lib/astal/io/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/battery/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/bluetooth/gir.py: -------------------------------------------------------------------------------- 1 | ../gir.py -------------------------------------------------------------------------------- /lib/bluetooth/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/hyprland/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/network/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/powerprofiles/gir.py: -------------------------------------------------------------------------------- 1 | ../gir.py -------------------------------------------------------------------------------- /lib/wireplumber/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/network/src/vpn.vala: -------------------------------------------------------------------------------- 1 | // TODO: 2 | -------------------------------------------------------------------------------- /lib/powerprofiles/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /lib/wayland-glib/version: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /examples/gtk3/simple-bar/py/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lang/gjs/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./src" 2 | -------------------------------------------------------------------------------- /lib/cava/.gitignore: -------------------------------------------------------------------------------- 1 | /subprojects/**/ 2 | -------------------------------------------------------------------------------- /examples/gtk3/simple-bar/py/widget/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/py/src/app/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/py/src/bar/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/river/subprojects/wayland-glib: -------------------------------------------------------------------------------- 1 | ../../wayland-glib -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.1.0 4 | 5 | WIP 6 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/src/style.scss: -------------------------------------------------------------------------------- 1 | @use "./bar/Bar.scss"; 2 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/py/src/style.scss: -------------------------------------------------------------------------------- 1 | @use "./bar/Bar.scss"; 2 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/vala/src/style.scss: -------------------------------------------------------------------------------- 1 | @use "./bar/Bar.scss"; 2 | -------------------------------------------------------------------------------- /lang/gjs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | result/ 3 | dist/ 4 | @girs/ 5 | -------------------------------------------------------------------------------- /docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | export { default as default } from "../vitepress.config" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | build/ 3 | result 4 | result-dev 5 | .cache/ 6 | test.sh 7 | tmp/ 8 | -------------------------------------------------------------------------------- /lang/lua/stylua.toml: -------------------------------------------------------------------------------- 1 | indent_type = "Spaces" 2 | indent_width = 4 3 | column_width = 100 4 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | __pycache__/ 3 | @types/ 4 | build/ 5 | dist/ 6 | result 7 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/vala/meson.build: -------------------------------------------------------------------------------- 1 | project('simple-bar', 'vala') 2 | 3 | subdir('src') 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | examples/** linguist-vendored 2 | lang/** linguist-vendored 3 | docs/** linguist-vendored 4 | -------------------------------------------------------------------------------- /docs/public/showcase/aylur.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aylur/astal/HEAD/docs/public/showcase/aylur.webp -------------------------------------------------------------------------------- /docs/public/showcase/ezerinz.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aylur/astal/HEAD/docs/public/showcase/ezerinz.webp -------------------------------------------------------------------------------- /docs/public/showcase/tokyob0t.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aylur/astal/HEAD/docs/public/showcase/tokyob0t.webp -------------------------------------------------------------------------------- /docs/public/showcase/delta-shell.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aylur/astal/HEAD/docs/public/showcase/delta-shell.webp -------------------------------------------------------------------------------- /lib/mpris/meson.options: -------------------------------------------------------------------------------- 1 | option('lib', type: 'boolean', value: true) 2 | option('cli', type: 'boolean', value: true) 3 | -------------------------------------------------------------------------------- /docs/public/showcase/hyprpanel_showcase.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aylur/astal/HEAD/docs/public/showcase/hyprpanel_showcase.webp -------------------------------------------------------------------------------- /docs/public/showcase/kotontrion-kompass.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aylur/astal/HEAD/docs/public/showcase/kotontrion-kompass.webp -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier": { 3 | "semi": false, 4 | "tabWidth": 4 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/src/main.in.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LD_PRELOAD="@LAYER_SHELL_PREFIX@/lib/libgtk4-layer-shell.so" "@INDEX@" $@ 4 | -------------------------------------------------------------------------------- /docs/public/showcase/retrozinndev-colorshell.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aylur/astal/HEAD/docs/public/showcase/retrozinndev-colorshell.webp -------------------------------------------------------------------------------- /docs/showcases/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | --- 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | result/ 3 | .vitepress/cache/ 4 | 5 | node_modules/ 6 | 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | pnpm-debug.log* 11 | -------------------------------------------------------------------------------- /lib/apps/meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'lib', 3 | type: 'boolean', 4 | value: true, 5 | ) 6 | 7 | option( 8 | 'cli', 9 | type: 'boolean', 10 | value: true, 11 | ) 12 | -------------------------------------------------------------------------------- /lib/battery/meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'lib', 3 | type: 'boolean', 4 | value: true, 5 | ) 6 | 7 | option( 8 | 'cli', 9 | type: 'boolean', 10 | value: true, 11 | ) 12 | -------------------------------------------------------------------------------- /lib/greet/meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'lib', 3 | type: 'boolean', 4 | value: true, 5 | ) 6 | 7 | option( 8 | 'cli', 9 | type: 'boolean', 10 | value: true, 11 | ) 12 | -------------------------------------------------------------------------------- /lib/hyprland/meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'lib', 3 | type: 'boolean', 4 | value: true, 5 | ) 6 | 7 | option( 8 | 'cli', 9 | type: 'boolean', 10 | value: true, 11 | ) 12 | -------------------------------------------------------------------------------- /lib/notifd/meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'lib', 3 | type: 'boolean', 4 | value: true, 5 | ) 6 | 7 | option( 8 | 'cli', 9 | type: 'boolean', 10 | value: true, 11 | ) 12 | -------------------------------------------------------------------------------- /lib/tray/meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'lib', 3 | type: 'boolean', 4 | value: true, 5 | ) 6 | 7 | option( 8 | 'cli', 9 | type: 'boolean', 10 | value: true, 11 | ) 12 | -------------------------------------------------------------------------------- /lib/powerprofiles/meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'lib', 3 | type: 'boolean', 4 | value: true, 5 | ) 6 | 7 | option( 8 | 'cli', 9 | type: 'boolean', 10 | value: true, 11 | ) 12 | -------------------------------------------------------------------------------- /lib/auth/pam/astal-auth: -------------------------------------------------------------------------------- 1 | # PAM configuration file for the astal-auth library. 2 | # By default, it only includes the 'login' 3 | # configuration file (see /etc/pam.d/login) 4 | 5 | auth include login 6 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "ES2020", 5 | "module": "ES2022", 6 | "moduleResolution": "Bundler" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lang/gjs/src/gtk3/app.ts: -------------------------------------------------------------------------------- 1 | import Gtk from "gi://Gtk?version=3.0" 2 | import Astal from "gi://Astal?version=3.0" 3 | import { mkApp } from "../_app" 4 | 5 | Gtk.init(null) 6 | 7 | export default mkApp(Astal.Application) 8 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from "vitepress/theme" 2 | import "../../vitepress.theme.css" 3 | import "devicon/devicon.min.css" 4 | import "font-logos/assets/font-logos.css" 5 | 6 | export default DefaultTheme 7 | -------------------------------------------------------------------------------- /lang/README.md: -------------------------------------------------------------------------------- 1 | # Higher Level Libraries 2 | 3 | Do not use these, they will be removed before the first release. 4 | 5 | GJS code moved to 6 | Lua moved to 7 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/py/src/gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bar/Bar.ui 5 | style.css 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/vala/src/gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | style.css 5 | bar/Bar.ui 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/bluetooth/default.nix: -------------------------------------------------------------------------------- 1 | {mkAstalPkg, ...}: 2 | mkAstalPkg { 3 | pname = "astal-bluetooth"; 4 | src = ./.; 5 | 6 | libname = "bluetooth"; 7 | authors = "Aylur"; 8 | gir-suffix = "Bluetooth"; 9 | description = "DBus proxy for bluez"; 10 | } 11 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/src/gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | index.js 5 | style.css 6 | bar/Bar.ui 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/astal/io/default.nix: -------------------------------------------------------------------------------- 1 | {mkAstalPkg, ...}: 2 | mkAstalPkg { 3 | pname = "astal"; 4 | src = ./.; 5 | 6 | libname = "io"; 7 | gir-suffix = "IO"; 8 | authors = "Aylur"; 9 | description = "Astal Core library"; 10 | repo-path = "astal/io"; 11 | website-path = "io"; 12 | } 13 | -------------------------------------------------------------------------------- /lib/cava/subprojects/cava.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = cava-0.10.6 3 | source_url = https://github.com/LukashonakV/cava/archive/0.10.6.tar.gz 4 | source_filename = cava-0.10.6.tar.gz 5 | source_hash = e715c4c6a625b8dc063e57e8e81c80e4d1015ec1b98db69a283b2c6770f839f4 6 | [provide] 7 | cava = cava_dep 8 | -------------------------------------------------------------------------------- /lib/apps/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: 6 | mkAstalPkg { 7 | pname = "astal-apps"; 8 | src = ./.; 9 | packages = [pkgs.json-glib]; 10 | 11 | libname = "apps"; 12 | gir-suffix = "Apps"; 13 | authors = "Aylur"; 14 | description = "Application query library"; 15 | } 16 | -------------------------------------------------------------------------------- /lib/auth/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: 6 | mkAstalPkg { 7 | pname = "astal-auth"; 8 | src = ./.; 9 | packages = [pkgs.pam]; 10 | 11 | libname = "auth"; 12 | gir-suffix = "Auth"; 13 | authors = "kotontrion"; 14 | description = "Authentication using pam"; 15 | } 16 | -------------------------------------------------------------------------------- /lib/greet/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: 6 | mkAstalPkg { 7 | pname = "astal-greet"; 8 | src = ./.; 9 | packages = [pkgs.json-glib]; 10 | 11 | libname = "greet"; 12 | authors = "Aylur"; 13 | gir-suffix = "Greet"; 14 | description = "IPC client for greetd"; 15 | } 16 | -------------------------------------------------------------------------------- /lib/auth/meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'introspection', 3 | type: 'boolean', 4 | value: true, 5 | description: 'Build gobject-introspection data', 6 | ) 7 | option( 8 | 'vapi', 9 | type: 'boolean', 10 | value: true, 11 | description: 'Generate vapi data (needs vapigen & introspection option)', 12 | ) 13 | -------------------------------------------------------------------------------- /lib/mpris/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: 6 | mkAstalPkg { 7 | pname = "astal-mpris"; 8 | src = ./.; 9 | packages = [pkgs.gvfs pkgs.json-glib]; 10 | 11 | libname = "mpris"; 12 | authors = "Aylur"; 13 | gir-suffix = "Mpris"; 14 | description = "Control mpris players"; 15 | } 16 | -------------------------------------------------------------------------------- /lib/cava/meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'introspection', 3 | type: 'boolean', 4 | value: true, 5 | description: 'Build gobject-introspection data', 6 | ) 7 | 8 | option( 9 | 'vapi', 10 | type: 'boolean', 11 | value: true, 12 | description: 'Generate vapi data (needs vapigen & introspection option)', 13 | ) 14 | -------------------------------------------------------------------------------- /lib/hyprland/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: 6 | mkAstalPkg { 7 | pname = "astal-hyprland"; 8 | src = ./.; 9 | packages = [pkgs.json-glib]; 10 | 11 | libname = "hyprland"; 12 | authors = "Aylur"; 13 | gir-suffix = "Hyprland"; 14 | description = "IPC client for Hyprland"; 15 | } 16 | -------------------------------------------------------------------------------- /lib/river/meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'introspection', 3 | type: 'boolean', 4 | value: true, 5 | description: 'Build gobject-introspection data', 6 | ) 7 | 8 | option( 9 | 'vapi', 10 | type: 'boolean', 11 | value: true, 12 | description: 'Generate vapi data (needs vapigen & introspection option)', 13 | ) 14 | -------------------------------------------------------------------------------- /lang/gjs/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./overrides.js" 2 | export { default as AstalIO } from "gi://AstalIO?version=0.1" 3 | export * from "./process.js" 4 | export * from "./time.js" 5 | export * from "./file.js" 6 | export * from "./gobject.js" 7 | export { Binding, bind } from "./binding.js" 8 | export { Variable, derive } from "./variable.js" 9 | -------------------------------------------------------------------------------- /lib/battery/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: 6 | mkAstalPkg { 7 | pname = "astal-battery"; 8 | src = ./.; 9 | packages = [pkgs.json-glib]; 10 | 11 | libname = "battery"; 12 | authors = "Aylur"; 13 | gir-suffix = "Battery"; 14 | description = "DBus proxy for upowerd devices"; 15 | } 16 | -------------------------------------------------------------------------------- /lib/wireplumber/meson_options.txt: -------------------------------------------------------------------------------- 1 | option( 2 | 'introspection', 3 | type: 'boolean', 4 | value: true, 5 | description: 'Build gobject-introspection data', 6 | ) 7 | 8 | option( 9 | 'vapi', 10 | type: 'boolean', 11 | value: true, 12 | description: 'Generate vapi data (needs vapigen & introspection option)', 13 | ) 14 | -------------------------------------------------------------------------------- /lib/astal/io/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode (gir_namespace = "AstalIO", gir_version = "@API_VERSION@")] 2 | namespace AstalIO { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lib/notifd/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: 6 | mkAstalPkg { 7 | pname = "astal-notifd"; 8 | src = ./.; 9 | packages = with pkgs; [json-glib gdk-pixbuf]; 10 | 11 | libname = "notifd"; 12 | authors = "Aylur"; 13 | gir-suffix = "Notifd"; 14 | description = "Notification daemon library"; 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Astal 2 | 3 | Astal is a collection of libraries written in Vala and C, designed to serve as 4 | the foundation for both lightweight widgets and fully-featured desktop shells. 5 | It handles the backend logic, so you can focus entirely on building the 6 | frontend. 7 | 8 | To get started read the [wiki](https://aylur.github.io/astal/) 9 | -------------------------------------------------------------------------------- /lib/apps/src/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode(gir_namespace = "AstalApps", gir_version = "@API_VERSION@")] 2 | namespace AstalApps { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lib/astal/gtk3/src/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode (gir_namespace = "Astal", gir_version = "@API_VERSION@")] 2 | namespace Astal { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lib/astal/gtk4/src/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode (gir_namespace = "Astal", gir_version = "@API_VERSION@")] 2 | namespace Astal { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lib/tray/src/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode(gir_namespace = "AstalTray", gir_version = "@API_VERSION@")] 2 | namespace AstalTray { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lib/greet/src/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode(gir_namespace = "AstalGreet", gir_version = "@API_VERSION@")] 2 | namespace AstalGreet { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lib/mpris/src/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode(gir_namespace = "AstalMpris", gir_version = "@API_VERSION@")] 2 | namespace AstalMpris { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lib/notifd/src/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode(gir_namespace = "AstalNotifd", gir_version = "@API_VERSION@")] 2 | namespace AstalNotifd { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lang/lua/astal/gtk3/init.lua: -------------------------------------------------------------------------------- 1 | local lgi = require("lgi") 2 | 3 | return { 4 | App = require("astal.gtk3.app"), 5 | astalify = require("astal.gtk3.astalify"), 6 | Widget = require("astal.gtk3.widget"), 7 | 8 | Gtk = lgi.require("Gtk", "3.0"), 9 | Gdk = lgi.require("Gdk", "3.0"), 10 | Astal = lgi.require("Astal", "3.0"), 11 | } 12 | -------------------------------------------------------------------------------- /lib/battery/src/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode(gir_namespace = "AstalBattery", gir_version = "@API_VERSION@")] 2 | namespace AstalBattery { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lib/hyprland/src/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode(gir_namespace = "AstalHyprland", gir_version = "@API_VERSION@")] 2 | namespace AstalHyprland { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lib/network/src/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode(gir_namespace = "AstalNetwork", gir_version = "@API_VERSION@")] 2 | namespace AstalNetwork { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lib/powerprofiles/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: 6 | mkAstalPkg { 7 | pname = "astal-powerprofiles"; 8 | src = ./.; 9 | packages = [pkgs.json-glib]; 10 | 11 | libname = "powerprofiles"; 12 | authors = "Aylur"; 13 | gir-suffix = "PowerProfiles"; 14 | description = "DBus proxy for upowerd profiles"; 15 | } 16 | -------------------------------------------------------------------------------- /lib/bluetooth/src/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode(gir_namespace = "AstalBluetooth", gir_version = "@API_VERSION@")] 2 | namespace AstalBluetooth { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lib/network/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: 6 | mkAstalPkg { 7 | pname = "astal-network"; 8 | src = ./.; 9 | packages = [pkgs.networkmanager]; 10 | 11 | libname = "network"; 12 | authors = "Aylur"; 13 | gir-suffix = "Network"; 14 | description = "NetworkManager wrapper library"; 15 | dependencies = ["NM-1.0"]; 16 | } 17 | -------------------------------------------------------------------------------- /lib/powerprofiles/src/config.vala.in: -------------------------------------------------------------------------------- 1 | [CCode(gir_namespace = "AstalPowerProfiles", gir_version = "@API_VERSION@")] 2 | namespace AstalPowerProfiles { 3 | public const int MAJOR_VERSION = @MAJOR_VERSION@; 4 | public const int MINOR_VERSION = @MINOR_VERSION@; 5 | public const int MICRO_VERSION = @MICRO_VERSION@; 6 | public const string VERSION = "@VERSION@"; 7 | } 8 | -------------------------------------------------------------------------------- /lang/gjs/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | self, 4 | ... 5 | }: 6 | pkgs.stdenvNoCC.mkDerivation { 7 | src = ./.; 8 | name = "astal-gjs"; 9 | nativeBuildInputs = [ 10 | pkgs.meson 11 | pkgs.ninja 12 | pkgs.pkg-config 13 | self.packages.${pkgs.stdenv.hostPlatform.system}.io 14 | self.packages.${pkgs.stdenv.hostPlatform.system}.astal3 15 | ]; 16 | } 17 | -------------------------------------------------------------------------------- /lib/notifd/src/enums.vala: -------------------------------------------------------------------------------- 1 | public enum AstalNotifd.Urgency { 2 | LOW = 0, 3 | NORMAL = 1, 4 | CRITICAL = 2, 5 | } 6 | 7 | public enum AstalNotifd.ClosedReason { 8 | EXPIRED = 1, 9 | DISMISSED_BY_USER = 2, 10 | CLOSED = 3, 11 | UNDEFINED = 4, 12 | } 13 | 14 | public enum AstalNotifd.State { 15 | DRAFT, 16 | SENT, 17 | RECEIVED 18 | } 19 | -------------------------------------------------------------------------------- /lib/wireplumber/include/astal/wireplumber/meson.build: -------------------------------------------------------------------------------- 1 | astal_wireplumber_subheaders = files( 2 | 'audio.h', 3 | 'device.h', 4 | 'node.h', 5 | 'endpoint.h', 6 | 'stream.h', 7 | 'profile.h', 8 | 'route.h', 9 | 'video.h', 10 | 'wp.h', 11 | 'enums.h', 12 | 'channel.h' 13 | ) 14 | 15 | install_headers(astal_wireplumber_subheaders, subdir: 'astal/wireplumber') 16 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/meson.build: -------------------------------------------------------------------------------- 1 | project('simple-bar') 2 | 3 | dependency('astal-4-4.0') 4 | dependency('astal-battery-0.1') 5 | dependency('astal-wireplumber-0.1') 6 | dependency('astal-network-0.1') 7 | dependency('astal-mpris-0.1') 8 | dependency('astal-power-profiles-0.1') 9 | dependency('astal-tray-0.1') 10 | dependency('astal-bluetooth-0.1') 11 | 12 | subdir('src') 13 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/py/meson.build: -------------------------------------------------------------------------------- 1 | project('simple-bar') 2 | 3 | dependency('astal-4-4.0') 4 | dependency('astal-battery-0.1') 5 | dependency('astal-wireplumber-0.1') 6 | dependency('astal-network-0.1') 7 | dependency('astal-mpris-0.1') 8 | dependency('astal-power-profiles-0.1') 9 | dependency('astal-tray-0.1') 10 | dependency('astal-bluetooth-0.1') 11 | 12 | subdir('src') 13 | -------------------------------------------------------------------------------- /lib/wireplumber/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: 6 | mkAstalPkg { 7 | pname = "astal-wireplumber"; 8 | src = ./.; 9 | packages = [pkgs.wireplumber]; 10 | 11 | libname = "wireplumber"; 12 | authors = "kotontrion"; 13 | gir-suffix = "Wp"; 14 | description = "Wrapper library over the wireplumber API"; 15 | dependencies = ["WP-0.5"]; 16 | } 17 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | These are examples written in Vala, Python (PyGObject) and TypeScript (GJS) 4 | intended to show you how to write a Gtk application from scratch using 5 | gtk-layer-shell and Astal. 6 | 7 | > [!IMPORTANT] 8 | > If you are interested in using AGS, I don't recommend these examples, but 9 | > instead have a look at [AGS examples](https://github.com/Aylur/ags/tree/main/examples). 10 | -------------------------------------------------------------------------------- /lang/gjs/src/gtk4/index.ts: -------------------------------------------------------------------------------- 1 | import Astal from "gi://Astal?version=4.0" 2 | import Gtk from "gi://Gtk?version=4.0" 3 | import Gdk from "gi://Gdk?version=4.0" 4 | import astalify, { type ConstructProps } from "./astalify.js" 5 | 6 | export { Astal, Gtk, Gdk } 7 | export { default as App } from "./app.js" 8 | export { astalify, ConstructProps } 9 | export * as Widget from "./widget.js" 10 | export { hook } from "../_astal" 11 | -------------------------------------------------------------------------------- /lib/wireplumber/include/private/channel-private.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef ASTAL_WP_CHANNEL_PRIV_H 3 | #define ASTAL_WP_CHANNEL_PRIV_H 4 | 5 | #include 6 | 7 | #include "channel.h" 8 | #include "node.h" 9 | 10 | G_BEGIN_DECLS 11 | 12 | void astal_wp_channel_update_volume(AstalWpChannel *self, gdouble volume); 13 | AstalWpChannel *astal_wp_channel_new(AstalWpNode *ep, const gchar *name); 14 | 15 | #endif // #ASTAL_WP_CHANNEL_PRIV_H 16 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/src/main.in.js: -------------------------------------------------------------------------------- 1 | #!@GJS@ -m 2 | 3 | import { exit, programArgs } from "system" 4 | import Gio from "gi://Gio" 5 | import GLib from "gi://GLib" 6 | 7 | // makes sure `LD_PRELOAD` does not leak into subprocesses 8 | GLib.setenv("LD_PRELOAD", "", true) 9 | Gio.Resource.load("@PKGDATADIR@/data.gresource")._register() 10 | 11 | const module = await import("resource:///index.js") 12 | exit(await module.default.main(programArgs)) 13 | -------------------------------------------------------------------------------- /lang/gjs/src/gtk3/index.ts: -------------------------------------------------------------------------------- 1 | import Astal from "gi://Astal?version=3.0" 2 | import Gtk from "gi://Gtk?version=3.0" 3 | import Gdk from "gi://Gdk?version=3.0" 4 | import astalify, { type ConstructProps, type BindableProps } from "./astalify.js" 5 | 6 | export { Astal, Gtk, Gdk } 7 | export { default as App } from "./app.js" 8 | export { astalify, ConstructProps, BindableProps } 9 | export * as Widget from "./widget.js" 10 | export { hook } from "../_astal" 11 | -------------------------------------------------------------------------------- /lib/auth/include/meson.build: -------------------------------------------------------------------------------- 1 | 2 | config = configure_file( 3 | input: 'astal-auth.h.in', 4 | output: 'astal-auth.h', 5 | configuration: { 6 | 'VERSION': meson.project_version(), 7 | 'MAJOR_VERSION': version_split[0], 8 | 'MINOR_VERSION': version_split[1], 9 | 'MICRO_VERSION': version_split[2], 10 | }, 11 | ) 12 | 13 | 14 | astal_auth_inc = include_directories('.') 15 | astal_auth_headers = config 16 | 17 | install_headers(astal_auth_headers) 18 | -------------------------------------------------------------------------------- /lib/river/include/meson.build: -------------------------------------------------------------------------------- 1 | 2 | config = configure_file( 3 | input: 'astal-river.h.in', 4 | output: 'astal-river.h', 5 | configuration: { 6 | 'VERSION': meson.project_version(), 7 | 'MAJOR_VERSION': version_split[0], 8 | 'MINOR_VERSION': version_split[1], 9 | 'MICRO_VERSION': version_split[2], 10 | }, 11 | ) 12 | 13 | 14 | astal_river_inc = include_directories('.') 15 | astal_river_headers = config 16 | 17 | install_headers(astal_river_headers) 18 | -------------------------------------------------------------------------------- /docs/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | self, 3 | pkgs, 4 | }: let 5 | inherit (builtins) removeAttrs concatStringsSep map attrValues; 6 | packages = attrValues (removeAttrs self.packages.${pkgs.stdenv.hostPlatform.system} ["default" "docs"]); 7 | 8 | cp = pkg: '' 9 | doc="${pkg.doc}/share/doc" 10 | name=$(ls $doc) 11 | 12 | mkdir -p "$out/$name" 13 | cp -r "$doc/$name" $out 14 | ''; 15 | in 16 | pkgs.runCommand "docs" {} (concatStringsSep "" (map cp packages)) 17 | -------------------------------------------------------------------------------- /lib/wireplumber/include/private/wp-private.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_WP_WP_PRIV_H 2 | #define ASTAL_WP_WP_PRIV_H 3 | 4 | #include 5 | 6 | #include "wp.h" 7 | 8 | G_BEGIN_DECLS 9 | 10 | void astal_wp_wp_set_matadata(AstalWpWp* self, guint subject, const gchar* key, const gchar* type, 11 | const gchar* value); 12 | 13 | void astal_wp_wp_update_metadata(AstalWpWp* self, guint subject); 14 | G_END_DECLS 15 | 16 | #endif // !ASTAL_WP_WP_PRIV_H 17 | -------------------------------------------------------------------------------- /lib/astal/gtk3/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | self, 5 | }: 6 | mkAstalPkg { 7 | pname = "astal3"; 8 | src = ./.; 9 | packages = [ 10 | self.packages.${pkgs.stdenv.hostPlatform.system}.io 11 | pkgs.gtk3 12 | pkgs.gtk-layer-shell 13 | ]; 14 | 15 | libname = "astal3"; 16 | gir-suffix = ""; 17 | authors = "Aylur"; 18 | description = "Astal GTK3 widget library"; 19 | dependencies = ["AstalIO-0.1" "Gtk-3.0"]; 20 | repo-path = "astal/gtk3"; 21 | } 22 | -------------------------------------------------------------------------------- /lib/astal/gtk4/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | self, 5 | }: 6 | mkAstalPkg { 7 | pname = "astal4"; 8 | src = ./.; 9 | packages = [ 10 | self.packages.${pkgs.stdenv.hostPlatform.system}.io 11 | pkgs.gtk4 12 | pkgs.gtk4-layer-shell 13 | ]; 14 | 15 | libname = "astal4"; 16 | gir-suffix = ""; 17 | authors = "Aylur"; 18 | description = "Astal GTK4 widget library"; 19 | dependencies = ["AstalIO-0.1" "Gtk-4.0"]; 20 | repo-path = "astal/gtk4"; 21 | } 22 | -------------------------------------------------------------------------------- /lang/gjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "module": "ES2022", 5 | "target": "ES2023", 6 | "outDir": "dist", 7 | "strict": true, 8 | "moduleResolution": "Bundler", 9 | "skipLibCheck": true, 10 | "baseUrl": ".", 11 | }, 12 | "include": [ 13 | "@girs", 14 | "src/*.ts", 15 | // "src/gtk3/*", 16 | // "src/gtk4/*", 17 | "index.ts", 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /lib/astal/gtk3/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'astal', 3 | 'vala', 4 | 'c', 5 | version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), 6 | meson_version: '>= 0.62.0', 7 | default_options: [ 8 | 'warning_level=2', 9 | 'werror=false', 10 | 'c_std=gnu11', 11 | ], 12 | ) 13 | 14 | libdir = get_option('prefix') / get_option('libdir') 15 | pkgdatadir = get_option('prefix') / get_option('datadir') / 'astal' 16 | girpy = files('gir.py') 17 | 18 | subdir('src') 19 | -------------------------------------------------------------------------------- /lib/astal/gtk4/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'astal-4', 3 | 'vala', 4 | 'c', 5 | version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), 6 | meson_version: '>= 0.62.0', 7 | default_options: [ 8 | 'warning_level=2', 9 | 'werror=false', 10 | 'c_std=gnu11', 11 | ], 12 | ) 13 | 14 | libdir = get_option('prefix') / get_option('libdir') 15 | pkgdatadir = get_option('prefix') / get_option('datadir') / 'astal' 16 | girpy = files('gir.py') 17 | 18 | subdir('src') 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | A description or script to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Additional context** 20 | Add any other context about the problem here. 21 | -------------------------------------------------------------------------------- /lib/river/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: 6 | mkAstalPkg { 7 | pname = "astal-river"; 8 | src = ./.; 9 | packages = [pkgs.json-glib]; 10 | 11 | libname = "river"; 12 | authors = "kotontrion"; 13 | gir-suffix = "River"; 14 | description = "IPC client for River"; 15 | 16 | postUnpack = '' 17 | rm -rf $sourceRoot/subprojects 18 | mkdir -p $sourceRoot/subprojects 19 | cp -r --remove-destination ${../wayland-glib} $sourceRoot/subprojects/wayland-glib 20 | ''; 21 | } 22 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/src/bar/Bar.scss: -------------------------------------------------------------------------------- 1 | // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-4-16/gtk/theme/Default/_colors-public.scss 2 | $fg-color: #{"@theme_fg_color"}; 3 | $bg-color: #{"@theme_bg_color"}; 4 | 5 | window.Bar { 6 | > box { 7 | background: $bg-color; 8 | color: $fg-color; 9 | font-weight: bold; 10 | } 11 | 12 | button { 13 | min-height: 0; 14 | min-width: 0; 15 | border-radius: 8px; 16 | margin: 4px; 17 | padding: 4px 8px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/py/src/bar/Bar.scss: -------------------------------------------------------------------------------- 1 | // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-4-16/gtk/theme/Default/_colors-public.scss 2 | $fg-color: #{"@theme_fg_color"}; 3 | $bg-color: #{"@theme_bg_color"}; 4 | 5 | window.Bar { 6 | > box { 7 | background: $bg-color; 8 | color: $fg-color; 9 | font-weight: bold; 10 | } 11 | 12 | button { 13 | min-height: 0; 14 | min-width: 0; 15 | border-radius: 8px; 16 | margin: 4px; 17 | padding: 4px 8px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/vala/src/bar/Bar.scss: -------------------------------------------------------------------------------- 1 | // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-4-16/gtk/theme/Default/_colors-public.scss 2 | $fg-color: #{"@theme_fg_color"}; 3 | $bg-color: #{"@theme_bg_color"}; 4 | 5 | window.Bar { 6 | > box { 7 | background: $bg-color; 8 | color: $fg-color; 9 | font-weight: bold; 10 | } 11 | 12 | button { 13 | min-height: 0; 14 | min-width: 0; 15 | border-radius: 8px; 16 | margin: 4px; 17 | padding: 4px 8px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lang/gjs/src/time.ts: -------------------------------------------------------------------------------- 1 | import Astal from "gi://AstalIO" 2 | 3 | export type Time = Astal.Time 4 | export const Time = Astal.Time 5 | 6 | export function interval(interval: number, callback?: () => void) { 7 | return Astal.Time.interval(interval, () => void callback?.()) 8 | } 9 | 10 | export function timeout(timeout: number, callback?: () => void) { 11 | return Astal.Time.timeout(timeout, () => void callback?.()) 12 | } 13 | 14 | export function idle(callback?: () => void) { 15 | return Astal.Time.idle(() => void callback?.()) 16 | } 17 | -------------------------------------------------------------------------------- /lib/wireplumber/include/meson.build: -------------------------------------------------------------------------------- 1 | 2 | config = configure_file( 3 | input: 'astal-wp.h.in', 4 | output: 'astal-wp.h', 5 | configuration: { 6 | 'VERSION': meson.project_version(), 7 | 'MAJOR_VERSION': version_split[0], 8 | 'MINOR_VERSION': version_split[1], 9 | 'MICRO_VERSION': version_split[2], 10 | }, 11 | ) 12 | 13 | astal_wireplumber_inc = include_directories('.', 'astal/wireplumber', 'private') 14 | astal_wireplumber_headers = config 15 | 16 | install_headers(astal_wireplumber_headers) 17 | 18 | subdir('astal/wireplumber') 19 | -------------------------------------------------------------------------------- /lib/astal/gtk3/src/widget/levelbar.vala: -------------------------------------------------------------------------------- 1 | public class Astal.LevelBar : Gtk.LevelBar { 2 | /** 3 | * Corresponds to [property@Gtk.Orientable :orientation]. 4 | */ 5 | [CCode (notify = false)] 6 | public bool vertical { 7 | get { return orientation == Gtk.Orientation.VERTICAL; } 8 | set { orientation = value ? Gtk.Orientation.VERTICAL : Gtk.Orientation.HORIZONTAL; } 9 | } 10 | 11 | construct { 12 | notify["orientation"].connect(() => { 13 | notify_property("vertical"); 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/wireplumber/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'astal_wireplumber', 3 | 'c', 4 | version: '0.1.0', 5 | default_options: ['c_std=gnu11', 'warning_level=3', 'prefix=/usr'], 6 | ) 7 | 8 | add_project_arguments([ 9 | '-Wno-pedantic', 10 | '-Wno-unused-parameter', 11 | '-DG_LOG_DOMAIN="AstalWp"', 12 | ], language: 'c') 13 | 14 | version_split = meson.project_version().split('.') 15 | lib_so_version = version_split[0] + '.' + version_split[1] 16 | 17 | pkg_config = import('pkgconfig') 18 | gnome = import('gnome') 19 | 20 | subdir('include') 21 | subdir('src') 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question or ask for help 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Have you read through the documentation?** 11 | - [ ] [Astal docs](https://aylur.github.io/astal/) 12 | - [ ] [Library references](https://aylur.github.io/astal/guide/libraries/references) 13 | - [ ] [FAQ](https://aylur.github.io/astal/guide/typescript/faq) 14 | 15 | **Describe what have you tried so far** 16 | A description or example code of what you have got so far. 17 | 18 | **Describe your question** 19 | A question. 20 | -------------------------------------------------------------------------------- /lib/bluetooth/src/utils.vala: -------------------------------------------------------------------------------- 1 | namespace AstalBluetooth { 2 | internal string kebab_case(string pascal_case) { 3 | StringBuilder kebab_case = new StringBuilder(); 4 | 5 | for (int i = 0; i < pascal_case.length; i++) { 6 | char c = pascal_case[i]; 7 | 8 | if ((c >= 'A') && (c <= 'Z')) { 9 | if (i != 0) { 10 | kebab_case.append_c('-'); 11 | } 12 | 13 | kebab_case.append_c((char)(c + 32)); 14 | } else { 15 | kebab_case.append_c(c); 16 | } 17 | } 18 | 19 | return kebab_case.str; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/astal/gtk3/src/vapi/AstalInhibitManager.vapi: -------------------------------------------------------------------------------- 1 | [CCode (cprefix = "Astal", gir_namespace = "Astal", lower_case_cprefix = "astal_")] 2 | namespace Astal { 3 | [CCode (cheader_filename = "idle-inhibit.h", type_id = "astal_idle_inhibit_manager_get_type()")] 4 | public class InhibitManager : GLib.Object { 5 | public static unowned InhibitManager? get_default(); 6 | public Inhibitor inhibit (Gtk.Window window); 7 | } 8 | 9 | [CCode (cheader_filename = "idle-inhibit.h", free_function = "zwp_idle_inhibitor_v1_destroy")] 10 | [Compact] 11 | public class Inhibitor { } 12 | } 13 | -------------------------------------------------------------------------------- /lang/gjs/src/gtk4/app.ts: -------------------------------------------------------------------------------- 1 | import GLib from "gi://GLib?version=2.0" 2 | import Gtk from "gi://Gtk?version=4.0" 3 | import Astal from "gi://Astal?version=4.0" 4 | import { mkApp } from "../_app" 5 | 6 | Gtk.init() 7 | 8 | // stop this from leaking into subprocesses 9 | // and gio launch invocations 10 | GLib.unsetenv("LD_PRELOAD") 11 | 12 | // users might want to use Adwaita in which case it has to be initialized 13 | // it might be common pitfall to forget it because `App` is not `Adw.Application` 14 | await import("gi://Adw?version=1") 15 | .then(({ default: Adw }) => Adw.init()) 16 | .catch(() => void 0) 17 | 18 | export default mkApp(Astal.Application) 19 | -------------------------------------------------------------------------------- /lib/auth/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'astal_auth', 3 | 'c', 4 | version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), 5 | default_options: [ 6 | 'c_std=gnu11', 7 | 'warning_level=3', 8 | 'prefix=/usr', 9 | ], 10 | ) 11 | 12 | add_project_arguments(['-Wno-pedantic'], language: 'c') 13 | 14 | version_split = meson.project_version().split('.') 15 | lib_so_version = version_split[0] + '.' + version_split[1] 16 | 17 | pkg_config = import('pkgconfig') 18 | gnome = import('gnome') 19 | 20 | subdir('include') 21 | subdir('src') 22 | 23 | install_data('pam/astal-auth', install_dir: get_option('sysconfdir') / 'pam.d') 24 | -------------------------------------------------------------------------------- /lib/wireplumber/include/astal/wireplumber/channel.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_WP_CHANNEL_H 2 | #define ASTAL_WP_CHANNEL_H 3 | 4 | #include 5 | 6 | G_BEGIN_DECLS 7 | 8 | #define ASTAL_WP_TYPE_CHANNEL (astal_wp_channel_get_type()) 9 | 10 | G_DECLARE_FINAL_TYPE(AstalWpChannel, astal_wp_channel, ASTAL_WP, CHANNEL, GObject) 11 | 12 | gdouble astal_wp_channel_get_volume(AstalWpChannel *self); 13 | void astal_wp_channel_set_volume(AstalWpChannel *self, gdouble volume); 14 | const gchar *astal_wp_channel_get_name(AstalWpChannel *self); 15 | const gchar *astal_wp_channel_get_volume_icon(AstalWpChannel *self); 16 | 17 | G_END_DECLS 18 | 19 | #endif // !ASTAL_WP_CHANNEL_H 20 | -------------------------------------------------------------------------------- /lang/gjs/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from "@eslint/js" 2 | import tseslint from "typescript-eslint" 3 | import stylistic from "@stylistic/eslint-plugin" 4 | 5 | export default tseslint.config({ 6 | extends: [ 7 | eslint.configs.recommended, 8 | ...tseslint.configs.recommended, 9 | stylistic.configs.customize({ 10 | semi: false, 11 | indent: 4, 12 | quotes: "double", 13 | }), 14 | ], 15 | rules: { 16 | "@typescript-eslint/no-explicit-any": "off", 17 | "@stylistic/new-parens": "off", 18 | "@stylistic/brace-style": ["error", "1tbs", { allowSingleLine: true }], 19 | }, 20 | }) 21 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1761672384, 6 | "narHash": "sha256-o9KF3DJL7g7iYMZq9SWgfS1BFlNbsm6xplRjVlOCkXI=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "08dacfca559e1d7da38f3cf05f1f45ee9bfd213c", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "ref": "nixos-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /lib/river/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'astal_river', 3 | 'c', 4 | version: '0.1.0', 5 | default_options: ['c_std=gnu11', 'warning_level=3', 'prefix=/usr'], 6 | ) 7 | 8 | add_project_arguments([ 9 | '-Wno-pedantic', 10 | '-Wno-unused-parameter', 11 | '-DG_LOG_DOMAIN="AstalRiver"', 12 | ], language: 'c') 13 | 14 | version_split = meson.project_version().split('.') 15 | lib_so_version = version_split[0] + '.' + version_split[1] 16 | 17 | pkg_config = import('pkgconfig') 18 | gnome = import('gnome') 19 | 20 | wayland_glib = subproject('wayland-glib') 21 | wayland_glib_dep = wayland_glib.get_variable('wayland_glib') 22 | 23 | subdir('protocols') 24 | subdir('include') 25 | subdir('src') 26 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astal-docs", 3 | "type": "module", 4 | "devDependencies": { 5 | "vitepress": "latest", 6 | "vue": "latest" 7 | }, 8 | "scripts": { 9 | "dev": "vitepress dev --open", 10 | "build": "vitepress build", 11 | "preview": "vitepress preview", 12 | "vitepress": "vitepress", 13 | "lint": "eslint . --fix" 14 | }, 15 | "dependencies": { 16 | "devicon": "latest", 17 | "font-logos": "latest" 18 | }, 19 | "prettier": { 20 | "semi": false, 21 | "proseWrap": "always", 22 | "overrides": [ 23 | { 24 | "files": "**.md", 25 | "options": { 26 | "tabWidth": 4 27 | } 28 | } 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/astal/gtk3/src/idle-inhibit.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_IDLE_INHIBITOR_H 2 | #define ASTAL_IDLE_INHIBITOR_H 3 | 4 | #include 5 | #include 6 | 7 | #include "idle-inhibit-unstable-v1-client.h" 8 | 9 | G_BEGIN_DECLS 10 | 11 | #define ASTAL_TYPE_INHIBIT_MANAGER (astal_inhibit_manager_get_type()) 12 | 13 | G_DECLARE_FINAL_TYPE(AstalInhibitManager, astal_inhibit_manager, ASTAL, INHIBIT_MANAGER, GObject) 14 | 15 | typedef struct zwp_idle_inhibitor_v1 AstalInhibitor; 16 | 17 | AstalInhibitManager* astal_inhibit_manager_get_default(); 18 | AstalInhibitor* astal_inhibit_manager_inhibit(AstalInhibitManager* self, GtkWindow* window); 19 | 20 | G_END_DECLS 21 | 22 | #endif // !ASTAL_IDLE_INHIBITOR_H 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /examples/gtk3/simple-bar/py/README.md: -------------------------------------------------------------------------------- 1 | # Simple Bar Example 2 | 3 | ![simple-bar](https://github.com/user-attachments/assets/a306c864-56b7-44c4-8820-81f424f32b9b) 4 | 5 | A simple bar for Hyprland using 6 | 7 | - [Battery library](https://aylur.github.io/astal/guide/libraries/battery). 8 | - [Hyprland library](https://aylur.github.io/astal/guide/libraries/hyprland). 9 | - [Mpris library](https://aylur.github.io/astal/guide/libraries/mpris). 10 | - [Network library](https://aylur.github.io/astal/guide/libraries/network). 11 | - [Tray library](https://aylur.github.io/astal/guide/libraries/tray). 12 | - [WirePlumber library](https://aylur.github.io/astal/guide/libraries/wireplumber). 13 | - [dart-sass](https://sass-lang.com/dart-sass/) as the css precompiler 14 | -------------------------------------------------------------------------------- /examples/gtk3/simple-bar/vala/README.md: -------------------------------------------------------------------------------- 1 | # Simple Bar Example 2 | 3 | ![simple-bar](https://github.com/user-attachments/assets/a306c864-56b7-44c4-8820-81f424f32b9b) 4 | 5 | A simple bar for Hyprland using 6 | 7 | - [Battery library](https://aylur.github.io/astal/guide/libraries/battery). 8 | - [Hyprland library](https://aylur.github.io/astal/guide/libraries/hyprland). 9 | - [Mpris library](https://aylur.github.io/astal/guide/libraries/mpris). 10 | - [Network library](https://aylur.github.io/astal/guide/libraries/network). 11 | - [Tray library](https://aylur.github.io/astal/guide/libraries/tray). 12 | - [WirePlumber library](https://aylur.github.io/astal/guide/libraries/wireplumber). 13 | - [dart-sass](https://sass-lang.com/dart-sass/) as the css precompiler 14 | -------------------------------------------------------------------------------- /lib/wireplumber/include/astal/wireplumber/profile.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_WP_PROFILE_H 2 | #define ASTAL_WP_PROFILE_H 3 | 4 | #include 5 | 6 | #include "enums.h" 7 | 8 | G_BEGIN_DECLS 9 | 10 | #define ASTAL_WP_TYPE_PROFILE (astal_wp_profile_get_type()) 11 | 12 | G_DECLARE_FINAL_TYPE(AstalWpProfile, astal_wp_profile, ASTAL_WP, PROFILE, GObject) 13 | 14 | gint astal_wp_profile_get_index(AstalWpProfile *self); 15 | const gchar *astal_wp_profile_get_description(AstalWpProfile *self); 16 | const gchar *astal_wp_profile_get_name(AstalWpProfile *self); 17 | AstalWpAvailable astal_wp_profile_get_available(AstalWpProfile *self); 18 | gint astal_wp_profile_get_priority(AstalWpProfile *self); 19 | 20 | G_END_DECLS 21 | 22 | #endif // !ASTAL_WP_PROFILE_H 23 | -------------------------------------------------------------------------------- /nix/gi-docgen.patch: -------------------------------------------------------------------------------- 1 | diff --git a/gidocgen/gir/parser.py b/gidocgen/gir/parser.py 2 | index e62835d..7ee60fa 100644 3 | --- a/gidocgen/gir/parser.py 4 | +++ b/gidocgen/gir/parser.py 5 | @@ -288,7 +288,11 @@ class GirParser: 6 | 7 | content = child.text or "" 8 | 9 | - return ast.Doc(content=content, filename=child.attrib['filename'], line=int(child.attrib['line'])) 10 | + return ast.Doc( 11 | + content=content, 12 | + filename=child.attrib.get('filename', ''), 13 | + line=int(child.attrib.get('line', 0)), 14 | + ) 15 | 16 | def _maybe_parse_source_position(self, node: ET.Element) -> T.Optional[ast.SourcePosition]: 17 | child = node.find('core:source-position', GI_NAMESPACES) 18 | -------------------------------------------------------------------------------- /lang/gjs/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astal", 3 | "type": "module", 4 | "license": "LGPL-2.1", 5 | "exports": { 6 | ".": "./index.ts", 7 | "./gtk3": "./gtk3/index.ts", 8 | "./gtk4": "./gtk4/index.ts", 9 | "./gtk3/app": "./gtk3/app.ts", 10 | "./gtk4/app": "./gtk4/app.ts", 11 | "./gtk3/widget": "./gtk3/widget.ts", 12 | "./gtk4/widget": "./gtk4/widget.ts", 13 | "./gtk3/jsx-runtime": "./gtk3/jsx-runtime.ts", 14 | "./gtk4/jsx-runtime": "./gtk4/jsx-runtime.ts", 15 | "./binding": "./binding.ts", 16 | "./file": "./file.ts", 17 | "./gobject": "./gobject.ts", 18 | "./process": "./process.ts", 19 | "./time": "./time.ts", 20 | "./variable": "./variable.ts" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/wireplumber/include/astal-wp.h.in: -------------------------------------------------------------------------------- 1 | 2 | #ifndef WP_H 3 | #define WP_H 4 | 5 | #include "astal/wireplumber/audio.h" 6 | #include "astal/wireplumber/channel.h" 7 | #include "astal/wireplumber/device.h" 8 | #include "astal/wireplumber/endpoint.h" 9 | #include "astal/wireplumber/enums.h" 10 | #include "astal/wireplumber/node.h" 11 | #include "astal/wireplumber/profile.h" 12 | #include "astal/wireplumber/route.h" 13 | #include "astal/wireplumber/stream.h" 14 | #include "astal/wireplumber/video.h" 15 | #include "astal/wireplumber/wp.h" 16 | 17 | // clang-format off 18 | #define ASTAL_WP_MAJOR_VERSION @MAJOR_VERSION@ 19 | #define ASTAL_WP_MINOR_VERSION @MINOR_VERSION@ 20 | #define ASTAL_WP_MICRO_VERSION @MICRO_VERSION@ 21 | #define ASTAL_WP_VERSION "@VERSION@" 22 | // clang-format on 23 | 24 | #endif // WP_H 25 | -------------------------------------------------------------------------------- /lang/lua/astal/time.lua: -------------------------------------------------------------------------------- 1 | local lgi = require("lgi") 2 | local Astal = lgi.require("AstalIO", "0.1") 3 | local GObject = lgi.require("GObject", "2.0") 4 | 5 | local M = {} 6 | 7 | M.Time = Astal.Time 8 | 9 | ---@param interval number 10 | ---@param fn function 11 | ---@return { cancel: function, on_now: function } 12 | function M.interval(interval, fn) 13 | return Astal.Time.interval(interval, GObject.Closure(fn)) 14 | end 15 | 16 | ---@param timeout number 17 | ---@param fn function 18 | ---@return { cancel: function, on_now: function } 19 | function M.timeout(timeout, fn) 20 | return Astal.Time.timeout(timeout, GObject.Closure(fn)) 21 | end 22 | 23 | ---@param fn function 24 | ---@return { cancel: function, on_now: function } 25 | function M.idle(fn) 26 | return Astal.Time.idle(GObject.Closure(fn)) 27 | end 28 | 29 | return M 30 | -------------------------------------------------------------------------------- /lib/river/protocols/meson.build: -------------------------------------------------------------------------------- 1 | wayland_scanner = find_program('wayland-scanner') 2 | 3 | protocols = [ 4 | 'river-status-unstable-v1.xml', 5 | 'river-layout-v3.xml', 6 | 'river-control-unstable-v1.xml' 7 | ] 8 | 9 | gen_client_header = generator( 10 | wayland_scanner, 11 | output: ['@BASENAME@-client.h'], 12 | arguments: ['-c', 'client-header', '@INPUT@', '@BUILD_DIR@/@BASENAME@-client.h'], 13 | ) 14 | 15 | gen_private_code = generator( 16 | wayland_scanner, 17 | output: ['@BASENAME@.c'], 18 | arguments: ['-c', 'private-code', '@INPUT@', '@BUILD_DIR@/@BASENAME@.c'], 19 | ) 20 | 21 | client_protocol_srcs = [] 22 | 23 | foreach protocol : protocols 24 | client_header = gen_client_header.process(protocol) 25 | code = gen_private_code.process(protocol) 26 | client_protocol_srcs += [client_header, code] 27 | endforeach 28 | -------------------------------------------------------------------------------- /lib/wireplumber/include/astal/wireplumber/route.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_WP_ROUTE_H 2 | #define ASTAL_WP_ROUTE_H 3 | 4 | #include 5 | 6 | #include "enums.h" 7 | 8 | G_BEGIN_DECLS 9 | 10 | #define ASTAL_WP_TYPE_ROUTE (astal_wp_route_get_type()) 11 | 12 | G_DECLARE_FINAL_TYPE(AstalWpRoute, astal_wp_route, ASTAL_WP, ROUTE, GObject) 13 | 14 | gint astal_wp_route_get_index(AstalWpRoute *self); 15 | const gchar *astal_wp_route_get_description(AstalWpRoute *self); 16 | const gchar *astal_wp_route_get_name(AstalWpRoute *self); 17 | AstalWpDirection astal_wp_route_get_direction(AstalWpRoute *self); 18 | AstalWpAvailable astal_wp_route_get_available(AstalWpRoute *self); 19 | gint astal_wp_route_get_priority(AstalWpRoute *self); 20 | gint astal_wp_route_get_device(AstalWpRoute *self); 21 | 22 | G_END_DECLS 23 | 24 | #endif // !ASTAL_WP_ROUTE_H 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [aylur, kotontrion] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: aylur 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: ["https://ko-fi.com/kotontrion"] 16 | -------------------------------------------------------------------------------- /lib/notifd/src/gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | false 9 | 10 | 11 | [] 12 | 13 | 14 | -1 15 | 16 | 17 | [ 18 | 'action-icons', 19 | 'actions', 20 | 'body', 21 | 'body-hyperlinks', 22 | 'body-images', 23 | 'body-markup', 24 | 'icon-static', 25 | 'persistence', 26 | 'sound' 27 | ] 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib/astal/gtk3/src/widget/label.vala: -------------------------------------------------------------------------------- 1 | using Pango; 2 | 3 | public class Astal.Label : Gtk.Label { 4 | /** 5 | * Shortcut for setting [property@Gtk.Label:ellipsize] to [enum@Pango.EllipsizeMode.END] 6 | */ 7 | public bool truncate { 8 | set { ellipsize = value ? EllipsizeMode.END : EllipsizeMode.NONE; } 9 | get { return ellipsize == EllipsizeMode.END; } 10 | } 11 | 12 | /** 13 | * Shortcut for setting [property@Gtk.Label:justify] to [enum@Gtk.Justification.FILL] 14 | */ 15 | public new bool justify_fill { 16 | set { justify = value ? Gtk.Justification.FILL : Gtk.Justification.LEFT; } 17 | get { return justify == Gtk.Justification.FILL; } 18 | } 19 | 20 | construct { 21 | notify["ellipsize"].connect(() => notify_property("truncate")); 22 | notify["justify"].connect(() => notify_property("justify_fill")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/wireplumber/include/astal/wireplumber/stream.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_WIREPLUMBER_STREAM_H 2 | #define ASTAL_WIREPLUMBER_STREAM_H 3 | 4 | #include 5 | 6 | #include "endpoint.h" 7 | #include "node.h" 8 | 9 | G_BEGIN_DECLS 10 | 11 | #define ASTAL_WP_TYPE_STREAM (astal_wp_stream_get_type()) 12 | 13 | G_DECLARE_FINAL_TYPE(AstalWpStream, astal_wp_stream, ASTAL_WP, STREAM, AstalWpNode) 14 | 15 | AstalWpMediaRole astal_wp_stream_get_media_role(AstalWpStream *self); 16 | AstalWpMediaCategory astal_wp_stream_get_media_category(AstalWpStream *self); 17 | gint astal_wp_stream_get_target_serial(AstalWpStream *self); 18 | void astal_wp_stream_set_target_serial(AstalWpStream *self, gint serial); 19 | AstalWpEndpoint *astal_wp_stream_get_target_endpoint(AstalWpStream *self); 20 | void astal_wp_stream_set_target_endpoint(AstalWpStream *self, AstalWpEndpoint *target); 21 | 22 | G_END_DECLS 23 | 24 | #endif // !ASTAL_WIREPLUMBER_STREAM_H 25 | -------------------------------------------------------------------------------- /lang/lua/astal/init.lua: -------------------------------------------------------------------------------- 1 | if not table.unpack then 2 | table.unpack = unpack 3 | end 4 | 5 | local lgi = require("lgi") 6 | local Binding = require("astal.binding") 7 | local File = require("astal.file") 8 | local Process = require("astal.process") 9 | local Time = require("astal.time") 10 | ---@type Variable | fun(v: any): Variable 11 | local Variable = require("astal.variable") 12 | 13 | return { 14 | Variable = Variable, 15 | bind = Binding.new, 16 | 17 | interval = Time.interval, 18 | timeout = Time.timeout, 19 | idle = Time.idle, 20 | 21 | subprocess = Process.subprocess, 22 | exec = Process.exec, 23 | exec_async = Process.exec_async, 24 | 25 | read_file = File.read_file, 26 | read_file_async = File.read_file_async, 27 | write_file = File.write_file, 28 | write_file_async = File.write_file_async, 29 | monitor_file = File.monitor_file, 30 | 31 | require = lgi.require, 32 | } 33 | -------------------------------------------------------------------------------- /lib/wayland-glib/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'wayland-glib', 3 | 'vala', 4 | 'c', 5 | version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), 6 | meson_version: '>= 0.62.0', 7 | default_options: [ 8 | 'warning_level=2', 9 | 'werror=false', 10 | 'c_std=gnu11', 11 | ], 12 | ) 13 | 14 | version_split = meson.project_version().split('.') 15 | 16 | deps = [ 17 | dependency('glib-2.0'), 18 | dependency('gio-2.0'), 19 | dependency('gobject-2.0'), 20 | dependency('wayland-client'), 21 | ] 22 | 23 | sources = [ 24 | 'wl-source.vala', 25 | ] 26 | 27 | lib = static_library( 28 | meson.project_name(), 29 | sources, 30 | dependencies: deps, 31 | vala_header: meson.project_name() + '.h', 32 | vala_vapi: meson.project_name() + '.vapi', 33 | ) 34 | 35 | wayland_glib = declare_dependency( 36 | link_with: lib, 37 | include_directories: include_directories('.') 38 | ) 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/py/src/main.in.py: -------------------------------------------------------------------------------- 1 | #!@PYTHON@ 2 | 3 | import gi 4 | 5 | gi.require_version("Gio", "2.0") 6 | gi.require_version("GObject", "2.0") 7 | gi.require_version("GLib", "2.0") 8 | gi.require_version("Gtk", "4.0") 9 | gi.require_version("Astal", "4.0") 10 | 11 | gi.require_version("AstalBattery", "0.1") 12 | gi.require_version("AstalWp", "0.1") 13 | gi.require_version("AstalNetwork", "0.1") 14 | gi.require_version("AstalMpris", "0.1") 15 | gi.require_version("AstalPowerProfiles", "0.1") 16 | gi.require_version("AstalTray", "0.1") 17 | gi.require_version("AstalBluetooth", "0.1") 18 | 19 | from gi.repository import Gio 20 | from sys import argv, path 21 | from ctypes import CDLL 22 | 23 | CDLL("@LAYER_SHELL_PREFIX@/lib/libgtk4-layer-shell.so") 24 | path.insert(1, "@PKGDATADIR@") 25 | Gio.Resource.load("@PKGDATADIR@/data.gresource")._register() 26 | 27 | 28 | if __name__ == "__main__": 29 | from app.App import App 30 | 31 | App.main(argv) 32 | -------------------------------------------------------------------------------- /lib/tray/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: let 6 | vala-panel-appmenu = pkgs.fetchFromGitLab { 7 | owner = "vala-panel-project"; 8 | repo = "vala-panel-appmenu"; 9 | rev = "25.04"; 10 | hash = "sha256-v5J3nwViNiSKRPdJr+lhNUdKaPG82fShPDlnmix5tlY="; 11 | }; 12 | 13 | appmenu-glib-translator = pkgs.stdenv.mkDerivation { 14 | pname = "appmenu-glib-translator"; 15 | version = "25.04"; 16 | 17 | src = "${vala-panel-appmenu}/subprojects/appmenu-glib-translator"; 18 | 19 | buildInputs = with pkgs; [ 20 | glib 21 | ]; 22 | 23 | nativeBuildInputs = with pkgs; [ 24 | gobject-introspection 25 | meson 26 | pkg-config 27 | ninja 28 | vala 29 | ]; 30 | }; 31 | in 32 | mkAstalPkg { 33 | pname = "astal-tray"; 34 | src = ./.; 35 | packages = [pkgs.json-glib appmenu-glib-translator]; 36 | 37 | libname = "tray"; 38 | authors = "kotontrion"; 39 | gir-suffix = "Tray"; 40 | description = "StatusNotifierItem implementation"; 41 | } 42 | -------------------------------------------------------------------------------- /lang/gjs/meson.build: -------------------------------------------------------------------------------- 1 | project('astal-gjs') 2 | 3 | dest = get_option('prefix') / get_option('datadir') / 'astal' / 'gjs' 4 | 5 | dependency('astal-io-0.1') 6 | 7 | gtk3 = dependency('astal-3.0', required: false) 8 | gtk4 = dependency('astal-4-4.0', required: false) 9 | 10 | if (not gtk3.found() and not gtk4.found()) 11 | error('Neither astal-3.0 nor astal-4.0 was found.') 12 | endif 13 | 14 | install_data( 15 | [ 16 | 'src/_app.ts', 17 | 'src/_astal.ts', 18 | 'src/binding.ts', 19 | 'src/file.ts', 20 | 'src/gobject.ts', 21 | 'src/index.ts', 22 | 'src/overrides.ts', 23 | 'src/process.ts', 24 | 'src/time.ts', 25 | 'src/variable.ts', 26 | 'src/package.json', 27 | ], 28 | install_dir: dest, 29 | ) 30 | 31 | install_subdir('src/gtk3', install_dir: dest) 32 | install_subdir('src/gtk4', install_dir: dest) 33 | 34 | import('pkgconfig').generate( 35 | description: 'Astal GJS pacakge', 36 | name: meson.project_name(), 37 | install_dir: get_option('libdir') / 'pkgconfig', 38 | variables: { 39 | 'srcdir': dest, 40 | }, 41 | ) 42 | -------------------------------------------------------------------------------- /lib/astal/gtk4/src/widget/bin.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * A widget with one child. 3 | * It is useful for deriving subclasses, since it provides common code needed for handling a single child widget. 4 | */ 5 | public class Astal.Bin : Gtk.Widget, Gtk.Buildable { 6 | construct { set_layout_manager(new Gtk.BinLayout()); } 7 | 8 | Gtk.Widget _child; 9 | public Gtk.Widget? child { 10 | get { return _child; } 11 | set { 12 | if (_child != null) { 13 | _child.unparent(); 14 | } 15 | 16 | if (value != null) { 17 | _child = value; 18 | value.set_parent(this); 19 | } 20 | } 21 | } 22 | 23 | void add_child(Gtk.Builder builder, Object child, string? type) { 24 | if (child is Gtk.Widget) { 25 | this.child = child as Gtk.Widget; 26 | } else { 27 | base.add_child(builder, child, type); 28 | } 29 | } 30 | 31 | ~Bin() { 32 | if (child != null) { 33 | child.unparent(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/gtk3/simple-bar/vala/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; 4 | astal.url = "github:aylur/astal"; 5 | }; 6 | 7 | outputs = { 8 | self, 9 | nixpkgs, 10 | astal, 11 | }: let 12 | system = "x86_64-linux"; 13 | pkgs = nixpkgs.legacyPackages.${system}; 14 | in { 15 | packages.${system} = { 16 | default = pkgs.stdenv.mkDerivation { 17 | name = "simple-bar"; 18 | src = ./.; 19 | 20 | nativeBuildInputs = with pkgs; [ 21 | meson 22 | ninja 23 | pkg-config 24 | vala 25 | gobject-introspection 26 | dart-sass 27 | ]; 28 | 29 | buildInputs = [ 30 | astal.packages.${system}.astal3 31 | astal.packages.${system}.battery 32 | astal.packages.${system}.wireplumber 33 | astal.packages.${system}.network 34 | astal.packages.${system}.tray 35 | astal.packages.${system}.mpris 36 | astal.packages.${system}.hyprland 37 | ]; 38 | }; 39 | }; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/vala/README.md: -------------------------------------------------------------------------------- 1 | # Simple Astal Bar example in Vala 2 | 3 | This example shows you how to get a Vala+Blueprint+Sass project going. 4 | 5 | ## Dependencies 6 | 7 | - vala 8 | - meson 9 | - blueprint-compiler 10 | - sass 11 | - astal4 12 | - astal-battery 13 | - astal-wireplumber 14 | - astak-network 15 | - astal-mpris 16 | - astak-power-profiles 17 | - astal-tray 18 | - astal-bluetooth 19 | 20 | ## How to use 21 | 22 | > [!NOTE] 23 | > If you are on Nix, there is an example flake included 24 | > otherwise feel free to `rm flake.nix` 25 | 26 | - developing 27 | 28 | ```sh 29 | meson setup build --wipe --prefix "$(pwd)/result" 30 | meson install -C build 31 | ./result/bin/simple-bar 32 | ``` 33 | 34 | - installing 35 | 36 | ```sh 37 | meson setup build --wipe 38 | meson install -C build 39 | simple-bar 40 | ``` 41 | 42 | - adding new vala files will also have to be listed in `meson.build` 43 | - adding new scss files requires no additional steps as long as they are imported from `style.scss` 44 | - adding new ui (blueprint) files will also have to be listed in `meson.build` and in `gresource.xml` 45 | -------------------------------------------------------------------------------- /lib/wireplumber/include/astal/wireplumber/endpoint.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_WIREPLUMBER_ENDPOINT_H 2 | #define ASTAL_WIREPLUMBER_ENDPOINT_H 3 | 4 | #include 5 | 6 | #include "device.h" 7 | #include "node.h" 8 | 9 | G_BEGIN_DECLS 10 | 11 | #define ASTAL_WP_TYPE_ENDPOINT (astal_wp_endpoint_get_type()) 12 | 13 | G_DECLARE_FINAL_TYPE(AstalWpEndpoint, astal_wp_endpoint, ASTAL_WP, ENDPOINT, AstalWpNode) 14 | 15 | guint astal_wp_endpoint_get_device_id(AstalWpEndpoint* self); 16 | AstalWpDevice* astal_wp_endpoint_get_device(AstalWpEndpoint* self); 17 | gboolean astal_wp_endpoint_get_is_default(AstalWpEndpoint* self); 18 | void astal_wp_endpoint_set_is_default(AstalWpEndpoint* self, gboolean is_default); 19 | 20 | guint astal_wp_endpoint_get_route_id(AstalWpEndpoint* self); 21 | void astal_wp_endpoint_set_route_id(AstalWpEndpoint* self, guint route_id); 22 | AstalWpRoute* astal_wp_endpoint_get_route(AstalWpEndpoint* self); 23 | void astal_wp_endpoint_set_route(AstalWpEndpoint* self, AstalWpRoute* route); 24 | GList* astal_wp_endpoint_get_routes(AstalWpEndpoint* self); 25 | 26 | G_END_DECLS 27 | 28 | #endif // !ASTAL_WIREPLUMBER_ENDPOINT_H 29 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/py/README.md: -------------------------------------------------------------------------------- 1 | # Simple Astal Bar example in Python 2 | 3 | This example shows you how to get a Python+Blueprint+Sass project going. 4 | 5 | ## Dependencies 6 | 7 | - python3 8 | - pygobject 9 | - meson 10 | - blueprint-compiler 11 | - sass 12 | - astal4 13 | - astal-battery 14 | - astal-wireplumber 15 | - astak-network 16 | - astal-mpris 17 | - astak-powerprofiles 18 | - astal-tray 19 | - astal-bluetooth 20 | 21 | ## How to use 22 | 23 | > [!NOTE] 24 | > If you are on Nix, there is an example flake included 25 | > otherwise feel free to `rm flake.nix` 26 | 27 | - developing 28 | 29 | ```sh 30 | meson setup build --wipe --prefix "$(pwd)/result" 31 | meson install -C build 32 | ./result/bin/simple-bar 33 | ``` 34 | 35 | - installing 36 | 37 | ```sh 38 | meson setup build --wipe 39 | meson install -C build 40 | simple-bar 41 | ``` 42 | 43 | - adding new python files will also have to be listed in `meson.build` 44 | - adding new scss files requires no additional steps as long as they are imported from `style.scss` 45 | - adding new ui (blueprint) files will also have to be listed in `meson.build` and in `gresource.xml` 46 | -------------------------------------------------------------------------------- /lib/wireplumber/include/astal/wireplumber/video.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_WIREPLUMBER_VIDEO_H 2 | #define ASTAL_WIREPLUMBER_VIDEO_H 3 | 4 | #include 5 | 6 | #include "device.h" 7 | #include "endpoint.h" 8 | #include "stream.h" 9 | 10 | G_BEGIN_DECLS 11 | 12 | #define ASTAL_WP_TYPE_VIDEO (astal_wp_video_get_type()) 13 | 14 | G_DECLARE_FINAL_TYPE(AstalWpVideo, astal_wp_video, ASTAL_WP, VIDEO, GObject) 15 | 16 | AstalWpEndpoint *astal_wp_video_get_source(AstalWpVideo *self, guint id); 17 | AstalWpEndpoint *astal_wp_video_get_sink(AstalWpVideo *self, guint id); 18 | AstalWpStream *astal_wp_video_get_recorder(AstalWpVideo *self, guint id); 19 | AstalWpStream *astal_wp_video_get_stream(AstalWpVideo *self, guint id); 20 | AstalWpDevice *astal_wp_video_get_device(AstalWpVideo *self, guint id); 21 | 22 | GList *astal_wp_video_get_sources(AstalWpVideo *self); 23 | GList *astal_wp_video_get_sinks(AstalWpVideo *self); 24 | GList *astal_wp_video_get_recorders(AstalWpVideo *self); 25 | GList *astal_wp_video_get_streams(AstalWpVideo *self); 26 | GList *astal_wp_video_get_devices(AstalWpVideo *self); 27 | 28 | G_END_DECLS 29 | 30 | #endif // !ASTAL_WIREPLUMBER_VIDEO_H 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | You can contribute by: 4 | 5 | - [Suggesting new features](https://github.com/Aylur/astal/issues/new?assignees=&labels=enhancement&projects=&template=feature_request.md&title=) 6 | - [Reporting bugs](https://github.com/Aylur/astal/issues/new?assignees=&labels=bug&projects=&template=bug_report.md&title=) 7 | - Improving docs with additional contexts and examples 8 | - adding more distros to sections about installations e.g [building from source](https://aylur.github.io/astal/guide/getting-started/installation#building-from-source) 9 | - Adding more example projects to [examples](https://github.com/Aylur/astal/tree/main/examples) 10 | - Adding new language support/binding. For these open a PR for discussions. 11 | - Adding new libraries e.g support for more wayland compositors 12 | - [Adding](https://github.com/Aylur/astal/tree/main/docs#add-your-creation-to-the-showcases-page) your project to the [showcases page](https://aylur.github.io/astal/showcases/). 13 | - Creating packaging for distributions 14 | 15 | ## Adding new libraries 16 | 17 | Write libraries preferably in Vala. Only choose C if some dependency is only available in C e.g wayland. 18 | -------------------------------------------------------------------------------- /lib/bluetooth/src/battery.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * Object representing a [[https://github.com/bluez/bluez/blob/master/doc/org.bluez.Battery.rst|battery]]. 3 | */ 4 | internal class AstalBluetooth.Battery : Object { 5 | private IBattery proxy; 6 | 7 | internal ObjectPath object_path { owned get; private set; } 8 | 9 | internal Battery(IBattery proxy) { 10 | this.proxy = proxy; 11 | this.object_path = (ObjectPath)proxy.g_object_path; 12 | proxy.g_properties_changed.connect((props) => { 13 | var map = (HashTable)props; 14 | foreach (var key in map.get_keys()) { 15 | var prop = kebab_case(key); 16 | if (get_class().find_property(prop) != null) { 17 | notify_property(prop); 18 | } 19 | } 20 | }); 21 | } 22 | 23 | /** 24 | * The percentage of battery left as an unsigned 8-bit integer. 25 | */ 26 | public uint percentage { get { return proxy.percentage; } } 27 | 28 | /** 29 | * Describes where the battery information comes from. 30 | */ 31 | public string source { owned get { return proxy.source; } } 32 | } 33 | -------------------------------------------------------------------------------- /examples/gtk3/simple-bar/vala/meson.build: -------------------------------------------------------------------------------- 1 | project('simple-bar', 'vala', 'c') 2 | 3 | bindir = get_option('prefix') / get_option('bindir') 4 | libdir = get_option('prefix') / get_option('libdir') 5 | 6 | pkgconfig_deps = [ 7 | dependency('glib-2.0'), 8 | dependency('gobject-2.0'), 9 | dependency('gtk+-3.0'), 10 | dependency('libnm'), 11 | dependency('astal-io-0.1'), 12 | dependency('astal-3.0'), 13 | dependency('astal-battery-0.1'), 14 | dependency('astal-wireplumber-0.1'), 15 | dependency('astal-network-0.1'), 16 | dependency('astal-tray-0.1'), 17 | dependency('astal-mpris-0.1'), 18 | dependency('astal-hyprland-0.1'), 19 | ] 20 | 21 | # needed for GLib.Math 22 | deps = pkgconfig_deps + meson.get_compiler('c').find_library('m') 23 | 24 | main = configure_file( 25 | input: 'app.in.vala', 26 | output: 'app.vala', 27 | configuration: { 28 | 'STYLE': run_command( 29 | find_program('sass'), 30 | meson.project_source_root() / 'style.scss', 31 | ).stdout(), 32 | }, 33 | ) 34 | 35 | sources = files( 36 | 'widget/Bar.vala', 37 | ) 38 | 39 | executable( 40 | 'simple-bar', 41 | [sources, main], 42 | dependencies: deps, 43 | install: true, 44 | install_dir: bindir, 45 | ) 46 | -------------------------------------------------------------------------------- /lib/river/include/river-private.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_RIVER_OUTPUT_PRIVATE_H 2 | #define ASTAL_RIVER_OUTPUT_PRIVATE_H 3 | 4 | #include 5 | 6 | #include "astal-river.h" 7 | #include "river-control-unstable-v1-client.h" 8 | #include "river-layout-v3-client.h" 9 | #include "river-status-unstable-v1-client.h" 10 | 11 | G_BEGIN_DECLS 12 | 13 | AstalRiverOutput *astal_river_output_new(guint id, struct wl_output *wl_output, 14 | struct zriver_status_manager_v1 *status_manager, 15 | struct zriver_control_v1 *river_control, 16 | struct wl_seat *seat, struct wl_display *wl_display); 17 | 18 | struct wl_output *astal_river_output_get_wl_output(AstalRiverOutput *self); 19 | void astal_river_output_set_focused_view(AstalRiverOutput *self, const gchar *focused_view); 20 | 21 | AstalRiverLayout *astal_river_layout_new(AstalRiverRiver *river, 22 | struct river_layout_manager_v3 *layout_manager, 23 | struct wl_display *wl_display, const gchar *namespace); 24 | G_END_DECLS 25 | 26 | #endif // !ASTAL_RIVER_OUTPUT_PRIVATE_H 27 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/vala/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 4 | astal = { 5 | url = "github:aylur/astal"; 6 | inputs.nixpkgs.follows = "nixpkgs"; 7 | }; 8 | }; 9 | 10 | outputs = { 11 | self, 12 | nixpkgs, 13 | astal, 14 | }: let 15 | system = "x86_64-linux"; 16 | pkgs = nixpkgs.legacyPackages.${system}; 17 | 18 | nativeBuildInputs = with pkgs; [ 19 | meson 20 | ninja 21 | pkg-config 22 | gobject-introspection 23 | wrapGAppsHook4 24 | blueprint-compiler 25 | dart-sass 26 | vala 27 | ]; 28 | 29 | astalPackages = with astal.packages.${system}; [ 30 | astal4 31 | battery 32 | wireplumber 33 | network 34 | mpris 35 | powerprofiles 36 | tray 37 | bluetooth 38 | ]; 39 | in { 40 | packages.${system}.default = pkgs.stdenv.mkDerivation { 41 | name = "simple-bar"; 42 | src = ./.; 43 | inherit nativeBuildInputs; 44 | buildInputs = astalPackages; 45 | }; 46 | 47 | devShells.${system}.default = pkgs.mkShell { 48 | packages = nativeBuildInputs ++ astalPackages; 49 | }; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /lib/cava/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | mkAstalPkg, 3 | pkgs, 4 | ... 5 | }: let 6 | libcava = pkgs.stdenv.mkDerivation rec { 7 | pname = "cava"; 8 | version = "0.10.6"; 9 | 10 | src = pkgs.fetchFromGitHub { 11 | owner = "LukashonakV"; 12 | repo = "cava"; 13 | rev = "0.10.6"; 14 | hash = "sha256-63be1wypMiqhPA6sjMebmFE6yKpTj/bUE53sMWun554="; 15 | }; 16 | 17 | buildInputs = with pkgs; [ 18 | alsa-lib 19 | libpulseaudio 20 | ncurses 21 | iniparser 22 | sndio 23 | SDL2 24 | libGL 25 | portaudio 26 | jack2 27 | pipewire 28 | ]; 29 | 30 | propagatedBuildInputs = with pkgs; [ 31 | fftw 32 | ]; 33 | 34 | nativeBuildInputs = with pkgs; [ 35 | autoreconfHook 36 | autoconf-archive 37 | pkgconf 38 | meson 39 | ninja 40 | ]; 41 | 42 | preAutoreconf = '' 43 | echo ${version} > version 44 | ''; 45 | }; 46 | in 47 | mkAstalPkg { 48 | pname = "astal-cava"; 49 | src = ./.; 50 | packages = [libcava]; 51 | 52 | libname = "cava"; 53 | authors = "kotontrion"; 54 | gir-suffix = "Cava"; 55 | description = "Audio visualization library using cava"; 56 | } 57 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Astal Docs 2 | 3 | This directory contains the Astal documentation and Library references. Hosted 4 | at [aylur.github.io/astal](https://aylur.github.io/astal/) and 5 | [aylur.github.io/libastal](https://aylur.github.io/libastal/) 6 | 7 | ## Commands 8 | 9 | | Command | Action | 10 | | :---------------- | :------------------------------------------ | 11 | | `npm install` | Installs dependencies | 12 | | `npm run dev` | Starts local dev server at `localhost:5173` | 13 | | `npm run build` | Build your production site to `./dist/` | 14 | | `npm run preview` | Preview your build locally | 15 | 16 | ## Add your creation to the showcases page 17 | 18 | 1. Add your image as a webp to `public/showcase` 19 | 2. Add it to `showcases/showcases.ts` 20 | - `src` should be `/astal/showcase/your-name-optional-title.webp` 21 | - `url` should point to the source code of the showcased widget/setup 22 | - `author` should be your name/nickname 23 | 24 | ``` 25 | . 26 | ├── public/showcase 27 | │ └── your-name-optional-title.webp # 1. add image 28 | └── showcases/ 29 | └── showcases.ts # 2. add information 30 | ``` 31 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 4 | astal = { 5 | url = "github:aylur/astal"; 6 | inputs.nixpkgs.follows = "nixpkgs"; 7 | }; 8 | }; 9 | 10 | outputs = { 11 | self, 12 | nixpkgs, 13 | astal, 14 | }: let 15 | system = "x86_64-linux"; 16 | pkgs = nixpkgs.legacyPackages.${system}; 17 | 18 | nativeBuildInputs = with pkgs; [ 19 | meson 20 | ninja 21 | pkg-config 22 | gobject-introspection 23 | wrapGAppsHook4 24 | blueprint-compiler 25 | dart-sass 26 | esbuild 27 | ]; 28 | 29 | astalPackages = with astal.packages.${system}; [ 30 | astal4 31 | battery 32 | wireplumber 33 | network 34 | mpris 35 | powerprofiles 36 | tray 37 | bluetooth 38 | ]; 39 | in { 40 | packages.${system}.default = pkgs.stdenv.mkDerivation { 41 | name = "simple-bar"; 42 | src = ./.; 43 | inherit nativeBuildInputs; 44 | buildInputs = astalPackages ++ [pkgs.gjs]; 45 | }; 46 | 47 | devShells.${system}.default = pkgs.mkShell { 48 | packages = nativeBuildInputs ++ astalPackages ++ [pkgs.gjs]; 49 | }; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /lang/lua/astal-dev-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "astal" 2 | version = "dev-1" 3 | 4 | source = { 5 | url = "git+https://github.com/aylur/astal", 6 | } 7 | 8 | description = { 9 | summary = "lua bindings for libastal.", 10 | homepage = "https://aylur.github.io/astal/", 11 | license = "LGPL-2.1", 12 | } 13 | 14 | dependencies = { 15 | "lua >= 5.1, < 5.4", 16 | "lgi >= 0.9.2", 17 | } 18 | 19 | build = { 20 | type = "builtin", 21 | modules = { 22 | ["astal.binding"] = "astal/binding.lua", 23 | ["astal.file"] = "astal/file.lua", 24 | ["astal.init"] = "astal/init.lua", 25 | ["astal.process"] = "astal/process.lua", 26 | ["astal.time"] = "astal/time.lua", 27 | ["astal.variable"] = "astal/variable.lua", 28 | ["astal.gtk3.app"] = "astal/gtk3/app.lua", 29 | ["astal.gtk3.init"] = "astal/gtk3/init.lua", 30 | ["astal.gtk3.astalify"] = "astal/gtk3/astalify.lua", 31 | ["astal.gtk3.widget"] = "astal/gtk3/widget.lua", 32 | -- ["astal.gtk4.app"] = "astal/gtk4/app.lua", 33 | -- ["astal.gtk4.init"] = "astal/gtk4/init.lua", 34 | -- ["astal.gtk4.astalify"] = "astal/gtk4/astalify.lua", 35 | -- ["astal.gtk4.widget"] = "astal/gtk4/widget.lua", 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /lib/astal/gtk3/src/widget/stack.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * Subclass of [class@Gtk.Stack] that has a children setter which 3 | * invokes [method@Gt.Stack.add_named] with the child's [property@Gtk.Widget:name] property. 4 | */ 5 | public class Astal.Stack : Gtk.Stack { 6 | /** 7 | * Same as [property@Gtk.Stack:visible-child-name]. 8 | */ 9 | [CCode (notify = false)] 10 | public string shown { 11 | get { return visible_child_name; } 12 | set { visible_child_name = value; } 13 | } 14 | 15 | public List children { 16 | set { _set_children(value); } 17 | owned get { return get_children(); } 18 | } 19 | 20 | private void _set_children(List arr) { 21 | foreach(var child in get_children()) { 22 | remove(child); 23 | } 24 | 25 | var i = 0; 26 | foreach(var child in arr) { 27 | if (child.name != null) { 28 | add_named(child, child.name); 29 | } else { 30 | add_named(child, (++i).to_string()); 31 | } 32 | } 33 | } 34 | 35 | construct { 36 | notify["visible_child_name"].connect(() => { 37 | notify_property("shown"); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/vala/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "astal": { 4 | "inputs": { 5 | "nixpkgs": [ 6 | "nixpkgs" 7 | ] 8 | }, 9 | "locked": { 10 | "lastModified": 1754893912, 11 | "narHash": "sha256-kzU/3A4k+d3PsgMLohzSh4KJybTqvzqibUVqV2yXCGY=", 12 | "owner": "aylur", 13 | "repo": "astal", 14 | "rev": "5d4eef66392b0dff99a63a4f39ff886624bd69dd", 15 | "type": "github" 16 | }, 17 | "original": { 18 | "owner": "aylur", 19 | "repo": "astal", 20 | "type": "github" 21 | } 22 | }, 23 | "nixpkgs": { 24 | "locked": { 25 | "lastModified": 1756266583, 26 | "narHash": "sha256-cr748nSmpfvnhqSXPiCfUPxRz2FJnvf/RjJGvFfaCsM=", 27 | "owner": "nixos", 28 | "repo": "nixpkgs", 29 | "rev": "8a6d5427d99ec71c64f0b93d45778c889005d9c2", 30 | "type": "github" 31 | }, 32 | "original": { 33 | "owner": "nixos", 34 | "ref": "nixos-unstable", 35 | "repo": "nixpkgs", 36 | "type": "github" 37 | } 38 | }, 39 | "root": { 40 | "inputs": { 41 | "astal": "astal", 42 | "nixpkgs": "nixpkgs" 43 | } 44 | } 45 | }, 46 | "root": "root", 47 | "version": 7 48 | } 49 | -------------------------------------------------------------------------------- /lib/auth/include/astal-auth.h.in: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_AUTH_PAM_H 2 | #define ASTAL_AUTH_PAM_H 3 | 4 | #include 5 | #include 6 | 7 | 8 | #define ASTAL_AUTH_MAJOR_VERSION @MAJOR_VERSION@ 9 | #define ASTAL_AUTH_MINOR_VERSION @MINOR_VERSION@ 10 | #define ASTAL_AUTH_MICRO_VERSION @MICRO_VERSION@ 11 | #define ASTAL_AUTH_VERSION "@VERSION@" 12 | 13 | G_BEGIN_DECLS 14 | 15 | #define ASTAL_AUTH_TYPE_PAM (astal_auth_pam_get_type()) 16 | 17 | G_DECLARE_FINAL_TYPE(AstalAuthPam, astal_auth_pam, ASTAL_AUTH, PAM, GObject) 18 | 19 | void astal_auth_pam_set_username(AstalAuthPam *self, const gchar *username); 20 | 21 | const gchar *astal_auth_pam_get_username(AstalAuthPam *self); 22 | 23 | void astal_auth_pam_set_service(AstalAuthPam *self, const gchar *service); 24 | 25 | const gchar *astal_auth_pam_get_service(AstalAuthPam *self); 26 | 27 | gboolean astal_auth_pam_start_authenticate(AstalAuthPam *self); 28 | 29 | void astal_auth_pam_supply_secret(AstalAuthPam *self, const gchar *secret); 30 | 31 | gboolean astal_auth_pam_authenticate(const gchar *password, GAsyncReadyCallback result_callback, 32 | gpointer user_data); 33 | 34 | gssize astal_auth_pam_authenticate_finish(GAsyncResult *res, GError **error); 35 | 36 | G_END_DECLS 37 | 38 | #endif // !ASTAL_AUTH_PAM_H 39 | -------------------------------------------------------------------------------- /.github/workflows/vitepress.yml: -------------------------------------------------------------------------------- 1 | name: Deploy VitePress site 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | permissions: 8 | contents: read 9 | pages: write 10 | id-token: write 11 | 12 | jobs: 13 | build-vitepress: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 # Not needed if lastUpdated is not enabled 20 | 21 | - name: Setup Node 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 20 25 | 26 | - name: Setup Pages 27 | uses: actions/configure-pages@v4 28 | 29 | - name: Install dependencies 30 | run: npm ci 31 | working-directory: docs 32 | 33 | - name: Build with VitePress 34 | run: npm run build 35 | working-directory: docs 36 | 37 | - name: Upload artifact 38 | uses: actions/upload-pages-artifact@v3 39 | with: 40 | path: docs/dist 41 | 42 | deploy-vitepress: 43 | environment: 44 | name: github-pages 45 | url: ${{ steps.deployment.outputs.page_url }} 46 | needs: build-vitepress 47 | runs-on: ubuntu-latest 48 | steps: 49 | - name: Deploy to GitHub Pages 50 | id: deployment 51 | uses: actions/deploy-pages@v4 52 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/py/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 4 | astal = { 5 | url = "github:aylur/astal"; 6 | inputs.nixpkgs.follows = "nixpkgs"; 7 | }; 8 | }; 9 | 10 | outputs = { 11 | self, 12 | nixpkgs, 13 | astal, 14 | }: let 15 | system = "x86_64-linux"; 16 | pkgs = nixpkgs.legacyPackages.${system}; 17 | 18 | python = pkgs.python3.withPackages (ps: [ 19 | ps.pygobject3 20 | ]); 21 | 22 | nativeBuildInputs = with pkgs; [ 23 | meson 24 | ninja 25 | pkg-config 26 | gobject-introspection 27 | wrapGAppsHook4 28 | blueprint-compiler 29 | dart-sass 30 | ]; 31 | 32 | astalPackages = with astal.packages.${system}; [ 33 | astal4 34 | battery 35 | wireplumber 36 | network 37 | mpris 38 | powerprofiles 39 | tray 40 | bluetooth 41 | ]; 42 | in { 43 | packages.${system}.default = pkgs.stdenv.mkDerivation { 44 | name = "simple-bar"; 45 | src = ./.; 46 | inherit nativeBuildInputs; 47 | buildInputs = astalPackages ++ [python]; 48 | }; 49 | 50 | devShells.${system}.default = pkgs.mkShell { 51 | packages = nativeBuildInputs ++ astalPackages ++ [python]; 52 | }; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /lib/hyprland/src/cli.vala: -------------------------------------------------------------------------------- 1 | static bool help; 2 | static bool version; 3 | 4 | const OptionEntry[] options = { 5 | { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, 6 | { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, 7 | { null }, 8 | }; 9 | 10 | int main(string[] argv) { 11 | try { 12 | var opts = new OptionContext(); 13 | opts.add_main_entries(options, null); 14 | opts.set_help_enabled(false); 15 | opts.set_ignore_unknown_options(false); 16 | opts.parse(ref argv); 17 | } catch (OptionError err) { 18 | printerr(err.message); 19 | return 1; 20 | } 21 | 22 | if (help) { 23 | print("Usage:\n"); 24 | print(" %s [flags]\n\n", argv[0]); 25 | print("Flags:\n"); 26 | print(" -h, --help Print this help and exit\n"); 27 | print(" -v, --version Print version number and exit\n"); 28 | return 0; 29 | } 30 | 31 | if (version) { 32 | print(AstalHyprland.VERSION); 33 | return 0; 34 | } 35 | 36 | AstalHyprland.Hyprland.get_default().event.connect((event, args) => { 37 | print("{ event: \"%s\", payload: \"%s\" }\n", event, args); 38 | }); 39 | 40 | new MainLoop(null, false).run(); 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /lang/lua/astal/file.lua: -------------------------------------------------------------------------------- 1 | local lgi = require("lgi") 2 | local Astal = lgi.require("AstalIO", "0.1") 3 | local GObject = lgi.require("GObject", "2.0") 4 | 5 | local M = {} 6 | 7 | ---@param path string 8 | ---@return string 9 | function M.read_file(path) 10 | return Astal.read_file(path) 11 | end 12 | 13 | ---@param path string 14 | ---@param callback fun(content: string, err: string): nil 15 | function M.read_file_async(path, callback) 16 | Astal.read_file_async(path, function(_, res) 17 | local content, err = Astal.read_file_finish(res) 18 | callback(content, err) 19 | end) 20 | end 21 | 22 | ---@param path string 23 | ---@param content string 24 | function M.write_file(path, content) 25 | Astal.write_file(path, content) 26 | end 27 | 28 | ---@param path string 29 | ---@param content string 30 | ---@param callback? fun(err: string): nil 31 | function M.write_file_async(path, content, callback) 32 | Astal.write_file_async(path, content, function(_, res) 33 | if type(callback) == "function" then 34 | callback(Astal.write_file_finish(res)) 35 | end 36 | end) 37 | end 38 | 39 | ---@param path string 40 | ---@param callback fun(file: string, event: integer): nil 41 | function M.monitor_file(path, callback) 42 | return Astal.monitor_file(path, GObject.Closure(callback)) 43 | end 44 | 45 | return M 46 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/README.md: -------------------------------------------------------------------------------- 1 | # Simple Astal Bar example in TypeScript 2 | 3 | This example shows you how to get a TypeScript+Blueprint+Sass project going. 4 | 5 | ## Dependencies 6 | 7 | - gjs 8 | - meson 9 | - esbuild 10 | - blueprint-compiler 11 | - sass 12 | - astal4 13 | - astal-battery 14 | - astal-wireplumber 15 | - astak-network 16 | - astal-mpris 17 | - astak-power-profiles 18 | - astal-tray 19 | - astal-bluetooth 20 | 21 | ## How to use 22 | 23 | > [!NOTE] 24 | > If you are on Nix, there is an example flake included 25 | > otherwise feel free to `rm flake.nix` 26 | 27 | - generate types with `ts-for-gir` 28 | 29 | ```sh 30 | # might take a while 31 | # also, don't worry about warning and error logs 32 | npx @ts-for-gir/cli generate --ignoreVersionConflicts 33 | ``` 34 | 35 | - developing 36 | 37 | ```sh 38 | meson setup build --wipe --prefix "$(pwd)/result" 39 | meson install -C build 40 | ./result/bin/simple-bar 41 | ``` 42 | 43 | - installing 44 | 45 | ```sh 46 | meson setup build --wipe 47 | meson install -C build 48 | simple-bar 49 | ``` 50 | 51 | - adding new typescript files requires no additional steps 52 | - adding new scss files requires no additional steps as long as they are imported from `style.scss` 53 | - adding new ui (blueprint) files will also have to be listed in `meson.build` and in `gresource.xml` 54 | -------------------------------------------------------------------------------- /lib/wireplumber/include/astal/wireplumber/audio.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_WIREPLUMBER_AUDIO_H 2 | #define ASTAL_WIREPLUMBER_AUDIO_H 3 | 4 | #include 5 | 6 | #include "device.h" 7 | #include "endpoint.h" 8 | #include "stream.h" 9 | 10 | G_BEGIN_DECLS 11 | 12 | #define ASTAL_WP_TYPE_AUDIO (astal_wp_audio_get_type()) 13 | 14 | G_DECLARE_FINAL_TYPE(AstalWpAudio, astal_wp_audio, ASTAL_WP, AUDIO, GObject) 15 | 16 | AstalWpEndpoint *astal_wp_audio_get_speaker(AstalWpAudio *self, guint id); 17 | AstalWpEndpoint *astal_wp_audio_get_microphone(AstalWpAudio *self, guint id); 18 | AstalWpStream *astal_wp_audio_get_recorder(AstalWpAudio *self, guint id); 19 | AstalWpStream *astal_wp_audio_get_stream(AstalWpAudio *self, guint id); 20 | AstalWpNode *astal_wp_audio_get_node(AstalWpAudio *self, guint id); 21 | AstalWpDevice *astal_wp_audio_get_device(AstalWpAudio *self, guint id); 22 | 23 | AstalWpEndpoint *astal_wp_audio_get_default_speaker(AstalWpAudio *self); 24 | AstalWpEndpoint *astal_wp_audio_get_default_microphone(AstalWpAudio *self); 25 | 26 | GList *astal_wp_audio_get_microphones(AstalWpAudio *self); 27 | GList *astal_wp_audio_get_speakers(AstalWpAudio *self); 28 | GList *astal_wp_audio_get_recorders(AstalWpAudio *self); 29 | GList *astal_wp_audio_get_streams(AstalWpAudio *self); 30 | GList *astal_wp_audio_get_devices(AstalWpAudio *self); 31 | 32 | G_END_DECLS 33 | 34 | #endif // !ASTAL_WIREPLUMBER_AUDIO_H 35 | -------------------------------------------------------------------------------- /lib/wireplumber/include/astal/wireplumber/wp.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_WIREPLUMBER_H 2 | #define ASTAL_WIREPLUMBER_H 3 | 4 | #include 5 | 6 | #include "audio.h" 7 | #include "device.h" 8 | #include "enums.h" 9 | #include "node.h" 10 | #include "video.h" 11 | 12 | G_BEGIN_DECLS 13 | 14 | #define ASTAL_WP_TYPE_WP (astal_wp_wp_get_type()) 15 | 16 | G_DECLARE_FINAL_TYPE(AstalWpWp, astal_wp_wp, ASTAL_WP, WP, GObject) 17 | 18 | AstalWpWp* astal_wp_wp_get_default(); 19 | AstalWpWp* astal_wp_get_default(); 20 | 21 | AstalWpAudio* astal_wp_wp_get_audio(AstalWpWp* self); 22 | AstalWpVideo* astal_wp_wp_get_video(AstalWpWp* self); 23 | AstalWpVideo* astal_wp_video_new(AstalWpWp* wp); 24 | AstalWpAudio* astal_wp_audio_new(AstalWpWp* wp); 25 | 26 | AstalWpNode* astal_wp_wp_get_node(AstalWpWp* self, guint id); 27 | GList* astal_wp_wp_get_nodes(AstalWpWp* self); 28 | AstalWpNode* astal_wp_wp_get_node_by_serial(AstalWpWp* self, gint serial); 29 | 30 | AstalWpDevice* astal_wp_wp_get_device(AstalWpWp* self, guint id); 31 | GList* astal_wp_wp_get_devices(AstalWpWp* self); 32 | 33 | AstalWpEndpoint* astal_wp_wp_get_default_speaker(AstalWpWp* self); 34 | AstalWpEndpoint* astal_wp_wp_get_default_microphone(AstalWpWp* self); 35 | 36 | AstalWpScale astal_wp_wp_get_scale(AstalWpWp* self); 37 | void astal_wp_wp_set_scale(AstalWpWp* self, AstalWpScale scale); 38 | 39 | G_END_DECLS 40 | 41 | #endif // !ASTAL_WIREPLUMBER_H 42 | -------------------------------------------------------------------------------- /lib/wireplumber/include/private/node-private.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_WP_NODE_PRIV_H 2 | #define ASTAL_WP_NODE_PRIV_H 3 | 4 | #include 5 | #include 6 | 7 | #include "endpoint.h" 8 | #include "node.h" 9 | #include "stream.h" 10 | #include "wp.h" 11 | 12 | G_BEGIN_DECLS 13 | 14 | AstalWpStream *astal_wp_stream_new(WpNode *node, WpPlugin *mixer, AstalWpWp *wp); 15 | 16 | AstalWpEndpoint *astal_wp_endpoint_new(WpNode *node, WpPlugin *mixer, WpPlugin *defaults, 17 | AstalWpWp *wp); 18 | AstalWpEndpoint *astal_wp_endpoint_new_default(AstalWpWp *wp); 19 | 20 | void astal_wp_endpoint_init_as_default(AstalWpEndpoint *self, WpPlugin *mixer, WpPlugin *defaults, 21 | AstalWpMediaClass type); 22 | 23 | void astal_wp_node_update_default(AstalWpNode *self, gboolean is_default); 24 | void astal_wp_node_update_volume(AstalWpNode *self); 25 | void astal_wp_node_set_channel_volume(AstalWpNode *self, const gchar *name, gdouble volume); 26 | void astal_wp_node_set_icon(AstalWpNode *self, const gchar *icon); 27 | void astal_wp_node_set_node(AstalWpNode *self, WpNode *node); 28 | void astal_wp_node_set_mixer(AstalWpNode *self, WpPlugin *mixer); 29 | void astal_wp_node_set_type(AstalWpNode *self, AstalWpMediaClass type); 30 | 31 | void astal_wp_node_properties_changed(AstalWpNode *self); 32 | G_END_DECLS 33 | 34 | #endif // !ASTAL_WP_NODE_PRIV_H 35 | -------------------------------------------------------------------------------- /lang/gjs/src/file.ts: -------------------------------------------------------------------------------- 1 | import Astal from "gi://AstalIO" 2 | import Gio from "gi://Gio?version=2.0" 3 | 4 | export { Gio } 5 | 6 | export function readFile(path: string): string { 7 | return Astal.read_file(path) || "" 8 | } 9 | 10 | export function readFileAsync(path: string): Promise { 11 | return new Promise((resolve, reject) => { 12 | Astal.read_file_async(path, (_, res) => { 13 | try { 14 | resolve(Astal.read_file_finish(res) || "") 15 | } catch (error) { 16 | reject(error) 17 | } 18 | }) 19 | }) 20 | } 21 | 22 | export function writeFile(path: string, content: string): void { 23 | Astal.write_file(path, content) 24 | } 25 | 26 | export function writeFileAsync(path: string, content: string): Promise { 27 | return new Promise((resolve, reject) => { 28 | Astal.write_file_async(path, content, (_, res) => { 29 | try { 30 | resolve(Astal.write_file_finish(res)) 31 | } catch (error) { 32 | reject(error) 33 | } 34 | }) 35 | }) 36 | } 37 | 38 | export function monitorFile( 39 | path: string, 40 | callback: (file: string, event: Gio.FileMonitorEvent) => void, 41 | ): Gio.FileMonitor { 42 | return Astal.monitor_file(path, (file: string, event: Gio.FileMonitorEvent) => { 43 | callback(file, event) 44 | })! 45 | } 46 | -------------------------------------------------------------------------------- /lib/astal/gtk3/src/widget/scrollable.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * Subclass of [class@Gtk.ScrolledWindow] which has its policy default to 3 | * [enum@Gtk.PolicyType.AUTOMATIC]. 4 | * 5 | * Its css selector is `scrollable`. 6 | * Its child getter returns the child of the inner 7 | * [class@Gtk.Viewport], instead of the viewport. 8 | */ 9 | public class Astal.Scrollable : Gtk.ScrolledWindow { 10 | private Gtk.PolicyType _hscroll = Gtk.PolicyType.AUTOMATIC; 11 | private Gtk.PolicyType _vscroll = Gtk.PolicyType.AUTOMATIC; 12 | 13 | public Gtk.PolicyType hscroll { 14 | get { return _hscroll; } 15 | set { 16 | _hscroll = value; 17 | set_policy(value, vscroll); 18 | } 19 | } 20 | 21 | public Gtk.PolicyType vscroll { 22 | get { return _vscroll; } 23 | set { 24 | _vscroll = value; 25 | set_policy(hscroll, value); 26 | } 27 | } 28 | 29 | static construct { 30 | set_css_name("scrollable"); 31 | } 32 | 33 | construct { 34 | if (hadjustment != null) 35 | hadjustment = new Gtk.Adjustment(0,0,0,0,0,0); 36 | 37 | if (vadjustment != null) 38 | vadjustment = new Gtk.Adjustment(0,0,0,0,0,0); 39 | } 40 | 41 | public new Gtk.Widget get_child() { 42 | var ch = base.get_child(); 43 | if (ch is Gtk.Viewport) { 44 | return ch.get_child(); 45 | } 46 | return ch; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/wayland-glib/wl-source.vala: -------------------------------------------------------------------------------- 1 | namespace WlGlib { 2 | public class WlSource : Source { 3 | 4 | public Wl.Display display; 5 | public void* fd; 6 | public int error; 7 | 8 | public override bool dispatch(SourceFunc callback) { 9 | IOCondition revents = this.query_unix_fd(this.fd); 10 | if (this.error > 0 || (revents & (IOCondition.ERR | IOCondition.HUP)) != 0) { 11 | errno = this.error; 12 | if(callback != null) return callback(); 13 | return Source.REMOVE; 14 | } 15 | if (((revents & IOCondition.IN) != 0) && this.display.dispatch() < 0) { 16 | if(callback != null) return callback(); 17 | return Source.REMOVE; 18 | } 19 | return Source.CONTINUE; 20 | } 21 | 22 | public override bool check() { 23 | IOCondition revents = this.query_unix_fd(this.fd); 24 | return revents > 0; 25 | } 26 | 27 | public override bool prepare(out int timeout) { 28 | if(this.display.flush() < 0) 29 | this.error = errno; 30 | timeout = -1; 31 | return false; 32 | } 33 | 34 | public WlSource() { 35 | base(); 36 | this.display = new Wl.Display.connect(null); 37 | if(this.display == null) return; 38 | this.fd = this.add_unix_fd(this.display.get_fd(), 39 | IOCondition.IN | IOCondition.ERR | IOCondition.HUP); 40 | this.attach(null); 41 | } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | outputs = { 3 | self, 4 | nixpkgs, 5 | }: let 6 | forAllSystems = nixpkgs.lib.genAttrs ["x86_64-linux" "aarch64-linux"]; 7 | in { 8 | packages = forAllSystems (system: let 9 | pkgs = nixpkgs.legacyPackages.${system}; 10 | mkPkg = src: 11 | import src { 12 | inherit self pkgs; 13 | mkAstalPkg = import ./nix/mkAstalPkg.nix pkgs; 14 | }; 15 | in { 16 | default = self.packages.${system}.io; 17 | docs = import ./docs {inherit self pkgs;}; 18 | 19 | io = mkPkg ./lib/astal/io; 20 | astal3 = mkPkg ./lib/astal/gtk3; 21 | astal4 = mkPkg ./lib/astal/gtk4; 22 | apps = mkPkg ./lib/apps; 23 | auth = mkPkg ./lib/auth; 24 | battery = mkPkg ./lib/battery; 25 | bluetooth = mkPkg ./lib/bluetooth; 26 | cava = mkPkg ./lib/cava; 27 | greet = mkPkg ./lib/greet; 28 | hyprland = mkPkg ./lib/hyprland; 29 | mpris = mkPkg ./lib/mpris; 30 | network = mkPkg ./lib/network; 31 | notifd = mkPkg ./lib/notifd; 32 | powerprofiles = mkPkg ./lib/powerprofiles; 33 | river = mkPkg ./lib/river; 34 | tray = mkPkg ./lib/tray; 35 | wireplumber = mkPkg ./lib/wireplumber; 36 | }); 37 | 38 | devShells = forAllSystems (system: 39 | import ./nix/devshell.nix { 40 | inherit self; 41 | pkgs = nixpkgs.legacyPackages.${system}; 42 | }); 43 | }; 44 | 45 | inputs = { 46 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /docs/showcases/Showcases.vue: -------------------------------------------------------------------------------- 1 | 5 | 37 | 38 | 64 | -------------------------------------------------------------------------------- /lib/river/src/astal-river.c: -------------------------------------------------------------------------------- 1 | #include "astal-river.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "gio/gio.h" 8 | 9 | GMainLoop* loop; 10 | 11 | void print_json(AstalRiverRiver* river) { 12 | JsonNode* json = json_gobject_serialize(G_OBJECT(river)); 13 | 14 | gchar* json_str = json_to_string(json, FALSE); 15 | g_print("%s\n", json_str); 16 | json_node_free(json); 17 | g_free(json_str); 18 | } 19 | 20 | int main(int argc, char** argv) { 21 | gboolean daemon = FALSE; 22 | 23 | int opt; 24 | const char* optstring = "d"; 25 | 26 | static struct option long_options[] = {{"daemon", no_argument, NULL, 'd'}, {NULL, 0, NULL, 0}}; 27 | 28 | while ((opt = getopt_long(argc, argv, optstring, long_options, NULL)) != -1) { 29 | switch (opt) { 30 | case 'd': 31 | daemon = TRUE; 32 | break; 33 | default: 34 | g_print("Usage: %s [-d]\n", argv[0]); 35 | exit(EXIT_FAILURE); 36 | } 37 | } 38 | 39 | GError* error = NULL; 40 | AstalRiverRiver* river = g_initable_new(ASTAL_RIVER_TYPE_RIVER, NULL, &error, NULL); 41 | if (error) { 42 | g_critical("%s\n", error->message); 43 | exit(EXIT_FAILURE); 44 | } 45 | if (daemon) { 46 | loop = g_main_loop_new(NULL, FALSE); 47 | g_signal_connect(river, "changed", G_CALLBACK(print_json), NULL); 48 | g_main_loop_run(loop); 49 | } else { 50 | print_json(river); 51 | g_object_unref(river); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/astal/gtk3/src/widget/box.vala: -------------------------------------------------------------------------------- 1 | public class Astal.Box : Gtk.Box { 2 | /** 3 | * Corresponds to [property@Gtk.Orientable :orientation]. 4 | */ 5 | [CCode (notify = false)] 6 | public bool vertical { 7 | get { return orientation == Gtk.Orientation.VERTICAL; } 8 | set { orientation = value ? Gtk.Orientation.VERTICAL : Gtk.Orientation.HORIZONTAL; } 9 | } 10 | 11 | public List children { 12 | set { _set_children(value); } 13 | owned get { return get_children(); } 14 | } 15 | 16 | public new Gtk.Widget child { 17 | owned get { return _get_child(); } 18 | set { _set_child(value); } 19 | } 20 | 21 | construct { 22 | notify["orientation"].connect(() => { 23 | notify_property("vertical"); 24 | }); 25 | } 26 | 27 | private void _set_child(Gtk.Widget child) { 28 | var list = new List(); 29 | list.append(child); 30 | _set_children(list); 31 | } 32 | 33 | private Gtk.Widget? _get_child() { 34 | foreach(var child in get_children()) 35 | return child; 36 | 37 | return null; 38 | } 39 | 40 | private void _set_children(List arr) { 41 | foreach(var child in get_children()) { 42 | remove(child); 43 | } 44 | 45 | foreach(var child in arr) 46 | add(child); 47 | } 48 | 49 | public Box(bool vertical, List children) { 50 | this.vertical = vertical; 51 | _set_children(children); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/astal/gtk4/src/widget/box.vala: -------------------------------------------------------------------------------- 1 | [Version (deprecated = true, deprecated_since="", replacement="")] 2 | public class Astal.Box : Gtk.Box { 3 | /** 4 | * Corresponds to [property@Gtk.Orientable :orientation]. 5 | */ 6 | [CCode (notify = false)] 7 | public bool vertical { 8 | get { return orientation == Gtk.Orientation.VERTICAL; } 9 | set { orientation = value ? Gtk.Orientation.VERTICAL : Gtk.Orientation.HORIZONTAL; } 10 | } 11 | 12 | construct { 13 | notify["orientation"].connect(() => { 14 | notify_property("vertical"); 15 | }); 16 | } 17 | 18 | public List children { 19 | set { 20 | foreach (var child in children) { 21 | remove(child); 22 | } 23 | foreach (var child in value) { 24 | append(child); 25 | } 26 | } 27 | owned get { 28 | var list = new List(); 29 | var child = get_first_child(); 30 | while (child != null) { 31 | list.append(child); 32 | child = child.get_next_sibling(); 33 | } 34 | return list; 35 | } 36 | } 37 | 38 | public Gtk.Widget? child { 39 | owned get { 40 | foreach (var child in children) { 41 | return child; 42 | } 43 | return null; 44 | } 45 | set { 46 | var list = new List(); 47 | list.append(child); 48 | this.children = children; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/auth/src/meson.build: -------------------------------------------------------------------------------- 1 | srcs = files( 2 | 'pam.c', 3 | ) 4 | 5 | deps = [dependency('gobject-2.0'), dependency('gio-2.0'), dependency('pam')] 6 | 7 | astal_auth_lib = library( 8 | 'astal-auth', 9 | sources: srcs, 10 | include_directories: astal_auth_inc, 11 | dependencies: deps, 12 | version: meson.project_version(), 13 | install: true, 14 | ) 15 | 16 | libastal_auth = declare_dependency(link_with: astal_auth_lib, include_directories: astal_auth_inc) 17 | 18 | executable( 19 | 'astal-auth', 20 | files('astal-auth.c'), 21 | dependencies: [dependency('gobject-2.0'), libastal_auth], 22 | install: true, 23 | ) 24 | 25 | pkg_config_name = 'astal-auth-' + lib_so_version 26 | 27 | if get_option('introspection') 28 | gir = gnome.generate_gir( 29 | astal_auth_lib, 30 | sources: srcs + astal_auth_headers, 31 | nsversion: '0.1', 32 | namespace: 'AstalAuth', 33 | symbol_prefix: 'astal_auth', 34 | identifier_prefix: 'AstalAuth', 35 | includes: ['GObject-2.0', 'Gio-2.0'], 36 | header: 'astal-auth.h', 37 | export_packages: pkg_config_name, 38 | install: true, 39 | ) 40 | 41 | if get_option('vapi') 42 | gnome.generate_vapi( 43 | pkg_config_name, 44 | sources: [gir[0]], 45 | packages: ['gobject-2.0', 'gio-2.0'], 46 | install: true, 47 | ) 48 | endif 49 | endif 50 | 51 | pkg_config.generate( 52 | name: 'astal-auth', 53 | version: meson.project_version(), 54 | libraries: [astal_auth_lib], 55 | filebase: pkg_config_name, 56 | subdirs: 'astal', 57 | description: 'astal authentication module', 58 | url: 'https://github.com/astal-sh/auth', 59 | ) 60 | -------------------------------------------------------------------------------- /.github/workflows/gi-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy GI docs 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | permissions: 8 | contents: write 9 | pages: write 10 | id-token: write 11 | 12 | jobs: 13 | gi-docs: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Install Nix 17 | uses: DeterminateSystems/nix-installer-action@main 18 | with: 19 | logger: pretty 20 | 21 | - name: Checkout Source Repository 22 | uses: actions/checkout@v4 23 | with: 24 | path: src 25 | 26 | - name: Build Documentation 27 | run: | 28 | cd src 29 | nix build .#docs --print-build-logs 30 | 31 | - name: Checkout Destination Repo 32 | uses: actions/checkout@v4 33 | with: 34 | token: ${{ secrets.token }} 35 | repository: aylur/aylur.github.io 36 | ref: main 37 | path: dist 38 | 39 | - name: Configure Git 40 | run: | 41 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 42 | git config --global user.name "GitHub Actions Bot" 43 | 44 | - name: Clean and Copy Files 45 | run: | 46 | rm -rf dist/libastal 47 | mkdir dist/libastal 48 | cp -r src/result/* dist/libastal 49 | 50 | - name: Push to Pages Repo 51 | run: | 52 | cd dist 53 | git add . 54 | if [ -n "$(git diff --cached)" ]; then 55 | git commit -m "Deployed from https://github.com/${{ github.repository }}/commit/${{ github.sha }}" 56 | git push origin main 57 | fi 58 | -------------------------------------------------------------------------------- /lib/wireplumber/include/astal/wireplumber/device.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_WP_DEVICE_H 2 | #define ASTAL_WP_DEVICE_H 3 | 4 | #include 5 | 6 | #include "profile.h" 7 | #include "route.h" 8 | 9 | G_BEGIN_DECLS 10 | 11 | #define ASTAL_WP_TYPE_DEVICE (astal_wp_device_get_type()) 12 | 13 | G_DECLARE_FINAL_TYPE(AstalWpDevice, astal_wp_device, ASTAL_WP, DEVICE, GObject) 14 | 15 | guint astal_wp_device_get_id(AstalWpDevice *self); 16 | const gchar *astal_wp_device_get_description(AstalWpDevice *self); 17 | const gchar *astal_wp_device_get_icon(AstalWpDevice *self); 18 | AstalWpDeviceType astal_wp_device_get_device_type(AstalWpDevice *self); 19 | const gchar *astal_wp_device_get_form_factor(AstalWpDevice *self); 20 | 21 | AstalWpProfile *astal_wp_device_get_profile(AstalWpDevice *self, gint id); 22 | GList *astal_wp_device_get_profiles(AstalWpDevice *self); 23 | void astal_wp_device_set_active_profile_id(AstalWpDevice *self, int profile_id); 24 | gint astal_wp_device_get_active_profile_id(AstalWpDevice *self); 25 | 26 | gint astal_wp_device_get_input_route_id(AstalWpDevice *self); 27 | gint astal_wp_device_get_output_route_id(AstalWpDevice *self); 28 | AstalWpRoute *astal_wp_device_get_route(AstalWpDevice *self, gint id); 29 | void astal_wp_device_set_route(AstalWpDevice *self, AstalWpRoute *route, guint card_device); 30 | GList *astal_wp_device_get_routes(AstalWpDevice *self); 31 | GList *astal_wp_device_get_input_routes(AstalWpDevice *self); 32 | GList *astal_wp_device_get_output_routes(AstalWpDevice *self); 33 | 34 | gchar *astal_wp_device_get_pw_property(AstalWpDevice *self, const gchar *key); 35 | G_END_DECLS 36 | 37 | #endif // !ASTAL_WP_DEVICE_H 38 | -------------------------------------------------------------------------------- /nix/devshell.nix: -------------------------------------------------------------------------------- 1 | { 2 | self, 3 | pkgs, 4 | }: let 5 | lua = pkgs.lua.withPackages (ps: [ 6 | ps.lgi 7 | (ps.luaPackages.toLuaModule (pkgs.stdenv.mkDerivation { 8 | name = "astal"; 9 | src = "${self}/lang/lua/astal"; 10 | dontBuild = true; 11 | installPhase = '' 12 | mkdir -p $out/share/lua/${ps.lua.luaversion}/astal 13 | cp -r * $out/share/lua/${ps.lua.luaversion}/astal 14 | ''; 15 | })) 16 | ]); 17 | 18 | python = pkgs.python3.withPackages (ps: [ 19 | ps.pygobject3 20 | ps.pygobject-stubs 21 | ]); 22 | 23 | buildInputs = with pkgs; [ 24 | wrapGAppsHook3 25 | gobject-introspection 26 | meson 27 | pkg-config 28 | ninja 29 | vala 30 | gtk3 31 | gtk4 32 | gtk-layer-shell 33 | gtk4-layer-shell 34 | json-glib 35 | pam 36 | gvfs 37 | networkmanager 38 | gdk-pixbuf 39 | wireplumber 40 | libdbusmenu-gtk3 41 | wayland 42 | blueprint-compiler 43 | libadwaita 44 | wayland-scanner 45 | dart-sass 46 | esbuild 47 | lua 48 | python 49 | gjs 50 | ]; 51 | 52 | dev = with pkgs; [ 53 | nodejs 54 | mesonlsp 55 | vala-language-server 56 | vtsls 57 | vscode-langservers-extracted 58 | markdownlint-cli2 59 | pyright 60 | ruff 61 | uncrustify 62 | ]; 63 | in { 64 | default = pkgs.mkShell { 65 | packages = buildInputs ++ dev; 66 | }; 67 | astal = pkgs.mkShell { 68 | packages = 69 | buildInputs 70 | ++ dev 71 | ++ builtins.attrValues ( 72 | builtins.removeAttrs self.packages.${pkgs.stdenv.hostPlatform.system} ["docs"] 73 | ); 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /lib/tray/src/cli.vala: -------------------------------------------------------------------------------- 1 | static bool version; 2 | static bool daemonize; 3 | 4 | const OptionEntry[] options = { 5 | { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, "Print version number", null }, 6 | { "daemonize", 'd', OptionFlags.NONE, OptionArg.NONE, ref daemonize, "Monitor the systemtray", null }, 7 | { null }, 8 | }; 9 | 10 | int main(string[] argv) { 11 | try { 12 | var opts = new OptionContext(); 13 | opts.add_main_entries(options, null); 14 | opts.set_help_enabled(true); 15 | opts.set_ignore_unknown_options(false); 16 | opts.parse(ref argv); 17 | } catch (OptionError err) { 18 | printerr(err.message); 19 | return 1; 20 | } 21 | 22 | if (version) { 23 | print(AstalTray.VERSION); 24 | return 0; 25 | } 26 | 27 | if (daemonize) { 28 | var loop = new MainLoop(); 29 | var tray = new AstalTray.Tray(); 30 | 31 | tray.item_added.connect((id) => { 32 | AstalTray.TrayItem item = tray.get_item(id); 33 | 34 | stdout.printf("{\"event\":\"item_added\",\"id\":\"%s\",\"item\":%s}\n", 35 | id, item.to_json_string()); 36 | stdout.flush(); 37 | 38 | item.changed.connect(() => { 39 | stdout.printf("{\"event\":\"item_changed\",\"id\":\"%s\",\"item\":%s}\n", 40 | id, item.to_json_string()); 41 | stdout.flush(); 42 | }); 43 | }); 44 | 45 | tray.item_removed.connect((id) => { 46 | stdout.printf("{\"event\":\"item_removed\",\"id\":\"%s\"}\n", id); 47 | stdout.flush(); 48 | }); 49 | 50 | loop.run(); 51 | } 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /examples/gtk3/simple-bar/vala/app.in.vala: -------------------------------------------------------------------------------- 1 | class App : Gtk.Application { 2 | static App instance; 3 | 4 | private Bar bar; 5 | 6 | private void init_css() { 7 | var provider = new Gtk.CssProvider(); 8 | provider.load_from_data("""@STYLE@"""); 9 | 10 | Gtk.StyleContext.add_provider_for_screen( 11 | Gdk.Screen.get_default(), 12 | provider, 13 | Gtk.STYLE_PROVIDER_PRIORITY_USER 14 | ); 15 | } 16 | 17 | // this is the method that will be invoked on `app.run()` 18 | // this is where everything should be initialized and instantiated 19 | public override int command_line(ApplicationCommandLine command_line) { 20 | var argv = command_line.get_arguments(); 21 | 22 | if (command_line.is_remote) { 23 | // app is already running we can print to remote 24 | command_line.print_literal("hello from the main instance\n"); 25 | 26 | // for example, we could toggle the visibility of the bar 27 | if (argv.length >= 3 && argv[1] == "toggle" && argv[2] == "bar") { 28 | bar.visible = !bar.visible; 29 | } 30 | } else { 31 | // main instance, initialize stuff here 32 | init_css(); 33 | add_window((bar = new Bar())); 34 | } 35 | 36 | return 0; 37 | } 38 | 39 | private App() { 40 | application_id = "my.awesome.simple-bar"; 41 | flags = ApplicationFlags.HANDLES_COMMAND_LINE; 42 | } 43 | 44 | // entry point of our app 45 | static int main(string[] argv) { 46 | App.instance = new App(); 47 | Environment.set_prgname("simple-bar"); 48 | return App.instance.run(argv); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/py/src/app/App.py: -------------------------------------------------------------------------------- 1 | from gi.repository import Gio, GLib, Gtk, Gdk, GLib 2 | from bar.Bar import Bar 3 | 4 | 5 | class App(Gtk.Application): 6 | __gtype_name__ = "App" 7 | 8 | def _init_css(self): 9 | provider = Gtk.CssProvider() 10 | provider.load_from_resource("/style.css") 11 | 12 | Gtk.StyleContext.add_provider_for_display( 13 | Gdk.Display.get_default(), 14 | provider, 15 | Gtk.STYLE_PROVIDER_PRIORITY_USER, 16 | ) 17 | 18 | # this is the method that will be invoked on `app.run()` 19 | # this is where everything should be initialized and instantiated 20 | def do_command_line(self, command_line): 21 | argv = command_line.get_arguments() 22 | 23 | if command_line.get_is_remote(): 24 | # app is already running we can print to remote 25 | command_line.print_literal("hello from the main instance\n") 26 | 27 | # or for example, we could toggle the visibility of the bar 28 | if len(argv) >= 3 and argv[1] == "toggle" and argv[2] == "bar": 29 | self.bar.set_visible(not self.bar.get_visible()) 30 | else: 31 | # main instance, initialize stuff here 32 | self._init_css() 33 | self.bar = Bar() 34 | self.add_window(self.bar) 35 | 36 | return 0 37 | 38 | def __init__(self) -> None: 39 | super().__init__( 40 | application_id="my.awesome.simple-bar", 41 | flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, 42 | ) 43 | 44 | @staticmethod 45 | def main(argv): 46 | App.instance = App() 47 | GLib.set_prgname("simple-bar") 48 | return App.instance.run(argv) 49 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/py/src/meson.build: -------------------------------------------------------------------------------- 1 | pkgdatadir = get_option('prefix') / get_option('datadir') / meson.project_name() 2 | bindir = get_option('prefix') / get_option('bindir') 3 | 4 | blp = find_program('blueprint-compiler', required: true) 5 | sass = find_program('sass', required: true) 6 | python = find_program('python3', required: true) 7 | layer_shell = dependency('gtk4-layer-shell-0') 8 | 9 | blueprint_sources = files( 10 | 'bar/Bar.blp', 11 | ) 12 | 13 | # transplie blueprints 14 | ui = custom_target( 15 | 'blueprint', 16 | input: blueprint_sources, 17 | output: '.', 18 | command: [ 19 | blp, 20 | 'batch-compile', 21 | '@OUTPUT@', 22 | '@CURRENT_SOURCE_DIR@', 23 | '@INPUT@', 24 | ], 25 | ) 26 | 27 | # bundle styles 28 | css = custom_target( 29 | 'scss', 30 | input: files('style.scss'), 31 | command: [sass, '@INPUT@', '@OUTPUT@'], 32 | output: 'style.css', 33 | ) 34 | 35 | # compiling ui and css into a binary 36 | import('gnome').compile_resources( 37 | 'data', 38 | files('gresource.xml'), 39 | dependencies: [ui, css], 40 | gresource_bundle: true, 41 | install: true, 42 | install_dir: pkgdatadir, 43 | ) 44 | 45 | # install python sources 46 | install_data( 47 | 'app/__init__.py', 48 | 'app/App.py', 49 | install_dir: pkgdatadir / 'app', 50 | ) 51 | 52 | install_data( 53 | 'bar/__init__.py', 54 | 'bar/Bar.py', 55 | install_dir: pkgdatadir / 'bar', 56 | ) 57 | 58 | # configure the main python entry file 59 | configure_file( 60 | input: 'main.in.py', 61 | output: meson.project_name(), 62 | configuration: { 63 | 'PYTHON': python.full_path(), 64 | 'LAYER_SHELL_PREFIX': layer_shell.get_variable('prefix'), 65 | 'PKGDATADIR': pkgdatadir, 66 | }, 67 | install: true, 68 | install_dir: bindir, 69 | ) 70 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/vala/src/meson.build: -------------------------------------------------------------------------------- 1 | pkgdatadir = get_option('prefix') / get_option('datadir') 2 | bindir = get_option('prefix') / get_option('bindir') 3 | blp = find_program('blueprint-compiler', required: true) 4 | sass = find_program('sass', required: true) 5 | 6 | dependencies = [ 7 | dependency('gtk4-layer-shell-0'), 8 | dependency('astal-io-0.1'), 9 | dependency('glib-2.0'), 10 | dependency('astal-4-4.0'), 11 | dependency('astal-battery-0.1'), 12 | dependency('astal-wireplumber-0.1'), 13 | dependency('astal-network-0.1'), 14 | dependency('libnm'), 15 | dependency('astal-mpris-0.1'), 16 | dependency('astal-power-profiles-0.1'), 17 | dependency('astal-tray-0.1'), 18 | dependency('astal-bluetooth-0.1'), 19 | ] 20 | 21 | blueprint_sources = files( 22 | 'bar/Bar.blp', 23 | ) 24 | 25 | vala_sources = files( 26 | 'bar/Bar.vala', 27 | 'App.vala', 28 | ) 29 | 30 | # transplie blueprints 31 | ui = custom_target( 32 | 'blueprint', 33 | input: blueprint_sources, 34 | output: '.', 35 | command: [ 36 | blp, 37 | 'batch-compile', 38 | '@OUTPUT@', 39 | '@CURRENT_SOURCE_DIR@', 40 | '@INPUT@', 41 | ], 42 | ) 43 | 44 | # bundle scss files 45 | css = custom_target( 46 | 'scss', 47 | input: files('style.scss'), 48 | command: [sass, '@INPUT@', '@OUTPUT@'], 49 | output: ['style.css'], 50 | ) 51 | 52 | # compiling data files into a binary 53 | resource = import('gnome').compile_resources( 54 | 'data', 55 | files('gresource.xml'), 56 | dependencies: [ui, css], 57 | source_dir: meson.current_build_dir(), 58 | ) 59 | 60 | executable( 61 | meson.project_name(), 62 | dependencies: dependencies, 63 | sources: [vala_sources, resource], 64 | link_args: ['-lm'], # Link math library 65 | install: true, 66 | install_dir: bindir, 67 | ) 68 | -------------------------------------------------------------------------------- /lib/astal/gtk4/src/widget/slider.vala: -------------------------------------------------------------------------------- 1 | public class Astal.Slider : Gtk.Scale { 2 | construct { 3 | if (adjustment == null) 4 | adjustment = new Gtk.Adjustment(0,0,0,0,0,0); 5 | 6 | if (max == 0 && min == 0) { 7 | max = 1; 8 | } 9 | 10 | if (step == 0) { 11 | step = 0.05; 12 | } 13 | 14 | if (page == 0) { 15 | page = 0.01; 16 | } 17 | 18 | this.change_value.connect((range, scroll, value) => { 19 | if (this.value == value) { 20 | this.notify_property("value"); 21 | } 22 | }); 23 | } 24 | 25 | /** 26 | * Value of this slider. Defaults to `0`. 27 | */ 28 | [CCode (notify = false)] 29 | public double value { 30 | get { return adjustment.value; } 31 | set { adjustment.value = value; } 32 | } 33 | 34 | /** 35 | * Minimum possible value of this slider. Defaults to `0`. 36 | */ 37 | public double min { 38 | get { return adjustment.lower; } 39 | set { adjustment.lower = value; } 40 | } 41 | 42 | /** 43 | * Maximum possible value of this slider. Defaults to `1`. 44 | */ 45 | public double max { 46 | get { return adjustment.upper; } 47 | set { adjustment.upper = value; } 48 | } 49 | 50 | /** 51 | * Size of step increments. Defaults to `0.05`. 52 | */ 53 | public double step { 54 | get { return adjustment.step_increment; } 55 | set { adjustment.step_increment = value; } 56 | } 57 | 58 | /** 59 | * Size of page increments. Defaults to `0.01`. 60 | */ 61 | public double page { 62 | get { return adjustment.page_increment; } 63 | set { adjustment.page_increment = value; } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/vala/src/App.vala: -------------------------------------------------------------------------------- 1 | class App : Gtk.Application { 2 | // alternative is to rely on GLib.Application.get_default 3 | static App instance; 4 | 5 | private Bar bar; 6 | 7 | private void init_css() { 8 | var provider = new Gtk.CssProvider(); 9 | provider.load_from_resource("/style.css"); 10 | 11 | Gtk.StyleContext.add_provider_for_display( 12 | Gdk.Display.get_default(), 13 | provider, 14 | Gtk.STYLE_PROVIDER_PRIORITY_USER 15 | ); 16 | } 17 | 18 | // this is the method that will be invoked on `app.run()` 19 | // this is where everything should be initialized and instantiated 20 | public override int command_line(ApplicationCommandLine command_line) { 21 | var argv = command_line.get_arguments(); 22 | 23 | if (command_line.is_remote) { 24 | // app is already running we can print to remote 25 | command_line.print_literal("hello from the main instance\n"); 26 | 27 | // for example, we could toggle the visibility of the bar 28 | if (argv.length >= 3 && argv[1] == "toggle" && argv[2] == "bar") { 29 | bar.visible = !bar.visible; 30 | } 31 | } else { 32 | // main instance, initialize stuff here 33 | init_css(); 34 | add_window((bar = new Bar())); 35 | } 36 | 37 | return 0; 38 | } 39 | 40 | private App() { 41 | application_id = "my.awesome.simple-bar"; 42 | flags = ApplicationFlags.HANDLES_COMMAND_LINE; 43 | } 44 | 45 | // entry point of our app 46 | static int main(string[] argv) { 47 | App.instance = new App(); 48 | Environment.set_prgname("simple-bar"); 49 | return App.instance.run(argv); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lang/gjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astal", 3 | "version": "0.1.0", 4 | "description": "Building blocks for building linux desktop shell", 5 | "type": "module", 6 | "author": "Aylur", 7 | "license": "LGPL-2.1", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/aylur/astal.git", 11 | "directory": "lang/gjs" 12 | }, 13 | "funding": { 14 | "type": "kofi", 15 | "url": "https://ko-fi.com/aylur" 16 | }, 17 | "exports": { 18 | ".": "./index.ts", 19 | "./gtk3": "./src/gtk3/index.ts", 20 | "./gtk4": "./src/gtk4/index.ts", 21 | "./gtk3/app": "./src/gtk3/app.ts", 22 | "./gtk4/app": "./src/gtk4/app.ts", 23 | "./gtk3/widget": "./src/gtk3/widget.ts", 24 | "./gtk4/widget": "./src/gtk4/widget.ts", 25 | "./gtk3/jsx-runtime": "./src/gtk3/jsx-runtime.ts", 26 | "./gtk4/jsx-runtime": "./src/gtk4/jsx-runtime.ts", 27 | "./binding": "./src/binding.ts", 28 | "./file": "./src/file.ts", 29 | "./gobject": "./src/gobject.ts", 30 | "./process": "./src/process.ts", 31 | "./time": "./src/time.ts", 32 | "./variable": "./src/variable.ts" 33 | }, 34 | "engines": { 35 | "gjs": ">=1.79.0" 36 | }, 37 | "os": [ 38 | "linux" 39 | ], 40 | "devDependencies": { 41 | "@eslint/js": "^9.12.0", 42 | "@stylistic/eslint-plugin": "^2.9.0", 43 | "@ts-for-gir/cli": "^4.0.0-beta.16", 44 | "@types/eslint__js": "^8.42.3", 45 | "eslint": "^8.57.1", 46 | "typescript": "^5.6.3", 47 | "typescript-eslint": "^7.18.0" 48 | }, 49 | "scripts": { 50 | "lint": "eslint . --fix", 51 | "types": "ts-for-gir generate -o @girs --ignoreVersionConflicts" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/battery/src/cli.vala: -------------------------------------------------------------------------------- 1 | static bool help; 2 | static bool version; 3 | static bool monitor; 4 | static bool pretty; 5 | 6 | const OptionEntry[] options = { 7 | { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, 8 | { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, 9 | { "monitor", 'm', OptionFlags.NONE, OptionArg.NONE, ref monitor, null, null }, 10 | { "pretty", 'p', OptionFlags.NONE, OptionArg.NONE, ref pretty, null, null }, 11 | { null }, 12 | }; 13 | 14 | int main(string[] argv) { 15 | try { 16 | var opts = new OptionContext(); 17 | opts.add_main_entries(options, null); 18 | opts.set_help_enabled(false); 19 | opts.set_ignore_unknown_options(false); 20 | opts.parse(ref argv); 21 | } catch (OptionError err) { 22 | printerr(err.message); 23 | return 1; 24 | } 25 | 26 | if (help) { 27 | print("Usage:\n"); 28 | print(" %s [flags]\n\n", argv[0]); 29 | print("Flags:\n"); 30 | print(" -h, --help Print this help and exit\n"); 31 | print(" -v, --version Print version number and exit\n"); 32 | print(" -m, --monitor Monitor property changes\n"); 33 | print(" -p, --pretty Pretty print json output\n"); 34 | return 0; 35 | } 36 | 37 | if (version) { 38 | print(AstalBattery.VERSION); 39 | return 0; 40 | } 41 | 42 | var battery = AstalBattery.get_default(); 43 | print_state(battery); 44 | 45 | if (monitor) { 46 | battery.notify.connect(() => { print_state(battery); }); 47 | new GLib.MainLoop(null, false).run(); 48 | } 49 | 50 | return 0; 51 | } 52 | 53 | void print_state(AstalBattery.Device battery) { 54 | stdout.printf("%s\n", Json.to_string(Json.gobject_serialize(battery), pretty)); 55 | stdout.flush(); 56 | } 57 | -------------------------------------------------------------------------------- /lib/notifd/src/action.vala: -------------------------------------------------------------------------------- 1 | /** 2 | * Notification action. 3 | */ 4 | public class AstalNotifd.Action : Object { 5 | /** Id of this action. */ 6 | public string id { construct set; get; default = "1"; } 7 | 8 | /** Label of this action that should be displayed to user. */ 9 | public string label { construct set; get; default = ""; } 10 | 11 | /** Emitted when the notification this action was added to invoked this action. */ 12 | public signal void invoked(); 13 | 14 | public Action(string id, string label) { 15 | Object(id: id, label: label); 16 | } 17 | 18 | private Notification _notification; 19 | internal Notification notification { 20 | get { return _notification; } 21 | set { 22 | if (_notification == null) { 23 | _notification = value; 24 | value.invoked.connect((action_id) => { 25 | if (action_id == id) { 26 | invoked(); 27 | } 28 | }); 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * Invoke this action. 35 | * Note that this method just notifies the client that this action was invoked 36 | * by the user. If for example this notification persists through the lifetime 37 | * of the sending application this action will have no effect. 38 | */ 39 | public void invoke() { 40 | if (notification == null) { 41 | critical("cannot invoke action: not added to any notification"); 42 | } else { 43 | notification.invoked(id); 44 | } 45 | } 46 | 47 | internal static List new_list(string[] strv) { 48 | var actions = new List(); 49 | for (var i = 0; i < strv.length; i += 2) { 50 | actions.append(new Action(strv[i], strv[i + 1])); 51 | } 52 | return actions; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/river/src/meson.build: -------------------------------------------------------------------------------- 1 | srcs = files( 2 | 'river-output.c', 3 | 'river-layout.c', 4 | 'river.c' 5 | ) 6 | 7 | deps = [ 8 | dependency('gobject-2.0'), 9 | dependency('gio-2.0'), 10 | dependency('wayland-client'), 11 | dependency('json-glib-1.0'), 12 | wayland_glib_dep 13 | ] 14 | 15 | astal_river_lib = library( 16 | 'astal-river', 17 | sources: srcs + client_protocol_srcs, 18 | include_directories: astal_river_inc, 19 | dependencies: deps, 20 | version: meson.project_version(), 21 | install: true, 22 | ) 23 | 24 | libastal_river = declare_dependency(link_with: astal_river_lib, include_directories: astal_river_inc) 25 | 26 | executable( 27 | 'astal-river', 28 | files('astal-river.c'), 29 | dependencies: [ 30 | dependency('gobject-2.0'), 31 | dependency('gio-2.0'), 32 | dependency('json-glib-1.0'), 33 | libastal_river, 34 | ], 35 | install: true, 36 | ) 37 | 38 | pkg_config_name = 'astal-river-' + lib_so_version 39 | 40 | if get_option('introspection') 41 | gir = gnome.generate_gir( 42 | astal_river_lib, 43 | sources: srcs + astal_river_headers, 44 | nsversion: '0.1', 45 | namespace: 'AstalRiver', 46 | symbol_prefix: 'astal_river', 47 | identifier_prefix: 'AstalRiver', 48 | includes: ['GObject-2.0', 'Gio-2.0'], 49 | header: 'astal-river.h', 50 | export_packages: pkg_config_name, 51 | install: true, 52 | ) 53 | 54 | if get_option('vapi') 55 | gnome.generate_vapi( 56 | pkg_config_name, 57 | sources: [gir[0]], 58 | packages: ['gobject-2.0', 'gio-2.0'], 59 | install: true, 60 | ) 61 | endif 62 | endif 63 | 64 | pkg_config.generate( 65 | name: 'astal-river', 66 | version: meson.project_version(), 67 | libraries: [astal_river_lib], 68 | filebase: pkg_config_name, 69 | subdirs: 'astal', 70 | description: 'astal riverentication module', 71 | url: 'https://github.com/astal-sh/river', 72 | ) 73 | -------------------------------------------------------------------------------- /docs/guide/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Arch 4 | 5 | maintainer: [@kotontrion](https://github.com/kotontrion) 6 | 7 | ```sh [Every Library] 8 | yay -S libastal-meta 9 | ``` 10 | 11 | ## Nix 12 | 13 | maintainer: [@Aylur](https://github.com/Aylur) 14 | 15 | Read more about it on the [nix page](./nix#astal) 16 | 17 | ## Building From Source 18 | 19 | Most libraries will require you to follow these three steps to build and install 20 | them. These steps are shown for each library on their individual pages. 21 | 22 | 1. Install the following dependencies 23 | 24 | :::code-group 25 | 26 | ```sh [ Arch] 27 | sudo pacman -Syu \ 28 | meson vala valadoc gobject-introspection \ 29 | gtk3 gtk-layer-shell \ 30 | gtk4 gtk4-layer-shell 31 | ``` 32 | 33 | ```sh [ Fedora] 34 | sudo dnf install \ 35 | meson vala valadoc gobject-introspection-devel wayland-protocols-devel \ 36 | gtk3-devel gtk-layer-shell-devel \ 37 | gtk4-devel gtk4-layer-shell-devel 38 | ``` 39 | 40 | ```sh [ Ubuntu] 41 | sudo apt install \ 42 | meson valac valadoc gobject-introspection libgirepository1.0-dev \ 43 | libgtk-3-dev libgtk-layer-shell-dev \ 44 | libgtk-4-dev libgtk4-layer-shell-dev 45 | ``` 46 | 47 | ::: 48 | 49 | 2. Clone the repo 50 | 51 | ```sh 52 | git clone https://github.com/aylur/astal.git 53 | ``` 54 | 55 | 3. Build and install with `meson` 56 | 57 | ::: code-group 58 | 59 | ```sh [astal-io] 60 | cd lib/astal/io 61 | meson setup build 62 | meson install -C build 63 | ``` 64 | 65 | ```sh [astal3] 66 | cd lib/astal/gtk3 67 | meson setup build 68 | meson install -C build 69 | ``` 70 | 71 | ```sh [astal4] 72 | cd lib/astal/gtk4 73 | meson setup build 74 | meson install -C build 75 | ``` 76 | 77 | ::: 78 | -------------------------------------------------------------------------------- /lib/astal/gtk3/src/widget/overlay.vala: -------------------------------------------------------------------------------- 1 | public class Astal.Overlay : Gtk.Overlay { 2 | public bool pass_through { get; set; } 3 | 4 | /** 5 | * First [property@Astal.Overlay:overlays] element. 6 | * 7 | * WARNING: setting this value will remove every overlay but the first. 8 | */ 9 | public Gtk.Widget? overlay { 10 | get { return overlays.nth_data(0); } 11 | set { 12 | foreach (var ch in get_children()) { 13 | if (ch != child) 14 | remove(ch); 15 | } 16 | 17 | if (value != null) 18 | add_overlay(value); 19 | } 20 | } 21 | 22 | /** 23 | * Sets the overlays of this Overlay. [method@Gtk.Overlay.add_overlay]. 24 | */ 25 | public List overlays { 26 | owned get { return get_children(); } 27 | set { 28 | foreach (var ch in get_children()) { 29 | if (ch != child) 30 | remove(ch); 31 | } 32 | 33 | foreach (var ch in value) 34 | add_overlay(ch); 35 | } 36 | } 37 | 38 | public new Gtk.Widget? child { 39 | get { return get_child(); } 40 | set { 41 | var ch = get_child(); 42 | if (ch != null) 43 | remove(ch); 44 | 45 | if (value != null) 46 | add(value); 47 | } 48 | } 49 | 50 | construct { 51 | notify["pass-through"].connect(() => { 52 | update_pass_through(); 53 | }); 54 | } 55 | 56 | private void update_pass_through() { 57 | foreach (var child in get_children()) 58 | set_overlay_pass_through(child, pass_through); 59 | } 60 | 61 | public new void add_overlay(Gtk.Widget widget) { 62 | base.add_overlay(widget); 63 | set_overlay_pass_through(widget, pass_through); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /docs/showcases/showcases.ts: -------------------------------------------------------------------------------- 1 | type Showcase = { 2 | image: string 3 | url: string 4 | icon?: string // https://devicon.dev/ 5 | title?: string 6 | description?: string 7 | author: string 8 | } 9 | 10 | // TODO: support more layouts 11 | type Grid = T | [T, T] 12 | 13 | export default [ 14 | { 15 | image: "/astal/showcase/aylur.webp", 16 | url: "https://github.com/Aylur/dotfiles", 17 | icon: "devicon-typescript-plain", 18 | title: "Marble Shell", 19 | author: "Aylur", 20 | }, 21 | { 22 | image: "/astal/showcase/tokyob0t.webp", 23 | url: "https://github.com/tokyob0t/dotfiles", 24 | icon: "devicon-lua-plain", 25 | title: "Tokyob0t's Desktop", 26 | description: "Abandonad toda esperanza, vosotros que entráis aquí.", 27 | author: "tokyob0t", 28 | }, 29 | { 30 | image: "/astal/showcase/kotontrion-kompass.webp", 31 | url: "https://github.com/kotontrion/kompass", 32 | icon: "devicon-vala-plain", 33 | title: "kompass", 34 | author: "kotontrion", 35 | }, 36 | { 37 | image: "/astal/showcase/ezerinz.webp", 38 | url: "https://github.com/ezerinz/epik-shell", 39 | icon: "devicon-javascript-plain", 40 | title: "Epik Shell", 41 | author: "ezerinz", 42 | }, 43 | { 44 | image: "/astal/showcase/hyprpanel_showcase.webp", 45 | url: "https://github.com/Jas-SinghFSU/hyprpanel", 46 | icon: "devicon-javascript-plain", 47 | title: "HyprPanel", 48 | author: "Jas", 49 | }, 50 | { 51 | image: "/astal/showcase/retrozinndev-colorshell.webp", 52 | url: "https://github.com/retrozinndev/colorshell", 53 | icon: "devicon-typescript-plain", 54 | title: "colorshell", 55 | author: "retrozinndev", 56 | }, 57 | { 58 | image: "/astal/showcase/delta-shell.webp", 59 | url: "https://github.com/Sinomor/delta-shell/", 60 | icon: "devicon-typescript-plain", 61 | title: "Delta Shell", 62 | author: "Sinomor", 63 | }, 64 | 65 | // add more showcases here~ 66 | ] satisfies Grid[] 67 | -------------------------------------------------------------------------------- /lib/hyprland/src/workspace.vala: -------------------------------------------------------------------------------- 1 | namespace AstalHyprland { 2 | public class Workspace : Object { 3 | public signal void removed(); 4 | 5 | public List _clients = new List(); 6 | 7 | public int id { get; private set; } 8 | public string name { get; private set; } 9 | public Monitor monitor { get; private set; } 10 | public List clients { owned get { return _clients.copy(); } } 11 | public bool has_fullscreen { get; private set; } 12 | public Client last_client { get; private set; } 13 | 14 | public Workspace.dummy(int id, Monitor ? monitor) { 15 | this.id = id; 16 | this.name = id.to_string(); 17 | this.monitor = monitor; 18 | } 19 | 20 | internal List filter_clients() { 21 | var hyprland = Hyprland.get_default(); 22 | var list = new List(); 23 | foreach (var client in hyprland.clients) { 24 | if (client.workspace == this) { 25 | list.append(client); 26 | } 27 | } 28 | 29 | return list; 30 | } 31 | 32 | internal void sync(Json.Object obj) { 33 | var hyprland = Hyprland.get_default(); 34 | 35 | id = (int)obj.get_int_member("id"); 36 | name = obj.get_string_member("name"); 37 | has_fullscreen = obj.get_boolean_member("hasfullscreen"); 38 | 39 | monitor = hyprland.get_monitor((int)obj.get_int_member("monitorID")); 40 | last_client = hyprland.get_client(obj.get_string_member("lastwindow")); 41 | 42 | var list = filter_clients(); 43 | if (_clients.length() != list.length()) { 44 | _clients = list.copy(); 45 | notify_property("clients"); 46 | } 47 | } 48 | 49 | public void focus() { 50 | Hyprland.get_default().dispatch("workspace", id.to_string()); 51 | } 52 | 53 | public void move_to(Monitor m) { 54 | Hyprland.get_default().dispatch("moveworkspacetomonitor", id.to_string() + " " + m.id.to_string()); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/wireplumber/include/astal/wireplumber/node.h: -------------------------------------------------------------------------------- 1 | #ifndef ASTAL_WP_NODE_H 2 | #define ASTAL_WP_NODE_H 3 | 4 | #include 5 | 6 | #include "channel.h" 7 | #include "enums.h" 8 | 9 | G_BEGIN_DECLS 10 | 11 | #define ASTAL_WP_TYPE_NODE (astal_wp_node_get_type()) 12 | 13 | G_DECLARE_DERIVABLE_TYPE(AstalWpNode, astal_wp_node, ASTAL_WP, NODE, GObject) 14 | 15 | struct _AstalWpNodeClass { 16 | GObjectClass parent_class; 17 | 18 | void (*params_changed)(AstalWpNode *self, const gchar *id); 19 | 20 | void (*metadata_changed)(AstalWpNode *self, const gchar *key, const gchar *type, 21 | const gchar *value); 22 | }; 23 | 24 | void astal_wp_node_set_volume(AstalWpNode *self, gdouble volume); 25 | gdouble astal_wp_node_get_volume(AstalWpNode *self); 26 | 27 | void astal_wp_node_set_mute(AstalWpNode *self, gboolean mute); 28 | gboolean astal_wp_node_get_mute(AstalWpNode *self); 29 | 30 | gboolean astal_wp_node_get_lock_channels(AstalWpNode *self); 31 | void astal_wp_node_set_lock_channels(AstalWpNode *self, gboolean lock_channels); 32 | 33 | AstalWpMediaClass astal_wp_node_get_media_class(AstalWpNode *self); 34 | guint astal_wp_node_get_id(AstalWpNode *self); 35 | const gchar *astal_wp_node_get_description(AstalWpNode *self); 36 | const gchar *astal_wp_node_get_name(AstalWpNode *self); 37 | const gchar *astal_wp_node_get_icon(AstalWpNode *self); 38 | const gchar *astal_wp_node_get_volume_icon(AstalWpNode *self); 39 | gint astal_wp_node_get_serial(AstalWpNode *self); 40 | const gchar *astal_wp_node_get_path(AstalWpNode *self); 41 | AstalWpNodeState astal_wp_node_get_state(AstalWpNode *self); 42 | 43 | GList *astal_wp_node_get_channels(AstalWpNode *self); 44 | 45 | gchar *astal_wp_node_get_pw_property(AstalWpNode *self, const gchar *key); 46 | 47 | void astal_wp_node_metadata_changed(AstalWpNode *self, const gchar *key, const gchar *type, 48 | const gchar *value); 49 | void astal_wp_node_params_changed(AstalWpNode *self, const gchar *id); 50 | 51 | G_END_DECLS 52 | 53 | #endif // !ASTAL_WP_NODE_H 54 | -------------------------------------------------------------------------------- /docs/guide/libraries/network.md: -------------------------------------------------------------------------------- 1 | # Network 2 | 3 | Wrapper library over [networkmanager](https://networkmanager.dev/) to better 4 | integrate with Astal. 5 | 6 | ## Usage 7 | 8 | You can browse the 9 | [Network reference](https://aylur.github.io/libastal/network). 10 | 11 | ### CLI 12 | 13 | There is no CLI for this library, use the one provided by networkmanager. 14 | 15 | ```sh 16 | nmcli --help 17 | ``` 18 | 19 | ### Library 20 | 21 | :::code-group 22 | 23 | ```js [ JavaScript] 24 | import Network from "gi://AstalNetwork" 25 | 26 | const network = Network.get_default() 27 | 28 | print(network.wifi.ssid) 29 | ``` 30 | 31 | ```py [ Python] 32 | from gi.repository import AstalNetwork as Network 33 | 34 | network = Network.get_default() 35 | 36 | print(network.get_wifi().get_ssid()) 37 | ``` 38 | 39 | ```lua [ Lua] 40 | local Network = require("lgi").require("AstalNetwork") 41 | 42 | local network = Network.get_default() 43 | 44 | print(network.wifi.ssid) 45 | ``` 46 | 47 | ```vala [ Vala] 48 | var network = AstalNetwork.get_default(); 49 | 50 | print(network.wifi.ssid); 51 | ``` 52 | 53 | ::: 54 | 55 | ## Installation 56 | 57 | 1. install dependencies 58 | 59 | :::code-group 60 | 61 | ```sh [ Arch] 62 | sudo pacman -Syu meson vala valadoc libnm gobject-introspection 63 | ``` 64 | 65 | ```sh [ Fedora] 66 | sudo dnf install meson vala valadoc NetworkManager-libnm-devel gobject-introspection-devel 67 | ``` 68 | 69 | ```sh [ Ubuntu] 70 | sudo apt install meson valac valadoc libnm-dev gobject-introspection 71 | ``` 72 | 73 | ::: 74 | 75 | 2. clone repo 76 | 77 | ```sh 78 | git clone https://github.com/aylur/astal.git 79 | cd astal/lib/network 80 | ``` 81 | 82 | 3. install 83 | 84 | ```sh 85 | meson setup build 86 | meson install -C build 87 | ``` 88 | -------------------------------------------------------------------------------- /lib/cava/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'astal-cava', 3 | 'c', 4 | version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(), 5 | default_options: ['c_std=gnu11', 'warning_level=3', 'prefix=/usr'], 6 | ) 7 | 8 | add_project_arguments(['-Wno-pedantic', '-Wno-unused-parameter'], language: 'c') 9 | 10 | version_split = meson.project_version().split('.') 11 | lib_so_version = version_split[0] + '.' + version_split[1] 12 | 13 | pkg_config = import('pkgconfig') 14 | gnome = import('gnome') 15 | 16 | srcs = files( 17 | 'astal-cava.h', 18 | 'cava.c', 19 | ) 20 | 21 | install_headers('astal-cava.h') 22 | 23 | cava = dependency( 24 | 'cava', 25 | version: '>=0.10.6', 26 | required: true, 27 | fallback: ['cava', 'cava_dep'], 28 | ) 29 | 30 | deps = [ 31 | dependency('gobject-2.0'), 32 | dependency('gio-2.0'), 33 | cava, 34 | ] 35 | 36 | astal_cava_lib = library( 37 | 'astal-cava', 38 | sources: srcs, 39 | dependencies: deps, 40 | version: meson.project_version(), 41 | install: true, 42 | ) 43 | 44 | libastal_cava = declare_dependency(link_with: astal_cava_lib) 45 | 46 | pkg_config_name = 'astal-cava-' + lib_so_version 47 | 48 | if get_option('introspection') 49 | gir = gnome.generate_gir( 50 | astal_cava_lib, 51 | sources: srcs, 52 | nsversion: '0.1', 53 | namespace: 'AstalCava', 54 | symbol_prefix: 'astal_cava', 55 | identifier_prefix: 'AstalCava', 56 | includes: ['GObject-2.0', 'Gio-2.0'], 57 | header: 'astal-cava.h', 58 | export_packages: pkg_config_name, 59 | install: true, 60 | ) 61 | 62 | if get_option('vapi') 63 | gnome.generate_vapi( 64 | pkg_config_name, 65 | sources: [gir[0]], 66 | packages: ['gobject-2.0', 'gio-2.0'], 67 | install: true, 68 | ) 69 | endif 70 | endif 71 | 72 | pkg_config.generate( 73 | name: 'astal-cava', 74 | version: meson.project_version(), 75 | libraries: [astal_cava_lib], 76 | filebase: pkg_config_name, 77 | subdirs: 'astal', 78 | description: 'audio analyzing service using cava', 79 | url: 'https://github.com/Aylur/astal', 80 | ) 81 | -------------------------------------------------------------------------------- /docs/guide/libraries/river.md: -------------------------------------------------------------------------------- 1 | # River 2 | 3 | Library and CLI tool for monitoring the 4 | [River Wayland Compositor](https://isaacfreund.com/software/river/). 5 | 6 | ## Usage 7 | 8 | You can browse the [River reference](https://aylur.github.io/libastal/river). 9 | 10 | ### CLI 11 | 12 | ```sh 13 | astal-river --help 14 | ``` 15 | 16 | ### Library 17 | 18 | :::code-group 19 | 20 | ```js [ JavaScript] 21 | import River from "gi://AstalRiver" 22 | 23 | const river = River.get_default() 24 | 25 | for (const output of river.get_outputs()) { 26 | print(output.name) 27 | } 28 | ``` 29 | 30 | ```py [ Python] 31 | from gi.repository import AstalRiver as River 32 | 33 | river = River.get_default() 34 | 35 | for output in river.get_outputs(): 36 | print(output.get_name()) 37 | ``` 38 | 39 | ```lua [ Lua] 40 | local River = require("lgi").require("AstalRiver") 41 | 42 | local river = River.River.get_default() 43 | 44 | for _, o in ipairs(river.outputs) do 45 | print(o.name) 46 | end 47 | ``` 48 | 49 | ```vala [ Vala] 50 | var river = AstalRiver.get_default(); 51 | 52 | foreach (var output in river.get_outputs()) { 53 | print(output.name); 54 | } 55 | ``` 56 | 57 | ::: 58 | 59 | ## Installation 60 | 61 | 1. install dependencies 62 | 63 | :::code-group 64 | 65 | ```sh [ Arch] 66 | sudo pacman -Syu meson json-glib gobject-introspection 67 | ``` 68 | 69 | ```sh [ Fedora] 70 | sudo dnf install meson gcc json-glib-devel gobject-introspection-devel 71 | ``` 72 | 73 | ```sh [ Ubuntu] 74 | sudo apt install meson libjson-glib-dev gobject-introspection 75 | ``` 76 | 77 | ::: 78 | 79 | 2. clone repo 80 | 81 | ```sh 82 | git clone https://github.com/aylur/astal.git 83 | cd astal/lib/river 84 | ``` 85 | 86 | 3. install 87 | 88 | ```sh 89 | meson setup build 90 | meson install -C build 91 | ``` 92 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/src/App.ts: -------------------------------------------------------------------------------- 1 | import GObject from "gi://GObject" 2 | import Gio from "gi://Gio" 3 | import GLib from "gi://GLib" 4 | import Gtk from "gi://Gtk?version=4.0" 5 | import Gdk from "gi://Gdk?version=4.0" 6 | import Astal from "gi://Astal?version=4.0" 7 | import Bar from "./bar/Bar" 8 | 9 | export default class App extends Astal.Application { 10 | static { 11 | GObject.registerClass(this) 12 | } 13 | 14 | static instance: App 15 | private bar!: Bar 16 | 17 | constructor() { 18 | super({ 19 | applicationId: "my.awesome.simple-bar", 20 | flags: Gio.ApplicationFlags.HANDLES_COMMAND_LINE, 21 | }) 22 | } 23 | 24 | private initCss() { 25 | const provider = new Gtk.CssProvider() 26 | provider.load_from_resource("/style.css") 27 | 28 | Gtk.StyleContext.add_provider_for_display( 29 | Gdk.Display.get_default()!, 30 | provider, 31 | Gtk.STYLE_PROVIDER_PRIORITY_USER, 32 | ) 33 | } 34 | 35 | vfunc_command_line(command_line: Gio.ApplicationCommandLine): number { 36 | const argv = command_line.get_arguments() 37 | 38 | if (command_line.isRemote) { 39 | // app is already running we can print to remote 40 | command_line.print_literal("hello from the main instance\n") 41 | 42 | // for example, we could toggle the visibility of the bar 43 | if (argv.length >= 2 && argv[0] == "toggle" && argv[1] == "bar") { 44 | this.bar.visible = !this.bar.visible 45 | } 46 | 47 | // exit remote process 48 | command_line.done() 49 | } else { 50 | // main instance, initialize stuff here 51 | this.initCss() 52 | this.bar = new Bar() 53 | this.add_window(this.bar) 54 | } 55 | 56 | return 0 57 | } 58 | 59 | // entry point of our app 60 | static async main(argv: string[]): Promise { 61 | App.instance = new App() 62 | GLib.set_prgname("simple-bar") 63 | return App.instance.runAsync(argv) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /docs/guide/libraries/battery.md: -------------------------------------------------------------------------------- 1 | # Battery 2 | 3 | Library and CLI tool for monitoring [upowerd](https://upower.freedesktop.org/) 4 | devices. 5 | 6 | ## Usage 7 | 8 | You can browse the 9 | [Battery reference](https://aylur.github.io/libastal/battery). 10 | 11 | ### CLI 12 | 13 | ```sh 14 | astal-battery --help 15 | ``` 16 | 17 | ### Library 18 | 19 | :::code-group 20 | 21 | ```js [ JavaScript] 22 | import Battery from "gi://AstalBattery" 23 | 24 | const battery = Battery.get_default() 25 | 26 | print(battery.percentage) 27 | ``` 28 | 29 | ```py [ Python] 30 | from gi.repository import AstalBattery as Battery 31 | 32 | battery = Battery.get_default() 33 | 34 | print(battery.get_percentage()) 35 | ``` 36 | 37 | ```lua [ Lua] 38 | local Battery = require("lgi").require("AstalBattery") 39 | 40 | local battery = Battery.get_default() 41 | 42 | print(battery.percentage) 43 | ``` 44 | 45 | ```vala [ Vala] 46 | var battery = AstalBattery.get_default(); 47 | 48 | print(@"$(battery.percentage)\n"); 49 | ``` 50 | 51 | ::: 52 | 53 | ## Installation 54 | 55 | 1. install dependencies 56 | 57 | :::code-group 58 | 59 | ```sh [ Arch] 60 | sudo pacman -Syu meson vala valadoc json-glib gobject-introspection 61 | ``` 62 | 63 | ```sh [ Fedora] 64 | sudo dnf install meson vala valadoc json-glib-devel gobject-introspection-devel 65 | ``` 66 | 67 | ```sh [ Ubuntu] 68 | sudo apt install meson valac valadoc libjson-glib-dev gobject-introspection 69 | ``` 70 | 71 | ::: 72 | 73 | ::: info 74 | 75 | Although UPower is not a direct build dependency, it should be 76 | self-explanatory that the daemon is required to be available at runtime. 77 | 78 | ::: 79 | 80 | 2. clone repo 81 | 82 | ```sh 83 | git clone https://github.com/aylur/astal.git 84 | cd astal/lib/battery 85 | ``` 86 | 87 | 3. install 88 | 89 | ```sh 90 | meson setup build 91 | meson install -C build 92 | ``` 93 | -------------------------------------------------------------------------------- /lib/hyprland/src/structs.vala: -------------------------------------------------------------------------------- 1 | namespace AstalHyprland { 2 | public class Bind : Object { 3 | public bool locked { get; construct set; } 4 | public bool mouse { get; construct set; } 5 | public bool release { get; construct set; } 6 | public bool repeat { get; construct set; } 7 | public bool long_press { get; construct set; } 8 | public bool non_consuming { get; construct set; } 9 | public bool has_description { get; construct set; } 10 | public int64 modmask { get; construct set; } 11 | public string submap { get; construct set; } 12 | public string key { get; construct set; } 13 | public int64 keycode { get; construct set; } 14 | public bool catch_all { get; construct set; } 15 | public string description { get; construct set; } 16 | public string dispatcher { get; construct set; } 17 | public string arg { get; construct set; } 18 | 19 | internal Bind.from_json(Json.Object obj) { 20 | locked = obj.get_boolean_member("locked"); 21 | mouse = obj.get_boolean_member("mouse"); 22 | release = obj.get_boolean_member("release"); 23 | repeat = obj.get_boolean_member("repeat"); 24 | long_press = (obj.get_member("longPress") ? .get_boolean()) ?? false; 25 | non_consuming = obj.get_boolean_member("non_consuming"); 26 | has_description = (obj.get_member("has_description") ? .get_boolean()) ?? false; 27 | modmask = obj.get_int_member("modmask"); 28 | submap = obj.get_string_member("submap"); 29 | key = obj.get_string_member("key"); 30 | keycode = obj.get_int_member("keycode"); 31 | catch_all = obj.get_boolean_member("catch_all"); 32 | description = (obj.get_member("description") ? .get_string()) ?? ""; 33 | dispatcher = obj.get_string_member("dispatcher"); 34 | arg = obj.get_string_member("arg"); 35 | } 36 | } 37 | 38 | public class Position : Object { 39 | public int x { get; construct set; } 40 | public int y { get; construct set; } 41 | 42 | internal Position.cursorpos(string pos) { 43 | var xy = pos.split(","); 44 | x = int.parse(xy[0].strip()); 45 | y = int.parse(xy[1].strip()); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docs/guide/libraries/hyprland.md: -------------------------------------------------------------------------------- 1 | # Hyprland 2 | 3 | Library and CLI tool for monitoring the 4 | [Hyprland socket](https://wiki.hyprland.org/IPC/). 5 | 6 | ## Usage 7 | 8 | You can browse the 9 | [Hyprland reference](https://aylur.github.io/libastal/hyprland). 10 | 11 | ### CLI 12 | 13 | ```sh 14 | astal-hyprland # starts monitoring 15 | ``` 16 | 17 | ### Library 18 | 19 | :::code-group 20 | 21 | ```js [ JavaScript] 22 | import Hyprland from "gi://AstalHyprland" 23 | 24 | const hyprland = Hyprland.get_default() 25 | 26 | for (const client of hyprland.get_clients()) { 27 | print(client.title) 28 | } 29 | ``` 30 | 31 | ```py [ Python] 32 | from gi.repository import AstalHyprland as Hyprland 33 | 34 | hyprland = Hyprland.get_default() 35 | 36 | for client in hyprland.get_clients(): 37 | print(client.get_title()) 38 | ``` 39 | 40 | ```lua [ Lua] 41 | local Hyprland = require("lgi").require("AstalHyprland") 42 | 43 | local hyprland = Hyprland.get_default() 44 | 45 | for _, c in ipairs(hyprland.clients) do 46 | print(c.title) 47 | end 48 | ``` 49 | 50 | ```vala [ Vala] 51 | var hyprland = AstalHyprland.get_default(); 52 | 53 | foreach (var client in hyprland.clients) { 54 | print(client.title); 55 | } 56 | ``` 57 | 58 | ::: 59 | 60 | ## Installation 61 | 62 | 1. install dependencies 63 | 64 | :::code-group 65 | 66 | ```sh [ Arch] 67 | sudo pacman -Syu meson vala valadoc json-glib gobject-introspection 68 | ``` 69 | 70 | ```sh [ Fedora] 71 | sudo dnf install meson vala valadoc json-glib-devel gobject-introspection-devel 72 | ``` 73 | 74 | ```sh [ Ubuntu] 75 | sudo apt install meson valac valadoc libjson-glib-dev gobject-introspection 76 | ``` 77 | 78 | ::: 79 | 80 | 2. clone repo 81 | 82 | ```sh 83 | git clone https://github.com/aylur/astal.git 84 | cd astal/lib/hyprland 85 | ``` 86 | 87 | 3. install 88 | 89 | ```sh 90 | meson setup build 91 | meson install -C build 92 | ``` 93 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/src/meson.build: -------------------------------------------------------------------------------- 1 | pkgdatadir = get_option('prefix') / get_option('datadir') / meson.project_name() 2 | bindir = get_option('prefix') / get_option('bindir') 3 | blp = find_program('blueprint-compiler', required: true) 4 | sass = find_program('sass', required: true) 5 | esbuild = find_program('esbuild', required: true) 6 | gjs = find_program('gjs', required: true) 7 | layer_shell = dependency('gtk4-layer-shell-0') 8 | 9 | blueprint_sources = files( 10 | 'bar/Bar.blp', 11 | ) 12 | 13 | # transplie blueprints 14 | ui = custom_target( 15 | 'blueprint', 16 | input: blueprint_sources, 17 | output: '.', 18 | command: [ 19 | blp, 20 | 'batch-compile', 21 | '@OUTPUT@', 22 | '@CURRENT_SOURCE_DIR@', 23 | '@INPUT@', 24 | ], 25 | ) 26 | 27 | # bundle ts files 28 | js = custom_target( 29 | 'typescript', 30 | input: files('App.ts'), 31 | command: [ 32 | esbuild, 33 | '--bundle', '@INPUT@', 34 | '--format=esm', 35 | '--outfile=@OUTPUT@', 36 | '--sourcemap=inline', 37 | '--external:gi://*', 38 | '--external:gettext', 39 | '--external:system', 40 | ], 41 | output: 'index.js', 42 | ) 43 | 44 | # bundle scss files 45 | css = custom_target( 46 | 'scss', 47 | input: files('style.scss'), 48 | command: [sass, '@INPUT@', '@OUTPUT@'], 49 | output: 'style.css', 50 | ) 51 | 52 | # compiling source files into a binary 53 | import('gnome').compile_resources( 54 | 'data', 55 | files('gresource.xml'), 56 | dependencies: [ui, css, js], 57 | gresource_bundle: true, 58 | install: true, 59 | install_dir: pkgdatadir, 60 | ) 61 | 62 | # gresource can't be run with gjs, we still need an entry script 63 | configure_file( 64 | input: files('main.in.js'), 65 | output: 'main.js', 66 | configuration: { 67 | 'GJS': gjs.full_path(), 68 | 'PKGDATADIR': pkgdatadir, 69 | }, 70 | install: true, 71 | install_dir: pkgdatadir, 72 | ) 73 | 74 | # and we need to wrap the entry script to preload gtk4-layer-shell 75 | configure_file( 76 | input: 'main.in.sh', 77 | output: meson.project_name(), 78 | configuration: { 79 | 'LAYER_SHELL_PREFIX': layer_shell.get_variable('prefix'), 80 | 'INDEX': pkgdatadir / 'main.js', 81 | }, 82 | install: true, 83 | install_dir: bindir, 84 | ) 85 | -------------------------------------------------------------------------------- /lang/gjs/src/process.ts: -------------------------------------------------------------------------------- 1 | import Astal from "gi://AstalIO" 2 | 3 | type Args = { 4 | cmd: string | string[] 5 | out?: (stdout: string) => void 6 | err?: (stderr: string) => void 7 | } 8 | 9 | export type Process = Astal.Process 10 | export const Process = Astal.Process 11 | 12 | export function subprocess(args: Args): Astal.Process 13 | 14 | export function subprocess( 15 | cmd: string | string[], 16 | onOut?: (stdout: string) => void, 17 | onErr?: (stderr: string) => void, 18 | ): Astal.Process 19 | 20 | export function subprocess( 21 | argsOrCmd: Args | string | string[], 22 | onOut: (stdout: string) => void = print, 23 | onErr: (stderr: string) => void = printerr, 24 | ) { 25 | const args = Array.isArray(argsOrCmd) || typeof argsOrCmd === "string" 26 | const { cmd, err, out } = { 27 | cmd: args ? argsOrCmd : argsOrCmd.cmd, 28 | err: args ? onErr : argsOrCmd.err || onErr, 29 | out: args ? onOut : argsOrCmd.out || onOut, 30 | } 31 | 32 | const proc = Array.isArray(cmd) 33 | ? Astal.Process.subprocessv(cmd) 34 | : Astal.Process.subprocess(cmd) 35 | 36 | proc.connect("stdout", (_, stdout: string) => out(stdout)) 37 | proc.connect("stderr", (_, stderr: string) => err(stderr)) 38 | return proc 39 | } 40 | 41 | /** @throws {GLib.Error} Throws stderr */ 42 | export function exec(cmd: string | string[]) { 43 | return Array.isArray(cmd) 44 | ? Astal.Process.execv(cmd) 45 | : Astal.Process.exec(cmd) 46 | } 47 | 48 | export function execAsync(cmd: string | string[]): Promise { 49 | return new Promise((resolve, reject) => { 50 | if (Array.isArray(cmd)) { 51 | Astal.Process.exec_asyncv(cmd, (_, res) => { 52 | try { 53 | resolve(Astal.Process.exec_asyncv_finish(res)) 54 | } catch (error) { 55 | reject(error) 56 | } 57 | }) 58 | } else { 59 | Astal.Process.exec_async(cmd, (_, res) => { 60 | try { 61 | resolve(Astal.Process.exec_finish(res)) 62 | } catch (error) { 63 | reject(error) 64 | } 65 | }) 66 | } 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /lang/gjs/src/gtk4/jsx-runtime.ts: -------------------------------------------------------------------------------- 1 | import Gtk from "gi://Gtk?version=4.0" 2 | import { type BindableChild } from "./astalify.js" 3 | import { mergeBindings, jsx as _jsx } from "../_astal.js" 4 | import * as Widget from "./widget.js" 5 | 6 | export function Fragment({ children = [], child }: { 7 | child?: BindableChild 8 | children?: Array 9 | }) { 10 | if (child) children.push(child) 11 | return mergeBindings(children) 12 | } 13 | 14 | export function jsx( 15 | ctor: keyof typeof ctors | typeof Gtk.Widget, 16 | props: any, 17 | ) { 18 | return _jsx(ctors, ctor as any, props) 19 | } 20 | 21 | const ctors = { 22 | box: Widget.Box, 23 | button: Widget.Button, 24 | centerbox: Widget.CenterBox, 25 | // circularprogress: Widget.CircularProgress, 26 | // drawingarea: Widget.DrawingArea, 27 | entry: Widget.Entry, 28 | image: Widget.Image, 29 | label: Widget.Label, 30 | levelbar: Widget.LevelBar, 31 | overlay: Widget.Overlay, 32 | revealer: Widget.Revealer, 33 | slider: Widget.Slider, 34 | stack: Widget.Stack, 35 | switch: Widget.Switch, 36 | window: Widget.Window, 37 | menubutton: Widget.MenuButton, 38 | popover: Widget.Popover, 39 | } 40 | 41 | declare global { 42 | // eslint-disable-next-line @typescript-eslint/no-namespace 43 | namespace JSX { 44 | type Element = Gtk.Widget 45 | type ElementClass = Gtk.Widget 46 | interface IntrinsicElements { 47 | box: Widget.BoxProps 48 | button: Widget.ButtonProps 49 | centerbox: Widget.CenterBoxProps 50 | // circularprogress: Widget.CircularProgressProps 51 | // drawingarea: Widget.DrawingAreaProps 52 | entry: Widget.EntryProps 53 | image: Widget.ImageProps 54 | label: Widget.LabelProps 55 | levelbar: Widget.LevelBarProps 56 | overlay: Widget.OverlayProps 57 | revealer: Widget.RevealerProps 58 | slider: Widget.SliderProps 59 | stack: Widget.StackProps 60 | switch: Widget.SwitchProps 61 | window: Widget.WindowProps 62 | menubutton: Widget.MenuButtonProps 63 | popover: Widget.PopoverProps 64 | } 65 | } 66 | } 67 | 68 | export const jsxs = jsx 69 | -------------------------------------------------------------------------------- /lib/apps/src/cli.vala: -------------------------------------------------------------------------------- 1 | static bool help; 2 | static bool version; 3 | static string search; 4 | static string launch; 5 | static bool json; 6 | 7 | const OptionEntry[] options = { 8 | { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, 9 | { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, 10 | { "search", 's', OptionFlags.NONE, OptionArg.STRING, ref search, null, null }, 11 | { "launch", 'l', OptionFlags.NONE, OptionArg.STRING, ref launch, null, null }, 12 | { "json", 'j', OptionFlags.NONE, OptionArg.NONE, ref json, null, null }, 13 | { null }, 14 | }; 15 | 16 | int main(string[] argv) { 17 | try { 18 | var opts = new OptionContext(); 19 | opts.add_main_entries(options, null); 20 | opts.set_help_enabled(false); 21 | opts.set_ignore_unknown_options(false); 22 | opts.parse(ref argv); 23 | } catch (OptionError err) { 24 | printerr(err.message); 25 | return 1; 26 | } 27 | 28 | if (help) { 29 | print("Usage:\n"); 30 | print(" %s [flags]\n\n", argv[0]); 31 | print("Flags:\n"); 32 | print(" -h, --help Print this help and exit\n"); 33 | print(" -v, --version Print version number and exit\n"); 34 | print(" -s, --search Sort by a search term\n"); 35 | print(" -l, --launch Launch an application\n"); 36 | print(" -j, --json Print list in json format\n"); 37 | return 0; 38 | } 39 | 40 | if (version) { 41 | print(AstalApps.VERSION); 42 | return 0; 43 | } 44 | 45 | var apps = new AstalApps.Apps(); 46 | 47 | if (launch != null) { 48 | apps.query(launch).first().data.launch(); 49 | return 0; 50 | } 51 | 52 | if (json) { 53 | var b = new Json.Builder().begin_array(); 54 | foreach (var app in apps.query(search)) { 55 | b.add_value(app.to_json()); 56 | } 57 | 58 | var generator = new Json.Generator(); 59 | generator.set_root(b.end_array().get_root()); 60 | stdout.printf(generator.to_data(null)); 61 | } else { 62 | foreach (var app in apps.query(search)) { 63 | stdout.printf("%s\n", app.entry); 64 | } 65 | } 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /lang/lua/astal/process.lua: -------------------------------------------------------------------------------- 1 | local lgi = require("lgi") 2 | local Astal = lgi.require("AstalIO", "0.1") 3 | 4 | local M = {} 5 | 6 | M.Process = Astal.Process 7 | 8 | ---@param commandline string | string[] 9 | ---@param on_stdout? fun(out: string): nil 10 | ---@param on_stderr? fun(err: string): nil 11 | ---@return { kill: function } | nil proc 12 | function M.subprocess(commandline, on_stdout, on_stderr) 13 | on_stdout = on_stdout or function(out) 14 | io.stdout:write(tostring(out) .. "\n") 15 | end 16 | 17 | on_stderr = on_stderr or function(err) 18 | io.stderr:write(tostring(err) .. "\n") 19 | end 20 | 21 | 22 | local proc, err 23 | 24 | if type(commandline) == "table" then 25 | proc, err = Astal.Process.subprocessv(commandline) 26 | else 27 | proc, err = Astal.Process.subprocess(commandline) 28 | end 29 | 30 | if err ~= nil then 31 | err(err) 32 | return nil 33 | end 34 | proc.on_stdout = function(_, stdout) 35 | on_stdout(stdout) 36 | end 37 | proc.on_stderr = function(_, stderr) 38 | on_stderr(stderr) 39 | end 40 | return proc 41 | end 42 | 43 | ---@param commandline string | string[] 44 | ---@return string, string 45 | function M.exec(commandline) 46 | if type(commandline) == "table" then 47 | return Astal.Process.execv(commandline) 48 | else 49 | return Astal.Process.exec(commandline) 50 | end 51 | end 52 | 53 | ---@param commandline string | string[] 54 | ---@param callback? fun(out: string, err: string): nil 55 | function M.exec_async(commandline, callback) 56 | callback = callback or function(out, err) 57 | if err ~= nil then 58 | io.stdout:write(tostring(out) .. "\n") 59 | else 60 | io.stderr:write(tostring(err) .. "\n") 61 | end 62 | end 63 | 64 | if type(commandline) == "table" then 65 | Astal.Process.exec_asyncv(commandline, function(_, res) 66 | local out, err = Astal.Process.exec_asyncv_finish(res) 67 | callback(out, err) 68 | end) 69 | else 70 | Astal.Process.exec_async(commandline, function(_, res) 71 | local out, err = Astal.Process.exec_finish(res) 72 | callback(out, err) 73 | end) 74 | end 75 | end 76 | 77 | return M 78 | -------------------------------------------------------------------------------- /lib/battery/src/ifaces.vala: -------------------------------------------------------------------------------- 1 | [DBus(name = "org.freedesktop.UPower")] 2 | private interface AstalBattery.IUPower : DBusProxy { 3 | public abstract ObjectPath[] enumerate_devices() throws Error; 4 | public abstract ObjectPath get_display_device() throws Error; 5 | public abstract string get_critical_action() throws Error; 6 | 7 | public signal void device_added(ObjectPath object_path); 8 | public signal void device_removed(ObjectPath object_path); 9 | 10 | public abstract string daemon_version { owned get; } 11 | public abstract bool on_battery { get; } 12 | public abstract bool lid_is_closed { get; } 13 | public abstract bool lid_is_present { get; } 14 | } 15 | 16 | [DBus(name = "org.freedesktop.UPower.Device")] 17 | private interface AstalBattery.IUPowerDevice : DBusProxy { 18 | public abstract uint Type { get; } 19 | public abstract string native_path { owned get; } 20 | public abstract string vendor { owned get; } 21 | public abstract string model { owned get; } 22 | public abstract string serial { owned get; } 23 | public abstract uint64 update_time { get; } 24 | public abstract bool power_supply { get; } 25 | public abstract bool has_history { get; } 26 | public abstract bool has_statistics { get; } 27 | public abstract bool online { get; } 28 | public abstract double energy { get; } 29 | public abstract double energy_empty { get; } 30 | public abstract double energy_full { get; } 31 | public abstract double energy_full_design { get; } 32 | public abstract double energy_rate { get; } 33 | public abstract double voltage { get; } 34 | public abstract int32 charge_cycles { get; } 35 | public abstract double luminosity { get; } 36 | public abstract int64 time_to_empty { get; } 37 | public abstract int64 time_to_full { get; } 38 | public abstract double percentage { get; } 39 | public abstract double temperature { get; } 40 | public abstract bool is_present { get; } 41 | public abstract uint state { get; } 42 | public abstract bool is_rechargable { get; } 43 | public abstract double capacity { get; } 44 | public abstract uint technology { get; } 45 | public abstract uint32 warning_level { get; } 46 | public abstract uint32 battery_level { get; } 47 | public abstract string icon_name { owned get; } 48 | } 49 | -------------------------------------------------------------------------------- /docs/guide/libraries/powerprofiles.md: -------------------------------------------------------------------------------- 1 | # Power Profiles 2 | 3 | Library and CLI tool for monitoring [upowerd](https://upower.freedesktop.org/) 4 | powerprofiles. 5 | 6 | ## Usage 7 | 8 | You can browse the 9 | [PowerProfiles reference](https://aylur.github.io/libastal/powerprofiles). 10 | 11 | ### CLI 12 | 13 | ```sh 14 | astal-power-profiles --help 15 | ``` 16 | 17 | ### Library 18 | 19 | :::code-group 20 | 21 | ```js [ JavaScript] 22 | import PowerProfiles from "gi://AstalPowerProfiles" 23 | 24 | const powerprofiles = PowerProfiles.get_default() 25 | 26 | print(powerprofiles.activeProfile) 27 | ``` 28 | 29 | ```py [ Python] 30 | from gi.repository import AstalPowerProfiles as PowerProfiles 31 | 32 | powerprofiles = PowerProfiles.get_default() 33 | 34 | print(powerprofiles.get_active_profile()) 35 | ``` 36 | 37 | ```lua [ Lua] 38 | local PowerProfiles = require("lgi").require("AstalPowerProfiles") 39 | 40 | local powerprofiles = PowerProfiles.get_default() 41 | 42 | print(powerprofiles.active_profile) 43 | ``` 44 | 45 | ```vala [ Vala] 46 | var powerprofiles = AstalPowerProfiles.get_default(); 47 | 48 | print(powerprofiles.activeProfile); 49 | ``` 50 | 51 | ::: 52 | 53 | ## Installation 54 | 55 | 1. install dependencies 56 | 57 | :::code-group 58 | 59 | ```sh [ Arch] 60 | sudo pacman -Syu meson vala valadoc json-glib gobject-introspection 61 | ``` 62 | 63 | ```sh [ Fedora] 64 | sudo dnf install meson vala valadoc json-glib-devel gobject-introspection-devel 65 | ``` 66 | 67 | ```sh [ Ubuntu] 68 | sudo apt install meson valac valadoc libjson-glib-dev gobject-introspection 69 | ``` 70 | 71 | ::: 72 | 73 | ::: info 74 | 75 | Although UPower is not a direct build dependency, it should be 76 | self-explanatory that the daemon is required to be available at runtime. 77 | 78 | ::: 79 | 80 | 2. clone repo 81 | 82 | ```sh 83 | git clone https://github.com/aylur/astal.git 84 | cd astal/lib/powerprofiles 85 | ``` 86 | 87 | 3. install 88 | 89 | ```sh 90 | meson setup build 91 | meson install -C build 92 | ``` 93 | -------------------------------------------------------------------------------- /docs/guide/libraries/cava.md: -------------------------------------------------------------------------------- 1 | # Cava 2 | 3 | Audio visualizer using [cava](https://github.com/karlstav/cava). 4 | 5 | ## Usage 6 | 7 | You can browse the [Cava reference](https://aylur.github.io/libastal/cava). 8 | 9 | ### CLI 10 | 11 | There is no CLI for this library, use the one provided by cava. 12 | 13 | ```sh 14 | cava 15 | ``` 16 | 17 | ### Library 18 | 19 | :::code-group 20 | 21 | ```js [ JavaScript] 22 | import Cava from "gi://AstalCava" 23 | 24 | const cava = Cava.get_default() 25 | 26 | cava.connect("notify::values", () => { 27 | print(cava.get_values()) 28 | }) 29 | ``` 30 | 31 | ```py [ Python] 32 | from gi.repository import AstalCava as Cava 33 | 34 | cava = Cava.get_default() 35 | 36 | def callback(self, pspec): 37 | print(cava.get_values()) 38 | 39 | cava.connect("notify::values", callback) 40 | ``` 41 | 42 | ```lua [ Lua] 43 | local Cava = require("lgi").require("AstalCava") 44 | 45 | local cava = Cava.get_default() 46 | 47 | cava.on_notify.values = function() 48 | print(cava.values) 49 | end 50 | ``` 51 | 52 | ```vala [ Vala] 53 | var cava = AstalCava.get_default(); 54 | 55 | cava.notify["values"].connect(() => { 56 | foreach (var value in cava.values) { 57 | print(value); 58 | } 59 | }); 60 | ``` 61 | 62 | ::: 63 | 64 | ## Installation 65 | 66 | 1. install dependencies 67 | 68 | Note that it requires [libcava](https://github.com/LukashonakV/cava), a fork 69 | of cava, which provides cava as a shared library. 70 | 71 | :::code-group 72 | 73 | ```sh [ Arch] 74 | sudo pacman -Syu meson vala gobject-introspection 75 | paru -S libcava 76 | ``` 77 | 78 | ```sh [ Fedora] 79 | # Not yet documented 80 | ``` 81 | 82 | ```sh [ Ubuntu] 83 | # Not yet documented 84 | ``` 85 | 86 | ::: 87 | 88 | 2. clone repo 89 | 90 | ```sh 91 | git clone https://github.com/aylur/astal.git 92 | cd astal/lib/cava 93 | ``` 94 | 95 | 3. install 96 | 97 | ```sh 98 | meson setup build 99 | meson install -C build 100 | ``` 101 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/js/src/bar/Bar.blp: -------------------------------------------------------------------------------- 1 | using Gtk 4.0; 2 | using Astal 4.0; 3 | 4 | template $Bar: Astal.Window { 5 | CenterBox centerbox { 6 | start-widget: Box { 7 | MenuButton { 8 | Label { 9 | label: bind template.clock; 10 | } 11 | 12 | popover: Popover popover { 13 | Calendar calendar { 14 | show-day-names: true; 15 | show-heading: true; 16 | show-week-numbers: true; 17 | } 18 | }; 19 | } 20 | }; 21 | 22 | center-widget: Box { 23 | Box { 24 | visible: bind template.mpris-visible; 25 | 26 | Image { 27 | file: bind template.mpris-art; 28 | } 29 | 30 | Label { 31 | label: bind template.mpris-label; 32 | } 33 | } 34 | }; 35 | 36 | end-widget: Box { 37 | spacing: 4; 38 | 39 | Image { 40 | visible: bind template.bluetooth-visible; 41 | icon-name: "bluetooth-symbolic"; 42 | } 43 | 44 | Image { 45 | icon-name: bind template.power-profile-icon; 46 | } 47 | 48 | Image { 49 | icon-name: bind template.network-icon; 50 | } 51 | 52 | Box { 53 | Image { 54 | icon-name: bind template.volume-icon; 55 | } 56 | 57 | Scale { 58 | width-request: 100; 59 | change-value => $change_volume(); 60 | 61 | adjustment: Adjustment { 62 | value: bind template.volume; 63 | lower: 0; 64 | upper: 1; 65 | }; 66 | } 67 | } 68 | 69 | Box { 70 | Image { 71 | icon-name: bind template.battery-icon; 72 | } 73 | 74 | Label { 75 | label: bind template.battery-label; 76 | } 77 | } 78 | 79 | Box traybox { 80 | spacing: 4; 81 | } 82 | }; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/py/src/bar/Bar.blp: -------------------------------------------------------------------------------- 1 | using Gtk 4.0; 2 | using Astal 4.0; 3 | 4 | template $Bar: Astal.Window { 5 | CenterBox centerbox { 6 | start-widget: Box { 7 | MenuButton { 8 | Label { 9 | label: bind template.clock; 10 | } 11 | 12 | popover: Popover popover { 13 | Calendar calendar { 14 | show-day-names: true; 15 | show-heading: true; 16 | show-week-numbers: true; 17 | } 18 | }; 19 | } 20 | }; 21 | 22 | center-widget: Box { 23 | Box { 24 | visible: bind template.mpris-visible; 25 | 26 | Image { 27 | file: bind template.mpris-art; 28 | } 29 | 30 | Label { 31 | label: bind template.mpris-label; 32 | } 33 | } 34 | }; 35 | 36 | end-widget: Box { 37 | spacing: 4; 38 | 39 | Image { 40 | visible: bind template.bluetooth-visible; 41 | icon-name: "bluetooth-symbolic"; 42 | } 43 | 44 | Image { 45 | icon-name: bind template.power-profile-icon; 46 | } 47 | 48 | Image { 49 | icon-name: bind template.network-icon; 50 | } 51 | 52 | Box { 53 | Image { 54 | icon-name: bind template.volume-icon; 55 | } 56 | 57 | Scale { 58 | width-request: 100; 59 | change-value => $change_volume(); 60 | 61 | adjustment: Adjustment { 62 | value: bind template.volume; 63 | lower: 0; 64 | upper: 1; 65 | }; 66 | } 67 | } 68 | 69 | Box { 70 | Image { 71 | icon-name: bind template.battery-icon; 72 | } 73 | 74 | Label { 75 | label: bind template.battery-label; 76 | } 77 | } 78 | 79 | Box traybox { 80 | spacing: 4; 81 | } 82 | }; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /examples/gtk4/simple-bar/vala/src/bar/Bar.blp: -------------------------------------------------------------------------------- 1 | using Gtk 4.0; 2 | using Astal 4.0; 3 | 4 | template $Bar: Astal.Window { 5 | CenterBox centerbox { 6 | start-widget: Box { 7 | MenuButton { 8 | Label { 9 | label: bind template.clock; 10 | } 11 | 12 | popover: Popover popover { 13 | Calendar calendar { 14 | show-day-names: true; 15 | show-heading: true; 16 | show-week-numbers: true; 17 | } 18 | }; 19 | } 20 | }; 21 | 22 | center-widget: Box { 23 | Box { 24 | visible: bind template.mpris-visible; 25 | 26 | Image { 27 | file: bind template.mpris-art; 28 | } 29 | 30 | Label { 31 | label: bind template.mpris-label; 32 | } 33 | } 34 | }; 35 | 36 | end-widget: Box { 37 | spacing: 4; 38 | 39 | Image { 40 | visible: bind template.bluetooth-visible; 41 | icon-name: "bluetooth-symbolic"; 42 | } 43 | 44 | Image { 45 | icon-name: bind template.power-profile-icon; 46 | } 47 | 48 | Image { 49 | icon-name: bind template.network-icon; 50 | } 51 | 52 | Box { 53 | Image { 54 | icon-name: bind template.volume-icon; 55 | } 56 | 57 | Scale { 58 | width-request: 100; 59 | change-value => $change_volume(); 60 | 61 | adjustment: Adjustment { 62 | value: bind template.volume; 63 | lower: 0; 64 | upper: 1; 65 | }; 66 | } 67 | } 68 | 69 | Box { 70 | Image { 71 | icon-name: bind template.battery-icon; 72 | } 73 | 74 | Label { 75 | label: bind template.battery-label; 76 | } 77 | } 78 | 79 | Box traybox { 80 | spacing: 4; 81 | } 82 | }; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /examples/gtk3/simple-bar/py/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import gi 3 | 4 | gi.require_version("Astal", "3.0") 5 | gi.require_version("Gtk", "3.0") 6 | gi.require_version("Gdk", "3.0") 7 | gi.require_version("Gio", "2.0") 8 | gi.require_version("GObject", "2.0") 9 | 10 | gi.require_version("AstalBattery", "0.1") 11 | gi.require_version("AstalWp", "0.1") 12 | gi.require_version("AstalNetwork", "0.1") 13 | gi.require_version("AstalTray", "0.1") 14 | gi.require_version("AstalMpris", "0.1") 15 | gi.require_version("AstalHyprland", "0.1") 16 | 17 | import sys 18 | import subprocess 19 | from gi.repository import Gtk, Gdk, Gio, GLib 20 | from widget.Bar import Bar 21 | from pathlib import Path 22 | 23 | scss = str(Path(__file__).parent.resolve() / "style.scss") 24 | css = "/tmp/style.css" 25 | 26 | 27 | class App(Gtk.Application): 28 | __gtype_name__ = "App" 29 | 30 | def _init_css(self): 31 | subprocess.run(["sass", scss, css]) 32 | provider = Gtk.CssProvider() 33 | provider.load_from_path(css) 34 | 35 | Gtk.StyleContext.add_provider_for_screen( 36 | Gdk.Screen.get_default(), 37 | provider, 38 | Gtk.STYLE_PROVIDER_PRIORITY_USER, 39 | ) 40 | 41 | # this is the method that will be invoked on `app.run()` 42 | # this is where everything should be initialized and instantiated 43 | def do_command_line(self, command_line): 44 | argv = command_line.get_arguments() 45 | 46 | if command_line.get_is_remote(): 47 | # app is already running we can print to remote 48 | command_line.print_literal("hello from the main instance\n") 49 | 50 | # or for example, we could toggle the visibility of the bar 51 | if len(argv) >= 3 and argv[1] == "toggle" and argv[2] == "bar": 52 | self.bar.set_visible(not self.bar.get_visible()) 53 | else: 54 | # main instance, initialize stuff here 55 | self._init_css() 56 | self.bar = Bar() 57 | self.add_window(self.bar) 58 | 59 | return 0 60 | 61 | def __init__(self) -> None: 62 | super().__init__( 63 | application_id="my.awesome.simple-bar", 64 | flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, 65 | ) 66 | 67 | 68 | if __name__ == "__main__": 69 | app = App() 70 | GLib.set_prgname("simple-bar") 71 | app.run(sys.argv) 72 | -------------------------------------------------------------------------------- /lib/astal/gtk3/src/widget/centerbox.vala: -------------------------------------------------------------------------------- 1 | public class Astal.CenterBox : Gtk.Box, Gtk.Buildable { 2 | /** 3 | * Corresponds to [property@Gtk.Orientable :orientation]. 4 | */ 5 | [CCode (notify = false)] 6 | public bool vertical { 7 | get { return orientation == Gtk.Orientation.VERTICAL; } 8 | set { orientation = value ? Gtk.Orientation.VERTICAL : Gtk.Orientation.HORIZONTAL; } 9 | } 10 | 11 | construct { 12 | notify["orientation"].connect(() => { 13 | notify_property("vertical"); 14 | }); 15 | } 16 | 17 | static construct { 18 | set_css_name("centerbox"); 19 | } 20 | 21 | private Gtk.Widget _start_widget; 22 | public Gtk.Widget start_widget { 23 | get { return _start_widget; } 24 | set { 25 | if (_start_widget != null) 26 | remove(_start_widget); 27 | 28 | if (value != null) 29 | pack_start(value, true, true, 0); 30 | } 31 | } 32 | 33 | private Gtk.Widget _end_widget; 34 | public Gtk.Widget end_widget { 35 | get { return _end_widget; } 36 | set { 37 | if (_end_widget != null) 38 | remove(_end_widget); 39 | 40 | if (value != null) 41 | pack_end(value, true, true, 0); 42 | } 43 | } 44 | 45 | public Gtk.Widget center_widget { 46 | get { return get_center_widget(); } 47 | set { 48 | if (center_widget != null) 49 | remove(center_widget); 50 | 51 | if (value != null) 52 | set_center_widget(value); 53 | } 54 | } 55 | 56 | void add_child(Gtk.Builder builder, Object child, string? type) { 57 | if (child is Gtk.Widget) { 58 | switch (type) { 59 | case "start": 60 | start_widget = child as Gtk.Widget; 61 | break; 62 | case "center": 63 | center_widget = child as Gtk.Widget; 64 | break; 65 | case "end": 66 | end_widget = child as Gtk.Widget; 67 | break; 68 | default: 69 | base.add_child(builder, child, type); 70 | break; 71 | } 72 | } else { 73 | base.add_child(builder, child, type); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/bluetooth/src/interfaces.vala: -------------------------------------------------------------------------------- 1 | [DBus(name = "org.bluez.Adapter1")] 2 | private interface AstalBluetooth.IAdapter : DBusProxy { 3 | public abstract void remove_device(ObjectPath device) throws Error; 4 | public abstract void start_discovery() throws Error; 5 | public abstract void stop_discovery() throws Error; 6 | 7 | public abstract string[] uuids { owned get; } 8 | public abstract bool discoverable { get; set; } 9 | public abstract bool discovering { get; } 10 | public abstract bool pairable { get; set; } 11 | public abstract bool powered { get; set; } 12 | public abstract string address { owned get; } 13 | public abstract string alias { owned get; set; } 14 | public abstract string modalias { owned get; } 15 | public abstract string name { owned get; } 16 | public abstract uint class { get; } 17 | public abstract uint discoverable_timeout { get; set; } 18 | public abstract uint pairable_timeout { get; set; } 19 | } 20 | 21 | [DBus(name = "org.bluez.Device1")] 22 | private interface AstalBluetooth.IDevice : DBusProxy { 23 | public abstract void cancel_pairing() throws Error; 24 | public abstract async void connect() throws Error; 25 | public abstract void connect_profile(string uuid) throws Error; 26 | public abstract async void disconnect() throws Error; 27 | public abstract void disconnect_profile(string uuid) throws Error; 28 | public abstract void pair() throws Error; 29 | 30 | public abstract string[] uuids { owned get; } 31 | public abstract bool blocked { get; set; } 32 | public abstract bool connected { get; } 33 | public abstract bool legacy_pairing { get; } 34 | public abstract bool paired { get; } 35 | public abstract bool trusted { get; set; } 36 | public abstract int16 rssi { get; } 37 | public abstract ObjectPath adapter { owned get; } 38 | public abstract string address { owned get; } 39 | public abstract string alias { owned get; set; } 40 | public abstract string icon { owned get; } 41 | public abstract string modalias { owned get; } 42 | public abstract string name { owned get; } 43 | public abstract uint16 appearance { get; } 44 | public abstract uint32 class { get; } 45 | } 46 | 47 | [DBus(name = "org.bluez.Battery1")] 48 | private interface AstalBluetooth.IBattery : DBusProxy { 49 | public abstract uint8 percentage { get; } 50 | public abstract string source { owned get; } 51 | } 52 | -------------------------------------------------------------------------------- /lib/greet/src/cli.vala: -------------------------------------------------------------------------------- 1 | static bool help; 2 | static bool version; 3 | static string username; 4 | static string password; 5 | static string cmd; 6 | [CCode(array_length = false, array_null_terminated = true)] 7 | static string[] env; 8 | 9 | const OptionEntry[] options = { 10 | { "version", 'v', OptionFlags.NONE, OptionArg.NONE, ref version, null, null }, 11 | { "help", 'h', OptionFlags.NONE, OptionArg.NONE, ref help, null, null }, 12 | { "username", 'u', OptionFlags.NONE, OptionArg.STRING, ref username, null, null }, 13 | { "password", 'p', OptionFlags.NONE, OptionArg.STRING, ref password, null, null }, 14 | { "cmd", 'c', OptionFlags.NONE, OptionArg.STRING, ref cmd, null, null }, 15 | { "env", 'e', OptionFlags.NONE, OptionArg.STRING_ARRAY, ref env, null, null }, 16 | { null }, 17 | }; 18 | 19 | async int main(string[] argv) { 20 | try { 21 | var opts = new OptionContext(); 22 | opts.add_main_entries(options, null); 23 | opts.set_help_enabled(false); 24 | opts.set_ignore_unknown_options(false); 25 | opts.parse(ref argv); 26 | } catch (OptionError err) { 27 | printerr(err.message); 28 | return 1; 29 | } 30 | 31 | if (help) { 32 | print("Usage:\n"); 33 | print(" %s [flags]\n\n", argv[0]); 34 | print("Flags:\n"); 35 | print(" -h, --help Print this help and exit\n"); 36 | print(" -v, --version Print version number and exit\n"); 37 | print(" -u, --username User to login to\n"); 38 | print(" -p, --password Password of the user\n"); 39 | print(" -c, --cmd Command to start the session with\n"); 40 | print(" -e, --env Additional env vars to set for the session\n"); 41 | return 0; 42 | } 43 | 44 | if (version) { 45 | printerr(AstalGreet.VERSION); 46 | return 0; 47 | } 48 | 49 | if (username == null) { 50 | printerr("missing username\n"); 51 | return 1; 52 | } 53 | 54 | if (password == null) { 55 | printerr("missing password\n"); 56 | return 1; 57 | } 58 | 59 | if (cmd == null) { 60 | printerr("missing cmd\n"); 61 | return 1; 62 | } 63 | 64 | try { 65 | yield AstalGreet.login_with_env(username, password, cmd, env); 66 | } catch (Error err) { 67 | printerr(err.message); 68 | return 1; 69 | } 70 | 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /lib/wireplumber/src/meson.build: -------------------------------------------------------------------------------- 1 | srcs = files( 2 | 'audio.c', 3 | 'device.c', 4 | 'node.c', 5 | 'endpoint.c', 6 | 'stream.c', 7 | 'profile.c', 8 | 'route.c', 9 | 'video.c', 10 | 'wireplumber.c', 11 | 'channel.c', 12 | 'enums.c' 13 | ) 14 | 15 | deps = [ 16 | dependency('gobject-2.0'), 17 | dependency('gio-2.0'), 18 | dependency('wireplumber-0.5'), 19 | ] 20 | 21 | enums = gnome.mkenums_simple( 22 | 'astal-wp-enum-types', 23 | identifier_prefix: 'AstalWp', 24 | symbol_prefix: 'astal_wp', 25 | sources: astal_wireplumber_subheaders, 26 | install_header: true, 27 | install_dir: join_paths(get_option('includedir'), 'astal/wireplumber') 28 | ) 29 | 30 | astal_wireplumber_lib = library( 31 | 'astal-wireplumber', 32 | sources: srcs + enums, 33 | include_directories: astal_wireplumber_inc, 34 | dependencies: deps, 35 | version: meson.project_version(), 36 | install: true, 37 | ) 38 | 39 | libastal_wireplumber = declare_dependency(link_with: astal_wireplumber_lib, include_directories: astal_wireplumber_inc) 40 | 41 | # astal_wireplumber_executable = executable( 42 | # 'astal-wireplumber', 43 | # files('astal-wireplumber.c'), 44 | # dependencies : [ 45 | # dependency('gobject-2.0'), 46 | # dependency('gio-2.0'), 47 | # dependency('json-glib-1.0'), 48 | # libastal_wireplumber 49 | # ], 50 | # install : true) 51 | 52 | pkg_config_name = 'astal-wireplumber-' + lib_so_version 53 | 54 | if get_option('introspection') 55 | gir = gnome.generate_gir( 56 | astal_wireplumber_lib, 57 | sources: srcs + astal_wireplumber_headers + astal_wireplumber_subheaders + enums[1], 58 | nsversion: '0.1', 59 | namespace: 'AstalWp', 60 | symbol_prefix: 'astal_wp', 61 | identifier_prefix: 'AstalWp', 62 | includes: ['GObject-2.0', 'Gio-2.0'], 63 | header: 'astal-wp.h', 64 | export_packages: pkg_config_name, 65 | install: true, 66 | ) 67 | 68 | if get_option('vapi') 69 | gnome.generate_vapi( 70 | pkg_config_name, 71 | sources: [gir[0]], 72 | packages: ['gobject-2.0', 'gio-2.0'], 73 | install: true, 74 | ) 75 | endif 76 | endif 77 | 78 | pkg_config.generate( 79 | name: 'astal-wireplumber', 80 | version: meson.project_version(), 81 | libraries: [astal_wireplumber_lib], 82 | filebase: pkg_config_name, 83 | subdirs: 'astal', 84 | description: 'astal wireplumber module', 85 | url: 'https://github.com/astal-sh/wireplumber', 86 | ) 87 | -------------------------------------------------------------------------------- /docs/guide/libraries/tray.md: -------------------------------------------------------------------------------- 1 | # Tray 2 | 3 | Library for managing the systemtray by implementing the 4 | [StatusNotifierItem](https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/) 5 | protocol. 6 | 7 | ## Usage 8 | 9 | You can browse the [Tray reference](https://aylur.github.io/libastal/tray). 10 | 11 | ### CLI 12 | 13 | ```sh 14 | astal-tray --help 15 | ``` 16 | 17 | ### Library 18 | 19 | :::code-group 20 | 21 | ```js [ JavaScript] 22 | import Tray from "gi://AstalTray" 23 | 24 | const tray = Tray.get_default() 25 | 26 | for (const item of tray.get_items()) { 27 | print(item.title) 28 | } 29 | ``` 30 | 31 | ```py [ Python] 32 | from gi.repository import AstalTray as Tray 33 | 34 | tray = Tray.get_default() 35 | 36 | for item in tray.get_items(): 37 | print(item.title) 38 | ``` 39 | 40 | ```lua [ Lua] 41 | local Tray = require("lgi").require("AstalTray") 42 | 43 | local tray = Tray.get_default() 44 | 45 | for _, i in ipairs(tray.items) do 46 | print(i.title) 47 | end 48 | ``` 49 | 50 | ```vala [ Vala] 51 | var tray = AstalTray.get_default(); 52 | 53 | foreach (var item in tray.get_items()) { 54 | print(item.title); 55 | } 56 | ``` 57 | 58 | ::: 59 | 60 | ## Installation 61 | 62 | 1. install dependencies 63 | 64 | :::code-group 65 | 66 | ```sh [ Arch] 67 | sudo pacman -Syu meson json-glib gobject-introspection 68 | ``` 69 | 70 | ```sh [ Fedora] 71 | sudo dnf install meson json-glib-devel gobject-introspection-devel 72 | ``` 73 | 74 | ```sh [ Ubuntu] 75 | sudo apt install meson libjson-glib-dev gobject-introspection 76 | ``` 77 | 78 | ::: 79 | 80 | 2. install `appmenu-glib-translator` 81 | 82 | ```sh 83 | git clone https://github.com/rilian-la-te/vala-panel-appmenu.git 84 | cd vala-panel-appmenu/subprojects/appmenu-glib-translator 85 | meson setup build 86 | meson install -C build 87 | ``` 88 | 89 | 3. clone repo 90 | 91 | ```sh 92 | git clone https://github.com/aylur/astal.git 93 | cd astal/lib/tray 94 | ``` 95 | 96 | 4. install 97 | 98 | ```sh 99 | meson setup build 100 | meson install -C build 101 | ``` 102 | -------------------------------------------------------------------------------- /lib/apps/src/fuzzy.vala: -------------------------------------------------------------------------------- 1 | namespace AstalApps { 2 | private int fuzzy_match_string(string pattern, string str) { 3 | const int unmatched_letter_penalty = -1; 4 | int score = 100; 5 | int not_found_score = -10; 6 | 7 | if (pattern.length == 0) return score; 8 | if (str.length < pattern.length) return not_found_score; 9 | 10 | bool found = fuzzy_match_recurse(pattern, str, score, true, out score); 11 | score += unmatched_letter_penalty * (str.length - pattern.length); 12 | 13 | if (!found) score = not_found_score; 14 | return score; 15 | } 16 | 17 | private bool fuzzy_match_recurse(string pattern, string str, int score, bool first_char, out int result) { 18 | result = score; 19 | if (pattern.length == 0) return true; 20 | 21 | int match_idx = 0; 22 | int offset = 0; 23 | unichar search = pattern.casefold().get_char(0); 24 | int best_score = int.MIN; 25 | 26 | while ((match_idx = str.casefold().substring(offset).index_of_char(search)) >= 0) { 27 | offset += match_idx; 28 | int subscore; 29 | bool found = fuzzy_match_recurse( 30 | pattern.substring(1), 31 | str.substring(offset + 1), 32 | compute_score(offset, first_char, str, offset), false, out subscore); 33 | if (!found) break; 34 | best_score = int.max(best_score, subscore); 35 | offset++; 36 | } 37 | 38 | if (best_score == int.MIN) return false; 39 | result += best_score; 40 | return true; 41 | } 42 | 43 | private int compute_score(int jump, bool first_char, string match, int idx) { 44 | const int adjacency_bonus = 15; 45 | const int separator_bonus = 30; 46 | const int camel_bonus = 30; 47 | const int first_letter_bonus = 15; 48 | const int leading_letter_penalty = -5; 49 | const int max_leading_letter_penalty = -15; 50 | 51 | int score = 0; 52 | 53 | if (!first_char && (jump == 0)) { 54 | score += adjacency_bonus; 55 | } 56 | if (!first_char || (jump > 0)) { 57 | if (match[idx].isupper() && match[idx - 1].islower()) { 58 | score += camel_bonus; 59 | } 60 | if (match[idx].isalnum() && !match[idx - 1].isalnum()) { 61 | score += separator_bonus; 62 | } 63 | } 64 | if (first_char && (jump == 0)) { 65 | score += first_letter_bonus; 66 | } 67 | if (first_char) { 68 | score += int.max(leading_letter_penalty * jump, max_leading_letter_penalty); 69 | } 70 | 71 | return score; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /docs/guide/libraries/bluetooth.md: -------------------------------------------------------------------------------- 1 | # Bluetooth 2 | 3 | Library for monitoring [bluez](https://www.bluez.org/) over dbus. 4 | 5 | ## Usage 6 | 7 | You can browse the 8 | [Bluetooth reference](https://aylur.github.io/libastal/bluetooth). 9 | 10 | ### CLI 11 | 12 | There is no CLI for this library, use the one provided by bluez. 13 | 14 | ```sh 15 | bluetoothctl --help 16 | ``` 17 | 18 | ### Library 19 | 20 | :::code-group 21 | 22 | ```js [ JavaScript] 23 | import Bluetooth from "gi://AstalBluetooth" 24 | 25 | const bluetooth = Bluetooth.get_default() 26 | 27 | for (const device of bluetooth.get_devices()) { 28 | print(device.name) 29 | } 30 | ``` 31 | 32 | ```py [ Python] 33 | from gi.repository import AstalBluetooth as Bluetooth 34 | 35 | bluetooth = Bluetooth.get_default() 36 | 37 | for device in bluetooth.get_devices(): 38 | print(device.get_name()) 39 | ``` 40 | 41 | ```lua [ Lua] 42 | local Bluetooth = require("lgi").require("AstalBluetooth") 43 | 44 | local bluetooth = Bluetooth.get_default() 45 | 46 | for _, d in ipairs(bluetooth.devices) do 47 | print(d.name) 48 | end 49 | ``` 50 | 51 | ```vala [ Vala] 52 | var bluetooth = AstalBluetooth.get_default(); 53 | 54 | foreach (var device in bluetooth.get_devices()) { 55 | print("%s\n", device.name); 56 | } 57 | ``` 58 | 59 | ::: 60 | 61 | ## Installation 62 | 63 | 1. install dependencies 64 | 65 | :::code-group 66 | 67 | ```sh [ Arch] 68 | sudo pacman -Syu meson vala valadoc gobject-introspection 69 | ``` 70 | 71 | ```sh [ Fedora] 72 | sudo dnf install meson vala valadoc gobject-introspection-devel 73 | ``` 74 | 75 | ```sh [ Ubuntu] 76 | sudo apt install meson valac valadoc gobject-introspection 77 | ``` 78 | 79 | ::: 80 | 81 | ::: info 82 | 83 | Although bluez is not a direct build dependency, it should be 84 | self-explanatory that the daemon is required to be available at runtime. 85 | 86 | ::: 87 | 88 | 2. clone repo 89 | 90 | ```sh 91 | git clone https://github.com/aylur/astal.git 92 | cd astal/lib/bluetooth 93 | ``` 94 | 95 | 3. install 96 | 97 | ```sh 98 | meson setup build 99 | meson install -C build 100 | ``` 101 | -------------------------------------------------------------------------------- /lang/lua/astal/binding.lua: -------------------------------------------------------------------------------- 1 | local lgi = require("lgi") 2 | local GObject = lgi.require("GObject", "2.0") 3 | 4 | ---@class Binding 5 | ---@field emitter table | Variable | userdata 6 | ---@field property? string 7 | ---@field transform_fn function 8 | ---@overload fun(emitter: table | userdata, property?: string): Binding 9 | local Binding = {} 10 | Binding.__index = Binding 11 | 12 | ---@param emitter table | Variable | userdata 13 | ---@param property? string 14 | ---@return Binding 15 | function Binding.new(emitter, property) 16 | return setmetatable({ 17 | emitter = emitter, 18 | property = property, 19 | transform_fn = function(v) 20 | return v 21 | end, 22 | }, Binding) 23 | end 24 | 25 | function Binding:__tostring() 26 | local str = "Binding<" .. tostring(self.emitter) 27 | if self.property ~= nil then 28 | str = str .. ", " .. self.property 29 | end 30 | return str .. ">" 31 | end 32 | 33 | ---@return any 34 | function Binding:get() 35 | if self.property ~= nil and GObject.Object:is_type_of(self.emitter) then 36 | return self.transform_fn(self.emitter[self.property]) 37 | elseif type(self.emitter.get) == "function" then 38 | return self.transform_fn(self.emitter:get()) 39 | else 40 | error("can not get: Not a GObject or a Variable " + self) 41 | end 42 | end 43 | 44 | ---@param transform fun(value: any): any 45 | ---@return Binding 46 | function Binding:as(transform) 47 | local b = Binding.new(self.emitter, self.property) 48 | b.transform_fn = function(v) 49 | return transform(self.transform_fn(v)) 50 | end 51 | return b 52 | end 53 | 54 | ---@param callback fun(value: any) 55 | ---@return function 56 | function Binding:subscribe(callback) 57 | if self.property ~= nil and GObject.Object:is_type_of(self.emitter) then 58 | local id = self.emitter.on_notify:connect(function() 59 | callback(self:get()) 60 | end, self.property, false) 61 | return function() 62 | GObject.signal_handler_disconnect(self.emitter, id) 63 | end 64 | elseif type(self.emitter.subscribe) == "function" then 65 | return self.emitter:subscribe(function() 66 | callback(self:get()) 67 | end) 68 | else 69 | error("can not subscribe: Not a GObject or a Variable " + self) 70 | end 71 | end 72 | 73 | return setmetatable(Binding, { 74 | __call = function(_, emitter, prop) 75 | return Binding.new(emitter, prop) 76 | end, 77 | }) 78 | --------------------------------------------------------------------------------