diff options
author | Aylur <[email protected]> | 2024-09-12 00:40:04 +0200 |
---|---|---|
committer | GitHub <[email protected]> | 2024-09-12 00:40:04 +0200 |
commit | 6c5d7659a75c093588117c4c28dd046409b3ac8f (patch) | |
tree | c120efe485c6d699d29480e12298c2cd7fe4626c | |
parent | d203255ec20bb6e3b2917dd4aee53dee3a090137 (diff) | |
parent | 8b75dadc76274692988eb317d3cc6ce1aaa44780 (diff) |
Merge pull request #5 from Aylur/nix/lua-builder
lua example, nix builder
-rw-r--r-- | core/gjs/src/variable.ts | 4 | ||||
-rw-r--r-- | core/lua/astal/binding.lua | 20 | ||||
-rw-r--r-- | core/lua/astal/process.lua | 4 | ||||
-rw-r--r-- | core/lua/astal/variable.lua | 4 | ||||
-rw-r--r-- | core/lua/test.lua | 8 | ||||
-rw-r--r-- | docs/getting-started/installation.md | 2 | ||||
-rw-r--r-- | docs/getting-started/nix.md | 31 | ||||
-rw-r--r-- | docs/getting-started/supported-languages.md | 3 | ||||
-rw-r--r-- | examples/js/simple-bar/README.md | 2 | ||||
-rw-r--r-- | examples/js/simple-bar/widget/Bar.tsx | 34 | ||||
-rw-r--r-- | examples/lua/simple-bar/README.md | 12 | ||||
-rw-r--r-- | examples/lua/simple-bar/app.lua | 20 | ||||
-rw-r--r-- | examples/lua/simple-bar/lib.lua | 25 | ||||
-rw-r--r-- | examples/lua/simple-bar/style.scss | 88 | ||||
-rw-r--r-- | examples/lua/simple-bar/widget/Bar.lua | 192 | ||||
-rw-r--r-- | flake.nix | 47 | ||||
-rw-r--r-- | nix/devshell.nix | 57 | ||||
-rw-r--r-- | nix/lua.nix | 58 |
18 files changed, 531 insertions, 80 deletions
diff --git a/core/gjs/src/variable.ts b/core/gjs/src/variable.ts index 9528ffe..84f8cc5 100644 --- a/core/gjs/src/variable.ts +++ b/core/gjs/src/variable.ts @@ -1,6 +1,6 @@ import Binding, { type Connectable } from "./binding.js" import { Astal } from "./imports.js" -import { interval } from "./time.js" +import { interval, idle } from "./time.js" import { execAsync, subprocess } from "./process.js" class VariableWrapper<T> extends Function { @@ -101,7 +101,7 @@ class VariableWrapper<T> extends Function { drop() { this.variable.emit("dropped") - this.variable.run_dispose() + idle(() => this.variable.run_dispose()) } onDropped(callback: () => void) { diff --git a/core/lua/astal/binding.lua b/core/lua/astal/binding.lua index 50509d1..ba1e6e4 100644 --- a/core/lua/astal/binding.lua +++ b/core/lua/astal/binding.lua @@ -29,10 +29,13 @@ function Binding:__tostring() end function Binding:get() + if self.property ~= nil and GObject.Object:is_type_of(self.emitter) then + return self.transformFn(self.emitter[self.property]) + end if type(self.emitter.get) == "function" then return self.transformFn(self.emitter:get()) end - return self.transformFn(self.emitter[self.property]) + error("can not get: Not a GObject or a Variable " + self) end ---@param transform fun(value: any): any @@ -48,17 +51,20 @@ end ---@param callback fun(value: any) ---@return function function Binding:subscribe(callback) + if self.property ~= nil and GObject.Object:is_type_of(self.emitter) then + local id = self.emitter.on_notify:connect(function() + callback(self:get()) + end, self.property, false) + return function() + GObject.signal_handler_disconnect(self.emitter, id) + end + end if type(self.emitter.subscribe) == "function" then return self.emitter:subscribe(function() callback(self:get()) end) end - local id = self.emitter.on_notify:connect(function() - callback(self:get()) - end, self.property, false) - return function() - GObject.signal_handler_disconnect(self.emitter, id) - end + error("can not subscribe: Not a GObject or a Variable " + self) end Binding.__index = Binding diff --git a/core/lua/astal/process.lua b/core/lua/astal/process.lua index 3d10f8b..6f73613 100644 --- a/core/lua/astal/process.lua +++ b/core/lua/astal/process.lua @@ -72,7 +72,7 @@ function M.exec_async(commandline, on_stdout, on_stderr) local out, err = defualt_proc_args(on_stdout, on_stderr) if type(commandline) == "table" then Astal.Process.exec_asyncv(commandline, function(_, res) - local stdout, fail = Astal.exec_asyncv_finish(res) + local stdout, fail = Astal.Process.exec_asyncv_finish(res) if fail ~= nil then err(fail) else @@ -81,7 +81,7 @@ function M.exec_async(commandline, on_stdout, on_stderr) end) else Astal.Process.exec_async(commandline, function(_, res) - local stdout, fail = Astal.exec_finish(res) + local stdout, fail = Astal.Process.exec_finish(res) if fail ~= nil then err(fail) else diff --git a/core/lua/astal/variable.lua b/core/lua/astal/variable.lua index 1e894b5..02d6b45 100644 --- a/core/lua/astal/variable.lua +++ b/core/lua/astal/variable.lua @@ -123,7 +123,9 @@ end function Variable:drop() self.variable.emit_dropped() - self.variable.run_dispose() + Astal.Time.idle(GObject.Closure(function() + self.variable.run_dispose() + end)) end ---@param callback function diff --git a/core/lua/test.lua b/core/lua/test.lua deleted file mode 100644 index f5123a3..0000000 --- a/core/lua/test.lua +++ /dev/null @@ -1,8 +0,0 @@ -local App = require("astal.application") - -App:start({ - instance_name = "test", - main = function() - App:quit(1) - end, -}) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index bbdced9..ef5c6e8 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -4,7 +4,7 @@ maintainer: [@Aylur](https://github.com/Aylur) -Read more about it on the [nix page](./nix) +Read more about it on the [nix page](./nix#astal) ## Arch diff --git a/docs/getting-started/nix.md b/docs/getting-started/nix.md index 3c3e8a9..2f320f3 100644 --- a/docs/getting-started/nix.md +++ b/docs/getting-started/nix.md @@ -6,19 +6,32 @@ Using Astal on Nix will require you to package your project. :::code-group -```nix [typescript.nix] -# Not documented yet -``` +```nix [<i class="devicon-lua-plain"></i> Lua] +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + astal = { + inputs.nixpkgs.follows = "nixpkgs"; + url = "github:nixos/nixpkgs/nixos-unstable"; + }; + }; -```nix [lua.nix] -# Not documented yet + outputs = { self, nixpkgs, astal }: let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + in { + packages.${system}.default = astal.lib.mkLuaPacakge { + inherit pkgs; + }; + }; +} ``` -```nix [python.nix] +```nix [<i class="devicon-python-plain"></i> Python] # Not documented yet ``` -```nix [vala.nix] +```nix [<i class="devicon-vala-plain"></i> Vala] # Not documented yet ``` @@ -34,7 +47,7 @@ Example content of a `flake.nix` file that contains your `homeConfigurations`. :::code-group -```nix [flake.nix] +```nix [<i class="devicon-nixos-plain"></i> flake.nix] { inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; @@ -71,7 +84,7 @@ Example content of `home.nix` file :::code-group -```nix [home.nix] +```nix [<i class="devicon-nixos-plain"></i> home.nix] { inputs, pkgs, ... }: { # add the home manager module diff --git a/docs/getting-started/supported-languages.md b/docs/getting-started/supported-languages.md index f69dd19..7d8fc5f 100644 --- a/docs/getting-started/supported-languages.md +++ b/docs/getting-started/supported-languages.md @@ -25,7 +25,8 @@ components that don't render child nodes dynamically, bars and panels for exampl Examples: -- TODO +- [Simple Bar](https://github.com/Aylur/astal/tree/main/examples/lua/simple-bar) + ## Python diff --git a/examples/js/simple-bar/README.md b/examples/js/simple-bar/README.md index 3a4316e..8f733da 100644 --- a/examples/js/simple-bar/README.md +++ b/examples/js/simple-bar/README.md @@ -1,6 +1,6 @@ # Simple Bar Example - + A simple bar for Hyprland using diff --git a/examples/js/simple-bar/widget/Bar.tsx b/examples/js/simple-bar/widget/Bar.tsx index d669fd5..492ab1d 100644 --- a/examples/js/simple-bar/widget/Bar.tsx +++ b/examples/js/simple-bar/widget/Bar.tsx @@ -22,7 +22,7 @@ function SysTray() { onClickRelease={self => { menu?.popup_at_widget(self, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, null) }}> - <icon g_icon={bind(item, "gicon")}/> + <icon gIcon={bind(item, "gicon")} /> </button> }))} </box> @@ -64,21 +64,27 @@ function BatteryLevel() { } function Media() { - const player = Mpris.Player.new("spotify") + const mpris = Mpris.get_default() return <box className="Media"> - <box - className="Cover" - valign={Gtk.Align.CENTER} - css={bind(player, "coverArt").as(cover => - `background-image: url('${cover}');` - )} - /> - <label - label={bind(player, "title").as(() => - `${player.title} - ${player.artist}` - )} - /> + {bind(mpris, "players").as(ps => ps[0] ? ( + <box> + <box + className="Cover" + valign={Gtk.Align.CENTER} + css={bind(ps[0], "coverArt").as(cover => + `background-image: url('${cover}');` + )} + /> + <label + label={bind(ps[0], "title").as(() => + `${ps[0].title} - ${ps[0].artist}` + )} + /> + </box> + ) : ( + "Nothing Playing" + ))} </box> } diff --git a/examples/lua/simple-bar/README.md b/examples/lua/simple-bar/README.md new file mode 100644 index 0000000..1cebdac --- /dev/null +++ b/examples/lua/simple-bar/README.md @@ -0,0 +1,12 @@ +# Simple Bar Example + + + +A simple bar for Hyprland using + +- [Audio library](https://aylur.github.io/astal/libraries/audio). +- [Battery library](https://aylur.github.io/astal/libraries/battery). +- [Hyprland library](https://aylur.github.io/astal/libraries/hyprland). +- [Mpris library](https://aylur.github.io/astal/libraries/mpris). +- [Network library](https://aylur.github.io/astal/libraries/network). +- [dart-sass](https://sass-lang.com/dart-sass/) as the css precompiler diff --git a/examples/lua/simple-bar/app.lua b/examples/lua/simple-bar/app.lua new file mode 100644 index 0000000..8c3359b --- /dev/null +++ b/examples/lua/simple-bar/app.lua @@ -0,0 +1,20 @@ +#!/usr/bin/lua +local astal = require("astal") +local App = astal.App + +local Bar = require("widget.Bar") +local src = require("lib").src + +local scss = src("style.scss") +local css = "/tmp/style.css" + +astal.exec("sass " .. scss .. " " .. css) + +App:start({ + css = css, + main = function() + for _, mon in pairs(App.monitors) do + Bar(mon) + end + end, +}) diff --git a/examples/lua/simple-bar/lib.lua b/examples/lua/simple-bar/lib.lua new file mode 100644 index 0000000..d94db5c --- /dev/null +++ b/examples/lua/simple-bar/lib.lua @@ -0,0 +1,25 @@ +local Variable = require("astal").Variable + +local M = {} + +function M.src(path) + local str = debug.getinfo(2, "S").source:sub(2) + local src = str:match("(.*/)") or str:match("(.*\\)") or "./" + return src .. path +end + +---@generic T, R +---@param arr T[] +---@param func fun(T, integer): R +---@return R[] +function M.map(arr, func) + local new_arr = {} + for i, v in ipairs(arr) do + new_arr[i] = func(v, i) + end + return new_arr +end + +M.date = Variable(""):poll(1000, "date") + +return M diff --git a/examples/lua/simple-bar/style.scss b/examples/lua/simple-bar/style.scss new file mode 100644 index 0000000..f98286e --- /dev/null +++ b/examples/lua/simple-bar/style.scss @@ -0,0 +1,88 @@ +$bg: #212223; +$fg: #f1f1f1; +$accent: #378DF7; +$radius: 7px; + +window.Bar { + border: none; + box-shadow: none; + background-color: $bg; + color: $fg; + font-size: 1.1em; + font-weight: bold; + + button { + all: unset; + background-color: transparent; + + &:hover label { + background-color: transparentize($fg, 0.84); + border-color: transparentize($accent, 0.8); + } + + &:active label { + background-color: transparentize($fg, 0.8) + } + } + + label { + transition: 200ms; + padding: 0 8px; + margin: 2px; + border-radius: $radius; + border: 1pt solid transparent; + } + + .Workspaces .focused label { + color: $accent; + border-color: $accent; + } + + .FocusedClient { + color: $accent; + } + + .Media .Cover { + min-height: 1.2em; + min-width: 1.2em; + border-radius: $radius; + background-position: center; + background-size: contain; + } + + .Battery label { + padding-left: 0; + margin-left: 0; + } + + .AudioSlider { + * { + all: unset; + } + + icon { + margin-right: .6em; + } + + margin: 0 1em; + + trough { + background-color: transparentize($fg, 0.8); + border-radius: $radius; + } + + highlight { + background-color: $accent; + min-height: .8em; + border-radius: $radius; + } + + slider { + background-color: $fg; + border-radius: $radius; + min-height: 1em; + min-width: 1em; + margin: -.2em; + } + } +} diff --git a/examples/lua/simple-bar/widget/Bar.lua b/examples/lua/simple-bar/widget/Bar.lua new file mode 100644 index 0000000..bf70cd5 --- /dev/null +++ b/examples/lua/simple-bar/widget/Bar.lua @@ -0,0 +1,192 @@ +local astal = require("astal") +local App = astal.App +local Widget = astal.Widget +local Variable = astal.Variable +local Gdk = astal.Gdk +local GLib = astal.GLib +local bind = astal.bind +local Mpris = astal.require("AstalMpris") +local Battery = astal.require("AstalBattery") +local Wp = astal.require("AstalWp") +local Network = astal.require("AstalNetwork") +local Tray = astal.require("AstalTray") +local Hyprland = astal.require("AstalHyprland") +local map = require("lib").map + +local function SysTray() + local tray = Tray.get_default() + + return Widget.Box({ + bind(tray, "items"):as(function(items) + return map(items, function(item) + if item.icon_theme_path ~= nil then + App:add_icons(item.icon_theme_path) + end + + local menu = item:create_menu() + + return Widget.Button({ + tooltip_markup = bind(item, "tooltip_markup"), + on_destroy = function() + if menu ~= nil then + menu:destroy() + end + end, + on_click_release = function(self) + if menu ~= nil then + menu:popup_at_widget(self, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH, nil) + end + end, + Widget.Icon({ + g_icon = bind(item, "gicon"), + }), + }) + end) + end), + }) +end + +local function FocusedClient() + local hypr = Hyprland.get_default() + local focused = bind(hypr, "focused-client") + + return Widget.Box({ + class_name = "Focused", + visible = focused, + focused:as(function(client) + return client and Widget.Label({ + label = bind(client, "title"):as(tostring), + }) + end), + }) +end + +local function Wifi() + local wifi = Network.get_default().wifi + + return Widget.Icon({ + tooltip_text = bind(wifi, "ssid"):as(tostring), + class_name = "Wifi", + icon = bind(wifi, "icon-name"), + }) +end + +local function AudioSlider() + local speaker = Wp.get_default_wp().audio.default_speaker + + return Widget.Box({ + class_name = "AudioSlider", + css = "min-width: 140px;", + Widget.Icon({ + icon = bind(speaker, "volume-icon"), + }), + Widget.Slider({ + hexpand = true, + on_dragged = function(self) + speaker.volume = self.value + end, + value = bind(speaker, "volume"), + }), + }) +end + +local function BatteryLevel() + local bat = Battery.get_default() + + return Widget.Box({ + class_name = "Battery", + visible = bind(bat, "is-present"), + Widget.Icon({ + icon = bind(bat, "icon-name"), + }), + Widget.Label({ + label = bind(bat, "percentage"):as(function(p) + return tostring(math.floor(p * 100)) .. " %" + end), + }), + }) +end + +local function Media() + local player = Mpris.Player.new("spotify") + + return Widget.Box({ + class_name = "Media", + visible = bind(player, "available"), + Widget.Box({ + class_name = "Cover", + valign = "CENTER", + css = bind(player, "cover-art"):as(function(cover) + return "background-image: url('" .. (cover or "") .. "');" + end), + }), + Widget.Label({ + label = bind(player, "metadata"):as(function() + return (player.title or "") .. " - " .. (player.artist or "") + end), + }), + }) +end + +local function Workspaces() + local hypr = Hyprland.get_default() + + return Widget.Box({ + class_name = "Workspaces", + bind(hypr, "workspaces"):as(function(wss) + return map(wss, function(ws) + return Widget.Button({ + class_name = bind(hypr, "focused-workspace"):as(function(fw) + return fw == ws and "focused" or "" + end), + on_clicked = function() + ws:focus() + end, + label = bind(ws, "id"):as(tostring), + }) + end) + end), + }) +end + +local function Time(format) + local time = Variable(""):poll(1000, function() + return GLib.DateTime.new_now_local():format(format) + end) + + return Widget.Label({ + class_name = "Time", + on_destroy = function() + time:drop() + end, + label = time(), + }) +end + +return function(gdkmonitor) + return Widget.Window({ + class_name = "Bar", + gdkmonitor = gdkmonitor, + anchor = astal.Astal.WindowAnchor.TOP + astal.Astal.WindowAnchor.LEFT + astal.Astal.WindowAnchor.RIGHT, + exclusivity = "EXCLUSIVE", + + Widget.CenterBox({ + Widget.Box({ + halign = "START", + Workspaces(), + FocusedClient(), + }), + Widget.Box({ + Media(), + }), + Widget.Box({ + halign = "END", + Wifi(), + AudioSlider(), + BatteryLevel(), + SysTray(), + Time("%H:%M - %A %e."), + }), + }), + }) +end @@ -8,8 +8,8 @@ inherit (builtins) replaceStrings readFile; version = replaceStrings ["\n"] [""] (readFile ./version); - system = "x86_64-linux"; - pkgs = import nixpkgs {inherit system;}; + system = "x86_64-linux"; # TODO: other architectures + pkgs = nixpkgs.legacyPackages.${system}; mkPkg = name: src: inputs: pkgs.stdenv.mkDerivation { @@ -29,6 +29,17 @@ outputs = ["out" "dev"]; }; in { + devShells.${system} = import ./nix/devshell.nix { + inherit self pkgs; + }; + + lib = { + mkLuaPackage = import ./nix/lua.nix { + inherit pkgs; + astal = self; + }; + }; + packages.${system} = with pkgs; { docs = import ./docs {inherit self pkgs;}; default = self.packages.${system}.astal; @@ -47,37 +58,5 @@ tray = mkPkg "astal-tray" ./lib/tray [gtk3 gdk-pixbuf libdbusmenu-gtk3 json-glib]; wireplumber = mkPkg "astal-wireplumber" ./lib/wireplumber [wireplumber]; }; - - devShells.${system} = let - buildInputs = with pkgs; [ - wrapGAppsHook - gobject-introspection - meson - pkg-config - ninja - vala - gtk3 - gtk-layer-shell - json-glib - pam - gvfs - networkmanager - gdk-pixbuf - wireplumber - libdbusmenu-gtk3 - wayland - - (lua.withPackages (ps: [ps.lgi])) - (python3.withPackages (ps: [ps.pygobject3 ps.pygobject-stubs])) - gjs - ]; - in { - default = pkgs.mkShell { - inherit buildInputs; - }; - astal = pkgs.mkShell { - buildInputs = buildInputs ++ (builtins.attrValues self.packages.${system}); - }; - }; }; } diff --git a/nix/devshell.nix b/nix/devshell.nix new file mode 100644 index 0000000..936f4b4 --- /dev/null +++ b/nix/devshell.nix @@ -0,0 +1,57 @@ +{ + self, + pkgs, +}: let + lua = pkgs.lua.withPackages (ps: [ + ps.lgi + (ps.luaPackages.toLuaModule (pkgs.stdenv.mkDerivation { + name = "astal"; + src = "${self}/core/lua"; + dontBuild = true; + installPhase = '' + mkdir -p $out/share/lua/${ps.lua.luaversion}/astal + cp -r astal/* $out/share/lua/${ps.lua.luaversion}/astal + ''; + })) + ]); + + python = pkgs.python3.withPackages (ps: [ + ps.pygobject3 + ps.pygobject-stubs + ]); + + buildInputs = with pkgs; [ + wrapGAppsHook + gobject-introspection + meson + pkg-config + ninja + vala + gtk3 + gtk-layer-shell + json-glib + pam + gvfs + networkmanager + gdk-pixbuf + wireplumber + libdbusmenu-gtk3 + wayland + + dart-sass + lua + python + gjs + ]; +in { + default = pkgs.mkShell { + inherit buildInputs; + }; + astal = pkgs.mkShell { + buildInputs = + buildInputs + ++ builtins.attrValues ( + builtins.removeAttrs self.packages.${pkgs.system} ["docs"] + ); + }; +} diff --git a/nix/lua.nix b/nix/lua.nix new file mode 100644 index 0000000..a790021 --- /dev/null +++ b/nix/lua.nix @@ -0,0 +1,58 @@ +defaults: { + pkgs ? defaults.pkgs, + astal ? defaults.astal, + name ? "astal-lua", + src, + extraLuaPackages ? (ps: []), + extraPackages ? [], +}: let + lua = pkgs.lua.withPackages (ps: + (extraLuaPackages ps) + ++ [ + ps.lgi + (ps.luaPackages.toLuaModule (pkgs.stdenv.mkDerivation { + name = "astal"; + version = "0.1.0"; + src = "${astal}/core/lua"; + dontBuild = true; + installPhase = '' + mkdir -p $out/share/lua/${ps.lua.luaversion}/astal + cp -r astal/* $out/share/lua/${ps.lua.luaversion}/astal + ''; + })) + ]); + + script = '' + #!${lua}/bin/lua + package.path = package.path .. ";${src}/?.lua" + require "app" + ''; +in + pkgs.stdenvNoCC.mkDerivation { + inherit src name; + + nativeBuildInputs = with pkgs; [ + wrapGAppsHook + gobject-introspection + ]; + + buildInputs = + extraPackages + ++ [ + lua + astal.packages.${pkgs.system}.default + ]; + + installPhase = '' + runHook preInstall + mkdir -p $out/bin + cp -r * $out/bin + echo '${script}' > astal-lua + install -m 755 astal-lua $out/bin/${name} + runHook postInstall + ''; + + gappsWrapperArgs = [ + "--prefix PATH : ${pkgs.lib.makeBinPath extraPackages}" + ]; + } |