Merge pull request #5 from Aylur/nix/lua-builder

lua example, nix builder
This commit is contained in:
Aylur
2024-09-12 00:40:04 +02:00
committed by GitHub
18 changed files with 534 additions and 83 deletions
+2 -2
View File
@@ -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) {
+13 -7
View File
@@ -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
+2 -2
View File
@@ -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
+3 -1
View File
@@ -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
-8
View File
@@ -1,8 +0,0 @@
local App = require("astal.application")
App:start({
instance_name = "test",
main = function()
App:quit(1)
end,
})
+1 -1
View File
@@ -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
+25 -12
View File
@@ -6,19 +6,32 @@ Using Astal on Nix will require you to package your project.
:::code-group
```nix [typescript.nix]
```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";
};
};
outputs = { self, nixpkgs, astal }: let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
packages.${system}.default = astal.lib.mkLuaPacakge {
inherit pkgs;
};
};
}
```
```nix [<i class="devicon-python-plain"></i> Python]
# Not documented yet
```
```nix [lua.nix]
# Not documented yet
```
```nix [python.nix]
# 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
+2 -1
View File
@@ -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)
![simple-bar](https://github.com/user-attachments/assets/a306c864-56b7-44c4-8820-81f424f32b9b)
## Python
+1 -1
View File
@@ -1,6 +1,6 @@
# Simple Bar Example
![sime-bar](https://github.com/user-attachments/assets/a306c864-56b7-44c4-8820-81f424f32b9b)
![simple-bar](https://github.com/user-attachments/assets/a306c864-56b7-44c4-8820-81f424f32b9b)
A simple bar for Hyprland using
+20 -14
View File
@@ -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>
}
+12
View File
@@ -0,0 +1,12 @@
# Simple Bar Example
![simple-bar](https://github.com/user-attachments/assets/a306c864-56b7-44c4-8820-81f424f32b9b)
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
+20
View File
@@ -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,
})
+25
View File
@@ -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
+88
View File
@@ -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;
}
}
}
+192
View File
@@ -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
+13 -34
View File
@@ -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});
};
};
};
}
+57
View File
@@ -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"]
);
};
}
+58
View File
@@ -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}"
];
}