From 2f71cd4c08bb4514efe43533e6a5d03535204c29 Mon Sep 17 00:00:00 2001 From: Aylur Date: Tue, 15 Oct 2024 01:26:32 +0200 Subject: refactor lua and gjs lib --- lang/lua/astal-dev-1.rockspec | 31 +++++ lang/lua/gtk3/app.lua | 96 +++++++++++++++ lang/lua/gtk3/astalify.lua | 236 ++++++++++++++++++++++++++++++++++++ lang/lua/gtk3/widget.lua | 90 ++++++++++++++ lang/lua/init.lua | 27 +++++ lang/lua/lib/binding.lua | 71 +++++++++++ lang/lua/lib/file.lua | 45 +++++++ lang/lua/lib/process.lua | 78 ++++++++++++ lang/lua/lib/time.lua | 27 +++++ lang/lua/lib/variable.lua | 276 ++++++++++++++++++++++++++++++++++++++++++ lang/lua/stylua.toml | 3 + 11 files changed, 980 insertions(+) create mode 100644 lang/lua/astal-dev-1.rockspec create mode 100644 lang/lua/gtk3/app.lua create mode 100644 lang/lua/gtk3/astalify.lua create mode 100644 lang/lua/gtk3/widget.lua create mode 100644 lang/lua/init.lua create mode 100644 lang/lua/lib/binding.lua create mode 100644 lang/lua/lib/file.lua create mode 100644 lang/lua/lib/process.lua create mode 100644 lang/lua/lib/time.lua create mode 100644 lang/lua/lib/variable.lua create mode 100644 lang/lua/stylua.toml (limited to 'lang/lua') diff --git a/lang/lua/astal-dev-1.rockspec b/lang/lua/astal-dev-1.rockspec new file mode 100644 index 0000000..d392a79 --- /dev/null +++ b/lang/lua/astal-dev-1.rockspec @@ -0,0 +1,31 @@ +package = "astal" +version = "dev-1" + +source = { + url = "git+https://github.com/aylur/astal", +} + +description = { + summary = "lua bindings for libastal.", + homepage = "https://aylur.github.io/astal/", + license = "GPL-3", +} + +dependencies = { + "lua >= 5.1, < 5.4", + "lgi >= 0.9.2", +} + +build = { + type = "builtin", + modules = { + ["astal.application"] = "lib/application.lua", + ["astal.binding"] = "lib/binding.lua", + ["astal.init"] = "lib/init.lua", + ["astal.process"] = "lib/process.lua", + ["astal.time"] = "lib/time.lua", + ["astal.variable"] = "lib/variable.lua", + ["astal.widget"] = "lib/widget.lua", + ["astal.file"] = "lib/file.lua", + }, +} diff --git a/lang/lua/gtk3/app.lua b/lang/lua/gtk3/app.lua new file mode 100644 index 0000000..7895f69 --- /dev/null +++ b/lang/lua/gtk3/app.lua @@ -0,0 +1,96 @@ +local lgi = require("lgi") +local Astal = lgi.require("Astal", "3.0") +local AstalIO = lgi.require("AstalIO", "0.1") + +local AstalLua = Astal.Application:derive("AstalLua") +local request_handler + +function AstalLua:do_request(msg, conn) + if type(request_handler) == "function" then + request_handler(msg, function(response) + AstalIO.write_sock(conn, tostring(response), function(_, res) + AstalIO.write_sock_finish(res) + end) + end) + else + Astal.Application.do_request(self, msg, conn) + end +end + +function AstalLua:quit(code) + Astal.Application.quit(self) + os.exit(code) +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 + +---@param config StartConfig | nil +function Astal.Application:start(config) + if config == nil then + config = {} + end + + if config.client == nil then + config.client = function() + print('Astal instance "' .. app.instance_name .. '" is already running') + os.exit(1) + end + end + + if config.hold == nil then + config.hold = true + end + + request_handler = config.request_handler + + if config.css then + self:apply_css(config.css) + end + 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 + end + + app.on_activate = function() + if type(config.main) == "function" then + config.main(table.unpack(arg)) + end + if config.hold then + self:hold() + end + end + + local _, err = app:acquire_socket() + if err ~= nil then + return config.client(function(msg) + return AstalIO.send_message(self.instance_name, msg) + end, table.unpack(arg)) + end + + self:run(nil) +end + +return app diff --git a/lang/lua/gtk3/astalify.lua b/lang/lua/gtk3/astalify.lua new file mode 100644 index 0000000..065de40 --- /dev/null +++ b/lang/lua/gtk3/astalify.lua @@ -0,0 +1,236 @@ +local lgi = require("lgi") +local Astal = lgi.require("Astal", "3.0") +local Gtk = lgi.require("Gtk", "3.0") +local GObject = lgi.require("GObject", "2.0") +local Binding = require("astal.lib.binding") +local Variable = require("astal.lib.variable") +local exec_async = require("astal.lib.process").exec_async + +local function filter(tbl, fn) + local copy = {} + for key, value in pairs(tbl) do + if fn(value, key) then + if type(key) == "number" then + table.insert(copy, value) + else + copy[key] = value + end + end + end + return copy +end + +local function map(tbl, fn) + local copy = {} + for key, value in pairs(tbl) do + copy[key] = fn(value) + end + return copy +end + +local flatten +flatten = function(tbl) + local copy = {} + for _, value in pairs(tbl) do + if type(value) == "table" and getmetatable(value) == nil then + for _, inner in pairs(flatten(value)) do + table.insert(copy, inner) + end + else + table.insert(copy, value) + end + end + return copy +end + +local function includes(tbl, elem) + for _, value in pairs(tbl) do + if value == elem then + return true + end + end + return false +end + +local function set_children(parent, children) + children = map(flatten(children), function(item) + if Gtk.Widget:is_type_of(item) then + return item + end + return Gtk.Label({ + visible = true, + label = tostring(item), + }) + end) + + -- remove + if Gtk.Bin:is_type_of(parent) then + local ch = parent:get_child() + if ch ~= nil then + parent:remove(ch) + end + if ch ~= nil and not includes(children, ch) and not parent.no_implicit_destroy then + ch:destroy() + end + elseif Gtk.Container:is_type_of(parent) then + for _, ch in ipairs(parent:get_children()) do + parent:remove(ch) + if ch ~= nil and not includes(children, ch) and not parent.no_implicit_destroy then + ch:destroy() + end + end + end + + -- TODO: add more container types + if Astal.Box:is_type_of(parent) then + parent:set_children(children) + elseif Astal.Stack:is_type_of(parent) then + parent:set_children(children) + elseif Astal.CenterBox:is_type_of(parent) then + parent.start_widget = children[1] + parent.center_widget = children[2] + parent.end_widget = children[3] + elseif Astal.Overlay:is_type_of(parent) then + parent:set_child(children[1]) + children[1] = nil + parent:set_overlays(children) + elseif Gtk.Container:is_type_of(parent) then + for _, child in pairs(children) do + if Gtk.Widget:is_type_of(child) then + parent:add(child) + end + end + end +end + +local function merge_bindings(array) + local function get_values(...) + local args = { ... } + local i = 0 + return map(array, function(value) + if getmetatable(value) == Binding then + i = i + 1 + return args[i] + else + return value + end + end) + end + + local bindings = filter(array, function(v) + return getmetatable(v) == Binding + end) + + if #bindings == 0 then + return array + end + + if #bindings == 1 then + return bindings[1]:as(get_values) + end + + return Variable.derive(bindings, get_values)() +end + +return function(ctor) + function ctor:hook(object, signalOrCallback, callback) + if GObject.Object:is_type_of(object) and type(signalOrCallback) == "string" then + local id + if string.sub(signalOrCallback, 1, 8) == "notify::" then + local prop = string.gsub(signalOrCallback, "notify::", "") + id = object.on_notify:connect(function() + callback(self, object[prop]) + end, prop, false) + else + id = object["on_" .. signalOrCallback]:connect(function(_, ...) + callback(self, ...) + end) + end + self.on_destroy = function() + GObject.signal_handler_disconnect(object, id) + end + elseif type(object.subscribe) == "function" then + local unsub = object.subscribe(function(...) + signalOrCallback(self, ...) + end) + self.on_destroy = unsub + else + error("can not hook: not gobject+signal or subscribable") + end + end + + function ctor:toggle_class_name(name, on) + Astal.widget_toggle_class_name(self, name, on) + end + + return function(tbl) + if tbl == nil then + tbl = {} + end + + local bindings = {} + local setup = tbl.setup + + -- collect children + local children = merge_bindings(flatten(filter(tbl, function(_, key) + return type(key) == "number" + end))) + + -- default visible to true + if type(tbl.visible) ~= "boolean" then + tbl.visible = true + end + + -- collect props + local props = filter(tbl, function(_, key) + return type(key) == "string" and key ~= "setup" + end) + + -- collect signal handlers + for prop, value in pairs(props) do + if string.sub(prop, 0, 2) == "on" and type(value) ~= "function" then + props[prop] = function() + exec_async(value, print) + end + end + end + + -- collect bindings + for prop, value in pairs(props) do + if getmetatable(value) == Binding then + bindings[prop] = value + props[prop] = value:get() + end + end + + -- construct, attach bindings, add children + local widget = ctor() + + if getmetatable(children) == Binding then + set_children(widget, children:get()) + widget.on_destroy = children:subscribe(function(v) + set_children(widget, v) + end) + else + if #children > 0 then + set_children(widget, children) + end + end + + for prop, binding in pairs(bindings) do + widget.on_destroy = binding:subscribe(function(v) + widget[prop] = v + end) + end + + for prop, value in pairs(props) do + widget[prop] = value + end + + if type(setup) == "function" then + setup(widget) + end + + return widget + end +end diff --git a/lang/lua/gtk3/widget.lua b/lang/lua/gtk3/widget.lua new file mode 100644 index 0000000..beaad6c --- /dev/null +++ b/lang/lua/gtk3/widget.lua @@ -0,0 +1,90 @@ +local lgi = require("lgi") +local Astal = lgi.require("Astal", "3.0") +local Gtk = lgi.require("Gtk", "3.0") +local astalify = require("astal.gtk3.astalify") + +local Widget = { + astalify = astalify, + Box = astalify(Astal.Box), + Button = astalify(Astal.Button), + CenterBox = astalify(Astal.CenterBox), + CircularProgress = astalify(Astal.CircularProgress), + DrawingArea = astalify(Gtk.DrawingArea), + Entry = astalify(Gtk.Entry), + EventBox = astalify(Astal.EventBox), + -- TODO: Fixed + -- TODO: FlowBox + Icon = astalify(Astal.Icon), + Label = astalify(Gtk.Label), + LevelBar = astalify(Astal.LevelBar), + -- TODO: ListBox + Overlay = astalify(Astal.Overlay), + Revealer = astalify(Gtk.Revealer), + Scrollable = astalify(Astal.Scrollable), + Slider = astalify(Astal.Slider), + Stack = astalify(Astal.Stack), + Switch = astalify(Gtk.Switch), + Window = astalify(Astal.Window), +} + +Gtk.Widget._attribute.css = { + get = Astal.widget_get_css, + set = Astal.widget_set_css, +} + +Gtk.Widget._attribute.class_name = { + get = function(self) + local result = "" + local strings = Astal.widget_get_class_names(self) + for i, str in ipairs(strings) do + result = result .. str + if i < #strings then + result = result .. " " + end + end + return result + end, + set = function(self, class_name) + local names = {} + for word in class_name:gmatch("%S+") do + table.insert(names, word) + end + Astal.widget_set_class_names(self, names) + end, +} + +Gtk.Widget._attribute.cursor = { + get = Astal.widget_get_cursor, + set = Astal.widget_set_cursor, +} + +Gtk.Widget._attribute.click_through = { + get = Astal.widget_get_click_through, + set = Astal.widget_set_click_through, +} + +local no_implicit_destroy = {} +Gtk.Widget._attribute.no_implicit_destroy = { + get = function(self) + return no_implicit_destroy[self] or false + end, + set = function(self, v) + if no_implicit_destroy[self] == nil then + self.on_destroy = function() + no_implicit_destroy[self] = nil + end + end + no_implicit_destroy[self] = v + end, +} + +Astal.Box._attribute.children = { + get = Astal.Box.get_children, + set = Astal.Box.set_children, +} + +return setmetatable(Widget, { + __call = function(_, ctor) + return astalify(ctor) + end, +}) diff --git a/lang/lua/init.lua b/lang/lua/init.lua new file mode 100644 index 0000000..b6ab30c --- /dev/null +++ b/lang/lua/init.lua @@ -0,0 +1,27 @@ +local lgi = require("lgi") +local Binding = require("astal.lib.binding") +local File = require("astal.lib.file") +local Process = require("astal.lib.process") +local Time = require("astal.lib.time") +local Variable = require("astal.lib.variable") + +return { + Variable = Variable, + bind = Binding.new, + + interval = Time.interval, + timeout = Time.timeout, + idle = Time.idle, + + subprocess = Process.subprocess, + exec = Process.exec, + exec_async = Process.exec_async, + + read_file = File.read_file, + read_file_async = File.read_file_async, + write_file = File.write_file, + write_file_async = File.write_file_async, + monitor_file = File.monitor_file, + + require = lgi.require, +} diff --git a/lang/lua/lib/binding.lua b/lang/lua/lib/binding.lua new file mode 100644 index 0000000..ba1e6e4 --- /dev/null +++ b/lang/lua/lib/binding.lua @@ -0,0 +1,71 @@ +local lgi = require("lgi") +local GObject = lgi.require("GObject", "2.0") + +---@class Binding +---@field emitter table|Variable +---@field property? string +---@field transformFn function +local Binding = {} + +---@param emitter table +---@param property? string +---@return Binding +function Binding.new(emitter, property) + return setmetatable({ + emitter = emitter, + property = property, + transformFn = function(v) + return v + end, + }, Binding) +end + +function Binding:__tostring() + local str = "Binding<" .. tostring(self.emitter) + if self.property ~= nil then + str = str .. ", " .. self.property + end + return str .. ">" +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 + error("can not get: Not a GObject or a Variable " + self) +end + +---@param transform fun(value: any): any +---@return Binding +function Binding:as(transform) + local b = Binding.new(self.emitter, self.property) + b.transformFn = function(v) + return transform(self.transformFn(v)) + end + return b +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 + error("can not subscribe: Not a GObject or a Variable " + self) +end + +Binding.__index = Binding +return Binding diff --git a/lang/lua/lib/file.lua b/lang/lua/lib/file.lua new file mode 100644 index 0000000..e3be783 --- /dev/null +++ b/lang/lua/lib/file.lua @@ -0,0 +1,45 @@ +local lgi = require("lgi") +local Astal = lgi.require("AstalIO", "0.1") +local GObject = lgi.require("GObject", "2.0") + +local M = {} + +---@param path string +---@return string +function M.read_file(path) + return Astal.read_file(path) +end + +---@param path string +---@param callback fun(content: string, err: string): nil +function M.read_file_async(path, callback) + Astal.read_file_async(path, function(_, res) + local content, err = Astal.read_file_finish(res) + callback(content, err) + end) +end + +---@param path string +---@param content string +function M.write_file(path, content) + Astal.write_file(path, content) +end + +---@param path string +---@param content string +---@param callback? fun(err: string): nil +function M.write_file_async(path, content, callback) + Astal.write_file_async(path, content, function(_, res) + if type(callback) == "function" then + callback(Astal.write_file_finish(res)) + end + end) +end + +---@param path string +---@param callback fun(file: string, event: integer): nil +function M.monitor_file(path, callback) + return Astal.monitor_file(path, GObject.Closure(callback)) +end + +return M diff --git a/lang/lua/lib/process.lua b/lang/lua/lib/process.lua new file mode 100644 index 0000000..b8b7436 --- /dev/null +++ b/lang/lua/lib/process.lua @@ -0,0 +1,78 @@ +local lgi = require("lgi") +local Astal = lgi.require("AstalIO", "0.1") + +local M = {} + +---@param commandline string | string[] +---@param on_stdout? fun(out: string): nil +---@param on_stderr? fun(err: string): nil +---@return { kill: function } | nil proc +function M.subprocess(commandline, on_stdout, on_stderr) + if on_stdout == nil then + on_stdout = function(out) + io.stdout:write(tostring(out) .. "\n") + end + end + + if on_stderr == nil then + on_stderr = function(err) + io.stderr:write(tostring(err) .. "\n") + end + end + + local proc, err + if type(commandline) == "table" then + proc, err = Astal.Process.subprocessv(commandline) + else + proc, err = Astal.Process.subprocess(commandline) + end + if err ~= nil then + err(err) + return nil + end + proc.on_stdout = function(_, stdoud) + on_stdout(stdoud) + end + proc.on_stderr = function(_, stderr) + on_stderr(stderr) + end + return proc +end + +---@param commandline string | string[] +---@return string, string +function M.exec(commandline) + if type(commandline) == "table" then + return Astal.Process.execv(commandline) + else + return Astal.Process.exec(commandline) + end +end + +---@param commandline string | string[] +---@param callback? fun(out: string, err: string): nil +function M.exec_async(commandline, callback) + if callback == nil then + callback = function(out, err) + if err ~= nil then + io.stdout:write(tostring(out) .. "\n") + else + io.stderr:write(tostring(err) .. "\n") + end + end + end + + if type(commandline) == "table" then + Astal.Process.exec_asyncv(commandline, function(_, res) + local out, err = Astal.Process.exec_asyncv_finish(res) + callback(out, err) + end) + else + Astal.Process.exec_async(commandline, function(_, res) + local out, err = Astal.Process.exec_finish(res) + callback(out, err) + end) + end +end + +return M diff --git a/lang/lua/lib/time.lua b/lang/lua/lib/time.lua new file mode 100644 index 0000000..7719da9 --- /dev/null +++ b/lang/lua/lib/time.lua @@ -0,0 +1,27 @@ +local lgi = require("lgi") +local Astal = lgi.require("AstalIO", "0.1") +local GObject = lgi.require("GObject", "2.0") + +local M = {} + +---@param interval number +---@param fn function +---@return { cancel: function, on_now: function } +function M.interval(interval, fn) + return Astal.Time.interval(interval, GObject.Closure(fn)) +end + +---@param timeout number +---@param fn function +---@return { cancel: function, on_now: function } +function M.timeout(timeout, fn) + return Astal.Time.timeout(timeout, GObject.Closure(fn)) +end + +---@param fn function +---@return { cancel: function, on_now: function } +function M.idle(fn) + return Astal.Time.idle(GObject.Closure(fn)) +end + +return M diff --git a/lang/lua/lib/variable.lua b/lang/lua/lib/variable.lua new file mode 100644 index 0000000..c93d04d --- /dev/null +++ b/lang/lua/lib/variable.lua @@ -0,0 +1,276 @@ +local lgi = require("lgi") +local Astal = lgi.require("AstalIO", "0.1") +local GObject = lgi.require("GObject", "2.0") +local Binding = require("astal.lib.binding") +local Time = require("astal.lib.time") +local Process = require("astal.lib.process") + +---@class Variable +---@field private variable table +---@field private err_handler? function +---@field private _value any +---@field private _poll? table +---@field private _watch? table +---@field private poll_interval number +---@field private poll_exec? string[] | string +---@field private poll_transform? fun(next: any, prev: any): any +---@field private poll_fn? function +---@field private watch_transform? fun(next: any, prev: any): any +---@field private watch_exec? string[] | string +local Variable = {} +Variable.__index = Variable + +---@param value any +---@return Variable +function Variable.new(value) + local v = Astal.VariableBase() + local variable = setmetatable({ + variable = v, + _value = value, + }, Variable) + v.on_dropped = function() + variable:stop_watch() + variable:stop_watch() + end + v.on_error = function(_, err) + if variable.err_handler then + variable.err_handler(err) + end + end + return variable +end + +---@param transform function +---@return Binding +function Variable:__call(transform) + if transform == nil then + transform = function(v) + return v + end + return Binding.new(self) + end + return Binding.new(self):as(transform) +end + +function Variable:__tostring() + return "Variable<" .. tostring(self:get()) .. ">" +end + +function Variable:get() + return self._value or nil +end + +function Variable:set(value) + if value ~= self:get() then + self._value = value + self.variable:emit_changed() + end +end + +function Variable:start_poll() + if self._poll ~= nil then + return + end + + if self.poll_fn then + self._poll = Time.interval(self.poll_interval, function() + self:set(self.poll_fn(self:get())) + end) + elseif self.poll_exec then + self._poll = Time.interval(self.poll_interval, function() + Process.exec_async(self.poll_exec, function(out, err) + if err ~= nil then + return self.variable.emit_error(err) + end + self:set(self.poll_transform(out, self:get())) + end) + end) + end +end + +function Variable:start_watch() + if self._watch then + return + end + + self._watch = Process.subprocess(self.watch_exec, function(out) + self:set(self.watch_transform(out, self:get())) + end, function(err) + self.variable.emit_error(err) + end) +end + +function Variable:stop_poll() + if self._poll then + self._poll.cancel() + end + self._poll = nil +end + +function Variable:stop_watch() + if self._watch then + self._watch.kill() + end + self._watch = nil +end + +function Variable:is_polling() + return self._poll ~= nil +end + +function Variable:is_watching() + return self._watch ~= nil +end + +function Variable:drop() + self.variable.emit_dropped() +end + +---@param callback function +---@return Variable +function Variable:on_dropped(callback) + self.variable.on_dropped = callback + return self +end + +---@param callback function +---@return Variable +function Variable:on_error(callback) + self.err_handler = nil + self.variable.on_eror = function(_, err) + callback(err) + end + return self +end + +---@param callback fun(value: any) +---@return function +function Variable:subscribe(callback) + local id = self.variable.on_changed:connect(function() + callback(self:get()) + end) + return function() + GObject.signal_handler_disconnect(self.variable, id) + end +end + +---@param interval number +---@param exec string | string[] | function +---@param transform? fun(next: any, prev: any): any +function Variable:poll(interval, exec, transform) + if transform == nil then + transform = function(next) + return next + end + end + self:stop_poll() + self.poll_interval = interval + self.poll_transform = transform + + if type(exec) == "function" then + self.poll_fn = exec + self.poll_exec = nil + else + self.poll_exec = exec + self.poll_fn = nil + end + self:start_poll() + return self +end + +---@param exec string | string[] +---@param transform? fun(next: any, prev: any): any +function Variable:watch(exec, transform) + if transform == nil then + transform = function(next) + return next + end + end + self:stop_poll() + self.watch_exec = exec + self.watch_transform = transform + self:start_watch() + return self +end + +---@param object table | table[] +---@param sigOrFn string | fun(...): any +---@param callback fun(...): any +---@return Variable +function Variable:observe(object, sigOrFn, callback) + local f + if type(sigOrFn) == "function" then + f = sigOrFn + elseif type(callback) == "function" then + f = callback + else + f = function() + return self:get() + end + end + local set = function(...) + self:set(f(...)) + end + + if type(sigOrFn) == "string" then + object["on_" .. sigOrFn]:connect(set) + else + for _, obj in ipairs(object) do + obj[1]["on_" .. obj[2]]:connect(set) + end + end + return self +end + +---@param deps Variable | (Binding | Variable)[] +---@param transform? fun(...): any +---@return Variable +function Variable.derive(deps, transform) + if type(transform) == "nil" then + transform = function(...) + return { ... } + end + end + + if getmetatable(deps) == Variable then + local var = Variable.new(transform(deps:get())) + deps:subscribe(function(v) + var:set(transform(v)) + end) + return var + end + + for i, var in ipairs(deps) do + if getmetatable(var) == Variable then + deps[i] = Binding.new(var) + end + end + + local update = function() + local params = {} + for i, binding in ipairs(deps) do + params[i] = binding:get() + end + return transform(table.unpack(params), 1, #deps) + end + + local var = Variable.new(update()) + + local unsubs = {} + for i, b in ipairs(deps) do + unsubs[i] = b:subscribe(update) + end + + var.variable.on_dropped = function() + for _, unsub in ipairs(unsubs) do + unsub() + end + end + return var +end + +return setmetatable(Variable, { + __call = function(_, v) + return Variable.new(v) + end, +}) diff --git a/lang/lua/stylua.toml b/lang/lua/stylua.toml new file mode 100644 index 0000000..d4a4951 --- /dev/null +++ b/lang/lua/stylua.toml @@ -0,0 +1,3 @@ +indent_type = "Spaces" +indent_width = 4 +column_width = 100 -- cgit v1.2.3 From bafd48d3df9b43a1d49ec015eff30619d595468b Mon Sep 17 00:00:00 2001 From: Aylur Date: Tue, 15 Oct 2024 13:25:45 +0000 Subject: update lua and gjs layout installing the gjs package through meson or npm now results in the same exposed structure lua: fix rockspec docs: aur package --- lang/lua/astal-dev-1.rockspec | 22 ++-- lang/lua/astal/binding.lua | 71 ++++++++++ lang/lua/astal/file.lua | 45 +++++++ lang/lua/astal/gtk3/app.lua | 96 ++++++++++++++ lang/lua/astal/gtk3/astalify.lua | 236 +++++++++++++++++++++++++++++++++ lang/lua/astal/gtk3/init.lua | 5 + lang/lua/astal/gtk3/widget.lua | 90 +++++++++++++ lang/lua/astal/init.lua | 27 ++++ lang/lua/astal/process.lua | 78 +++++++++++ lang/lua/astal/time.lua | 27 ++++ lang/lua/astal/variable.lua | 276 +++++++++++++++++++++++++++++++++++++++ lang/lua/gtk3/app.lua | 96 -------------- lang/lua/gtk3/astalify.lua | 236 --------------------------------- lang/lua/gtk3/widget.lua | 90 ------------- lang/lua/init.lua | 27 ---- lang/lua/lib/binding.lua | 71 ---------- lang/lua/lib/file.lua | 45 ------- lang/lua/lib/process.lua | 78 ----------- lang/lua/lib/time.lua | 27 ---- lang/lua/lib/variable.lua | 276 --------------------------------------- 20 files changed, 965 insertions(+), 954 deletions(-) create mode 100644 lang/lua/astal/binding.lua create mode 100644 lang/lua/astal/file.lua create mode 100644 lang/lua/astal/gtk3/app.lua create mode 100644 lang/lua/astal/gtk3/astalify.lua create mode 100644 lang/lua/astal/gtk3/init.lua create mode 100644 lang/lua/astal/gtk3/widget.lua create mode 100644 lang/lua/astal/init.lua create mode 100644 lang/lua/astal/process.lua create mode 100644 lang/lua/astal/time.lua create mode 100644 lang/lua/astal/variable.lua delete mode 100644 lang/lua/gtk3/app.lua delete mode 100644 lang/lua/gtk3/astalify.lua delete mode 100644 lang/lua/gtk3/widget.lua delete mode 100644 lang/lua/init.lua delete mode 100644 lang/lua/lib/binding.lua delete mode 100644 lang/lua/lib/file.lua delete mode 100644 lang/lua/lib/process.lua delete mode 100644 lang/lua/lib/time.lua delete mode 100644 lang/lua/lib/variable.lua (limited to 'lang/lua') diff --git a/lang/lua/astal-dev-1.rockspec b/lang/lua/astal-dev-1.rockspec index d392a79..3970672 100644 --- a/lang/lua/astal-dev-1.rockspec +++ b/lang/lua/astal-dev-1.rockspec @@ -19,13 +19,19 @@ dependencies = { build = { type = "builtin", modules = { - ["astal.application"] = "lib/application.lua", - ["astal.binding"] = "lib/binding.lua", - ["astal.init"] = "lib/init.lua", - ["astal.process"] = "lib/process.lua", - ["astal.time"] = "lib/time.lua", - ["astal.variable"] = "lib/variable.lua", - ["astal.widget"] = "lib/widget.lua", - ["astal.file"] = "lib/file.lua", + ["astal.binding"] = "astal/binding.lua", + ["astal.file"] = "astal/file.lua", + ["astal.init"] = "astal/init.lua", + ["astal.process"] = "astal/process.lua", + ["astal.time"] = "astal/time.lua", + ["astal.variable"] = "astal/variable.lua", + ["astal.gtk3.app"] = "astal/gtk3/app.lua", + ["astal.gtk3.init"] = "astal/gtk3/init.lua", + ["astal.gtk3.astalify"] = "astal/gtk3/astalify.lua", + ["astal.gtk3.widget"] = "astal/gtk3/widget.lua", + -- ["astal.gtk4.app"] = "astal/gtk4/app.lua", + -- ["astal.gtk4.init"] = "astal/gtk4/init.lua", + -- ["astal.gtk4.astalify"] = "astal/gtk4/astalify.lua", + -- ["astal.gtk4.widget"] = "astal/gtk4/widget.lua", }, } diff --git a/lang/lua/astal/binding.lua b/lang/lua/astal/binding.lua new file mode 100644 index 0000000..ba1e6e4 --- /dev/null +++ b/lang/lua/astal/binding.lua @@ -0,0 +1,71 @@ +local lgi = require("lgi") +local GObject = lgi.require("GObject", "2.0") + +---@class Binding +---@field emitter table|Variable +---@field property? string +---@field transformFn function +local Binding = {} + +---@param emitter table +---@param property? string +---@return Binding +function Binding.new(emitter, property) + return setmetatable({ + emitter = emitter, + property = property, + transformFn = function(v) + return v + end, + }, Binding) +end + +function Binding:__tostring() + local str = "Binding<" .. tostring(self.emitter) + if self.property ~= nil then + str = str .. ", " .. self.property + end + return str .. ">" +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 + error("can not get: Not a GObject or a Variable " + self) +end + +---@param transform fun(value: any): any +---@return Binding +function Binding:as(transform) + local b = Binding.new(self.emitter, self.property) + b.transformFn = function(v) + return transform(self.transformFn(v)) + end + return b +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 + error("can not subscribe: Not a GObject or a Variable " + self) +end + +Binding.__index = Binding +return Binding diff --git a/lang/lua/astal/file.lua b/lang/lua/astal/file.lua new file mode 100644 index 0000000..e3be783 --- /dev/null +++ b/lang/lua/astal/file.lua @@ -0,0 +1,45 @@ +local lgi = require("lgi") +local Astal = lgi.require("AstalIO", "0.1") +local GObject = lgi.require("GObject", "2.0") + +local M = {} + +---@param path string +---@return string +function M.read_file(path) + return Astal.read_file(path) +end + +---@param path string +---@param callback fun(content: string, err: string): nil +function M.read_file_async(path, callback) + Astal.read_file_async(path, function(_, res) + local content, err = Astal.read_file_finish(res) + callback(content, err) + end) +end + +---@param path string +---@param content string +function M.write_file(path, content) + Astal.write_file(path, content) +end + +---@param path string +---@param content string +---@param callback? fun(err: string): nil +function M.write_file_async(path, content, callback) + Astal.write_file_async(path, content, function(_, res) + if type(callback) == "function" then + callback(Astal.write_file_finish(res)) + end + end) +end + +---@param path string +---@param callback fun(file: string, event: integer): nil +function M.monitor_file(path, callback) + return Astal.monitor_file(path, GObject.Closure(callback)) +end + +return M diff --git a/lang/lua/astal/gtk3/app.lua b/lang/lua/astal/gtk3/app.lua new file mode 100644 index 0000000..7895f69 --- /dev/null +++ b/lang/lua/astal/gtk3/app.lua @@ -0,0 +1,96 @@ +local lgi = require("lgi") +local Astal = lgi.require("Astal", "3.0") +local AstalIO = lgi.require("AstalIO", "0.1") + +local AstalLua = Astal.Application:derive("AstalLua") +local request_handler + +function AstalLua:do_request(msg, conn) + if type(request_handler) == "function" then + request_handler(msg, function(response) + AstalIO.write_sock(conn, tostring(response), function(_, res) + AstalIO.write_sock_finish(res) + end) + end) + else + Astal.Application.do_request(self, msg, conn) + end +end + +function AstalLua:quit(code) + Astal.Application.quit(self) + os.exit(code) +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 + +---@param config StartConfig | nil +function Astal.Application:start(config) + if config == nil then + config = {} + end + + if config.client == nil then + config.client = function() + print('Astal instance "' .. app.instance_name .. '" is already running') + os.exit(1) + end + end + + if config.hold == nil then + config.hold = true + end + + request_handler = config.request_handler + + if config.css then + self:apply_css(config.css) + end + 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 + end + + app.on_activate = function() + if type(config.main) == "function" then + config.main(table.unpack(arg)) + end + if config.hold then + self:hold() + end + end + + local _, err = app:acquire_socket() + if err ~= nil then + return config.client(function(msg) + return AstalIO.send_message(self.instance_name, msg) + end, table.unpack(arg)) + end + + self:run(nil) +end + +return app diff --git a/lang/lua/astal/gtk3/astalify.lua b/lang/lua/astal/gtk3/astalify.lua new file mode 100644 index 0000000..065de40 --- /dev/null +++ b/lang/lua/astal/gtk3/astalify.lua @@ -0,0 +1,236 @@ +local lgi = require("lgi") +local Astal = lgi.require("Astal", "3.0") +local Gtk = lgi.require("Gtk", "3.0") +local GObject = lgi.require("GObject", "2.0") +local Binding = require("astal.lib.binding") +local Variable = require("astal.lib.variable") +local exec_async = require("astal.lib.process").exec_async + +local function filter(tbl, fn) + local copy = {} + for key, value in pairs(tbl) do + if fn(value, key) then + if type(key) == "number" then + table.insert(copy, value) + else + copy[key] = value + end + end + end + return copy +end + +local function map(tbl, fn) + local copy = {} + for key, value in pairs(tbl) do + copy[key] = fn(value) + end + return copy +end + +local flatten +flatten = function(tbl) + local copy = {} + for _, value in pairs(tbl) do + if type(value) == "table" and getmetatable(value) == nil then + for _, inner in pairs(flatten(value)) do + table.insert(copy, inner) + end + else + table.insert(copy, value) + end + end + return copy +end + +local function includes(tbl, elem) + for _, value in pairs(tbl) do + if value == elem then + return true + end + end + return false +end + +local function set_children(parent, children) + children = map(flatten(children), function(item) + if Gtk.Widget:is_type_of(item) then + return item + end + return Gtk.Label({ + visible = true, + label = tostring(item), + }) + end) + + -- remove + if Gtk.Bin:is_type_of(parent) then + local ch = parent:get_child() + if ch ~= nil then + parent:remove(ch) + end + if ch ~= nil and not includes(children, ch) and not parent.no_implicit_destroy then + ch:destroy() + end + elseif Gtk.Container:is_type_of(parent) then + for _, ch in ipairs(parent:get_children()) do + parent:remove(ch) + if ch ~= nil and not includes(children, ch) and not parent.no_implicit_destroy then + ch:destroy() + end + end + end + + -- TODO: add more container types + if Astal.Box:is_type_of(parent) then + parent:set_children(children) + elseif Astal.Stack:is_type_of(parent) then + parent:set_children(children) + elseif Astal.CenterBox:is_type_of(parent) then + parent.start_widget = children[1] + parent.center_widget = children[2] + parent.end_widget = children[3] + elseif Astal.Overlay:is_type_of(parent) then + parent:set_child(children[1]) + children[1] = nil + parent:set_overlays(children) + elseif Gtk.Container:is_type_of(parent) then + for _, child in pairs(children) do + if Gtk.Widget:is_type_of(child) then + parent:add(child) + end + end + end +end + +local function merge_bindings(array) + local function get_values(...) + local args = { ... } + local i = 0 + return map(array, function(value) + if getmetatable(value) == Binding then + i = i + 1 + return args[i] + else + return value + end + end) + end + + local bindings = filter(array, function(v) + return getmetatable(v) == Binding + end) + + if #bindings == 0 then + return array + end + + if #bindings == 1 then + return bindings[1]:as(get_values) + end + + return Variable.derive(bindings, get_values)() +end + +return function(ctor) + function ctor:hook(object, signalOrCallback, callback) + if GObject.Object:is_type_of(object) and type(signalOrCallback) == "string" then + local id + if string.sub(signalOrCallback, 1, 8) == "notify::" then + local prop = string.gsub(signalOrCallback, "notify::", "") + id = object.on_notify:connect(function() + callback(self, object[prop]) + end, prop, false) + else + id = object["on_" .. signalOrCallback]:connect(function(_, ...) + callback(self, ...) + end) + end + self.on_destroy = function() + GObject.signal_handler_disconnect(object, id) + end + elseif type(object.subscribe) == "function" then + local unsub = object.subscribe(function(...) + signalOrCallback(self, ...) + end) + self.on_destroy = unsub + else + error("can not hook: not gobject+signal or subscribable") + end + end + + function ctor:toggle_class_name(name, on) + Astal.widget_toggle_class_name(self, name, on) + end + + return function(tbl) + if tbl == nil then + tbl = {} + end + + local bindings = {} + local setup = tbl.setup + + -- collect children + local children = merge_bindings(flatten(filter(tbl, function(_, key) + return type(key) == "number" + end))) + + -- default visible to true + if type(tbl.visible) ~= "boolean" then + tbl.visible = true + end + + -- collect props + local props = filter(tbl, function(_, key) + return type(key) == "string" and key ~= "setup" + end) + + -- collect signal handlers + for prop, value in pairs(props) do + if string.sub(prop, 0, 2) == "on" and type(value) ~= "function" then + props[prop] = function() + exec_async(value, print) + end + end + end + + -- collect bindings + for prop, value in pairs(props) do + if getmetatable(value) == Binding then + bindings[prop] = value + props[prop] = value:get() + end + end + + -- construct, attach bindings, add children + local widget = ctor() + + if getmetatable(children) == Binding then + set_children(widget, children:get()) + widget.on_destroy = children:subscribe(function(v) + set_children(widget, v) + end) + else + if #children > 0 then + set_children(widget, children) + end + end + + for prop, binding in pairs(bindings) do + widget.on_destroy = binding:subscribe(function(v) + widget[prop] = v + end) + end + + for prop, value in pairs(props) do + widget[prop] = value + end + + if type(setup) == "function" then + setup(widget) + end + + return widget + end +end diff --git a/lang/lua/astal/gtk3/init.lua b/lang/lua/astal/gtk3/init.lua new file mode 100644 index 0000000..6fb5455 --- /dev/null +++ b/lang/lua/astal/gtk3/init.lua @@ -0,0 +1,5 @@ +return { + App = require("astal.gtk3.app"), + astalify = require("astal.gtk3.astalify"), + Widget = require("astal.gtk3.widget"), +} diff --git a/lang/lua/astal/gtk3/widget.lua b/lang/lua/astal/gtk3/widget.lua new file mode 100644 index 0000000..beaad6c --- /dev/null +++ b/lang/lua/astal/gtk3/widget.lua @@ -0,0 +1,90 @@ +local lgi = require("lgi") +local Astal = lgi.require("Astal", "3.0") +local Gtk = lgi.require("Gtk", "3.0") +local astalify = require("astal.gtk3.astalify") + +local Widget = { + astalify = astalify, + Box = astalify(Astal.Box), + Button = astalify(Astal.Button), + CenterBox = astalify(Astal.CenterBox), + CircularProgress = astalify(Astal.CircularProgress), + DrawingArea = astalify(Gtk.DrawingArea), + Entry = astalify(Gtk.Entry), + EventBox = astalify(Astal.EventBox), + -- TODO: Fixed + -- TODO: FlowBox + Icon = astalify(Astal.Icon), + Label = astalify(Gtk.Label), + LevelBar = astalify(Astal.LevelBar), + -- TODO: ListBox + Overlay = astalify(Astal.Overlay), + Revealer = astalify(Gtk.Revealer), + Scrollable = astalify(Astal.Scrollable), + Slider = astalify(Astal.Slider), + Stack = astalify(Astal.Stack), + Switch = astalify(Gtk.Switch), + Window = astalify(Astal.Window), +} + +Gtk.Widget._attribute.css = { + get = Astal.widget_get_css, + set = Astal.widget_set_css, +} + +Gtk.Widget._attribute.class_name = { + get = function(self) + local result = "" + local strings = Astal.widget_get_class_names(self) + for i, str in ipairs(strings) do + result = result .. str + if i < #strings then + result = result .. " " + end + end + return result + end, + set = function(self, class_name) + local names = {} + for word in class_name:gmatch("%S+") do + table.insert(names, word) + end + Astal.widget_set_class_names(self, names) + end, +} + +Gtk.Widget._attribute.cursor = { + get = Astal.widget_get_cursor, + set = Astal.widget_set_cursor, +} + +Gtk.Widget._attribute.click_through = { + get = Astal.widget_get_click_through, + set = Astal.widget_set_click_through, +} + +local no_implicit_destroy = {} +Gtk.Widget._attribute.no_implicit_destroy = { + get = function(self) + return no_implicit_destroy[self] or false + end, + set = function(self, v) + if no_implicit_destroy[self] == nil then + self.on_destroy = function() + no_implicit_destroy[self] = nil + end + end + no_implicit_destroy[self] = v + end, +} + +Astal.Box._attribute.children = { + get = Astal.Box.get_children, + set = Astal.Box.set_children, +} + +return setmetatable(Widget, { + __call = function(_, ctor) + return astalify(ctor) + end, +}) diff --git a/lang/lua/astal/init.lua b/lang/lua/astal/init.lua new file mode 100644 index 0000000..f442db0 --- /dev/null +++ b/lang/lua/astal/init.lua @@ -0,0 +1,27 @@ +local lgi = require("lgi") +local Binding = require("astal.binding") +local File = require("astal.file") +local Process = require("astal.proc") +local Time = require("astal.time") +local Variable = require("astal.variable") + +return { + Variable = Variable, + bind = Binding.new, + + interval = Time.interval, + timeout = Time.timeout, + idle = Time.idle, + + subprocess = Process.subprocess, + exec = Process.exec, + exec_async = Process.exec_async, + + read_file = File.read_file, + read_file_async = File.read_file_async, + write_file = File.write_file, + write_file_async = File.write_file_async, + monitor_file = File.monitor_file, + + require = lgi.require, +} diff --git a/lang/lua/astal/process.lua b/lang/lua/astal/process.lua new file mode 100644 index 0000000..b8b7436 --- /dev/null +++ b/lang/lua/astal/process.lua @@ -0,0 +1,78 @@ +local lgi = require("lgi") +local Astal = lgi.require("AstalIO", "0.1") + +local M = {} + +---@param commandline string | string[] +---@param on_stdout? fun(out: string): nil +---@param on_stderr? fun(err: string): nil +---@return { kill: function } | nil proc +function M.subprocess(commandline, on_stdout, on_stderr) + if on_stdout == nil then + on_stdout = function(out) + io.stdout:write(tostring(out) .. "\n") + end + end + + if on_stderr == nil then + on_stderr = function(err) + io.stderr:write(tostring(err) .. "\n") + end + end + + local proc, err + if type(commandline) == "table" then + proc, err = Astal.Process.subprocessv(commandline) + else + proc, err = Astal.Process.subprocess(commandline) + end + if err ~= nil then + err(err) + return nil + end + proc.on_stdout = function(_, stdoud) + on_stdout(stdoud) + end + proc.on_stderr = function(_, stderr) + on_stderr(stderr) + end + return proc +end + +---@param commandline string | string[] +---@return string, string +function M.exec(commandline) + if type(commandline) == "table" then + return Astal.Process.execv(commandline) + else + return Astal.Process.exec(commandline) + end +end + +---@param commandline string | string[] +---@param callback? fun(out: string, err: string): nil +function M.exec_async(commandline, callback) + if callback == nil then + callback = function(out, err) + if err ~= nil then + io.stdout:write(tostring(out) .. "\n") + else + io.stderr:write(tostring(err) .. "\n") + end + end + end + + if type(commandline) == "table" then + Astal.Process.exec_asyncv(commandline, function(_, res) + local out, err = Astal.Process.exec_asyncv_finish(res) + callback(out, err) + end) + else + Astal.Process.exec_async(commandline, function(_, res) + local out, err = Astal.Process.exec_finish(res) + callback(out, err) + end) + end +end + +return M diff --git a/lang/lua/astal/time.lua b/lang/lua/astal/time.lua new file mode 100644 index 0000000..7719da9 --- /dev/null +++ b/lang/lua/astal/time.lua @@ -0,0 +1,27 @@ +local lgi = require("lgi") +local Astal = lgi.require("AstalIO", "0.1") +local GObject = lgi.require("GObject", "2.0") + +local M = {} + +---@param interval number +---@param fn function +---@return { cancel: function, on_now: function } +function M.interval(interval, fn) + return Astal.Time.interval(interval, GObject.Closure(fn)) +end + +---@param timeout number +---@param fn function +---@return { cancel: function, on_now: function } +function M.timeout(timeout, fn) + return Astal.Time.timeout(timeout, GObject.Closure(fn)) +end + +---@param fn function +---@return { cancel: function, on_now: function } +function M.idle(fn) + return Astal.Time.idle(GObject.Closure(fn)) +end + +return M diff --git a/lang/lua/astal/variable.lua b/lang/lua/astal/variable.lua new file mode 100644 index 0000000..5a5e169 --- /dev/null +++ b/lang/lua/astal/variable.lua @@ -0,0 +1,276 @@ +local lgi = require("lgi") +local Astal = lgi.require("AstalIO", "0.1") +local GObject = lgi.require("GObject", "2.0") +local Binding = require("astal.binding") +local Time = require("astal.time") +local Process = require("astal.process") + +---@class Variable +---@field private variable table +---@field private err_handler? function +---@field private _value any +---@field private _poll? table +---@field private _watch? table +---@field private poll_interval number +---@field private poll_exec? string[] | string +---@field private poll_transform? fun(next: any, prev: any): any +---@field private poll_fn? function +---@field private watch_transform? fun(next: any, prev: any): any +---@field private watch_exec? string[] | string +local Variable = {} +Variable.__index = Variable + +---@param value any +---@return Variable +function Variable.new(value) + local v = Astal.VariableBase() + local variable = setmetatable({ + variable = v, + _value = value, + }, Variable) + v.on_dropped = function() + variable:stop_watch() + variable:stop_watch() + end + v.on_error = function(_, err) + if variable.err_handler then + variable.err_handler(err) + end + end + return variable +end + +---@param transform function +---@return Binding +function Variable:__call(transform) + if transform == nil then + transform = function(v) + return v + end + return Binding.new(self) + end + return Binding.new(self):as(transform) +end + +function Variable:__tostring() + return "Variable<" .. tostring(self:get()) .. ">" +end + +function Variable:get() + return self._value or nil +end + +function Variable:set(value) + if value ~= self:get() then + self._value = value + self.variable:emit_changed() + end +end + +function Variable:start_poll() + if self._poll ~= nil then + return + end + + if self.poll_fn then + self._poll = Time.interval(self.poll_interval, function() + self:set(self.poll_fn(self:get())) + end) + elseif self.poll_exec then + self._poll = Time.interval(self.poll_interval, function() + Process.exec_async(self.poll_exec, function(out, err) + if err ~= nil then + return self.variable.emit_error(err) + end + self:set(self.poll_transform(out, self:get())) + end) + end) + end +end + +function Variable:start_watch() + if self._watch then + return + end + + self._watch = Process.subprocess(self.watch_exec, function(out) + self:set(self.watch_transform(out, self:get())) + end, function(err) + self.variable.emit_error(err) + end) +end + +function Variable:stop_poll() + if self._poll then + self._poll.cancel() + end + self._poll = nil +end + +function Variable:stop_watch() + if self._watch then + self._watch.kill() + end + self._watch = nil +end + +function Variable:is_polling() + return self._poll ~= nil +end + +function Variable:is_watching() + return self._watch ~= nil +end + +function Variable:drop() + self.variable.emit_dropped() +end + +---@param callback function +---@return Variable +function Variable:on_dropped(callback) + self.variable.on_dropped = callback + return self +end + +---@param callback function +---@return Variable +function Variable:on_error(callback) + self.err_handler = nil + self.variable.on_eror = function(_, err) + callback(err) + end + return self +end + +---@param callback fun(value: any) +---@return function +function Variable:subscribe(callback) + local id = self.variable.on_changed:connect(function() + callback(self:get()) + end) + return function() + GObject.signal_handler_disconnect(self.variable, id) + end +end + +---@param interval number +---@param exec string | string[] | function +---@param transform? fun(next: any, prev: any): any +function Variable:poll(interval, exec, transform) + if transform == nil then + transform = function(next) + return next + end + end + self:stop_poll() + self.poll_interval = interval + self.poll_transform = transform + + if type(exec) == "function" then + self.poll_fn = exec + self.poll_exec = nil + else + self.poll_exec = exec + self.poll_fn = nil + end + self:start_poll() + return self +end + +---@param exec string | string[] +---@param transform? fun(next: any, prev: any): any +function Variable:watch(exec, transform) + if transform == nil then + transform = function(next) + return next + end + end + self:stop_poll() + self.watch_exec = exec + self.watch_transform = transform + self:start_watch() + return self +end + +---@param object table | table[] +---@param sigOrFn string | fun(...): any +---@param callback fun(...): any +---@return Variable +function Variable:observe(object, sigOrFn, callback) + local f + if type(sigOrFn) == "function" then + f = sigOrFn + elseif type(callback) == "function" then + f = callback + else + f = function() + return self:get() + end + end + local set = function(...) + self:set(f(...)) + end + + if type(sigOrFn) == "string" then + object["on_" .. sigOrFn]:connect(set) + else + for _, obj in ipairs(object) do + obj[1]["on_" .. obj[2]]:connect(set) + end + end + return self +end + +---@param deps Variable | (Binding | Variable)[] +---@param transform? fun(...): any +---@return Variable +function Variable.derive(deps, transform) + if type(transform) == "nil" then + transform = function(...) + return { ... } + end + end + + if getmetatable(deps) == Variable then + local var = Variable.new(transform(deps:get())) + deps:subscribe(function(v) + var:set(transform(v)) + end) + return var + end + + for i, var in ipairs(deps) do + if getmetatable(var) == Variable then + deps[i] = Binding.new(var) + end + end + + local update = function() + local params = {} + for i, binding in ipairs(deps) do + params[i] = binding:get() + end + return transform(table.unpack(params), 1, #deps) + end + + local var = Variable.new(update()) + + local unsubs = {} + for i, b in ipairs(deps) do + unsubs[i] = b:subscribe(update) + end + + var.variable.on_dropped = function() + for _, unsub in ipairs(unsubs) do + unsub() + end + end + return var +end + +return setmetatable(Variable, { + __call = function(_, v) + return Variable.new(v) + end, +}) diff --git a/lang/lua/gtk3/app.lua b/lang/lua/gtk3/app.lua deleted file mode 100644 index 7895f69..0000000 --- a/lang/lua/gtk3/app.lua +++ /dev/null @@ -1,96 +0,0 @@ -local lgi = require("lgi") -local Astal = lgi.require("Astal", "3.0") -local AstalIO = lgi.require("AstalIO", "0.1") - -local AstalLua = Astal.Application:derive("AstalLua") -local request_handler - -function AstalLua:do_request(msg, conn) - if type(request_handler) == "function" then - request_handler(msg, function(response) - AstalIO.write_sock(conn, tostring(response), function(_, res) - AstalIO.write_sock_finish(res) - end) - end) - else - Astal.Application.do_request(self, msg, conn) - end -end - -function AstalLua:quit(code) - Astal.Application.quit(self) - os.exit(code) -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 - ----@param config StartConfig | nil -function Astal.Application:start(config) - if config == nil then - config = {} - end - - if config.client == nil then - config.client = function() - print('Astal instance "' .. app.instance_name .. '" is already running') - os.exit(1) - end - end - - if config.hold == nil then - config.hold = true - end - - request_handler = config.request_handler - - if config.css then - self:apply_css(config.css) - end - 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 - end - - app.on_activate = function() - if type(config.main) == "function" then - config.main(table.unpack(arg)) - end - if config.hold then - self:hold() - end - end - - local _, err = app:acquire_socket() - if err ~= nil then - return config.client(function(msg) - return AstalIO.send_message(self.instance_name, msg) - end, table.unpack(arg)) - end - - self:run(nil) -end - -return app diff --git a/lang/lua/gtk3/astalify.lua b/lang/lua/gtk3/astalify.lua deleted file mode 100644 index 065de40..0000000 --- a/lang/lua/gtk3/astalify.lua +++ /dev/null @@ -1,236 +0,0 @@ -local lgi = require("lgi") -local Astal = lgi.require("Astal", "3.0") -local Gtk = lgi.require("Gtk", "3.0") -local GObject = lgi.require("GObject", "2.0") -local Binding = require("astal.lib.binding") -local Variable = require("astal.lib.variable") -local exec_async = require("astal.lib.process").exec_async - -local function filter(tbl, fn) - local copy = {} - for key, value in pairs(tbl) do - if fn(value, key) then - if type(key) == "number" then - table.insert(copy, value) - else - copy[key] = value - end - end - end - return copy -end - -local function map(tbl, fn) - local copy = {} - for key, value in pairs(tbl) do - copy[key] = fn(value) - end - return copy -end - -local flatten -flatten = function(tbl) - local copy = {} - for _, value in pairs(tbl) do - if type(value) == "table" and getmetatable(value) == nil then - for _, inner in pairs(flatten(value)) do - table.insert(copy, inner) - end - else - table.insert(copy, value) - end - end - return copy -end - -local function includes(tbl, elem) - for _, value in pairs(tbl) do - if value == elem then - return true - end - end - return false -end - -local function set_children(parent, children) - children = map(flatten(children), function(item) - if Gtk.Widget:is_type_of(item) then - return item - end - return Gtk.Label({ - visible = true, - label = tostring(item), - }) - end) - - -- remove - if Gtk.Bin:is_type_of(parent) then - local ch = parent:get_child() - if ch ~= nil then - parent:remove(ch) - end - if ch ~= nil and not includes(children, ch) and not parent.no_implicit_destroy then - ch:destroy() - end - elseif Gtk.Container:is_type_of(parent) then - for _, ch in ipairs(parent:get_children()) do - parent:remove(ch) - if ch ~= nil and not includes(children, ch) and not parent.no_implicit_destroy then - ch:destroy() - end - end - end - - -- TODO: add more container types - if Astal.Box:is_type_of(parent) then - parent:set_children(children) - elseif Astal.Stack:is_type_of(parent) then - parent:set_children(children) - elseif Astal.CenterBox:is_type_of(parent) then - parent.start_widget = children[1] - parent.center_widget = children[2] - parent.end_widget = children[3] - elseif Astal.Overlay:is_type_of(parent) then - parent:set_child(children[1]) - children[1] = nil - parent:set_overlays(children) - elseif Gtk.Container:is_type_of(parent) then - for _, child in pairs(children) do - if Gtk.Widget:is_type_of(child) then - parent:add(child) - end - end - end -end - -local function merge_bindings(array) - local function get_values(...) - local args = { ... } - local i = 0 - return map(array, function(value) - if getmetatable(value) == Binding then - i = i + 1 - return args[i] - else - return value - end - end) - end - - local bindings = filter(array, function(v) - return getmetatable(v) == Binding - end) - - if #bindings == 0 then - return array - end - - if #bindings == 1 then - return bindings[1]:as(get_values) - end - - return Variable.derive(bindings, get_values)() -end - -return function(ctor) - function ctor:hook(object, signalOrCallback, callback) - if GObject.Object:is_type_of(object) and type(signalOrCallback) == "string" then - local id - if string.sub(signalOrCallback, 1, 8) == "notify::" then - local prop = string.gsub(signalOrCallback, "notify::", "") - id = object.on_notify:connect(function() - callback(self, object[prop]) - end, prop, false) - else - id = object["on_" .. signalOrCallback]:connect(function(_, ...) - callback(self, ...) - end) - end - self.on_destroy = function() - GObject.signal_handler_disconnect(object, id) - end - elseif type(object.subscribe) == "function" then - local unsub = object.subscribe(function(...) - signalOrCallback(self, ...) - end) - self.on_destroy = unsub - else - error("can not hook: not gobject+signal or subscribable") - end - end - - function ctor:toggle_class_name(name, on) - Astal.widget_toggle_class_name(self, name, on) - end - - return function(tbl) - if tbl == nil then - tbl = {} - end - - local bindings = {} - local setup = tbl.setup - - -- collect children - local children = merge_bindings(flatten(filter(tbl, function(_, key) - return type(key) == "number" - end))) - - -- default visible to true - if type(tbl.visible) ~= "boolean" then - tbl.visible = true - end - - -- collect props - local props = filter(tbl, function(_, key) - return type(key) == "string" and key ~= "setup" - end) - - -- collect signal handlers - for prop, value in pairs(props) do - if string.sub(prop, 0, 2) == "on" and type(value) ~= "function" then - props[prop] = function() - exec_async(value, print) - end - end - end - - -- collect bindings - for prop, value in pairs(props) do - if getmetatable(value) == Binding then - bindings[prop] = value - props[prop] = value:get() - end - end - - -- construct, attach bindings, add children - local widget = ctor() - - if getmetatable(children) == Binding then - set_children(widget, children:get()) - widget.on_destroy = children:subscribe(function(v) - set_children(widget, v) - end) - else - if #children > 0 then - set_children(widget, children) - end - end - - for prop, binding in pairs(bindings) do - widget.on_destroy = binding:subscribe(function(v) - widget[prop] = v - end) - end - - for prop, value in pairs(props) do - widget[prop] = value - end - - if type(setup) == "function" then - setup(widget) - end - - return widget - end -end diff --git a/lang/lua/gtk3/widget.lua b/lang/lua/gtk3/widget.lua deleted file mode 100644 index beaad6c..0000000 --- a/lang/lua/gtk3/widget.lua +++ /dev/null @@ -1,90 +0,0 @@ -local lgi = require("lgi") -local Astal = lgi.require("Astal", "3.0") -local Gtk = lgi.require("Gtk", "3.0") -local astalify = require("astal.gtk3.astalify") - -local Widget = { - astalify = astalify, - Box = astalify(Astal.Box), - Button = astalify(Astal.Button), - CenterBox = astalify(Astal.CenterBox), - CircularProgress = astalify(Astal.CircularProgress), - DrawingArea = astalify(Gtk.DrawingArea), - Entry = astalify(Gtk.Entry), - EventBox = astalify(Astal.EventBox), - -- TODO: Fixed - -- TODO: FlowBox - Icon = astalify(Astal.Icon), - Label = astalify(Gtk.Label), - LevelBar = astalify(Astal.LevelBar), - -- TODO: ListBox - Overlay = astalify(Astal.Overlay), - Revealer = astalify(Gtk.Revealer), - Scrollable = astalify(Astal.Scrollable), - Slider = astalify(Astal.Slider), - Stack = astalify(Astal.Stack), - Switch = astalify(Gtk.Switch), - Window = astalify(Astal.Window), -} - -Gtk.Widget._attribute.css = { - get = Astal.widget_get_css, - set = Astal.widget_set_css, -} - -Gtk.Widget._attribute.class_name = { - get = function(self) - local result = "" - local strings = Astal.widget_get_class_names(self) - for i, str in ipairs(strings) do - result = result .. str - if i < #strings then - result = result .. " " - end - end - return result - end, - set = function(self, class_name) - local names = {} - for word in class_name:gmatch("%S+") do - table.insert(names, word) - end - Astal.widget_set_class_names(self, names) - end, -} - -Gtk.Widget._attribute.cursor = { - get = Astal.widget_get_cursor, - set = Astal.widget_set_cursor, -} - -Gtk.Widget._attribute.click_through = { - get = Astal.widget_get_click_through, - set = Astal.widget_set_click_through, -} - -local no_implicit_destroy = {} -Gtk.Widget._attribute.no_implicit_destroy = { - get = function(self) - return no_implicit_destroy[self] or false - end, - set = function(self, v) - if no_implicit_destroy[self] == nil then - self.on_destroy = function() - no_implicit_destroy[self] = nil - end - end - no_implicit_destroy[self] = v - end, -} - -Astal.Box._attribute.children = { - get = Astal.Box.get_children, - set = Astal.Box.set_children, -} - -return setmetatable(Widget, { - __call = function(_, ctor) - return astalify(ctor) - end, -}) diff --git a/lang/lua/init.lua b/lang/lua/init.lua deleted file mode 100644 index b6ab30c..0000000 --- a/lang/lua/init.lua +++ /dev/null @@ -1,27 +0,0 @@ -local lgi = require("lgi") -local Binding = require("astal.lib.binding") -local File = require("astal.lib.file") -local Process = require("astal.lib.process") -local Time = require("astal.lib.time") -local Variable = require("astal.lib.variable") - -return { - Variable = Variable, - bind = Binding.new, - - interval = Time.interval, - timeout = Time.timeout, - idle = Time.idle, - - subprocess = Process.subprocess, - exec = Process.exec, - exec_async = Process.exec_async, - - read_file = File.read_file, - read_file_async = File.read_file_async, - write_file = File.write_file, - write_file_async = File.write_file_async, - monitor_file = File.monitor_file, - - require = lgi.require, -} diff --git a/lang/lua/lib/binding.lua b/lang/lua/lib/binding.lua deleted file mode 100644 index ba1e6e4..0000000 --- a/lang/lua/lib/binding.lua +++ /dev/null @@ -1,71 +0,0 @@ -local lgi = require("lgi") -local GObject = lgi.require("GObject", "2.0") - ----@class Binding ----@field emitter table|Variable ----@field property? string ----@field transformFn function -local Binding = {} - ----@param emitter table ----@param property? string ----@return Binding -function Binding.new(emitter, property) - return setmetatable({ - emitter = emitter, - property = property, - transformFn = function(v) - return v - end, - }, Binding) -end - -function Binding:__tostring() - local str = "Binding<" .. tostring(self.emitter) - if self.property ~= nil then - str = str .. ", " .. self.property - end - return str .. ">" -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 - error("can not get: Not a GObject or a Variable " + self) -end - ----@param transform fun(value: any): any ----@return Binding -function Binding:as(transform) - local b = Binding.new(self.emitter, self.property) - b.transformFn = function(v) - return transform(self.transformFn(v)) - end - return b -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 - error("can not subscribe: Not a GObject or a Variable " + self) -end - -Binding.__index = Binding -return Binding diff --git a/lang/lua/lib/file.lua b/lang/lua/lib/file.lua deleted file mode 100644 index e3be783..0000000 --- a/lang/lua/lib/file.lua +++ /dev/null @@ -1,45 +0,0 @@ -local lgi = require("lgi") -local Astal = lgi.require("AstalIO", "0.1") -local GObject = lgi.require("GObject", "2.0") - -local M = {} - ----@param path string ----@return string -function M.read_file(path) - return Astal.read_file(path) -end - ----@param path string ----@param callback fun(content: string, err: string): nil -function M.read_file_async(path, callback) - Astal.read_file_async(path, function(_, res) - local content, err = Astal.read_file_finish(res) - callback(content, err) - end) -end - ----@param path string ----@param content string -function M.write_file(path, content) - Astal.write_file(path, content) -end - ----@param path string ----@param content string ----@param callback? fun(err: string): nil -function M.write_file_async(path, content, callback) - Astal.write_file_async(path, content, function(_, res) - if type(callback) == "function" then - callback(Astal.write_file_finish(res)) - end - end) -end - ----@param path string ----@param callback fun(file: string, event: integer): nil -function M.monitor_file(path, callback) - return Astal.monitor_file(path, GObject.Closure(callback)) -end - -return M diff --git a/lang/lua/lib/process.lua b/lang/lua/lib/process.lua deleted file mode 100644 index b8b7436..0000000 --- a/lang/lua/lib/process.lua +++ /dev/null @@ -1,78 +0,0 @@ -local lgi = require("lgi") -local Astal = lgi.require("AstalIO", "0.1") - -local M = {} - ----@param commandline string | string[] ----@param on_stdout? fun(out: string): nil ----@param on_stderr? fun(err: string): nil ----@return { kill: function } | nil proc -function M.subprocess(commandline, on_stdout, on_stderr) - if on_stdout == nil then - on_stdout = function(out) - io.stdout:write(tostring(out) .. "\n") - end - end - - if on_stderr == nil then - on_stderr = function(err) - io.stderr:write(tostring(err) .. "\n") - end - end - - local proc, err - if type(commandline) == "table" then - proc, err = Astal.Process.subprocessv(commandline) - else - proc, err = Astal.Process.subprocess(commandline) - end - if err ~= nil then - err(err) - return nil - end - proc.on_stdout = function(_, stdoud) - on_stdout(stdoud) - end - proc.on_stderr = function(_, stderr) - on_stderr(stderr) - end - return proc -end - ----@param commandline string | string[] ----@return string, string -function M.exec(commandline) - if type(commandline) == "table" then - return Astal.Process.execv(commandline) - else - return Astal.Process.exec(commandline) - end -end - ----@param commandline string | string[] ----@param callback? fun(out: string, err: string): nil -function M.exec_async(commandline, callback) - if callback == nil then - callback = function(out, err) - if err ~= nil then - io.stdout:write(tostring(out) .. "\n") - else - io.stderr:write(tostring(err) .. "\n") - end - end - end - - if type(commandline) == "table" then - Astal.Process.exec_asyncv(commandline, function(_, res) - local out, err = Astal.Process.exec_asyncv_finish(res) - callback(out, err) - end) - else - Astal.Process.exec_async(commandline, function(_, res) - local out, err = Astal.Process.exec_finish(res) - callback(out, err) - end) - end -end - -return M diff --git a/lang/lua/lib/time.lua b/lang/lua/lib/time.lua deleted file mode 100644 index 7719da9..0000000 --- a/lang/lua/lib/time.lua +++ /dev/null @@ -1,27 +0,0 @@ -local lgi = require("lgi") -local Astal = lgi.require("AstalIO", "0.1") -local GObject = lgi.require("GObject", "2.0") - -local M = {} - ----@param interval number ----@param fn function ----@return { cancel: function, on_now: function } -function M.interval(interval, fn) - return Astal.Time.interval(interval, GObject.Closure(fn)) -end - ----@param timeout number ----@param fn function ----@return { cancel: function, on_now: function } -function M.timeout(timeout, fn) - return Astal.Time.timeout(timeout, GObject.Closure(fn)) -end - ----@param fn function ----@return { cancel: function, on_now: function } -function M.idle(fn) - return Astal.Time.idle(GObject.Closure(fn)) -end - -return M diff --git a/lang/lua/lib/variable.lua b/lang/lua/lib/variable.lua deleted file mode 100644 index c93d04d..0000000 --- a/lang/lua/lib/variable.lua +++ /dev/null @@ -1,276 +0,0 @@ -local lgi = require("lgi") -local Astal = lgi.require("AstalIO", "0.1") -local GObject = lgi.require("GObject", "2.0") -local Binding = require("astal.lib.binding") -local Time = require("astal.lib.time") -local Process = require("astal.lib.process") - ----@class Variable ----@field private variable table ----@field private err_handler? function ----@field private _value any ----@field private _poll? table ----@field private _watch? table ----@field private poll_interval number ----@field private poll_exec? string[] | string ----@field private poll_transform? fun(next: any, prev: any): any ----@field private poll_fn? function ----@field private watch_transform? fun(next: any, prev: any): any ----@field private watch_exec? string[] | string -local Variable = {} -Variable.__index = Variable - ----@param value any ----@return Variable -function Variable.new(value) - local v = Astal.VariableBase() - local variable = setmetatable({ - variable = v, - _value = value, - }, Variable) - v.on_dropped = function() - variable:stop_watch() - variable:stop_watch() - end - v.on_error = function(_, err) - if variable.err_handler then - variable.err_handler(err) - end - end - return variable -end - ----@param transform function ----@return Binding -function Variable:__call(transform) - if transform == nil then - transform = function(v) - return v - end - return Binding.new(self) - end - return Binding.new(self):as(transform) -end - -function Variable:__tostring() - return "Variable<" .. tostring(self:get()) .. ">" -end - -function Variable:get() - return self._value or nil -end - -function Variable:set(value) - if value ~= self:get() then - self._value = value - self.variable:emit_changed() - end -end - -function Variable:start_poll() - if self._poll ~= nil then - return - end - - if self.poll_fn then - self._poll = Time.interval(self.poll_interval, function() - self:set(self.poll_fn(self:get())) - end) - elseif self.poll_exec then - self._poll = Time.interval(self.poll_interval, function() - Process.exec_async(self.poll_exec, function(out, err) - if err ~= nil then - return self.variable.emit_error(err) - end - self:set(self.poll_transform(out, self:get())) - end) - end) - end -end - -function Variable:start_watch() - if self._watch then - return - end - - self._watch = Process.subprocess(self.watch_exec, function(out) - self:set(self.watch_transform(out, self:get())) - end, function(err) - self.variable.emit_error(err) - end) -end - -function Variable:stop_poll() - if self._poll then - self._poll.cancel() - end - self._poll = nil -end - -function Variable:stop_watch() - if self._watch then - self._watch.kill() - end - self._watch = nil -end - -function Variable:is_polling() - return self._poll ~= nil -end - -function Variable:is_watching() - return self._watch ~= nil -end - -function Variable:drop() - self.variable.emit_dropped() -end - ----@param callback function ----@return Variable -function Variable:on_dropped(callback) - self.variable.on_dropped = callback - return self -end - ----@param callback function ----@return Variable -function Variable:on_error(callback) - self.err_handler = nil - self.variable.on_eror = function(_, err) - callback(err) - end - return self -end - ----@param callback fun(value: any) ----@return function -function Variable:subscribe(callback) - local id = self.variable.on_changed:connect(function() - callback(self:get()) - end) - return function() - GObject.signal_handler_disconnect(self.variable, id) - end -end - ----@param interval number ----@param exec string | string[] | function ----@param transform? fun(next: any, prev: any): any -function Variable:poll(interval, exec, transform) - if transform == nil then - transform = function(next) - return next - end - end - self:stop_poll() - self.poll_interval = interval - self.poll_transform = transform - - if type(exec) == "function" then - self.poll_fn = exec - self.poll_exec = nil - else - self.poll_exec = exec - self.poll_fn = nil - end - self:start_poll() - return self -end - ----@param exec string | string[] ----@param transform? fun(next: any, prev: any): any -function Variable:watch(exec, transform) - if transform == nil then - transform = function(next) - return next - end - end - self:stop_poll() - self.watch_exec = exec - self.watch_transform = transform - self:start_watch() - return self -end - ----@param object table | table[] ----@param sigOrFn string | fun(...): any ----@param callback fun(...): any ----@return Variable -function Variable:observe(object, sigOrFn, callback) - local f - if type(sigOrFn) == "function" then - f = sigOrFn - elseif type(callback) == "function" then - f = callback - else - f = function() - return self:get() - end - end - local set = function(...) - self:set(f(...)) - end - - if type(sigOrFn) == "string" then - object["on_" .. sigOrFn]:connect(set) - else - for _, obj in ipairs(object) do - obj[1]["on_" .. obj[2]]:connect(set) - end - end - return self -end - ----@param deps Variable | (Binding | Variable)[] ----@param transform? fun(...): any ----@return Variable -function Variable.derive(deps, transform) - if type(transform) == "nil" then - transform = function(...) - return { ... } - end - end - - if getmetatable(deps) == Variable then - local var = Variable.new(transform(deps:get())) - deps:subscribe(function(v) - var:set(transform(v)) - end) - return var - end - - for i, var in ipairs(deps) do - if getmetatable(var) == Variable then - deps[i] = Binding.new(var) - end - end - - local update = function() - local params = {} - for i, binding in ipairs(deps) do - params[i] = binding:get() - end - return transform(table.unpack(params), 1, #deps) - end - - local var = Variable.new(update()) - - local unsubs = {} - for i, b in ipairs(deps) do - unsubs[i] = b:subscribe(update) - end - - var.variable.on_dropped = function() - for _, unsub in ipairs(unsubs) do - unsub() - end - end - return var -end - -return setmetatable(Variable, { - __call = function(_, v) - return Variable.new(v) - end, -}) -- cgit v1.2.3 From 4dd0b1840d343dc65f1c781c3d6b8731b6e79eda Mon Sep 17 00:00:00 2001 From: Kevin Date: Sun, 13 Oct 2024 12:57:37 -0300 Subject: core: lua compat 5.1/5.4/luajit --- lang/lua/astal/gtk3/app.lua | 16 +++++++++++++++- lang/lua/astal/gtk3/astalify.lua | 3 +-- lang/lua/astal/init.lua | 7 ++++++- lang/lua/astal/variable.lua | 3 ++- 4 files changed, 24 insertions(+), 5 deletions(-) (limited to 'lang/lua') diff --git a/lang/lua/astal/gtk3/app.lua b/lang/lua/astal/gtk3/app.lua index 7895f69..13347c3 100644 --- a/lang/lua/astal/gtk3/app.lua +++ b/lang/lua/astal/gtk3/app.lua @@ -5,6 +5,15 @@ local AstalIO = lgi.require("AstalIO", "0.1") local AstalLua = Astal.Application:derive("AstalLua") local request_handler +local function unpack(t, i) + i = i or 1 + if t[i] == nil then + return nil + else + return t[i], unpack(t, i + 1) + end +end + function AstalLua:do_request(msg, conn) if type(request_handler) == "function" then request_handler(msg, function(response) @@ -76,7 +85,7 @@ function Astal.Application:start(config) app.on_activate = function() if type(config.main) == "function" then - config.main(table.unpack(arg)) + config.main(unpack(arg)) end if config.hold then self:hold() @@ -86,8 +95,13 @@ function Astal.Application:start(config) local _, err = app:acquire_socket() if err ~= nil then return config.client(function(msg) +<<<<<<< HEAD:lang/lua/astal/gtk3/app.lua return AstalIO.send_message(self.instance_name, msg) end, table.unpack(arg)) +======= + return Astal.Application.send_message(self.instance_name, msg) + end, unpack(arg)) +>>>>>>> 18df91b (core: lua compat 5.1/5.4/luajit):core/lua/astal/application.lua end self:run(nil) diff --git a/lang/lua/astal/gtk3/astalify.lua b/lang/lua/astal/gtk3/astalify.lua index 065de40..c344c07 100644 --- a/lang/lua/astal/gtk3/astalify.lua +++ b/lang/lua/astal/gtk3/astalify.lua @@ -28,8 +28,7 @@ local function map(tbl, fn) return copy end -local flatten -flatten = function(tbl) +local function flatten(tbl) local copy = {} for _, value in pairs(tbl) do if type(value) == "table" and getmetatable(value) == nil then diff --git a/lang/lua/astal/init.lua b/lang/lua/astal/init.lua index f442db0..783c78a 100644 --- a/lang/lua/astal/init.lua +++ b/lang/lua/astal/init.lua @@ -1,7 +1,12 @@ +if not table.unpack then + table.unpack = unpack +end + + local lgi = require("lgi") local Binding = require("astal.binding") local File = require("astal.file") -local Process = require("astal.proc") +local Process = require("astal.process") local Time = require("astal.time") local Variable = require("astal.variable") diff --git a/lang/lua/astal/variable.lua b/lang/lua/astal/variable.lua index 5a5e169..c2ed337 100644 --- a/lang/lua/astal/variable.lua +++ b/lang/lua/astal/variable.lua @@ -5,6 +5,7 @@ local Binding = require("astal.binding") local Time = require("astal.time") local Process = require("astal.process") + ---@class Variable ---@field private variable table ---@field private err_handler? function @@ -273,4 +274,4 @@ return setmetatable(Variable, { __call = function(_, v) return Variable.new(v) end, -}) +}) \ No newline at end of file -- cgit v1.2.3 From 9e255738f835c0e47cc6ae4d0cfbb96a261b4a2f Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 14 Oct 2024 10:27:57 -0300 Subject: core: table.unpack compat in lua, fix typo in astal/init.lua --- lang/lua/astal/gtk3/app.lua | 16 +--------------- lang/lua/astal/init.lua | 1 - lang/lua/astal/variable.lua | 1 - 3 files changed, 1 insertion(+), 17 deletions(-) (limited to 'lang/lua') diff --git a/lang/lua/astal/gtk3/app.lua b/lang/lua/astal/gtk3/app.lua index 13347c3..7895f69 100644 --- a/lang/lua/astal/gtk3/app.lua +++ b/lang/lua/astal/gtk3/app.lua @@ -5,15 +5,6 @@ local AstalIO = lgi.require("AstalIO", "0.1") local AstalLua = Astal.Application:derive("AstalLua") local request_handler -local function unpack(t, i) - i = i or 1 - if t[i] == nil then - return nil - else - return t[i], unpack(t, i + 1) - end -end - function AstalLua:do_request(msg, conn) if type(request_handler) == "function" then request_handler(msg, function(response) @@ -85,7 +76,7 @@ function Astal.Application:start(config) app.on_activate = function() if type(config.main) == "function" then - config.main(unpack(arg)) + config.main(table.unpack(arg)) end if config.hold then self:hold() @@ -95,13 +86,8 @@ function Astal.Application:start(config) local _, err = app:acquire_socket() if err ~= nil then return config.client(function(msg) -<<<<<<< HEAD:lang/lua/astal/gtk3/app.lua return AstalIO.send_message(self.instance_name, msg) end, table.unpack(arg)) -======= - return Astal.Application.send_message(self.instance_name, msg) - end, unpack(arg)) ->>>>>>> 18df91b (core: lua compat 5.1/5.4/luajit):core/lua/astal/application.lua end self:run(nil) diff --git a/lang/lua/astal/init.lua b/lang/lua/astal/init.lua index 783c78a..5630ba4 100644 --- a/lang/lua/astal/init.lua +++ b/lang/lua/astal/init.lua @@ -2,7 +2,6 @@ if not table.unpack then table.unpack = unpack end - local lgi = require("lgi") local Binding = require("astal.binding") local File = require("astal.file") diff --git a/lang/lua/astal/variable.lua b/lang/lua/astal/variable.lua index c2ed337..f9be161 100644 --- a/lang/lua/astal/variable.lua +++ b/lang/lua/astal/variable.lua @@ -5,7 +5,6 @@ local Binding = require("astal.binding") local Time = require("astal.time") local Process = require("astal.process") - ---@class Variable ---@field private variable table ---@field private err_handler? function -- cgit v1.2.3