diff options
56 files changed, 609 insertions, 658 deletions
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 936a648..990ede7 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -10,7 +10,7 @@ assignees: '' **Have you read through the documentation?** - [ ] [Astal docs](https://aylur.github.io/astal/) - [ ] [Library references](https://aylur.github.io/astal/guide/libraries/references) -- [ ] [FAQ](https://aylur.github.io/astal/guide/ags/faq) +- [ ] [FAQ](https://aylur.github.io/astal/guide/typescript/faq) **Describe what have you tried so far** A description or example code of what you have got so far. @@ -1,8 +1,3 @@ # Astal -> [!WARNING] -> WIP: nothing is stable yet, every library is subject to change - -## Getting Started - To get started read the [wiki](https://aylur.github.io/astal/) diff --git a/docs/guide/getting-started/installation.md b/docs/guide/getting-started/installation.md index fa7863e..e32b6a9 100644 --- a/docs/guide/getting-started/installation.md +++ b/docs/guide/getting-started/installation.md @@ -1,11 +1,5 @@ # Installation -## Nix - -maintainer: [@Aylur](https://github.com/Aylur) - -Read more about it on the [nix page](./nix#astal) - ## Arch maintainer: [@kotontrion](https://github.com/kotontrion) @@ -22,7 +16,13 @@ yay -S libastal-meta ::: -## Bulding libastal from source +## Nix + +maintainer: [@Aylur](https://github.com/Aylur) + +Read more about it on the [nix page](./nix#astal) + +## Bulding From Source 1. Install the following dependencies diff --git a/docs/guide/getting-started/nix.md b/docs/guide/getting-started/nix.md index 1e0572e..6bc5d9b 100644 --- a/docs/guide/getting-started/nix.md +++ b/docs/guide/getting-started/nix.md @@ -5,13 +5,15 @@ next: --- # Nix -## Astal - Using Astal on Nix will require you to package your project. +## TypeScript + +Using [AGS](https://aylur.github.io/ags/) as the bundler. + :::code-group -```nix [<i class="devicon-lua-plain"></i> Lua] +```nix [<i class="devicon-nixos-plain"></i> flake.nix] { inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; @@ -19,80 +21,49 @@ Using Astal on Nix will require you to package your project. url = "github:aylur/astal"; inputs.nixpkgs.follows = "nixpkgs"; }; + ags = { + url = "github:aylur/ags"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; - outputs = { self, nixpkgs, astal }: let + outputs = { self, nixpkgs, astal, ags }: let system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; in { - packages.${system}.default = astal.lib.mkLuaPackage { - inherit pkgs; - src = ./path/to/project; # should contain init.lua - - # add extra glib packages or binaries - extraPackages = [ - astal.packages.${system}.battery - pkgs.dart-sass + packages.${system}. default = pkgs.stdenvNoCC.mkDerivation rec { + name = "my-shell"; + src = ./.; + + nativeBuildInputs = [ + ags.packages.${system}.default + pkgs.wrapGAppsHook + pkgs.gobject-introspection ]; - }; - }; -} -``` -```nix [<i class="devicon-python-plain"></i> Python] -# Not documented yet -``` - -```nix [<i class="devicon-vala-plain"></i> Vala] -# keep in mind that this is just the nix derivation -# and you still have to use some build tool like meson -{ - inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - astal.url = "github:aylur/astal"; - }; - - outputs = { self, nixpkgs, astal }: let - system = "x86_64-linux"; - pkgs = nixpkgs.legacyPackages.${system}; - in { - packages.${system} = { - default = pkgs.stdenv.mkDerivation { - name = "my-shell"; - src = ./.; - - nativeBuildInputs = with pkgs; [ - meson - ninja - pkg-config - vala - gobject-introspection - ]; + buildInputs = with astal.packages.${system}; [ + astal3 + io + # any other package + ]; - # add extra packages - buildInputs = [ - astal.packages.${system}.astal - ]; - }; + installPhase = '' + mkdir -p $out/bin + ags bundle app.ts $out/bin/${name} + ''; }; }; } ``` -```nix [<i class="devicon-typescript-plain"></i> TypeScript] -# The usage of AGS (read below) is recommended -# Usage without AGS is not yet documented -``` - ::: -## AGS - -The recommended way to use [AGS](../typescript/first-widgets#first-widgets) on NixOS is through the home-manager module. - -Example content of a `flake.nix` file that contains your `homeConfigurations`. +:::tip +You can use any other bundler too like `esbuild` +which is what `ags` uses under the hood. +::: -<!--TODO: remove v2 after merge--> +## Lua :::code-group @@ -100,28 +71,26 @@ Example content of a `flake.nix` file that contains your `homeConfigurations`. { inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; - home-manager = { - url = "github:nix-community/home-manager"; + astal = { + url = "github:aylur/astal"; inputs.nixpkgs.follows = "nixpkgs"; }; - - # add ags https://github.com/Aylur/ags/pull/504 - ags.url = "github:aylur/ags/v2"; }; - outputs = { home-manager, nixpkgs, ... }@inputs: - let + outputs = { self, nixpkgs, astal }: let system = "x86_64-linux"; - in - { - homeConfigurations."${username}" = home-manager.lib.homeManagerConfiguration { - pkgs = import nixpkgs { inherit system; }; - - # pass inputs as specialArgs - extraSpecialArgs = { inherit inputs; }; + pkgs = nixpkgs.legacyPackages.${system}; + in { + packages.${system}.default = astal.lib.mkLuaPackage { + inherit pkgs; + name = "my-shell"; # how to name the executable + src = ./path/to/project; # should contain init.lua - # import your home.nix - modules = [ ./home-manager/home.nix ]; + # add extra glib packages or binaries + extraPackages = [ + astal.packages.${system}.battery + pkgs.dart-sass + ]; }; }; } @@ -129,65 +98,58 @@ Example content of a `flake.nix` file that contains your `homeConfigurations`. ::: -Example content of `home.nix` file +## Python :::code-group -```nix [<i class="devicon-nixos-plain"></i> home.nix] -{ inputs, pkgs, ... }: -{ - # add the home manager module - imports = [ inputs.ags.homeManagerModules.default ]; - - programs.ags = { - enable = true; - configDir = ../ags; - - # additional packages to add to gjs's runtime - extraPackages = with pkgs; [ - inputs.ags.packages.${pkgs.system}.battery - fzf - ]; - }; -} +```nix [<i class="devicon-nixos-plain"></i> flake.nix] +# Not documented yet ``` ::: -AGS by default only includes the core `astal3/astal4` and `astal-io` libraries. -If you want to include any other [library](../libraries/references) you have to add them to `extraPackages`. -You can also add binaries which will be added to the gjs runtime. - -:::warning -The `configDir` option symlinks the given path to `~/.config/ags`. -If you already have your source code there leave it as `null`. -::: +## Vala -The AGS flake does not expose the `astal` cli to the home environment, you have to do that yourself if you want: +Keep in mind that this is just the nix derivation +and you still have to use some build tool like meson. :::code-group -```nix [<i class="devicon-nixos-plain"></i> home.nix] -home.packages = [ inputs.ags.packages.${pkgs.system}.io ]; -``` - -```sh [<i class="devicon-bash-plain"></i> sh] -astal --help -``` - -::: - -Same applies to the `extraPackages` option, it does not expose the passed packages to the home environment. -To make astal cli tools available to home environments, you have to add them yourself: - -:::code-group +```nix [<i class="devicon-nixos-plain"></i> flake.nix] +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + astal = { + url = "github:aylur/astal"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; -```nix [<i class="devicon-nixos-plain"></i> home.nix] -home.packages = [ inputs.ags.packages.${pkgs.system}.notifd ]; -``` + outputs = { self, nixpkgs, astal }: let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + in { + packages.${system}.default = pkgs.stdenv.mkDerivation { + name = "my-shell"; + src = ./.; + + nativeBuildInputs = with pkgs; [ + meson + ninja + pkg-config + vala + gobject-introspection + ]; -```sh [<i class="devicon-bash-plain"></i> sh] -astal-notifd --help + buildInputs = [ + astal.packages.${system}.io + astal.packages.${system}.astal3 + astal.packages.${system}.battery + # add extra packages + ]; + }; + }; +} ``` ::: diff --git a/docs/guide/libraries/tray.md b/docs/guide/libraries/tray.md index 39dffc1..43b3aa6 100644 --- a/docs/guide/libraries/tray.md +++ b/docs/guide/libraries/tray.md @@ -62,7 +62,7 @@ sudo pacman -Syu meson gtk3 gobject-introspection libdbusmenu-gtk3 ``` ```sh [<i class="devicon-fedora-plain"></i> Fedora] -sudo dnf install meson gcc gtk3-devel libdbusmenu-gtk3 gobject-introspection-devel +sudo dnf install meson gcc gtk3-devel libdbusmenu-gtk3-devel gobject-introspection-devel ``` ```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu] diff --git a/docs/guide/lua/examples.md b/docs/guide/lua/examples.md new file mode 100644 index 0000000..be46b6e --- /dev/null +++ b/docs/guide/lua/examples.md @@ -0,0 +1,4 @@ +# Lua examples + +## [Simple Bar](https://github.com/Aylur/astal/tree/main/examples/lua/simple-bar) + diff --git a/docs/guide/lua/first-widgets.md b/docs/guide/lua/first-widgets.md index efc1c4f..70cfe0c 100644 --- a/docs/guide/lua/first-widgets.md +++ b/docs/guide/lua/first-widgets.md @@ -13,8 +13,7 @@ local App = require("astal.gtk3.app") App:start({ main = function() -- you will instantiate Widgets here - -- or setup anything else if you need - print("hi") + -- and setup anything else if you need end }) ``` @@ -22,7 +21,7 @@ App:start({ ::: Then run `lua init.lua` in the terminal, and that's it! -Now you have an instance running with Lua. +Now you have an Astal instance running written in Lua. ## Root of every shell component: Window diff --git a/docs/guide/lua/installation.md b/docs/guide/lua/installation.md index b99d8df..f647ed7 100644 --- a/docs/guide/lua/installation.md +++ b/docs/guide/lua/installation.md @@ -9,7 +9,7 @@ Read more about it on the [nix page](../getting-started/nix) ## Arch ```sh -yay -S lua-libastal-git +yay -S libastal-lua-git ``` ## From Source diff --git a/docs/guide/lua/theming.md b/docs/guide/lua/theming.md index 502e8e9..4f556fc 100644 --- a/docs/guide/lua/theming.md +++ b/docs/guide/lua/theming.md @@ -39,7 +39,7 @@ App:start({ ::: :::warning -When using relative paths, so for example `./style.css` keep in mind that they +When using relative paths, for example `./style.css` keep in mind that they will be relative to the current working directory. ::: diff --git a/docs/guide/lua/widget.md b/docs/guide/lua/widget.md index d9f99fa..593628e 100644 --- a/docs/guide/lua/widget.md +++ b/docs/guide/lua/widget.md @@ -139,18 +139,18 @@ These widgets are available by default in Lua. - Button: [Astal.Button](https://aylur.github.io/libastal/astal3/class.Button.html) - CenterBox: [Astal.CenterBox](https://aylur.github.io/libastal/astal3/class.CenterBox.html) - CircularProgress: [Astal.CircularProgress](https://aylur.github.io/libastal/astal3/class.CircularProgress.html) -- DrawingArea: [Gtk.DrawingArea](https://docs.gtk.org/gtk3/astal3/class.DrawingArea.html) -- Entry: [Gtk.Entry](https://docs.gtk.org/gtk3/astal3/class.Entry.html) +- DrawingArea: [Gtk.DrawingArea](https://docs.gtk.org/gtk3/class.DrawingArea.html) +- Entry: [Gtk.Entry](https://docs.gtk.org/gtk3/class.Entry.html) - Eventbox: [Astal.EventBox](https://aylur.github.io/libastal/astal3/class.EventBox.html) - Icon: [Astal.Icon](https://aylur.github.io/libastal/astal3/class.Icon.html) - Label: [Astal.Label](https://aylur.github.io/libastal/astal3/class.Label.html) - Levelbar: [Astal.LevelBar](https://aylur.github.io/libastal/astal3/class.LevelBar.html) - Overlay: [Astal.Overlay](https://aylur.github.io/libastal/astal3/class.Overlay.html) -- Revealer: [Gtk.Revealer](https://docs.gtk.org/gtk3/astal3/class.Revealer.html) +- Revealer: [Gtk.Revealer](https://docs.gtk.org/gtk3/class.Revealer.html) - Scrollable: [Astal.Scrollable](https://aylur.github.io/libastal/astal3/class.Scrollable.html) - Slider: [Astal.Slider](https://aylur.github.io/libastal/astal3/class.Slider.html) - Stack: [Astal.Stack](https://aylur.github.io/libastal/astal3/class.Stack.html) -- Switch: [Gtk.Switch](https://docs.gtk.org/gtk3/astal3/class.Switch.html) +- Switch: [Gtk.Switch](https://docs.gtk.org/gtk3/class.Switch.html) - Window: [Astal.Window](https://aylur.github.io/libastal/astal3/class.Window.html) ## Gtk4 diff --git a/docs/guide/typescript/cli-app.md b/docs/guide/typescript/cli-app.md index 85b117c..9b299aa 100644 --- a/docs/guide/typescript/cli-app.md +++ b/docs/guide/typescript/cli-app.md @@ -6,7 +6,6 @@ Depending on gtk version import paths will differ ```ts import { App } from "astal/gtk3" - import { App } from "astal/gtk4" ``` @@ -53,20 +52,11 @@ App.start({ }) ``` -:::code-group - -```sh [astal] +```sh astal say hi # hi cli ``` -```sh [ags] -ags request "say hi" -# hi cli -``` - -::: - If you want to run arbitrary JavaScript from CLI, you can use `App.eval` which will evaluate the passed string as the body of an `async` function. @@ -102,7 +92,7 @@ In order for Astal to know about your windows, you have to register them. You can do this by specifying a **unique** `name` and calling `App.add_window` ```tsx {4} -import { App } from "astal" +import { App } from "astal/gtk3" function Bar() { return <window name="Bar" setup={self => App.add_window(self)}> @@ -114,7 +104,7 @@ function Bar() { You can also invoke `App.add_window` by simply passing the `App` to the `application` prop. ```tsx {4} -import { App } from "astal" +import { App } from "astal/gtk3" function Bar() { return <window name="Bar" application={App}> @@ -128,24 +118,13 @@ When assigning the `application` prop make sure `name` comes before. Props are set sequentially and if name is applied after application it won't work. ::: -:::code-group - ```sh [astal] astal -t Bar ``` -```sh [ags] -ags toggle Bar -``` - -::: - -## Bundled scripts - -The produced scripts when bundling can run as the main instance -and a "client" instance. +## Client -The first time you execute your bundled script the `main` function gets called. +The first time you execute your script the `main` function gets called. While that instance is running any subsequent execution of the script will call the `client` function. diff --git a/docs/guide/typescript/examples.md b/docs/guide/typescript/examples.md new file mode 100644 index 0000000..ec51e89 --- /dev/null +++ b/docs/guide/typescript/examples.md @@ -0,0 +1,14 @@ +# TypeScript Examples + +## Gtk3 + +### [Simple Bar](https://github.com/Aylur/astal/tree/main/examples/js/simple-bar) + + +### [Notification Popups](https://github.com/Aylur/astal/tree/main/examples/js/notifications) + +### [Applauncher](https://github.com/Aylur/astal/tree/main/examples/js/applauncher) + + +### [Media Player](https://github.com/Aylur/astal/tree/main/examples/js/media-player) + diff --git a/docs/guide/typescript/faq.md b/docs/guide/typescript/faq.md index 48c802c..4ee616b 100644 --- a/docs/guide/typescript/faq.md +++ b/docs/guide/typescript/faq.md @@ -7,7 +7,7 @@ the same as the compositor. Instead use the `gdkmonitor` property which expects a `Gdk.Monitor` object. ```tsx -import { App } from "astal" +import { App } from "astal/gtk3" function Bar(gdkmonitor) { return <window gdkmonitor={gdkmonitor} /> @@ -92,7 +92,7 @@ printerr("print this line to stderr") ## Populate the global scope with frequently accessed variables -It might be annoying to always import Gtk only for `Gtk.Align` enums. +It might be annoying to always import Gtk only for the `Gtk.Align` enum. :::code-group @@ -118,7 +118,7 @@ Object.assign(globalThis, { :::code-group -```tsx [Bar.tsx] +```tsx [Bar.tsx] {3} export default function Bar() { return <window> <box halign={START} /> @@ -131,11 +131,13 @@ export default function Bar() { :::code-group ```ts [app.ts] -import "./globals" +import "./globals" // don't forget to import it first // [!code ++] import Bar from "./Bar" App.start({ - main: Bar + main() { + Bar() + } }) ``` @@ -162,7 +164,7 @@ export default function Bar(gdkmonitor: Gdk.Monitor) { :::code-group ```ts [app.ts] -import { Gdk, Gtk } from "astal" +import { Gdk, Gtk } from "astal/gtk3" import Bar from "./Bar" function main() { @@ -197,11 +199,14 @@ These happen when accessing list type properties. Gjs fails to correctly bind import Notifd from "gi://AstalNotifd" const notifd = Notifd.get_default() -notifd.notifications // ❌ // [!code error] - -notifd.get_notifications() // ✅ +notifd.notifications // [!code --] +notifd.get_notifications() // [!code ++] ``` +:::tip +Open up an issue/PR to add a [workaround](https://github.com/Aylur/astal/blob/main/lang/gjs/src/overrides.ts). +::: + ## How to create regular floating windows Use `Gtk.Window` with [Widget.astalify](/guide/typescript/widget#how-to-use-non-builtin-gtk-widgets). diff --git a/docs/guide/typescript/first-widgets.md b/docs/guide/typescript/first-widgets.md index 96a1275..77b2f61 100644 --- a/docs/guide/typescript/first-widgets.md +++ b/docs/guide/typescript/first-widgets.md @@ -2,41 +2,26 @@ ## Getting Started -Start by initializing a project +Start by importing the singleton +[Astal.Application](https://aylur.github.io/libastal/astal3/class.Application.html) instance. -```sh -ags init -``` +:::code-group -then run `ags run` in the terminal +```ts [app.ts] +import { App } from "astal/gtk3" -```sh -ags run +App.start({ + main() { + // you will instantiate Widgets here + // and setup anything else if you need + } +}) ``` -:::details Usage without AGS -🚧 Not yet documented. 🚧 ::: -That's it! You have now a custom written bar using Gtk. - -:::tip -AGS will transpile every `.ts`, `.jsx` and `.tsx` files into regular JavaScript, then -it will bundle everything into a single JavaScript file which then GJS can execute. -::: - -The AGS init command will generate the following files - -```txt -. -├── @girs/ # generated types -├── widget/ -│ └── Bar.tsx -├── app.ts # entry proint -├── env.d.ts # additional types -├── style.css -└── tsconfig.json # needed by LSPs -``` +After your [bundle step](./installation.md) run `gjs -m app.js`, and that's it! +Now you have an Astal instance running written in TypeScript. ## Root of every shell component: Window @@ -290,7 +275,7 @@ function Counter() { <label label={bind(count).as(num => num.toString())} /> <button onClicked={increment}> Click to increment - <button> + </button> </box> } ``` @@ -369,42 +354,75 @@ inner state of widgets that does not need to be recreated. In this case you can create a [custom reactive structure](./binding#example-custom-subscribable) ::: -When there is at least one `Binding` passed as a child, the `children` -parameter will always be a flattened `Binding<Array<JSX.Element>>`. -When there is a single `Binding` passed as a child, the `child` parameter will -be a `Binding<JSX.Element>` or a flattened `Binding<Array<JSX.Element>>`. +# How children are passed + +Here is full list of how children and bound children can be passed to custom widgets. ```tsx -import { type Binding } from "astal" +import Binding from "astal/binding" -function MyContainer({ children }: { - children?: Binding<Array<JSX.Element>> -}) { - // children is a Binding over an Array of widgets -} +function Parent(props: { + child?: JSX.Element | Binding<JSX.Element> | Binding<Array<JSX.Element>> + children?: Array<JSX.Element> | Binding<Array<JSX.Element>> +}) -return <MyContainer> - <box /> - {num(n => range(n).map(i => ( - <button> - {i.toString()} - <button/> - )))} - [ - [ - <button /> - ] - <button /> - ] -</MyContainer> +// { child: JSX.Element } +<Parent> + <child /> +</Parent> + +// { children: Array<JSX.Element> } +<Parent> + <child /> + <child /> +</Parent> + +// { child: Binding<JSX.Element> } +<Parent> + {variable(c => ( + <child /> + ))} +</Parent> + +// { child: Binding<Array<JSX.Element>> } +<Parent> + {variable(c => ( + <child /> + <child /> + ))} +</Parent> + +// { children: Binding<Array<JSX.Element>> } +<Parent> + <child /> + {variable(c => ( + <child /> + ))} +</Parent> + + +// { children: Binding<Array<JSX.Element>> } +<Parent> + <child /> + {variable(c => ( + <child /> + <child /> + ))} +</Parent> ``` +:::tip +If you have a widget where you pass widgets in various ways, you can +wrap `child` in `children` in a [`Subscribable`](./faq#custom-widgets-with-bindable-properties) and handle all cases +as if they were bindings. +::: + :::info You can pass the followings as children: - widgets - deeply nested arrays of widgets -- bindings of widgets, +- bindings of widgets - bindings of deeply nested arrays of widgets [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy) values are not rendered and anything not from this list diff --git a/docs/guide/typescript/gobject.md b/docs/guide/typescript/gobject.md index f7f001d..4e40845 100644 --- a/docs/guide/typescript/gobject.md +++ b/docs/guide/typescript/gobject.md @@ -74,7 +74,7 @@ class MyObj extends GObject.Object { declare myProp: string constructor() { - super({ myProp: "default-value" }) + super({ myProp: "default-value" } as any) } } ``` diff --git a/docs/guide/typescript/installation.md b/docs/guide/typescript/installation.md index e0f1bd5..0c19325 100644 --- a/docs/guide/typescript/installation.md +++ b/docs/guide/typescript/installation.md @@ -1,89 +1,19 @@ # Installation -It is recommended to use [AGS](https://github.com/aylur/ags) +It is recommended to use [AGS](https://aylur.github.io/ags/) to scaffold and run projects in TypeScript. It lets you - generate TypeScript types using [ts-for-gir](https://github.com/gjsify/ts-for-gir) -- generate a tsconfig which is used by LSPs -- bundle your TypeScript and JavaScript code using [esbuild](https://esbuild.github.io/). +- generate a tsconfig which is used by LSPs to provide intellisense +- bundle your TypeScript and JavaScript code using [esbuild](https://esbuild.github.io/) :::details Trivia AGS is the predecessor of Astal, which was written purely in TypeScript and so only supported -JavaScript/TypeScript. Now it serves as a scaffolding tool for Astal+TypeScript+JSX projects. +JavaScript/TypeScript. Now it serves as a scaffolding tool for Astal+TypeScript projects. ::: -## Nix - -maintainer: [@Aylur](https://github.com/Aylur) - -Read more about it on the [nix page](../getting-started/nix#ags) - -You can try without installing. - -<!--TODO: remove v2 after merge--> -```sh -nix run github:aylur/ags/v2#agsFull -- --help -``` - -## From source - -1. [Install Astal](../getting-started/installation.md) if you have not already - -2. Install the Astal GJS package - -```sh -git clone https://github.com/aylur/astal /tmp/astal -cd /tmp/astal/lang/gjs -meson setup --prefix /usr build -meson install -C build -``` - -:::tip -You might be wondering why it is recommended to install a JavaScript -package on the system instead of installing it as a node module. -It is solely to keep it in **sync** with the core `astal-io` and `astal3`/`astal4` package. -::: - -3. Install the following dependencies - -:::code-group - -```sh [<i class="devicon-archlinux-plain"></i> Arch] -sudo pacman -Syu go npm gjs -``` - -```sh [<i class="devicon-fedora-plain"></i> Fedora] -sudo dnf install golang npm gjs -``` - -```sh [<i class="devicon-ubuntu-plain"></i> Ubuntu] -sudo apt install golang-go npm gjs -``` - -::: - -3. Clone the repo and Install - -<!--TODO: remove v2 after merge--> -```sh -git clone https://github.com/aylur/ags.git /tmp/ags -cd /tmp/ags -git checkout v2 # https://github.com/Aylur/ags/pull/504 -go install -``` - -:::tip -`go install` installs the `ags` binary to `$GOPATH/bin` so make sure its in your `$PATH`. -You can move it to another directory if you like. For example - -```sh -sudo mv $GOPATH/bin/ags /usr/bin/ags -``` - -::: - -## Without AGS +## Setting up a project 🚧 Setting up a dev environment without AGS is not yet documented. 🚧 diff --git a/docs/guide/typescript/theming.md b/docs/guide/typescript/theming.md index 10a3981..5944c4e 100644 --- a/docs/guide/typescript/theming.md +++ b/docs/guide/typescript/theming.md @@ -23,28 +23,24 @@ You can pass a path to a file or css as a string in `App.start` :::code-group ```ts [app.ts] -import style from "inline:./style.css" - -const inlineCssInCode = ` -window { - background-color: transparent; -} +const inlineCss = ` + window { + background-color: transparent; + } ` App.start({ - css: "./style.css", - css: style, - css: `${SRC}/style.css'`, css: inlineCss, + css: "./style.css", + css: "/path/to/style.css", }) ``` ::: -:::info -When using AGS the global `SRC` will point to the directory `app.ts` is in. -AGS will set the current working directory to `--config`, so `./style.css` also works. -If you are not using AGS you should inline import CSS instead. +:::warning +When using relative paths, for example `./style.css` keep in mind that they +will be relative to the current working directory. ::: ## Css Property on Widgets @@ -90,20 +86,11 @@ You can reset stylesheets with `App.resetCss` If you are not sure about the widget hierarchy or any CSS selector, you can use the [GTK inspector](https://wiki.gnome.org/Projects/GTK/Inspector) -::: code-group - -```sh [astal] +```sh # to bring up the inspector run astal --inspector ``` -```sh [ags] -# to bring up the inspector run -ags --inspector -``` - -::: - ## Using SCSS Gtk's CSS only supports a subset of what the web offers. @@ -126,54 +113,22 @@ npm install -g sass # not packaged on Ubuntu ::: -Importing `scss` files will simply return transpiled css. - :::code-group ```ts [app.ts] -import style from "./style.scss" +import { exec } from "astal/process" + +exec("sass", "./style.scss", "/tmp/style.css") App.start({ - css: style, + css: "/tmp/style.css", main() {}, }) ``` ::: -:::details Without AGS -AGS uses a plugin to transpile scss files before importing them. -If you are not using AGS, you can use a plugin for your chosen bundler. -::: - :::tip -If you for example want to set scss varibles from JS, -You can inline import, compose, and transpile yourself. - -```ts [app.ts] -import style1 from "inline:./style1.scss" -import style2 from "inline:./style2.scss" - -const tmpscss = "/tmp/style.scss" -const target = "/tmp/style.css" - -writeFile(tmpscss, ` - $var1: red; - $var1: blue; - ${style1} - ${style1} -`) - -exec(`sass ${tmpscss} ${target}`) - -App.start({ - css: target, -}) - -``` - -::: - -:::info -If you want other preprocessors support builtin open an Issue. +You could also transpile scss into css using a bundler +and simply passing the path of the resulting css file to `css`. ::: diff --git a/docs/package.json b/docs/package.json index fea7e8f..ad76c9a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,5 +1,5 @@ { - "name": "docs", + "name": "astal-docs", "type": "module", "devDependencies": { "@eslint/js": "^9.12.0", diff --git a/docs/vitepress.config.ts b/docs/vitepress.config.ts index 7e16eb7..3593b40 100644 --- a/docs/vitepress.config.ts +++ b/docs/vitepress.config.ts @@ -73,6 +73,7 @@ export default defineConfig({ { text: "GObject", link: "/gobject" }, { text: "Utilities", link: "/utilities" }, { text: "FAQ", link: "/faq" }, + { text: "Examples", link: "/examples" }, ], }, { @@ -90,6 +91,7 @@ export default defineConfig({ // { text: "GObject", link: "/gobject" }, { text: "Utilities", link: "/utilities" }, // { text: "FAQ", link: "/faq" }, + { text: "Examples", link: "/examples" }, ], }, { diff --git a/examples/js/simple-bar/README.md b/examples/js/simple-bar/README.md index 6488d14..f92b20e 100644 --- a/examples/js/simple-bar/README.md +++ b/examples/js/simple-bar/README.md @@ -4,7 +4,6 @@ A simple bar for Hyprland using -- [Audio library](https://aylur.github.io/astal/guide/libraries/audio). - [Battery library](https://aylur.github.io/astal/guide/libraries/battery). - [Hyprland library](https://aylur.github.io/astal/guide/libraries/hyprland). - [Mpris library](https://aylur.github.io/astal/guide/libraries/mpris). diff --git a/examples/lua/simple-bar/README.md b/examples/lua/simple-bar/README.md index bcc6bba..48cc27c 100644 --- a/examples/lua/simple-bar/README.md +++ b/examples/lua/simple-bar/README.md @@ -4,7 +4,6 @@ A simple bar for Hyprland using -- [Audio library](https://aylur.github.io/astal/guide/libraries/audio). - [Battery library](https://aylur.github.io/astal/guide/libraries/battery). - [Hyprland library](https://aylur.github.io/astal/guide/libraries/hyprland). - [Mpris library](https://aylur.github.io/astal/guide/libraries/mpris). diff --git a/examples/py/simple-bar/README.md b/examples/py/simple-bar/README.md index bcc6bba..48cc27c 100644 --- a/examples/py/simple-bar/README.md +++ b/examples/py/simple-bar/README.md @@ -4,7 +4,6 @@ A simple bar for Hyprland using -- [Audio library](https://aylur.github.io/astal/guide/libraries/audio). - [Battery library](https://aylur.github.io/astal/guide/libraries/battery). - [Hyprland library](https://aylur.github.io/astal/guide/libraries/hyprland). - [Mpris library](https://aylur.github.io/astal/guide/libraries/mpris). diff --git a/examples/vala/simple-bar/README.md b/examples/vala/simple-bar/README.md index bcc6bba..48cc27c 100644 --- a/examples/vala/simple-bar/README.md +++ b/examples/vala/simple-bar/README.md @@ -4,7 +4,6 @@ A simple bar for Hyprland using -- [Audio library](https://aylur.github.io/astal/guide/libraries/audio). - [Battery library](https://aylur.github.io/astal/guide/libraries/battery). - [Hyprland library](https://aylur.github.io/astal/guide/libraries/hyprland). - [Mpris library](https://aylur.github.io/astal/guide/libraries/mpris). diff --git a/examples/vala/simple-bar/widget/Bar.vala b/examples/vala/simple-bar/widget/Bar.vala index 17db831..ba4062c 100644 --- a/examples/vala/simple-bar/widget/Bar.vala +++ b/examples/vala/simple-bar/widget/Bar.vala @@ -113,9 +113,6 @@ class SysTray : Gtk.Box { var item = tray.get_item(id); - if (item.icon_theme_path != null) - App.instance.add_icons(item.icon_theme_path); - var menu = item.create_menu(); var btn = new Astal.Button(); var icon = new Astal.Icon(); @@ -131,8 +128,10 @@ class SysTray : Gtk.Box { }); item.bind_property("tooltip-markup", btn, "tooltip-markup", BindingFlags.SYNC_CREATE); - item.bind_property("gicon", icon, "gicon", BindingFlags.SYNC_CREATE); + item.bind_property("gicon", icon, "g-icon", BindingFlags.SYNC_CREATE); + btn.add(icon); add(btn); + btn.show_all(); items.set(id, btn); } diff --git a/lang/gjs/meson.build b/lang/gjs/meson.build index f4272ee..51496dc 100644 --- a/lang/gjs/meson.build +++ b/lang/gjs/meson.build @@ -14,6 +14,7 @@ install_data( 'src/process.ts', 'src/time.ts', 'src/variable.ts', + 'src/overrides.ts', 'src/_app.ts', ], install_dir: dest, diff --git a/lang/gjs/package.json b/lang/gjs/package.json index da88d90..43a7702 100644 --- a/lang/gjs/package.json +++ b/lang/gjs/package.json @@ -4,7 +4,7 @@ "description": "Building blocks for building linux desktop shell", "type": "module", "author": "Aylur", - "license": "GPL", + "license": "LGPL-2.1", "repository": { "type": "git", "url": "https://github.com/aylur/astal.git", diff --git a/lang/gjs/src/_app.ts b/lang/gjs/src/_app.ts index 82e8bb5..3dadd04 100644 --- a/lang/gjs/src/_app.ts +++ b/lang/gjs/src/_app.ts @@ -1,3 +1,4 @@ +import "./overrides.js" import { setConsoleLogDomain } from "console" import { exit, programArgs } from "system" import IO from "gi://AstalIO" diff --git a/lang/gjs/src/gobject.ts b/lang/gjs/src/gobject.ts index 59dd62a..b744cfb 100644 --- a/lang/gjs/src/gobject.ts +++ b/lang/gjs/src/gobject.ts @@ -51,6 +51,8 @@ export function register(options: MetaInfo = {}) { Properties: { ...cls[meta]?.Properties }, ...options, }, cls) + + delete cls[meta] } } @@ -124,7 +126,9 @@ export function signal( } } else { - target.constructor[meta].Signals[name] = declaration + target.constructor[meta].Signals[name] = declaration || { + param_types: [], + } } if (!desc) { diff --git a/lang/gjs/src/gtk3/astalify.ts b/lang/gjs/src/gtk3/astalify.ts index b9621be..6973805 100644 --- a/lang/gjs/src/gtk3/astalify.ts +++ b/lang/gjs/src/gtk3/astalify.ts @@ -43,7 +43,7 @@ function setProp(obj: any, prop: string, value: any) { export default function astalify< C extends { new(...args: any[]): Gtk.Widget }, ->(cls: C) { +>(cls: C, clsName = cls.name) { class Widget extends cls { get css(): string { return Astal.widget_get_css(this) } set css(css: string) { Astal.widget_set_css(this, css) } @@ -233,7 +233,7 @@ export default function astalify< } GObject.registerClass({ - GTypeName: `Astal_${cls.name}`, + GTypeName: `Astal_${clsName}`, Properties: { "class-name": GObject.ParamSpec.string( "class-name", "", "", GObject.ParamFlags.READWRITE, "", @@ -256,7 +256,7 @@ export default function astalify< return Widget } -type BindableProps<T> = { +export type BindableProps<T> = { [K in keyof T]: Binding<T[K]> | T[K]; } diff --git a/lang/gjs/src/gtk3/index.ts b/lang/gjs/src/gtk3/index.ts index cfafbda..ff641af 100644 --- a/lang/gjs/src/gtk3/index.ts +++ b/lang/gjs/src/gtk3/index.ts @@ -1,9 +1,9 @@ import Astal from "gi://Astal?version=3.0" import Gtk from "gi://Gtk?version=3.0" import Gdk from "gi://Gdk?version=3.0" -import astalify, { type ConstructProps } from "./astalify.js" +import astalify, { type ConstructProps, type BindableProps } from "./astalify.js" export { Astal, Gtk, Gdk } export { default as App } from "./app.js" -export { astalify, ConstructProps } +export { astalify, ConstructProps, BindableProps } export * as Widget from "./widget.js" diff --git a/lang/gjs/src/gtk3/jsx-runtime.ts b/lang/gjs/src/gtk3/jsx-runtime.ts index 22dc424..9da4bb6 100644 --- a/lang/gjs/src/gtk3/jsx-runtime.ts +++ b/lang/gjs/src/gtk3/jsx-runtime.ts @@ -10,7 +10,8 @@ export function Fragment({ children = [], child }: { child?: BindableChild children?: Array<BindableChild> }) { - return mergeBindings([...children, child]) + if (child) children.push(child) + return mergeBindings(children) } export function jsx( diff --git a/lang/gjs/src/index.ts b/lang/gjs/src/index.ts index cabc961..8fe8d01 100644 --- a/lang/gjs/src/index.ts +++ b/lang/gjs/src/index.ts @@ -1,3 +1,4 @@ +import "./overrides.js" export { default as AstalIO } from "gi://AstalIO?version=0.1" export * from "./process.js" export * from "./time.js" diff --git a/lang/gjs/src/overrides.ts b/lang/gjs/src/overrides.ts new file mode 100644 index 0000000..6643ba5 --- /dev/null +++ b/lang/gjs/src/overrides.ts @@ -0,0 +1,69 @@ +/** + * Workaround for "Can't convert non-null pointer to JS value " + */ + +export { } + +const snakeify = (str: string) => str + .replace(/([a-z])([A-Z])/g, "$1_$2") + .replaceAll("-", "_") + .toLowerCase() + +async function suppress<T>(mod: Promise<{ default: T }>, patch: (m: T) => void) { + return mod.then(m => patch(m.default)).catch(() => void 0) +} + +function patch<P extends object>(proto: P, prop: Extract<keyof P, string>) { + Object.defineProperty(proto, prop, { + get() { return this[`get_${snakeify(prop)}`]() }, + }) +} + +await suppress(import("gi://AstalApps"), ({ Apps, Application }) => { + patch(Apps.prototype, "list") + patch(Application.prototype, "keywords") + patch(Application.prototype, "categories") +}) + +await suppress(import("gi://AstalBattery"), ({ UPower }) => { + patch(UPower.prototype, "devices") +}) + +await suppress(import("gi://AstalBluetooth"), ({ Adapter, Bluetooth, Device }) => { + patch(Adapter.prototype, "uuids") + patch(Bluetooth.prototype, "adapters") + patch(Bluetooth.prototype, "devices") + patch(Device.prototype, "uuids") +}) + +await suppress(import("gi://AstalHyprland"), ({ Hyprland, Monitor, Workspace }) => { + patch(Hyprland.prototype, "monitors") + patch(Hyprland.prototype, "workspaces") + patch(Hyprland.prototype, "clients") + patch(Monitor.prototype, "availableModes") + patch(Monitor.prototype, "available_modes") + patch(Workspace.prototype, "clients") +}) + +await suppress(import("gi://AstalMpris"), ({ Mpris, Player }) => { + patch(Mpris.prototype, "players") + patch(Player.prototype, "supported_uri_schemas") + patch(Player.prototype, "supportedUriSchemas") + patch(Player.prototype, "supported_mime_types") + patch(Player.prototype, "supportedMimeTypes") + patch(Player.prototype, "comments") +}) + +await suppress(import("gi://AstalNetwork"), ({ Wifi }) => { + patch(Wifi.prototype, "access_points") + patch(Wifi.prototype, "accessPoints") +}) + +await suppress(import("gi://AstalNotifd"), ({ Notifd, Notification }) => { + patch(Notifd.prototype, "notifications") + patch(Notification.prototype, "actions") +}) + +await suppress(import("gi://AstalPowerProfiles"), ({ PowerProfiles }) => { + patch(PowerProfiles.prototype, "actions") +}) diff --git a/lang/gjs/tsconfig.json b/lang/gjs/tsconfig.json index 4e57e37..7a3a8c8 100644 --- a/lang/gjs/tsconfig.json +++ b/lang/gjs/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "experimentalDecorators": true, + "module": "ES2022", "target": "ES2023", "outDir": "dist", "strict": true, diff --git a/lang/lua/astal-dev-1.rockspec b/lang/lua/astal-dev-1.rockspec index 3970672..d8ee9be 100644 --- a/lang/lua/astal-dev-1.rockspec +++ b/lang/lua/astal-dev-1.rockspec @@ -8,7 +8,7 @@ source = { description = { summary = "lua bindings for libastal.", homepage = "https://aylur.github.io/astal/", - license = "GPL-3", + license = "LGPL-2.1", } dependencies = { diff --git a/lang/lua/astal/binding.lua b/lang/lua/astal/binding.lua index 2944ec4..dd2df7f 100644 --- a/lang/lua/astal/binding.lua +++ b/lang/lua/astal/binding.lua @@ -2,12 +2,14 @@ local lgi = require("lgi") local GObject = lgi.require("GObject", "2.0") ---@class Binding ----@field emitter table|Variable +---@field emitter table | Variable | userdata ---@field property? string ---@field transform_fn function +---@overload fun(emitter: table | userdata, property?: string): Binding local Binding = {} +Binding.__index = Binding ----@param emitter table +---@param emitter table | Variable | userdata ---@param property? string ---@return Binding function Binding.new(emitter, property) @@ -28,14 +30,15 @@ function Binding:__tostring() return str .. ">" end +---@return any function Binding:get() if self.property ~= nil and GObject.Object:is_type_of(self.emitter) then return self.transform_fn(self.emitter[self.property]) - end - if type(self.emitter.get) == "function" then + elseif type(self.emitter.get) == "function" then return self.transform_fn(self.emitter:get()) + else + error("can not get: Not a GObject or a Variable " + self) end - error("can not get: Not a GObject or a Variable " + self) end ---@param transform fun(value: any): any @@ -58,14 +61,17 @@ function Binding:subscribe(callback) return function() GObject.signal_handler_disconnect(self.emitter, id) end - end - if type(self.emitter.subscribe) == "function" then + elseif type(self.emitter.subscribe) == "function" then return self.emitter:subscribe(function() callback(self:get()) end) + else + error("can not subscribe: Not a GObject or a Variable " + self) end - error("can not subscribe: Not a GObject or a Variable " + self) end -Binding.__index = Binding -return Binding +return setmetatable(Binding, { + __call = function(_, emitter, prop) + return Binding.new(emitter, prop) + end, +}) diff --git a/lang/lua/astal/gtk3/app.lua b/lang/lua/astal/gtk3/app.lua index 7895f69..58564ce 100644 --- a/lang/lua/astal/gtk3/app.lua +++ b/lang/lua/astal/gtk3/app.lua @@ -25,29 +25,26 @@ end local app = AstalLua() ---@class StartConfig ----@field icons? string ----@field instance_name? string ----@field gtk_theme? string ----@field icon_theme? string ----@field cursor_theme? string ----@field css? string ----@field hold? boolean ----@field request_handler? fun(msg: string, response: fun(res: any)) ----@field main? fun(...): unknown ----@field client? fun(message: fun(msg: string): string, ...): unknown +---@field icons string? +---@field instance_name string? +---@field gtk_theme string? +---@field icon_theme string? +---@field cursor_theme string? +---@field css string? +---@field hold boolean? +---@field request_handler fun(msg: string, response: fun(res: any)): nil +---@field main fun(...): nil +---@field client fun(message: fun(msg: string): string, ...): nil ----@param config StartConfig | nil +---@param config? StartConfig function Astal.Application:start(config) - if config == nil then - config = {} - end + config = config or {} - if config.client == nil then - config.client = function() + config.client = config.client + or function() print('Astal instance "' .. app.instance_name .. '" is already running') os.exit(1) end - end if config.hold == nil then config.hold = true @@ -61,17 +58,11 @@ function Astal.Application:start(config) if config.icons then self:add_icons(config.icons) end - if config.instance_name then - self.instance_name = config.instance_name - end - if config.gtk_theme then - self.gtk_theme = config.gtk_theme - end - if config.icon_theme then - self.icon_theme = config.icon_theme - end - if config.cursor_theme then - self.cursor_theme = config.cursor_theme + + for _, key in ipairs({ "instance_name", "gtk_theme", "icon_theme", "cursor_theme" }) do + if config[key] then + self[key] = config[key] + end end app.on_activate = function() diff --git a/lang/lua/astal/gtk3/astalify.lua b/lang/lua/astal/gtk3/astalify.lua index 211a1d4..95faa2c 100644 --- a/lang/lua/astal/gtk3/astalify.lua +++ b/lang/lua/astal/gtk3/astalify.lua @@ -163,9 +163,7 @@ return function(ctor) end return function(tbl) - if tbl == nil then - tbl = {} - end + tbl = tbl or {} local bindings = {} local setup = tbl.setup diff --git a/lang/lua/astal/gtk3/widget.lua b/lang/lua/astal/gtk3/widget.lua index beaad6c..c8857e7 100644 --- a/lang/lua/astal/gtk3/widget.lua +++ b/lang/lua/astal/gtk3/widget.lua @@ -3,6 +3,7 @@ local Astal = lgi.require("Astal", "3.0") local Gtk = lgi.require("Gtk", "3.0") local astalify = require("astal.gtk3.astalify") +---@overload fun(ctor: any): function local Widget = { astalify = astalify, Box = astalify(Astal.Box), diff --git a/lang/lua/astal/init.lua b/lang/lua/astal/init.lua index 5630ba4..190994a 100644 --- a/lang/lua/astal/init.lua +++ b/lang/lua/astal/init.lua @@ -7,6 +7,7 @@ local Binding = require("astal.binding") local File = require("astal.file") local Process = require("astal.process") local Time = require("astal.time") +---@type Variable | fun(v: any): Variable local Variable = require("astal.variable") return { diff --git a/lang/lua/astal/variable.lua b/lang/lua/astal/variable.lua index 2305a71..ad59a3f 100644 --- a/lang/lua/astal/variable.lua +++ b/lang/lua/astal/variable.lua @@ -17,6 +17,7 @@ local Process = require("astal.process") ---@field private poll_fn? function ---@field private watch_transform? fun(next: any, prev: any): any ---@field private watch_exec? string[] | string +---@overload fun(transform?: fun(v: any): any): Binding local Variable = {} Variable.__index = Variable @@ -24,23 +25,23 @@ Variable.__index = Variable ---@return Variable function Variable.new(value) local v = Astal.VariableBase() - local variable = setmetatable({ - variable = v, - _value = value, - }, Variable) + local variable = setmetatable({ variable = v, _value = value }, Variable) + v.on_dropped = function() variable:stop_watch() variable:stop_poll() end + v.on_error = function(_, err) if variable.err_handler then variable.err_handler(err) end end + return variable end ----@param transform? fun(v: any): any +---@param transform? fun(v: any): any ---@return Binding function Variable:__call(transform) if type(transform) == "nil" then @@ -54,10 +55,13 @@ function Variable:__tostring() return "Variable<" .. tostring(self:get()) .. ">" end +---@return any function Variable:get() return self._value end +---@param value any +---@return nil function Variable:set(value) if value ~= self:get() then self._value = value @@ -107,7 +111,6 @@ function Variable:start_watch() end) end - function Variable:stop_poll() if self:is_polling() then self._poll.cancel() @@ -122,7 +125,6 @@ function Variable:stop_watch() self._watch = nil end - function Variable:drop() self.variable.emit_dropped() end @@ -180,8 +182,9 @@ end ---@param exec string | string[] ---@param transform? fun(next: any, prev: any): any +---@return Variable function Variable:watch(exec, transform) - transform = transform or function (next) + transform = transform or function(next) return next end @@ -240,7 +243,7 @@ function Variable.derive(deps, transform) for i, var in ipairs(deps) do if getmetatable(var) == Variable then - deps[i] = Binding.new(var) + deps[i] = var() end end @@ -249,7 +252,7 @@ function Variable.derive(deps, transform) for i, binding in ipairs(deps) do params[i] = binding:get() end - return transform(table.unpack(params), 1, #deps) + return transform(table.unpack(params, 1, #deps)) end local var = Variable.new(update()) @@ -272,4 +275,4 @@ return setmetatable(Variable, { __call = function(_, v) return Variable.new(v) end, -})
\ No newline at end of file +}) diff --git a/lib/astal/gtk3/src/widget/circularprogress.vala b/lib/astal/gtk3/src/widget/circularprogress.vala index a3ecdf1..df1635d 100644 --- a/lib/astal/gtk3/src/widget/circularprogress.vala +++ b/lib/astal/gtk3/src/widget/circularprogress.vala @@ -44,26 +44,53 @@ public class Astal.CircularProgress : Gtk.Bin { set_css_name("circular-progress"); } + public override Gtk.SizeRequestMode get_request_mode() { + if(get_child() != null) return get_child().get_request_mode(); + return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; + } + public override void get_preferred_height(out int minh, out int nath) { - var val = get_style_context().get_property("min-height", Gtk.StateFlags.NORMAL); - if (val.get_int() <= 0) { - minh = 40; - nath = 40; + if(get_child() != null) { + int minw, natw; + get_child().get_preferred_height(out minh, out nath); + get_child().get_preferred_width(out minw, out natw); + + minh = int.max(minw, minh); + nath = int.max(natw, nath); } + var w_val = get_style_context().get_property("min-width", Gtk.StateFlags.NORMAL); + var h_val = get_style_context().get_property("min-height", Gtk.StateFlags.NORMAL); + minh = int.max(w_val.get_int(), minh); + nath = int.max(w_val.get_int(), nath); + minh = int.max(h_val.get_int(), minh); + nath = int.max(h_val.get_int(), nath); + } - minh = val.get_int(); - nath = val.get_int(); + public override void get_preferred_height_for_width(int width, out int minh, out int nath) { + minh = width; + nath = width; } public override void get_preferred_width(out int minw, out int natw) { - var val = get_style_context().get_property("min-width", Gtk.StateFlags.NORMAL); - if (val.get_int() <= 0) { - minw = 40; - natw = 40; + if(get_child() != null) { + int minh, nath; + get_child().get_preferred_height(out minh, out nath); + get_child().get_preferred_width(out minw, out natw); + + minw = int.max(minw, minh); + natw = int.max(natw, nath); } + var w_val = get_style_context().get_property("min-width", Gtk.StateFlags.NORMAL); + var h_val = get_style_context().get_property("min-height", Gtk.StateFlags.NORMAL); + minw = int.max(w_val.get_int(), minw); + natw = int.max(w_val.get_int(), natw); + minw = int.max(h_val.get_int(), minw); + natw = int.max(h_val.get_int(), natw); + } - minw = val.get_int(); - natw = val.get_int(); + public override void get_preferred_width_for_height(int height, out int minw, out int natw) { + minw = height; + natw = height; } private double to_radian(double percentage) { @@ -115,6 +142,12 @@ public class Astal.CircularProgress : Gtk.Bin { Gtk.Allocation allocation; get_allocation(out allocation); + if (get_child() != null) { + get_child().size_allocate(allocation); + propagate_draw(get_child(), cr); + } + + var styles = get_style_context(); var width = allocation.width; var height = allocation.height; @@ -195,12 +228,6 @@ public class Astal.CircularProgress : Gtk.Bin { cr.arc(end_x, end_y, fg_stroke / 2, 0, 0 - 0.01); cr.fill(); } - - if (get_child() != null) { - get_child().size_allocate(allocation); - propagate_draw(get_child(), cr); - } - return true; } } diff --git a/lib/astal/gtk4/gir.py b/lib/astal/gtk4/gir.py index 9ef680f..16a3a64 100644..120000 --- a/lib/astal/gtk4/gir.py +++ b/lib/astal/gtk4/gir.py @@ -1,58 +1 @@ -""" -Vala's generated gir does not contain comments, -so we use valadoc to generate them. However, they are formatted -for valadoc and not gi-docgen so we need to fix it. -""" - -import xml.etree.ElementTree as ET -import html -import sys -import subprocess - - -def fix_gir(name: str, gir: str, out: str): - namespaces = { - "": "http://www.gtk.org/introspection/core/1.0", - "c": "http://www.gtk.org/introspection/c/1.0", - "glib": "http://www.gtk.org/introspection/glib/1.0", - } - for prefix, uri in namespaces.items(): - ET.register_namespace(prefix, uri) - - tree = ET.parse(gir) - root = tree.getroot() - - for doc in root.findall(".//doc", namespaces): - if doc.text: - doc.text = ( - html.unescape(doc.text).replace("<para>", "").replace("</para>", "") - ) - - if (inc := root.find("c:include", namespaces)) is not None: - inc.set("name", f"{name}.h") - else: - print("no c:include tag found", file=sys.stderr) - exit(1) - - tree.write(out, encoding="utf-8", xml_declaration=True) - - -def valadoc(name: str, gir: str, args: list[str]): - cmd = ["valadoc", "-o", "docs", "--package-name", name, "--gir", gir, *args] - try: - subprocess.run(cmd, check=True, text=True, capture_output=True) - except subprocess.CalledProcessError as e: - print(e.stderr, file=sys.stderr) - exit(1) - - -if __name__ == "__main__": - name = sys.argv[1] - in_out = sys.argv[2].split(":") - args = sys.argv[3:] - - gir = in_out[0] - out = in_out[1] if len(in_out) > 1 else gir - - valadoc(name, gir, args) - fix_gir(name, gir, out) +../../gir.py
\ No newline at end of file diff --git a/lib/astal/io/gir.py b/lib/astal/io/gir.py index 9ef680f..16a3a64 100644..120000 --- a/lib/astal/io/gir.py +++ b/lib/astal/io/gir.py @@ -1,58 +1 @@ -""" -Vala's generated gir does not contain comments, -so we use valadoc to generate them. However, they are formatted -for valadoc and not gi-docgen so we need to fix it. -""" - -import xml.etree.ElementTree as ET -import html -import sys -import subprocess - - -def fix_gir(name: str, gir: str, out: str): - namespaces = { - "": "http://www.gtk.org/introspection/core/1.0", - "c": "http://www.gtk.org/introspection/c/1.0", - "glib": "http://www.gtk.org/introspection/glib/1.0", - } - for prefix, uri in namespaces.items(): - ET.register_namespace(prefix, uri) - - tree = ET.parse(gir) - root = tree.getroot() - - for doc in root.findall(".//doc", namespaces): - if doc.text: - doc.text = ( - html.unescape(doc.text).replace("<para>", "").replace("</para>", "") - ) - - if (inc := root.find("c:include", namespaces)) is not None: - inc.set("name", f"{name}.h") - else: - print("no c:include tag found", file=sys.stderr) - exit(1) - - tree.write(out, encoding="utf-8", xml_declaration=True) - - -def valadoc(name: str, gir: str, args: list[str]): - cmd = ["valadoc", "-o", "docs", "--package-name", name, "--gir", gir, *args] - try: - subprocess.run(cmd, check=True, text=True, capture_output=True) - except subprocess.CalledProcessError as e: - print(e.stderr, file=sys.stderr) - exit(1) - - -if __name__ == "__main__": - name = sys.argv[1] - in_out = sys.argv[2].split(":") - args = sys.argv[3:] - - gir = in_out[0] - out = in_out[1] if len(in_out) > 1 else gir - - valadoc(name, gir, args) - fix_gir(name, gir, out) +../../gir.py
\ No newline at end of file @@ -9,6 +9,7 @@ import html import sys import subprocess import re +import os # valac fails on gi-docgen compliant markdown @@ -47,7 +48,7 @@ def fix_gir(name: str, gir: str, out: str): def valadoc(name: str, gir: str, args: list[str]): - cmd = ["valadoc", "-o", "docs", "--package-name", name, "--gir", gir, *args] + cmd = [os.getenv("VALADOC", "valadoc"), "-o", "docs", "--package-name", name, "--gir", gir, *args] try: subprocess.run(cmd, check=True, text=True, capture_output=True) except subprocess.CalledProcessError as e: diff --git a/lib/hyprland/hyprland.vala b/lib/hyprland/hyprland.vala index ea95cab..17c426c 100644 --- a/lib/hyprland/hyprland.vala +++ b/lib/hyprland/hyprland.vala @@ -381,20 +381,15 @@ public class Hyprland : Object { break; case "openwindow": - var addr = args[1].split(",")[0]; - var client = new Client(); - _clients.insert(addr, client); yield sync_clients(); yield sync_workspaces(); - client_added(client); - notify_property("clients"); break; case "closewindow": _clients.get(args[1]).removed(); _clients.remove(args[1]); - client_removed(args[1]); yield sync_workspaces(); + client_removed(args[1]); notify_property("clients"); break; @@ -426,6 +421,17 @@ public class Hyprland : Object { minimize(get_client(argv[0]), argv[1] == "0"); break; + // first event that signals a new window not openwindow + case "windowtitlev2": + var addr = args[1].split(",")[0]; + var client = new Client(); + _clients.insert(addr, client); + yield sync_clients(); + yield sync_workspaces(); + client_added(client); + notify_property("clients"); + break; + case "windowtitle": yield sync_clients(); break; diff --git a/lib/hyprland/monitor.vala b/lib/hyprland/monitor.vala index d7b8028..6c46142 100644 --- a/lib/hyprland/monitor.vala +++ b/lib/hyprland/monitor.vala @@ -1,5 +1,4 @@ -namespace AstalHyprland { -public class Monitor : Object { +public class AstalHyprland.Monitor : Object { public signal void removed (); public int id { get; private set; } @@ -20,6 +19,7 @@ public class Monitor : Object { public int reserved_left { get; private set; } public int reserved_right { get; private set; } public double scale { get; private set; } + public Transform transform { get; private set; } public bool focused { get; private set; } public bool dpms_status { get; private set; } public bool vrr { get; private set; } @@ -43,6 +43,7 @@ public class Monitor : Object { x = (int)obj.get_int_member("x"); y = (int)obj.get_int_member("y"); scale = obj.get_double_member("scale"); + transform = (Transform)obj.get_int_member("transform"); focused = obj.get_boolean_member("focused"); dpms_status = obj.get_boolean_member("dpmsStatus"); vrr = obj.get_boolean_member("vrr"); @@ -67,5 +68,22 @@ public class Monitor : Object { public void focus() { Hyprland.get_default().dispatch("focusmonitor", id.to_string()); } -} + + public enum Transform { + NORMAL = 0, + /** rotate by 90° counter clockwise */ + ROTATE_90_DEG = 1, + /** rotate by 180° */ + ROTATE_180_DEG = 2, + /** rotate by 270° counter clockwise */ + ROTATE_270_DEG = 3, + /** mirror both axis */ + FLIPPED = 4, + /** flip and rotate by 90° */ + FLIPPED_ROTATE_90_DEG = 5, + /** flip and rotate by 180° */ + FLIPPED_ROTATE_180_DEG = 6, + /** flip and rotate by 270° */ + FLIPPED_ROTATE_270_DEG = 7, + } } diff --git a/lib/mpris/cli.vala b/lib/mpris/cli.vala index b71def9..7e15c6e 100644 --- a/lib/mpris/cli.vala +++ b/lib/mpris/cli.vala @@ -179,7 +179,7 @@ int main(string[] argv) { Json.Node to_json(Player p) { var uris = new Json.Builder().begin_array(); - foreach (var uri in p.supported_uri_schemas) + foreach (var uri in p.supported_uri_schemes) uris.add_string_value(uri); uris.end_array(); @@ -189,7 +189,7 @@ Json.Node to_json(Player p) { .set_member_name("available").add_boolean_value(p.available) .set_member_name("identity").add_string_value(p.identity) .set_member_name("entry").add_string_value(p.entry) - .set_member_name("supported_uri_schemas").add_value(uris.get_root()) + .set_member_name("supported_uri_schemes").add_value(uris.get_root()) .set_member_name("loop_status").add_string_value(p.loop_status.to_string()) .set_member_name("shuffle_status").add_string_value(p.shuffle_status.to_string()) .set_member_name("rate").add_double_value(p.rate) diff --git a/lib/mpris/ifaces.vala b/lib/mpris/ifaces.vala index 298a288..8755723 100644 --- a/lib/mpris/ifaces.vala +++ b/lib/mpris/ifaces.vala @@ -21,7 +21,7 @@ private interface AstalMpris.IMpris : PropsIface { public abstract bool has_track_list { get; } public abstract string identity { owned get; } public abstract string desktop_entry { owned get; } - public abstract string[] supported_uri_schemas { owned get; } + public abstract string[] supported_uri_schemes { owned get; } public abstract string[] supported_mime_types { owned get; } } diff --git a/lib/mpris/player.vala b/lib/mpris/player.vala index 2050f61..c69eb21 100644 --- a/lib/mpris/player.vala +++ b/lib/mpris/player.vala @@ -87,7 +87,7 @@ public class AstalMpris.Player : Object { * Almost every media player will include support for the "file" scheme. * Other common schemes are "http" and "rtsp". */ - public string[] supported_uri_schemas { owned get; private set; } + public string[] supported_uri_schemes { owned get; private set; } /** * The mime-types supported by the player. @@ -160,7 +160,7 @@ public class AstalMpris.Player : Object { } /** - * uri scheme should be an element of [[email protected]:supported_uri_schemas] + * uri scheme should be an element of [[email protected]:supported_uri_schemes] * and the mime-type should match one of the elements of [[email protected]:supported_mime_types]. * * @param uri Uri of the track to load. @@ -425,7 +425,7 @@ public class AstalMpris.Player : Object { // has_track_list = proxy.has_track_list; identity = proxy.identity; entry = proxy.desktop_entry; - supported_uri_schemas = proxy.supported_uri_schemas; + supported_uri_schemes = proxy.supported_uri_schemes; supported_mime_types = proxy.supported_mime_types; if (position >= 0) diff --git a/lib/notifd/notification.vala b/lib/notifd/notification.vala index 29c6c56..c28138f 100644 --- a/lib/notifd/notification.vala +++ b/lib/notifd/notification.vala @@ -142,9 +142,11 @@ public class AstalNotifd.Notification : Object { return 0; var v = hints.get(hint); - if (v.get_type_string() == "b") + // daemon uses byte as per spec + if (v.get_type_string() == "y") return v.get_byte(); + // proxy uses int64 from json if (v.get_type_string() == "x") return (uint8)v.get_int64(); diff --git a/lib/powerprofiles/cli.vala b/lib/powerprofiles/cli.vala index 7be01d2..1e5cc70 100644 --- a/lib/powerprofiles/cli.vala +++ b/lib/powerprofiles/cli.vala @@ -56,16 +56,16 @@ int main(string[] argv) { if (daemonize) { var loop = new MainLoop(); - stdout.printf("%s\n", profiles.to_json_string()); + stdout.printf("%s\n", to_json_string(profiles)); stdout.flush(); profiles.notify.connect(() => { - stdout.printf("%s\n", profiles.to_json_string()); + stdout.printf("%s\n", to_json_string(profiles)); stdout.flush(); }); profiles.profile_released.connect(() => { - stdout.printf("%s\n", profiles.to_json_string()); + stdout.printf("%s\n", to_json_string(profiles)); stdout.flush(); }); @@ -73,8 +73,49 @@ int main(string[] argv) { } if (set == null && !daemonize) { - stdout.printf("%s\n", profiles.to_json_string()); + stdout.printf("%s\n", to_json_string(profiles)); } return 0; } + +string to_json_string(AstalPowerProfiles.PowerProfiles profiles) { + var acts = new Json.Builder().begin_array(); + foreach (var action in profiles.actions) { + acts.add_string_value(action); + } + + var active_holds = new Json.Builder().begin_array(); + foreach (var action in profiles.active_profile_holds) { + active_holds.add_value(new Json.Builder() + .begin_object() + .set_member_name("application_id").add_string_value(action.application_id) + .set_member_name("profile").add_string_value(action.profile) + .set_member_name("reason").add_string_value(action.reason) + .end_object() + .get_root()); + } + + var profs = new Json.Builder().begin_array(); + foreach (var prof in profiles.profiles) { + profs.add_value(new Json.Builder() + .begin_object() + .set_member_name("profie").add_string_value(prof.profile) + .set_member_name("driver").add_string_value(prof.driver) + .set_member_name("cpu_driver").add_string_value(prof.cpu_driver) + .set_member_name("platform_driver").add_string_value(prof.platform_driver) + .end_object() + .get_root()); + } + + return Json.to_string(new Json.Builder() + .begin_object() + .set_member_name("active_profile").add_string_value(profiles.active_profile) + .set_member_name("icon_name").add_string_value(profiles.icon_name) + .set_member_name("performance_degraded").add_string_value(profiles.performance_degraded) + .set_member_name("actions").add_value(acts.end_array().get_root()) + .set_member_name("active_profile_holds").add_value(active_holds.end_array().get_root()) + .set_member_name("profiles").add_value(profs.end_array().get_root()) + .end_object() + .get_root(), false); +} diff --git a/lib/powerprofiles/gir.py b/lib/powerprofiles/gir.py new file mode 120000 index 0000000..b5b4f1d --- /dev/null +++ b/lib/powerprofiles/gir.py @@ -0,0 +1 @@ +../gir.py
\ No newline at end of file diff --git a/lib/powerprofiles/meson.build b/lib/powerprofiles/meson.build index d0fe78f..cd8cc2b 100644 --- a/lib/powerprofiles/meson.build +++ b/lib/powerprofiles/meson.build @@ -39,32 +39,38 @@ deps = [ dependency('json-glib-1.0'), ] -sources = [ - config, +sources = [config] + files( 'power-profiles.vala', -] +) if get_option('lib') lib = library( meson.project_name(), sources, dependencies: deps, + vala_args: ['--vapi-comments'], vala_header: meson.project_name() + '.h', vala_vapi: meson.project_name() + '-' + api_version + '.vapi', - vala_gir: gir, version: meson.project_version(), install: true, - install_dir: [true, true, true, true], + install_dir: [true, true, true], ) - import('pkgconfig').generate( - lib, - name: meson.project_name(), - filebase: meson.project_name() + '-' + api_version, - version: meson.project_version(), - subdirs: meson.project_name(), - requires: deps, - install_dir: get_option('libdir') / 'pkgconfig', + pkgs = [] + foreach dep : deps + pkgs += ['--pkg=' + dep.name()] + endforeach + + gir_tgt = custom_target( + gir, + command: [find_program('python3'), files('gir.py'), meson.project_name(), gir] + + pkgs + + sources, + input: sources, + depends: lib, + output: gir, + install: true, + install_dir: get_option('datadir') / 'gir-1.0', ) custom_target( @@ -77,10 +83,20 @@ if get_option('lib') ], input: lib, output: typelib, - depends: lib, + depends: [lib, gir_tgt], install: true, install_dir: get_option('libdir') / 'girepository-1.0', ) + + import('pkgconfig').generate( + lib, + name: meson.project_name(), + filebase: meson.project_name() + '-' + api_version, + version: meson.project_version(), + subdirs: meson.project_name(), + requires: deps, + install_dir: get_option('libdir') / 'pkgconfig', + ) endif if get_option('cli') diff --git a/lib/powerprofiles/power-profiles.vala b/lib/powerprofiles/power-profiles.vala index ab98505..a104d2e 100644 --- a/lib/powerprofiles/power-profiles.vala +++ b/lib/powerprofiles/power-profiles.vala @@ -16,11 +16,17 @@ private interface IPowerProfiles : DBusProxy { } public PowerProfiles get_default() { + /** Gets the default singleton PowerProfiles instance. */ return PowerProfiles.get_default(); } +/** + * Client for [[https://freedesktop-team.pages.debian.net/power-profiles-daemon/gdbus-org.freedesktop.UPower.PowerProfiles.html|PowerProfiles]]. + */ public class PowerProfiles : Object { private static PowerProfiles instance; + + /** Gets the default singleton PowerProfiles instance. */ public static PowerProfiles get_default() { if (instance == null) instance = new PowerProfiles(); @@ -52,19 +58,34 @@ public class PowerProfiles : Object { } } + /** + * The type of the currently active profile. + * It might change automatically if a profile is held, + * using the [[email protected]_profile] method. + */ public string active_profile { owned get { return proxy.active_profile; } set { proxy.active_profile = value; } } + /** + * Return a named icon based [[email protected]:active_profile]. + */ public string icon_name { owned get { return @"power-profile-$active_profile-symbolic"; } } + /** + * List of the "actions" implemented in the running daemon. + * This can used to figure out whether particular functionality is available in the daemon. + */ public string[] actions { owned get { return proxy.actions.copy(); } } + /** + * List of dictionaries representing the current profile holds. + */ public Hold[] active_profile_holds { owned get { Hold[] holds = new Hold[proxy.active_profile_holds.length]; @@ -80,14 +101,21 @@ public class PowerProfiles : Object { } } + /** + * This will be set if the performance power profile is running in degraded mode, + * with the value being used to identify the reason for that degradation. + * Possible values are: + * - "lap-detected" (the computer is sitting on the user's lap) + * - "high-operating-temperature" (the computer is close to overheating) + * - "" (the empty string, if not performance is not degraded) + */ public string performance_degraded { owned get { return proxy.performance_degraded; } } - public string performance_inhibited { - owned get { return proxy.performance_degraded; } - } - + /** + * List of each profile. + */ public Profile[] profiles { owned get { Profile[] profs = new Profile[proxy.profiles.length]; @@ -104,12 +132,31 @@ public class PowerProfiles : Object { } } + /** + * The version of the power-profiles-daemon software. + */ public string version { owned get { return proxy.version; } } + /** + * Emitted when the profile is released because + * [[email protected]:active_profile] was manually changed. + * This will only be emitted to the process that originally called + * [[email protected]_profile]. + */ public signal void profile_released (uint cookie); + /** + * This forces the passed profile (either 'power-saver' or 'performance') + * to be activated until either the caller quits, + * [[email protected]_profile] is called, + * or the [[email protected]:active_profile] is changed by the user. + * When conflicting profiles are requested to be held, + * the 'power-saver' profile will be activated in preference to the 'performance' profile. + * Those holds will be automatically cancelled if the user manually switches to another profile, + * and the [[email protected]::profile_released] signal will be emitted. + */ public int hold_profile(string profile, string reason, string application_id) { try { return (int)proxy.hold_profile(profile, reason, application_id); @@ -119,6 +166,9 @@ public class PowerProfiles : Object { } } + /** + * This removes the hold that was set on a profile. + */ public void release_profile(uint cookie) { try { proxy.release_profile(cookie); @@ -126,54 +176,21 @@ public class PowerProfiles : Object { critical(error.message); } } - - public string to_json_string() { - var acts = new Json.Builder().begin_array(); - foreach (var action in actions) { - acts.add_string_value(action); - } - - var active_holds = new Json.Builder().begin_array(); - foreach (var action in active_profile_holds) { - active_holds.add_value(new Json.Builder() - .begin_object() - .set_member_name("application_id").add_string_value(action.application_id) - .set_member_name("profile").add_string_value(action.profile) - .set_member_name("reason").add_string_value(action.reason) - .end_object() - .get_root()); - } - - var profs = new Json.Builder().begin_array(); - foreach (var prof in profiles) { - profs.add_value(new Json.Builder() - .begin_object() - .set_member_name("profie").add_string_value(prof.profile) - .set_member_name("driver").add_string_value(prof.driver) - .set_member_name("cpu_driver").add_string_value(prof.cpu_driver) - .set_member_name("platform_driver").add_string_value(prof.platform_driver) - .end_object() - .get_root()); - } - - return Json.to_string(new Json.Builder() - .begin_object() - .set_member_name("active_profile").add_string_value(active_profile) - .set_member_name("icon_name").add_string_value(icon_name) - .set_member_name("performance_degraded").add_string_value(performance_degraded) - .set_member_name("performance_inhibited").add_string_value(performance_inhibited) - .set_member_name("actions").add_value(acts.end_array().get_root()) - .set_member_name("active_profile_holds").add_value(active_holds.end_array().get_root()) - .set_member_name("profiles").add_value(profs.end_array().get_root()) - .end_object() - .get_root(), false); - } } public struct Profile { + /** + * Will be one of: + * - "power-saver" (battery saving profile) + * - "balanced" (the default profile) + * - "performance" (a profile that does not care about noise or battery consumption) + */ public string profile; public string cpu_driver; public string platform_driver; + /** + * Identifies the power-profiles-daemon backend code used to implement the profile. + */ public string driver; } diff --git a/lib/tray/meson.build b/lib/tray/meson.build index fbb6672..fbf2f98 100644 --- a/lib/tray/meson.build +++ b/lib/tray/meson.build @@ -76,7 +76,7 @@ if get_option('lib') c_args: dbusmenu_cflags.split(' '), link_args: dbusmenu_libs.split(' '), install: true, - install_dir: true, + install_dir: [true, true, true] ) pkgs = ['--pkg', 'DbusmenuGtk3-0.4', '--pkg', 'Dbusmenu-0.4'] |