Compare commits

...

5 Commits

Author SHA1 Message Date
Daniel Barlow 60d4466857 terminate repl & cleanup when eof 2022-07-02 23:17:00 +01:00
Daniel Barlow 6ed0e63a8f we don't need a Makefile any more 2022-07-01 22:39:59 +01:00
Daniel Barlow 798dd9ed5c fennel repl on a unix socket 2022-07-01 22:28:41 +01:00
Daniel Barlow 03b32ffa7e make event_loop_add_fd available to Lua on kiwmi singleton
CAUTION - the implementation is unfinished, as it doesn't
clean up when the fd is closed
2022-07-01 22:17:41 +01:00
Daniel Barlow 49229c75f3 add per-view transformation matrices
Note that this breaks output layout. Would prefer to deal with that
stuff ("monitor 1 is left of monitor 2") in Lua for more flexibility
2022-05-21 20:30:54 +01:00
11 changed files with 252 additions and 41 deletions

View File

@ -1,6 +0,0 @@
default:
install: default
run:
lua -e 'os.execute("kiwmi -c init.lua")'

View File

@ -18,35 +18,42 @@ As of 2022 these principles are more aspirational than actual.
## Running it
$ nix-shell build.nix
nix-shell$ make run
$ nix-shell --run "kiwmi -c init.lua"
`make run` executes `lua -e 'os.execute("kiwmi -c init.lua")'`. This
is suboptimally hairy, at least for the moment: Nix makes a wrapper
script for the Lua executable that has appropriate `LUA_PATH` and
`LUA_CPATH` settings, but it doesn't do the same for kiwmi.
`shell.nix` sets `LUA_PATH` and `LUA_CPATH` settings appropriately -
if you want to write a real derivation (I'll get to it eventually)
you'll need to sort that out yourself. Nix generates a wrapper script
for the Lua interpreter itself, but it doesn't do the same for kiwmi.
## Connecting to the repl
If you are using the example rc.fnl, it opens a Unix socket that you
can connect to and interact with a Fennel REPL. I use
[socat](http://www.dest-unreach.org/socat/) for this purpose:
$ socat - unix-connect:${XDG_RUNTIME_DIR}/kiwmi-repl.wayland-1.socket
# TODO
## Packages
[X] notifications (crier)
[ ] web browser (just)
[ ] keyboard
[ ] wifi network chooser
[ ] settings: toggle network interfaces, change volume & screen brightness
- [X] notifications (crier)
- [X] web browser (just)
- [ ] keyboard
- [ ] wifi network chooser
- [ ] settings: toggle network interfaces, change volume & screen brightness
## Other
[ ] better window management
- gestures to switch view
- gesture to launch terminal?
- some way to kill an app
- kiwmi may or may not have touch support
- [ ] better window management
- gestures to switch view
- gesture to launch terminal?
- some way to kill an app
- kiwmi may or may not have touch support
[ ] some way to add launcher shortcuts for Fennel functions
[ ] hook up system to session bus, to handle incoming calls
[ ] kiwmi: support reloading config or otherwise making live changes
[ ] why are overlay windows overlapping regular view?
[ ] screen lock
- [ ] some way to add launcher shortcuts for Fennel functions
- [ ] hook up system to session bus, to handle incoming calls
- [X] kiwmi: support reloading config or otherwise making live changes
- [ ] why are overlay windows overlapping regular view?
- [ ] screen lock

View File

@ -54,6 +54,7 @@ struct kiwmi_view {
int x;
int y;
float matrix[9];
bool mapped;
bool hidden;

View File

@ -101,29 +101,36 @@ render_surface(struct wlr_surface *surface, int sx, int sy, void *data)
struct kiwmi_render_data *rdata = data;
struct kiwmi_view *view = rdata->data;
struct wlr_output *wlr_output = rdata->output;
float mat[9] = { 1,0,0, 0,1,0, 0,0,1 };
struct wlr_texture *texture = wlr_surface_get_texture(surface);
if (!texture) {
return;
}
int ox = rdata->output_lx + sx + view->x - view->geom.x;
int oy = rdata->output_ly + sy + view->y - view->geom.y;
wlr_matrix_translate(mat,
sx + view->x - view->geom.x,
sy + view->y - view->geom.y);
wlr_matrix_scale(mat, surface->current.width, surface->current.height);
wlr_matrix_multiply(mat, view->matrix, mat);
struct wlr_box box = {
.x = ox * wlr_output->scale,
.y = oy * wlr_output->scale,
.width = surface->current.width * wlr_output->scale,
.height = surface->current.height * wlr_output->scale,
};
float matrix[9];
enum wl_output_transform transform =
wlr_output_transform_invert(surface->current.transform);
wlr_matrix_project_box(
matrix, &box, transform, 0, wlr_output->transform_matrix);
wlr_render_texture_with_matrix(rdata->renderer, texture, matrix, 1);
if (transform != WL_OUTPUT_TRANSFORM_NORMAL) {
wlr_matrix_translate(mat, 0.5, 0.5);
wlr_matrix_transform(mat, transform);
wlr_matrix_translate(mat, -0.5, -0.5);
}
/* looking at the definition of wlr_matrix_project_box,
* wlr_output->transform_matrix would be
* better named wlr_output->projection_matrix
*/
wlr_matrix_multiply(mat, wlr_output->transform_matrix, mat);
wlr_render_texture_with_matrix(rdata->renderer, texture, mat, 1);
wlr_surface_send_frame_done(surface, rdata->when);
}

View File

@ -293,6 +293,11 @@ view_create(
view->x = 0;
view->y = 0;
for(int r=0; r < 3; r++) {
for(int c=0; c < 3; c++) {
view->matrix[r*3+c] = (r == c) ? 1 : 0;
}
}
wl_list_init(&view->children);

View File

@ -10,6 +10,7 @@
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <lauxlib.h>
#include <wayland-server.h>
@ -208,6 +209,73 @@ l_kiwmi_server_quit(lua_State *L)
return 0;
}
static int
kiwmi_server_event_loop_fd_handler(int fd, uint32_t mask, void *data)
{
struct kiwmi_lua_callback *lc = data;
lua_State *L = lc->server->lua->L;
lua_rawgeti(L, LUA_REGISTRYINDEX, lc->callback_ref);
lua_pushinteger(L, fd);
lua_pushinteger(L, mask);
if (lua_pcall(L, 2, 1, 0)) {
wlr_log(WLR_ERROR, "%s", lua_tostring(L, -1));
}
/* if callback returns false/nil, remove the handler */
if (! lua_toboolean(L, -1)) {
wl_event_source_remove(lc->event_source);
luaL_unref(L, LUA_REGISTRYINDEX, lc->callback_ref);
free(lc);
}
lua_pop(L, -1);
return true;
}
static int
l_kiwmi_server_event_loop_add_fd(lua_State *L)
{
/* args : self, fd, mode, handler */
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_server");
struct kiwmi_server *server = obj->object;
int fd = lua_tonumber(L, 2);
int mode = lua_tonumber(L, 3);
luaL_checktype(L, 4, LUA_TFUNCTION); // callback
int event_types;
switch(mode) {
case O_WRONLY: event_types = WL_EVENT_WRITABLE; break;
case O_RDONLY: event_types = WL_EVENT_READABLE; break;
default: event_types = WL_EVENT_WRITABLE | WL_EVENT_READABLE; break;
}
struct kiwmi_lua_callback *lc = malloc(sizeof(*lc));
if (!lc) {
return luaL_error(L, "failed to allocate kiwmi_lua_callback");
}
wlr_log(WLR_INFO, "add fd %d lc=%x\n", fd, lc);
lc->server = server;
/* luaL_ref pops last parameter (callback) from stack */
lc->callback_ref = luaL_ref(L, LUA_REGISTRYINDEX);
lc->event_source =
wl_event_loop_add_fd(server->wl_event_loop,
fd,
event_types,
kiwmi_server_event_loop_fd_handler,
lc);
return 0;
}
static int
kiwmi_server_schedule_handler(void *data)
{
@ -395,6 +463,7 @@ static const luaL_Reg kiwmi_server_methods[] = {
{"verbosity", l_kiwmi_server_verbosity},
{"view_at", l_kiwmi_server_view_at},
{"outputs", l_kiwmi_server_next_output},
{"event_loop_add_fd", l_kiwmi_server_event_loop_add_fd},
{NULL, NULL},
};

View File

@ -247,6 +247,49 @@ l_kiwmi_view_move(lua_State *L)
return 0;
}
static int
l_kiwmi_view_matrix(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
lua_newtable(L);
for(int i=0; i< 9; i++) {
lua_pushnumber(L, view->matrix[i]);
lua_seti(L, -2, 1 + i);
}
return 1;
}
static int
l_kiwmi_view_set_matrix(lua_State *L)
{
struct kiwmi_object *obj =
*(struct kiwmi_object **)luaL_checkudata(L, 1, "kiwmi_view");
if (!obj->valid) {
return luaL_error(L, "kiwmi_view no longer valid");
}
struct kiwmi_view *view = obj->object;
luaL_checktype(L, 2, LUA_TTABLE);
for(int i=0; i< 9; i++) {
lua_geti(L, 2, 1 + i);
view->matrix[i] = lua_tonumber(L, -1);
}
return 0;
}
static int
l_kiwmi_view_pid(lua_State *L)
{
@ -434,6 +477,8 @@ static const luaL_Reg kiwmi_view_methods[] = {
{"imove", l_kiwmi_view_imove},
{"iresize", l_kiwmi_view_iresize},
{"move", l_kiwmi_view_move},
{"matrix", l_kiwmi_view_matrix},
{"set_matrix", l_kiwmi_view_set_matrix},
{"on", luaK_callback_register_dispatch},
{"pid", l_kiwmi_view_pid},
{"pos", l_kiwmi_view_pos},

View File

@ -33,6 +33,16 @@ Sets the background color (shown behind all views) to `color` (in the format #rr
Returns a reference to the cursor object.
#### kiwmi:event_loop_add_fd(fd, mode, callback)
By registering with the event loop, arrange for `callback` to be called
whenever there is activity on the file descriptor `fd`. The `mode` parameter
is one of `O_RDWR`, `O_RDONLY`, `O_WRONLY` as defined in luaposix
`posix.fcntl` module.
If `callback` returns false or nil, the handler will be unregistered
from the event loop and not called again.
#### kiwmi:focused_view()
Returns the currently focused view.

9
rc.fnl
View File

@ -1,7 +1,16 @@
(local { : GdkPixbuf } (require :lgi))
(local { : view } (require :fennel))
(local socket-repl (require :socket-repl))
(let [repl-socket-name
(..
(: (os.getenv "XDG_RUNTIME_DIR") :gsub "/$" "")
"/kiwmi-repl."
(os.getenv "WAYLAND_DISPLAY")
".socket"
)]
(socket-repl.start repl-socket-name))
(fn texture-from-file [renderer filename]

View File

@ -1,7 +1,7 @@
with import <nixpkgs> {} ;
let p = callPackage ./. {};
in (p.overrideAttrs (o:{
nativeBuildInputs = [pkgs.gdb];
nativeBuildInputs = with pkgs; [gdb socat];
shellHook = ''
export LUA_PATH=`lua -e 'print(package.path)'`
export LUA_CPATH=`lua -e 'print(package.cpath)'`

64
socket-repl.fnl Normal file
View File

@ -0,0 +1,64 @@
(local { : fdopen } (require "posix.stdio"))
(local fcntl (require "posix.fcntl"))
(local unistd (require "posix.unistd"))
(local socket (require "posix.sys.socket"))
(local { : repl } (require :fennel))
(fn watch-fd [fd mode handler]
(kiwmi:event_loop_add_fd fd mode handler))
(fn unix-socket-listener [pathname on-connected]
(let [sa {:family socket.AF_UNIX
:path pathname
}
fd (socket.socket socket.AF_UNIX socket.SOCK_STREAM 0)]
(socket.bind fd sa)
(socket.listen fd 5)
(watch-fd
fd fcntl.O_RDWR
#(let [connected-fd (socket.accept $1)]
(on-connected connected-fd)))
))
(fn start [pathname]
(unistd.unlink pathname)
(unix-socket-listener
pathname
(fn [fd]
(let [sock (fdopen fd "w+")
repl-coro (coroutine.create repl)]
(watch-fd fd fcntl.O_RDONLY
(fn [fd]
(let [buf (unistd.read fd 1024)
input
(if (and buf (> (# buf) 0))
buf
"\n,exit")]
(coroutine.resume repl-coro input))
(if (= (coroutine.status repl-coro) :dead)
(do
(sock:write "bye!\n")
(sock:close)
(unistd.close fd)
false)
true
)))
(coroutine.resume repl-coro
{:readChunk
(fn [{: stack-size}]
(sock:write
(if (> stack-size 0) ".." ">> "))
(sock:flush)
(coroutine.yield))
:onValues
(fn [vals]
(sock:write (table.concat vals "\t"))
(sock:write "\n"))
:onError
(fn [errtype err]
(sock:write
(.. errtype " error: " (tostring err) "\n")))
})))))
{ : start }