aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomasz Kramkowski <tk@the-tk.com>2021-02-11 12:15:12 +0000
committerTomasz Kramkowski <tk@the-tk.com>2021-02-11 14:25:14 +0000
commitbb97e8aaa15217afe1f3fcdc93662ab03b8ae9d9 (patch)
tree64fa5b5ba419e72bc6acaddadbc51c73646295fa
downloadluiml-bb97e8aaa15217afe1f3fcdc93662ab03b8ae9d9.tar.gz
luiml-bb97e8aaa15217afe1f3fcdc93662ab03b8ae9d9.tar.xz
luiml-bb97e8aaa15217afe1f3fcdc93662ab03b8ae9d9.zip
init commit
-rwxr-xr-x.compile.do13
-rw-r--r--.gitignore8
-rwxr-xr-x.link-library.do12
-rwxr-xr-x.parse-depfile15
-rw-r--r--README.md44
-rwxr-xr-xall.do2
-rwxr-xr-xclean4
-rwxr-xr-xconfigure94
-rwxr-xr-xdefault.o.do4
-rw-r--r--libluiml.c18
-rwxr-xr-xlibluiml.so.do4
-rw-r--r--sdl.c533
-rw-r--r--sdl.h14
-rw-r--r--window.c179
-rw-r--r--window.h6
-rw-r--r--window_wait_event.c102
-rw-r--r--xlua.c56
-rw-r--r--xlua.h31
18 files changed, 1139 insertions, 0 deletions
diff --git a/.compile.do b/.compile.do
new file mode 100755
index 0000000..310ddd5
--- /dev/null
+++ b/.compile.do
@@ -0,0 +1,13 @@
+#!/bin/bash -e
+redo-ifchange .config.rc
+. ./.config.rc
+exec >"$3"
+echo "# generated by $0"
+echo "CC=$CC"
+echo "CFLAGS=(${CFLAGS[@]@Q})"
+echo "CPPFLAGS=(${CPPFLAGS[@]@Q})"
+echo '"$CC" -MMD -MF "${1%.o}.d" "${CFLAGS[@]}" "${CPPFLAGS[@]}" -c -o "$3" "${1%.o}.c"'
+command -v redo-stamp >/dev/null 2>&1 && redo-stamp <"$3"
+if command -v redo-stamp >/dev/null 2>&1; then
+ redo-stamp <"$3"
+fi
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..19f2438
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+*.d
+*.o
+*.so
+.compile
+.config.rc
+.link-library
+.redo
+all
diff --git a/.link-library.do b/.link-library.do
new file mode 100755
index 0000000..4d7abdd
--- /dev/null
+++ b/.link-library.do
@@ -0,0 +1,12 @@
+#!/bin/bash -e
+redo-ifchange .config.rc
+. ./.config.rc
+exec >"$3"
+echo "# generated by $0"
+echo "CC=$CC"
+echo "LDFLAGS=(${LDFLAGS[@]@Q})"
+echo "LDLIBS=(${LDLIBS[@]@Q})"
+echo '"$CC" "${LDFLAGS[@]}" "${objects[@]}" "${LDLIBS[@]}" -o "$3"'
+if command -v redo-stamp >/dev/null 2>&1; then
+ redo-stamp <"$3"
+fi
diff --git a/.parse-depfile b/.parse-depfile
new file mode 100755
index 0000000..cad20d9
--- /dev/null
+++ b/.parse-depfile
@@ -0,0 +1,15 @@
+#!/usr/bin/sed -f
+# remove target
+1s/^[^:]*: //
+# collapse lines
+:loop
+/\\$/ {
+ N
+ s/ \\\n//
+ b loop
+}
+# split on unescaped spaces
+s/\([^\]\) /\1\
+/g
+# unescape spaces
+s/\\ / /g
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a86107a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,44 @@
+# LUIML - Lua User Interface Markup Language
+
+This project is an early work in progress.
+
+The goal is to design a Lua library which turns a Lua data structure and
+produces a GUI from it.
+
+This is similar to some ideas behind [IUP][iup] but LUIML aims to be more
+functional and data driven.
+
+## Dependencies
+
+### Build
+
+* redo (works with [apenwarr redo][ap-redo] and [JdeBP redo][jdebp-redo])
+* bash
+* pkg-config (or equivalent such as pkgconf)
+* C compiler with C11 support (works with gcc and clang)
+* sed
+* xargs
+
+### Runtime
+
+* LuaJIT
+* SDL2
+* PangoFT2
+
+## Building
+
+```shell-session
+$ ./configure
+$ redo all
+```
+
+To avoid the creation of the "all" file when using JdeBP redo during development
+run `redo libluiml.so`.
+
+## Installing
+
+This is not supported at the moment.
+
+[IUP]: http://webserver2.tecgraf.puc-rio.br/iup/
+[ap-redo]: https://redo.readthedocs.io/en/latest/
+[jdebp-redo]: https://jdebp.eu/Softwares/redo/
diff --git a/all.do b/all.do
new file mode 100755
index 0000000..7ef0076
--- /dev/null
+++ b/all.do
@@ -0,0 +1,2 @@
+#!/bin/sh
+redo-ifchange libluiml.so
diff --git a/clean b/clean
new file mode 100755
index 0000000..c46dcba
--- /dev/null
+++ b/clean
@@ -0,0 +1,4 @@
+#!/bin/sh
+find . \( -name '*.d' -o -name '*.o' -o -name '*.so' \) -delete
+rm -f .compile
+rm -f .link-library
diff --git a/configure b/configure
new file mode 100755
index 0000000..2fa53e6
--- /dev/null
+++ b/configure
@@ -0,0 +1,94 @@
+#!/bin/bash
+
+libs=(luajit sdl2 pangoft2)
+CFLAGS+=(-std=c11 -fPIC)
+LDFLAGS+=(-shared)
+
+usage () {
+ echo "Usage: $0 [-h|options...]"
+}
+help () {
+ echo "Options:"
+ echo " -B ldlib Append ldlib to LDLIBS"
+ echo " -C cflag Append cflag to CFLAGS"
+ echo " -L ldflag Append ldflag to LDFLAGS"
+ echo " -P cppflag Append cppflag to CPPFLAGS"
+ echo " -c when Enable compiler colours (always|auto|off) [default: auto]"
+ echo " -d Enable debugging flags"
+ echo " -e Enable -Werror"
+ echo " -h Show this help"
+ echo " -o Enable optimisation flags"
+ echo " -v Print results of configuration"
+ echo " -w Enable warning flags"
+ echo "Environment:"
+ echo " CC C compiler"
+ echo " PKG_CONFIG pkg-config"
+}
+
+exists() {
+ command -v "$1" >/dev/null 2>&1
+}
+
+check() {
+ what=$1
+ shift
+ for p do
+ if exists "$p"; then
+ echo "$p"
+ return
+ fi
+ done
+ echo "$what not set or found" >&2
+ return 1
+}
+
+CC=$(check '$CC, cc, gcc or clang' "$CC" cc gcc clang) || exit
+pc=$(check '$PKG_CONFIG, pkg-config or pkg-conf' "$PKG_CONFIG" pkg-config pkg-conf) || exit
+
+for l in "${libs[@]}"; do
+ "$pc" --print-errors --exists "$l" || exit
+done
+CFLAGS+=($("$pc" --cflags-only-other "${libs[@]}"))
+CPPFLAGS+=($("$pc" --cflags-only-I "${libs[@]}"))
+LDFLAGS+=($("$pc" --libs-only-L --libs-only-other "${libs[@]}"))
+LDLIBS+=($("$pc" --libs-only-l "${libs[@]}"))
+
+colour=auto
+while getopts B:C:L:P:c:dehovw opt; do
+ case $opt in
+ B) LDLIBS+=("$OPTARG");;
+ C) CFLAGS+=("$OPTARG");;
+ L) LDFLAGS+=("$OPTARG");;
+ P) CPPFLAGS+=("$OPTARG");;
+ c) colour="$OPTARG";;
+ d)
+ CFLAGS+=(-Og -g)
+ LDFLAGS+=(-Og -g)
+ ;;
+ e) CFLAGS+=(-Werror);;
+ h) usage; help; exit;;
+ o)
+ CFLAGS+=(-O2 -flto)
+ LDFLAGS+=(-O2 -flto)
+ ;;
+ v) verbose=1;;
+ w) CFLAGS+=(-Wall -Wextra);;
+ ?) usage >&2; exit 1;;
+ esac
+done
+if [[ $colour == auto ]] && exists tput && (( $(tput colors) >= 8 )); then
+ colour=always
+fi
+if [[ $colour == always ]]; then
+ CFLAGS+=(-fdiagnostics-color)
+fi
+
+{
+ echo "# generated using $0 $@"
+ echo "CC=$CC"
+ echo "CFLAGS=(${CFLAGS[@]@Q})"
+ echo "CPPFLAGS=(${CPPFLAGS[@]@Q})"
+ echo "LDFLAGS=(${LDFLAGS[@]@Q})"
+ echo "LDLIBS=(${LDLIBS[@]@Q})"
+} >.config.rc
+[[ $verbose ]] && cat .config.rc
diff --git a/default.o.do b/default.o.do
new file mode 100755
index 0000000..121c3c0
--- /dev/null
+++ b/default.o.do
@@ -0,0 +1,4 @@
+#!/bin/bash -e
+redo-ifchange .compile .parse-depfile "${1%.o}.c"
+. ./.compile
+./.parse-depfile "${1%.o}.d" | tr '\n' '\0' | xargs -0 redo-ifchange
diff --git a/libluiml.c b/libluiml.c
new file mode 100644
index 0000000..468cc60
--- /dev/null
+++ b/libluiml.c
@@ -0,0 +1,18 @@
+#include <lauxlib.h>
+#include <lua.h>
+
+#include "window.h"
+
+__attribute__((visibility ("default")))
+int luaopen_libluiml(lua_State *L)
+{
+ static const struct luaL_Reg libluiml[] = {
+ {"create_window", create_window},
+ {NULL, NULL},
+ };
+
+ lua_createtable(L, 0, 1);
+ luaL_register(L, NULL, libluiml);
+
+ return 1;
+}
diff --git a/libluiml.so.do b/libluiml.so.do
new file mode 100755
index 0000000..c5daa66
--- /dev/null
+++ b/libluiml.so.do
@@ -0,0 +1,4 @@
+#!/bin/bash -e
+objects=(libluiml.o window.o sdl.o xlua.o)
+redo-ifchange .link-library "${objects[@]}"
+. ./.link-library
diff --git a/sdl.c b/sdl.c
new file mode 100644
index 0000000..a2acb45
--- /dev/null
+++ b/sdl.c
@@ -0,0 +1,533 @@
+#include <SDL.h>
+#include <lauxlib.h>
+
+#include "sdl.h"
+
+static unsigned long refcnt;
+
+int sdl_error(lua_State *L, const char *what)
+{
+ return luaL_error(L, "%s failed: %s", what, SDL_GetError());
+}
+
+void acquire_sdl()
+{
+ if (refcnt++ != 0) return;
+
+ SDL_Init(SDL_INIT_VIDEO);
+ SDL_EnableScreenSaver();
+}
+
+void release_sdl()
+{
+ if (refcnt == 0 || --refcnt != 0) return;
+
+ SDL_Quit();
+}
+
+const char *strbutton(Uint8 button)
+{
+ switch (button) {
+ case SDL_BUTTON_LEFT: return "LEFT";
+ case SDL_BUTTON_MIDDLE: return "MIDDLE";
+ case SDL_BUTTON_RIGHT: return "RIGHT";
+ case SDL_BUTTON_X1: return "X1";
+ case SDL_BUTTON_X2: return "X2";
+ default: return "UNK";
+ }
+}
+
+const char *strscancode(SDL_Scancode s)
+{
+ switch (s) {
+ case SDL_SCANCODE_A: return "A";
+ case SDL_SCANCODE_B: return "B";
+ case SDL_SCANCODE_C: return "C";
+ case SDL_SCANCODE_D: return "D";
+ case SDL_SCANCODE_E: return "E";
+ case SDL_SCANCODE_F: return "F";
+ case SDL_SCANCODE_G: return "G";
+ case SDL_SCANCODE_H: return "H";
+ case SDL_SCANCODE_I: return "I";
+ case SDL_SCANCODE_J: return "J";
+ case SDL_SCANCODE_K: return "K";
+ case SDL_SCANCODE_L: return "L";
+ case SDL_SCANCODE_M: return "M";
+ case SDL_SCANCODE_N: return "N";
+ case SDL_SCANCODE_O: return "O";
+ case SDL_SCANCODE_P: return "P";
+ case SDL_SCANCODE_Q: return "Q";
+ case SDL_SCANCODE_R: return "R";
+ case SDL_SCANCODE_S: return "S";
+ case SDL_SCANCODE_T: return "T";
+ case SDL_SCANCODE_U: return "U";
+ case SDL_SCANCODE_V: return "V";
+ case SDL_SCANCODE_W: return "W";
+ case SDL_SCANCODE_X: return "X";
+ case SDL_SCANCODE_Y: return "Y";
+ case SDL_SCANCODE_Z: return "Z";
+ case SDL_SCANCODE_1: return "1";
+ case SDL_SCANCODE_2: return "2";
+ case SDL_SCANCODE_3: return "3";
+ case SDL_SCANCODE_4: return "4";
+ case SDL_SCANCODE_5: return "5";
+ case SDL_SCANCODE_6: return "6";
+ case SDL_SCANCODE_7: return "7";
+ case SDL_SCANCODE_8: return "8";
+ case SDL_SCANCODE_9: return "9";
+ case SDL_SCANCODE_0: return "0";
+ case SDL_SCANCODE_RETURN: return "RETURN";
+ case SDL_SCANCODE_ESCAPE: return "ESCAPE";
+ case SDL_SCANCODE_BACKSPACE: return "BACKSPACE";
+ case SDL_SCANCODE_TAB: return "TAB";
+ case SDL_SCANCODE_SPACE: return "SPACE";
+ case SDL_SCANCODE_MINUS: return "MINUS";
+ case SDL_SCANCODE_EQUALS: return "EQUALS";
+ case SDL_SCANCODE_LEFTBRACKET: return "LEFTBRACKET";
+ case SDL_SCANCODE_RIGHTBRACKET: return "RIGHTBRACKET";
+ case SDL_SCANCODE_BACKSLASH: return "BACKSLASH";
+ case SDL_SCANCODE_NONUSHASH: return "NONUSHASH";
+ case SDL_SCANCODE_SEMICOLON: return "SEMICOLON";
+ case SDL_SCANCODE_APOSTROPHE: return "APOSTROPHE";
+ case SDL_SCANCODE_GRAVE: return "GRAVE";
+ case SDL_SCANCODE_COMMA: return "COMMA";
+ case SDL_SCANCODE_PERIOD: return "PERIOD";
+ case SDL_SCANCODE_SLASH: return "SLASH";
+ case SDL_SCANCODE_CAPSLOCK: return "CAPSLOCK";
+ case SDL_SCANCODE_F1: return "F1";
+ case SDL_SCANCODE_F2: return "F2";
+ case SDL_SCANCODE_F3: return "F3";
+ case SDL_SCANCODE_F4: return "F4";
+ case SDL_SCANCODE_F5: return "F5";
+ case SDL_SCANCODE_F6: return "F6";
+ case SDL_SCANCODE_F7: return "F7";
+ case SDL_SCANCODE_F8: return "F8";
+ case SDL_SCANCODE_F9: return "F9";
+ case SDL_SCANCODE_F10: return "F10";
+ case SDL_SCANCODE_F11: return "F11";
+ case SDL_SCANCODE_F12: return "F12";
+ case SDL_SCANCODE_PRINTSCREEN: return "PRINTSCREEN";
+ case SDL_SCANCODE_SCROLLLOCK: return "SCROLLLOCK";
+ case SDL_SCANCODE_PAUSE: return "PAUSE";
+ case SDL_SCANCODE_INSERT: return "INSERT";
+ case SDL_SCANCODE_HOME: return "HOME";
+ case SDL_SCANCODE_PAGEUP: return "PAGEUP";
+ case SDL_SCANCODE_DELETE: return "DELETE";
+ case SDL_SCANCODE_END: return "END";
+ case SDL_SCANCODE_PAGEDOWN: return "PAGEDOWN";
+ case SDL_SCANCODE_RIGHT: return "RIGHT";
+ case SDL_SCANCODE_LEFT: return "LEFT";
+ case SDL_SCANCODE_DOWN: return "DOWN";
+ case SDL_SCANCODE_UP: return "UP";
+ case SDL_SCANCODE_NUMLOCKCLEAR: return "NUMLOCKCLEAR";
+ case SDL_SCANCODE_KP_DIVIDE: return "KP_DIVIDE";
+ case SDL_SCANCODE_KP_MULTIPLY: return "KP_MULTIPLY";
+ case SDL_SCANCODE_KP_MINUS: return "KP_MINUS";
+ case SDL_SCANCODE_KP_PLUS: return "KP_PLUS";
+ case SDL_SCANCODE_KP_ENTER: return "KP_ENTER";
+ case SDL_SCANCODE_KP_1: return "KP_1";
+ case SDL_SCANCODE_KP_2: return "KP_2";
+ case SDL_SCANCODE_KP_3: return "KP_3";
+ case SDL_SCANCODE_KP_4: return "KP_4";
+ case SDL_SCANCODE_KP_5: return "KP_5";
+ case SDL_SCANCODE_KP_6: return "KP_6";
+ case SDL_SCANCODE_KP_7: return "KP_7";
+ case SDL_SCANCODE_KP_8: return "KP_8";
+ case SDL_SCANCODE_KP_9: return "KP_9";
+ case SDL_SCANCODE_KP_0: return "KP_0";
+ case SDL_SCANCODE_KP_PERIOD: return "KP_PERIOD";
+ case SDL_SCANCODE_NONUSBACKSLASH: return "NONUSBACKSLASH";
+ case SDL_SCANCODE_APPLICATION: return "APPLICATION";
+ case SDL_SCANCODE_POWER: return "POWER";
+ case SDL_SCANCODE_KP_EQUALS: return "KP_EQUALS";
+ case SDL_SCANCODE_F13: return "F13";
+ case SDL_SCANCODE_F14: return "F14";
+ case SDL_SCANCODE_F15: return "F15";
+ case SDL_SCANCODE_F16: return "F16";
+ case SDL_SCANCODE_F17: return "F17";
+ case SDL_SCANCODE_F18: return "F18";
+ case SDL_SCANCODE_F19: return "F19";
+ case SDL_SCANCODE_F20: return "F20";
+ case SDL_SCANCODE_F21: return "F21";
+ case SDL_SCANCODE_F22: return "F22";
+ case SDL_SCANCODE_F23: return "F23";
+ case SDL_SCANCODE_F24: return "F24";
+ case SDL_SCANCODE_EXECUTE: return "EXECUTE";
+ case SDL_SCANCODE_HELP: return "HELP";
+ case SDL_SCANCODE_MENU: return "MENU";
+ case SDL_SCANCODE_SELECT: return "SELECT";
+ case SDL_SCANCODE_STOP: return "STOP";
+ case SDL_SCANCODE_AGAIN: return "AGAIN";
+ case SDL_SCANCODE_UNDO: return "UNDO";
+ case SDL_SCANCODE_CUT: return "CUT";
+ case SDL_SCANCODE_COPY: return "COPY";
+ case SDL_SCANCODE_PASTE: return "PASTE";
+ case SDL_SCANCODE_FIND: return "FIND";
+ case SDL_SCANCODE_MUTE: return "MUTE";
+ case SDL_SCANCODE_VOLUMEUP: return "VOLUMEUP";
+ case SDL_SCANCODE_VOLUMEDOWN: return "VOLUMEDOWN";
+ case SDL_SCANCODE_KP_COMMA: return "KP_COMMA";
+ case SDL_SCANCODE_KP_EQUALSAS400: return "KP_EQUALSAS400";
+ case SDL_SCANCODE_INTERNATIONAL1: return "INTERNATIONAL1";
+ case SDL_SCANCODE_INTERNATIONAL2: return "INTERNATIONAL2";
+ case SDL_SCANCODE_INTERNATIONAL3: return "INTERNATIONAL3";
+ case SDL_SCANCODE_INTERNATIONAL4: return "INTERNATIONAL4";
+ case SDL_SCANCODE_INTERNATIONAL5: return "INTERNATIONAL5";
+ case SDL_SCANCODE_INTERNATIONAL6: return "INTERNATIONAL6";
+ case SDL_SCANCODE_INTERNATIONAL7: return "INTERNATIONAL7";
+ case SDL_SCANCODE_INTERNATIONAL8: return "INTERNATIONAL8";
+ case SDL_SCANCODE_INTERNATIONAL9: return "INTERNATIONAL9";
+ case SDL_SCANCODE_LANG1: return "LANG1";
+ case SDL_SCANCODE_LANG2: return "LANG2";
+ case SDL_SCANCODE_LANG3: return "LANG3";
+ case SDL_SCANCODE_LANG4: return "LANG4";
+ case SDL_SCANCODE_LANG5: return "LANG5";
+ case SDL_SCANCODE_LANG6: return "LANG6";
+ case SDL_SCANCODE_LANG7: return "LANG7";
+ case SDL_SCANCODE_LANG8: return "LANG8";
+ case SDL_SCANCODE_LANG9: return "LANG9";
+ case SDL_SCANCODE_ALTERASE: return "ALTERASE";
+ case SDL_SCANCODE_SYSREQ: return "SYSREQ";
+ case SDL_SCANCODE_CANCEL: return "CANCEL";
+ case SDL_SCANCODE_CLEAR: return "CLEAR";
+ case SDL_SCANCODE_PRIOR: return "PRIOR";
+ case SDL_SCANCODE_RETURN2: return "RETURN2";
+ case SDL_SCANCODE_SEPARATOR: return "SEPARATOR";
+ case SDL_SCANCODE_OUT: return "OUT";
+ case SDL_SCANCODE_OPER: return "OPER";
+ case SDL_SCANCODE_CLEARAGAIN: return "CLEARAGAIN";
+ case SDL_SCANCODE_CRSEL: return "CRSEL";
+ case SDL_SCANCODE_EXSEL: return "EXSEL";
+ case SDL_SCANCODE_KP_00: return "KP_00";
+ case SDL_SCANCODE_KP_000: return "KP_000";
+ case SDL_SCANCODE_THOUSANDSSEPARATOR: return "THOUSANDSSEPARATOR";
+ case SDL_SCANCODE_DECIMALSEPARATOR: return "DECIMALSEPARATOR";
+ case SDL_SCANCODE_CURRENCYUNIT: return "CURRENCYUNIT";
+ case SDL_SCANCODE_CURRENCYSUBUNIT: return "CURRENCYSUBUNIT";
+ case SDL_SCANCODE_KP_LEFTPAREN: return "KP_LEFTPAREN";
+ case SDL_SCANCODE_KP_RIGHTPAREN: return "KP_RIGHTPAREN";
+ case SDL_SCANCODE_KP_LEFTBRACE: return "KP_LEFTBRACE";
+ case SDL_SCANCODE_KP_RIGHTBRACE: return "KP_RIGHTBRACE";
+ case SDL_SCANCODE_KP_TAB: return "KP_TAB";
+ case SDL_SCANCODE_KP_BACKSPACE: return "KP_BACKSPACE";
+ case SDL_SCANCODE_KP_A: return "KP_A";
+ case SDL_SCANCODE_KP_B: return "KP_B";
+ case SDL_SCANCODE_KP_C: return "KP_C";
+ case SDL_SCANCODE_KP_D: return "KP_D";
+ case SDL_SCANCODE_KP_E: return "KP_E";
+ case SDL_SCANCODE_KP_F: return "KP_F";
+ case SDL_SCANCODE_KP_XOR: return "KP_XOR";
+ case SDL_SCANCODE_KP_POWER: return "KP_POWER";
+ case SDL_SCANCODE_KP_PERCENT: return "KP_PERCENT";
+ case SDL_SCANCODE_KP_LESS: return "KP_LESS";
+ case SDL_SCANCODE_KP_GREATER: return "KP_GREATER";
+ case SDL_SCANCODE_KP_AMPERSAND: return "KP_AMPERSAND";
+ case SDL_SCANCODE_KP_DBLAMPERSAND: return "KP_DBLAMPERSAND";
+ case SDL_SCANCODE_KP_VERTICALBAR: return "KP_VERTICALBAR";
+ case SDL_SCANCODE_KP_DBLVERTICALBAR: return "KP_DBLVERTICALBAR";
+ case SDL_SCANCODE_KP_COLON: return "KP_COLON";
+ case SDL_SCANCODE_KP_HASH: return "KP_HASH";
+ case SDL_SCANCODE_KP_SPACE: return "KP_SPACE";
+ case SDL_SCANCODE_KP_AT: return "KP_AT";
+ case SDL_SCANCODE_KP_EXCLAM: return "KP_EXCLAM";
+ case SDL_SCANCODE_KP_MEMSTORE: return "KP_MEMSTORE";
+ case SDL_SCANCODE_KP_MEMRECALL: return "KP_MEMRECALL";
+ case SDL_SCANCODE_KP_MEMCLEAR: return "KP_MEMCLEAR";
+ case SDL_SCANCODE_KP_MEMADD: return "KP_MEMADD";
+ case SDL_SCANCODE_KP_MEMSUBTRACT: return "KP_MEMSUBTRACT";
+ case SDL_SCANCODE_KP_MEMMULTIPLY: return "KP_MEMMULTIPLY";
+ case SDL_SCANCODE_KP_MEMDIVIDE: return "KP_MEMDIVIDE";
+ case SDL_SCANCODE_KP_PLUSMINUS: return "KP_PLUSMINUS";
+ case SDL_SCANCODE_KP_CLEAR: return "KP_CLEAR";
+ case SDL_SCANCODE_KP_CLEARENTRY: return "KP_CLEARENTRY";
+ case SDL_SCANCODE_KP_BINARY: return "KP_BINARY";
+ case SDL_SCANCODE_KP_OCTAL: return "KP_OCTAL";
+ case SDL_SCANCODE_KP_DECIMAL: return "KP_DECIMAL";
+ case SDL_SCANCODE_KP_HEXADECIMAL: return "KP_HEXADECIMAL";
+ case SDL_SCANCODE_LCTRL: return "LCTRL";
+ case SDL_SCANCODE_LSHIFT: return "LSHIFT";
+ case SDL_SCANCODE_LALT: return "LALT";
+ case SDL_SCANCODE_LGUI: return "LGUI";
+ case SDL_SCANCODE_RCTRL: return "RCTRL";
+ case SDL_SCANCODE_RSHIFT: return "RSHIFT";
+ case SDL_SCANCODE_RALT: return "RALT";
+ case SDL_SCANCODE_RGUI: return "RGUI";
+ case SDL_SCANCODE_MODE: return "MODE";
+ case SDL_SCANCODE_AUDIONEXT: return "AUDIONEXT";
+ case SDL_SCANCODE_AUDIOPREV: return "AUDIOPREV";
+ case SDL_SCANCODE_AUDIOSTOP: return "AUDIOSTOP";
+ case SDL_SCANCODE_AUDIOPLAY: return "AUDIOPLAY";
+ case SDL_SCANCODE_AUDIOMUTE: return "AUDIOMUTE";
+ case SDL_SCANCODE_MEDIASELECT: return "MEDIASELECT";
+ case SDL_SCANCODE_WWW: return "WWW";
+ case SDL_SCANCODE_MAIL: return "MAIL";
+ case SDL_SCANCODE_CALCULATOR: return "CALCULATOR";
+ case SDL_SCANCODE_COMPUTER: return "COMPUTER";
+ case SDL_SCANCODE_AC_SEARCH: return "AC_SEARCH";
+ case SDL_SCANCODE_AC_HOME: return "AC_HOME";
+ case SDL_SCANCODE_AC_BACK: return "AC_BACK";
+ case SDL_SCANCODE_AC_FORWARD: return "AC_FORWARD";
+ case SDL_SCANCODE_AC_STOP: return "AC_STOP";
+ case SDL_SCANCODE_AC_REFRESH: return "AC_REFRESH";
+ case SDL_SCANCODE_AC_BOOKMARKS: return "AC_BOOKMARKS";
+ case SDL_SCANCODE_BRIGHTNESSDOWN: return "BRIGHTNESSDOWN";
+ case SDL_SCANCODE_BRIGHTNESSUP: return "BRIGHTNESSUP";
+ case SDL_SCANCODE_DISPLAYSWITCH: return "DISPLAYSWITCH";
+ case SDL_SCANCODE_KBDILLUMTOGGLE: return "KBDILLUMTOGGLE";
+ case SDL_SCANCODE_KBDILLUMDOWN: return "KBDILLUMDOWN";
+ case SDL_SCANCODE_KBDILLUMUP: return "KBDILLUMUP";
+ case SDL_SCANCODE_EJECT: return "EJECT";
+ case SDL_SCANCODE_SLEEP: return "SLEEP";
+ case SDL_SCANCODE_APP1: return "APP1";
+ case SDL_SCANCODE_APP2: return "APP2";
+ case SDL_SCANCODE_AUDIOREWIND: return "AUDIOREWIND";
+ case SDL_SCANCODE_AUDIOFASTFORWARD: return "AUDIOFASTFORWARD";
+ default: return "UNKNOWN";
+ }
+}
+
+const char *strsym(SDL_Keycode k)
+{
+ switch (k) {
+ case SDLK_RETURN: return "RETURN";
+ case SDLK_ESCAPE: return "ESCAPE";
+ case SDLK_BACKSPACE: return "BACKSPACE";
+ case SDLK_TAB: return "TAB";
+ case SDLK_SPACE: return "SPACE";
+ case SDLK_EXCLAIM: return "EXCLAIM";
+ case SDLK_QUOTEDBL: return "QUOTEDBL";
+ case SDLK_HASH: return "HASH";
+ case SDLK_PERCENT: return "PERCENT";
+ case SDLK_DOLLAR: return "DOLLAR";
+ case SDLK_AMPERSAND: return "AMPERSAND";
+ case SDLK_QUOTE: return "QUOTE";
+ case SDLK_LEFTPAREN: return "LEFTPAREN";
+ case SDLK_RIGHTPAREN: return "RIGHTPAREN";
+ case SDLK_ASTERISK: return "ASTERISK";
+ case SDLK_PLUS: return "PLUS";
+ case SDLK_COMMA: return "COMMA";
+ case SDLK_MINUS: return "MINUS";
+ case SDLK_PERIOD: return "PERIOD";
+ case SDLK_SLASH: return "SLASH";
+ case SDLK_0: return "0";
+ case SDLK_1: return "1";
+ case SDLK_2: return "2";
+ case SDLK_3: return "3";
+ case SDLK_4: return "4";
+ case SDLK_5: return "5";
+ case SDLK_6: return "6";
+ case SDLK_7: return "7";
+ case SDLK_8: return "8";
+ case SDLK_9: return "9";
+ case SDLK_COLON: return "COLON";
+ case SDLK_SEMICOLON: return "SEMICOLON";
+ case SDLK_LESS: return "LESS";
+ case SDLK_EQUALS: return "EQUALS";
+ case SDLK_GREATER: return "GREATER";
+ case SDLK_QUESTION: return "QUESTION";
+ case SDLK_AT: return "AT";
+ case SDLK_LEFTBRACKET: return "LEFTBRACKET";
+ case SDLK_BACKSLASH: return "BACKSLASH";
+ case SDLK_RIGHTBRACKET: return "RIGHTBRACKET";
+ case SDLK_CARET: return "CARET";
+ case SDLK_UNDERSCORE: return "UNDERSCORE";
+ case SDLK_BACKQUOTE: return "BACKQUOTE";
+ case SDLK_a: return "a";
+ case SDLK_b: return "b";
+ case SDLK_c: return "c";
+ case SDLK_d: return "d";
+ case SDLK_e: return "e";
+ case SDLK_f: return "f";
+ case SDLK_g: return "g";
+ case SDLK_h: return "h";
+ case SDLK_i: return "i";
+ case SDLK_j: return "j";
+ case SDLK_k: return "k";
+ case SDLK_l: return "l";
+ case SDLK_m: return "m";
+ case SDLK_n: return "n";
+ case SDLK_o: return "o";
+ case SDLK_p: return "p";
+ case SDLK_q: return "q";
+ case SDLK_r: return "r";
+ case SDLK_s: return "s";
+ case SDLK_t: return "t";
+ case SDLK_u: return "u";
+ case SDLK_v: return "v";
+ case SDLK_w: return "w";
+ case SDLK_x: return "x";
+ case SDLK_y: return "y";
+ case SDLK_z: return "z";
+ case SDLK_CAPSLOCK: return "CAPSLOCK";
+ case SDLK_F1: return "F1";
+ case SDLK_F2: return "F2";
+ case SDLK_F3: return "F3";
+ case SDLK_F4: return "F4";
+ case SDLK_F5: return "F5";
+ case SDLK_F6: return "F6";
+ case SDLK_F7: return "F7";
+ case SDLK_F8: return "F8";
+ case SDLK_F9: return "F9";
+ case SDLK_F10: return "F10";
+ case SDLK_F11: return "F11";
+ case SDLK_F12: return "F12";
+ case SDLK_PRINTSCREEN: return "PRINTSCREEN";
+ case SDLK_SCROLLLOCK: return "SCROLLLOCK";
+ case SDLK_PAUSE: return "PAUSE";
+ case SDLK_INSERT: return "INSERT";
+ case SDLK_HOME: return "HOME";
+ case SDLK_PAGEUP: return "PAGEUP";
+ case SDLK_DELETE: return "DELETE";
+ case SDLK_END: return "END";
+ case SDLK_PAGEDOWN: return "PAGEDOWN";
+ case SDLK_RIGHT: return "RIGHT";
+ case SDLK_LEFT: return "LEFT";
+ case SDLK_DOWN: return "DOWN";
+ case SDLK_UP: return "UP";
+ case SDLK_NUMLOCKCLEAR: return "NUMLOCKCLEAR";
+ case SDLK_KP_DIVIDE: return "KP_DIVIDE";
+ case SDLK_KP_MULTIPLY: return "KP_MULTIPLY";
+ case SDLK_KP_MINUS: return "KP_MINUS";
+ case SDLK_KP_PLUS: return "KP_PLUS";
+ case SDLK_KP_ENTER: return "KP_ENTER";
+ case SDLK_KP_1: return "KP_1";
+ case SDLK_KP_2: return "KP_2";
+ case SDLK_KP_3: return "KP_3";
+ case SDLK_KP_4: return "KP_4";
+ case SDLK_KP_5: return "KP_5";
+ case SDLK_KP_6: return "KP_6";
+ case SDLK_KP_7: return "KP_7";
+ case SDLK_KP_8: return "KP_8";
+ case SDLK_KP_9: return "KP_9";
+ case SDLK_KP_0: return "KP_0";
+ case SDLK_KP_PERIOD: return "KP_PERIOD";
+ case SDLK_APPLICATION: return "APPLICATION";
+ case SDLK_POWER: return "POWER";
+ case SDLK_KP_EQUALS: return "KP_EQUALS";
+ case SDLK_F13: return "F13";
+ case SDLK_F14: return "F14";
+ case SDLK_F15: return "F15";
+ case SDLK_F16: return "F16";
+ case SDLK_F17: return "F17";
+ case SDLK_F18: return "F18";
+ case SDLK_F19: return "F19";
+ case SDLK_F20: return "F20";
+ case SDLK_F21: return "F21";
+ case SDLK_F22: return "F22";
+ case SDLK_F23: return "F23";
+ case SDLK_F24: return "F24";
+ case SDLK_EXECUTE: return "EXECUTE";
+ case SDLK_HELP: return "HELP";
+ case SDLK_MENU: return "MENU";
+ case SDLK_SELECT: return "SELECT";
+ case SDLK_STOP: return "STOP";
+ case SDLK_AGAIN: return "AGAIN";
+ case SDLK_UNDO: return "UNDO";
+ case SDLK_CUT: return "CUT";
+ case SDLK_COPY: return "COPY";
+ case SDLK_PASTE: return "PASTE";
+ case SDLK_FIND: return "FIND";
+ case SDLK_MUTE: return "MUTE";
+ case SDLK_VOLUMEUP: return "VOLUMEUP";
+ case SDLK_VOLUMEDOWN: return "VOLUMEDOWN";
+ case SDLK_KP_COMMA: return "KP_COMMA";
+ case SDLK_KP_EQUALSAS400: return "KP_EQUALSAS400";
+ case SDLK_ALTERASE: return "ALTERASE";
+ case SDLK_SYSREQ: return "SYSREQ";
+ case SDLK_CANCEL: return "CANCEL";
+ case SDLK_CLEAR: return "CLEAR";
+ case SDLK_PRIOR: return "PRIOR";
+ case SDLK_RETURN2: return "RETURN2";
+ case SDLK_SEPARATOR: return "SEPARATOR";
+ case SDLK_OUT: return "OUT";
+ case SDLK_OPER: return "OPER";
+ case SDLK_CLEARAGAIN: return "CLEARAGAIN";
+ case SDLK_CRSEL: return "CRSEL";
+ case SDLK_EXSEL: return "EXSEL";
+ case SDLK_KP_00: return "KP_00";
+ case SDLK_KP_000: return "KP_000";
+ case SDLK_THOUSANDSSEPARATOR: return "THOUSANDSSEPARATOR";
+ case SDLK_DECIMALSEPARATOR: return "DECIMALSEPARATOR";
+ case SDLK_CURRENCYUNIT: return "CURRENCYUNIT";
+ case SDLK_CURRENCYSUBUNIT: return "CURRENCYSUBUNIT";
+ case SDLK_KP_LEFTPAREN: return "KP_LEFTPAREN";
+ case SDLK_KP_RIGHTPAREN: return "KP_RIGHTPAREN";
+ case SDLK_KP_LEFTBRACE: return "KP_LEFTBRACE";
+ case SDLK_KP_RIGHTBRACE: return "KP_RIGHTBRACE";
+ case SDLK_KP_TAB: return "KP_TAB";
+ case SDLK_KP_BACKSPACE: return "KP_BACKSPACE";
+ case SDLK_KP_A: return "KP_A";
+ case SDLK_KP_B: return "KP_B";
+ case SDLK_KP_C: return "KP_C";
+ case SDLK_KP_D: return "KP_D";
+ case SDLK_KP_E: return "KP_E";
+ case SDLK_KP_F: return "KP_F";
+ case SDLK_KP_XOR: return "KP_XOR";
+ case SDLK_KP_POWER: return "KP_POWER";
+ case SDLK_KP_PERCENT: return "KP_PERCENT";
+ case SDLK_KP_LESS: return "KP_LESS";
+ case SDLK_KP_GREATER: return "KP_GREATER";
+ case SDLK_KP_AMPERSAND: return "KP_AMPERSAND";
+ case SDLK_KP_DBLAMPERSAND: return "KP_DBLAMPERSAND";
+ case SDLK_KP_VERTICALBAR: return "KP_VERTICALBAR";
+ case SDLK_KP_DBLVERTICALBAR: return "KP_DBLVERTICALBAR";
+ case SDLK_KP_COLON: return "KP_COLON";
+ case SDLK_KP_HASH: return "KP_HASH";
+ case SDLK_KP_SPACE: return "KP_SPACE";
+ case SDLK_KP_AT: return "KP_AT";
+ case SDLK_KP_EXCLAM: return "KP_EXCLAM";
+ case SDLK_KP_MEMSTORE: return "KP_MEMSTORE";
+ case SDLK_KP_MEMRECALL: return "KP_MEMRECALL";
+ case SDLK_KP_MEMCLEAR: return "KP_MEMCLEAR";
+ case SDLK_KP_MEMADD: return "KP_MEMADD";
+ case SDLK_KP_MEMSUBTRACT: return "KP_MEMSUBTRACT";
+ case SDLK_KP_MEMMULTIPLY: return "KP_MEMMULTIPLY";
+ case SDLK_KP_MEMDIVIDE: return "KP_MEMDIVIDE";
+ case SDLK_KP_PLUSMINUS: return "KP_PLUSMINUS";
+ case SDLK_KP_CLEAR: return "KP_CLEAR";
+ case SDLK_KP_CLEARENTRY: return "KP_CLEARENTRY";
+ case SDLK_KP_BINARY: return "KP_BINARY";
+ case SDLK_KP_OCTAL: return "KP_OCTAL";
+ case SDLK_KP_DECIMAL: return "KP_DECIMAL";
+ case SDLK_KP_HEXADECIMAL: return "KP_HEXADECIMAL";
+ case SDLK_LCTRL: return "LCTRL";
+ case SDLK_LSHIFT: return "LSHIFT";
+ case SDLK_LALT: return "LALT";
+ case SDLK_LGUI: return "LGUI";
+ case SDLK_RCTRL: return "RCTRL";
+ case SDLK_RSHIFT: return "RSHIFT";
+ case SDLK_RALT: return "RALT";
+ case SDLK_RGUI: return "RGUI";
+ case SDLK_MODE: return "MODE";
+ case SDLK_AUDIONEXT: return "AUDIONEXT";
+ case SDLK_AUDIOPREV: return "AUDIOPREV";
+ case SDLK_AUDIOSTOP: return "AUDIOSTOP";
+ case SDLK_AUDIOPLAY: return "AUDIOPLAY";
+ case SDLK_AUDIOMUTE: return "AUDIOMUTE";
+ case SDLK_MEDIASELECT: return "MEDIASELECT";
+ case SDLK_WWW: return "WWW";
+ case SDLK_MAIL: return "MAIL";
+ case SDLK_CALCULATOR: return "CALCULATOR";
+ case SDLK_COMPUTER: return "COMPUTER";
+ case SDLK_AC_SEARCH: return "AC_SEARCH";
+ case SDLK_AC_HOME: return "AC_HOME";
+ case SDLK_AC_BACK: return "AC_BACK";
+ case SDLK_AC_FORWARD: return "AC_FORWARD";
+ case SDLK_AC_STOP: return "AC_STOP";
+ case SDLK_AC_REFRESH: return "AC_REFRESH";
+ case SDLK_AC_BOOKMARKS: return "AC_BOOKMARKS";
+ case SDLK_BRIGHTNESSDOWN: return "BRIGHTNESSDOWN";
+ case SDLK_BRIGHTNESSUP: return "BRIGHTNESSUP";
+ case SDLK_DISPLAYSWITCH: return "DISPLAYSWITCH";
+ case SDLK_KBDILLUMTOGGLE: return "KBDILLUMTOGGLE";
+ case SDLK_KBDILLUMDOWN: return "KBDILLUMDOWN";
+ case SDLK_KBDILLUMUP: return "KBDILLUMUP";
+ case SDLK_EJECT: return "EJECT";
+ case SDLK_SLEEP: return "SLEEP";
+ case SDLK_APP1: return "APP1";
+ case SDLK_APP2: return "APP2";
+ case SDLK_AUDIOREWIND: return "AUDIOREWIND";
+ case SDLK_AUDIOFASTFORWARD: return "AUDIOFASTFORWARD";
+ default: return "UNKNOWN";
+ }
+}
diff --git a/sdl.h b/sdl.h
new file mode 100644
index 0000000..2df6f81
--- /dev/null
+++ b/sdl.h
@@ -0,0 +1,14 @@
+#ifndef SDL_H
+#define SDL_H
+
+#include <SDL.h>
+#include <lua.h>
+
+int sdl_error(lua_State *L, const char *what);
+void acquire_sdl(void);
+void release_sdl(void);
+const char *strbutton(Uint8 button);
+const char *strscancode(SDL_Scancode s);
+const char *strsym(SDL_Keycode k);
+
+#endif /* !SDL_H */
diff --git a/window.c b/window.c
new file mode 100644
index 0000000..314d589
--- /dev/null
+++ b/window.c
@@ -0,0 +1,179 @@
+#include <SDL.h>
+#include <glib-object.h>
+#include <lauxlib.h>
+#include <lua.h>
+#include <pango/pangoft2.h>
+
+#include "window.h"
+#include "sdl.h"
+
+#define UD_WINDOW "libluiml.window"
+
+struct window {
+ SDL_Window *win;
+ SDL_Renderer *ren;
+ PangoContext *pc;
+};
+
+/* window:destroy_window() */
+static int destroy_window(lua_State *L)
+{
+ struct window *w;
+
+ w = luaL_checkudata(L, 1, UD_WINDOW);
+ SDL_DestroyWindow(w->win);
+ g_object_unref(w->pc);
+
+ release_sdl();
+
+ return 0;
+}
+
+#include "window_wait_event.c"
+
+/* window:get_dimensions() -> width, height */
+static int get_dimensions(lua_State *L)
+{
+ struct window *w;
+ int width, height;
+
+ w = luaL_checkudata(L, 1, UD_WINDOW);
+
+ SDL_GetWindowSize(w->win, &width, &height);
+
+ lua_pushnumber(L, width);
+ lua_pushnumber(L, height);
+
+ return 2;
+}
+
+double clamp(double v, double min, double max)
+{
+ if (v < min) return min;
+ if (v > max) return max;
+ return v;
+}
+
+/* window:draw_rect(x, y, width, height, r, g, b) */
+static int draw_rect(lua_State *L)
+{
+ struct window *w;
+ SDL_Rect r;
+ struct {
+ Uint8 r, g, b;
+ } colour;
+
+ w = luaL_checkudata(L, 1, UD_WINDOW);
+ r.x = luaL_checknumber(L, 2);
+ r.y = luaL_checknumber(L, 3);
+ r.w = luaL_checknumber(L, 4);
+ r.h = luaL_checknumber(L, 5);
+ colour.r = round(clamp(luaL_checknumber(L, 6), 0, 1) * 255.0);
+ colour.g = round(clamp(luaL_checknumber(L, 7), 0, 1) * 255.0);
+ colour.b = round(clamp(luaL_checknumber(L, 8), 0, 1) * 255.0);
+
+ SDL_SetRenderDrawColor(w->ren, colour.r, colour.g, colour.b, SDL_ALPHA_OPAQUE);
+ SDL_RenderFillRect(w->ren, &r);
+
+ return 0;
+}
+
+static int draw_text(lua_State *L)
+{
+ PangoRectangle extents;
+ PangoLayout *layout;
+ int width, height;
+ struct window *w;
+ const char *s;
+ SDL_Rect r;
+ size_t l;
+
+ w = luaL_checkudata(L, 1, UD_WINDOW);
+ r.x = luaL_checknumber(L, 2);
+ r.y = luaL_checknumber(L, 3);
+ r.w = luaL_checknumber(L, 4);
+ r.h = luaL_checknumber(L, 5);
+ s = luaL_checklstring(L, 6, &l);
+
+ layout = pango_layout_new(w->pc);
+ pango_layout_set_width(layout, r.w * PANGO_SCALE);
+ pango_layout_set_height(layout, r.h * PANGO_SCALE);
+ pango_layout_set_text(layout, s, l);
+ pango_layout_get_extents(layout, NULL, &extents);
+ width = PANGO_PIXELS(extents.width);
+ height = PANGO_PIXELS(extents.height);
+
+ return 0;
+}
+
+static int show(lua_State *L)
+{
+ struct window *w;
+
+ w = luaL_checkudata(L, 1, UD_WINDOW);
+
+ SDL_RenderPresent(w->ren);
+
+ return 0;
+}
+
+/* create_window(title) -> window */
+int create_window(lua_State *L)
+{
+ static const struct luaL_Reg window_metatable[] = {
+ {"__gc", destroy_window},
+ {"wait_event", wait_event},
+ {"get_dimensions", get_dimensions},
+ {"draw_rect", draw_rect},
+ {"draw_text", draw_text},
+ {"show", show},
+ {NULL, NULL},
+ };
+ PangoFontMap *fontmap;
+ const char *title, *failed;
+ struct window *w;
+
+ if (luaL_newmetatable(L, UD_WINDOW)) {
+ lua_pushstring(L, "__index");
+ lua_pushvalue(L, -2);
+ lua_settable(L, -3);
+ luaL_register(L, NULL, window_metatable);
+ lua_pop(L, 1);
+ }
+
+ title = luaL_checkstring(L, 1);
+
+ acquire_sdl();
+
+ w = lua_newuserdata(L, sizeof *w);
+ w->win = SDL_CreateWindow(title,
+ SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ 640, 480, SDL_WINDOW_RESIZABLE);
+
+ if (w->win == NULL) {
+ failed = "SDL_CreateWindow";
+ goto fail_win;
+ }
+
+ w->ren = SDL_CreateRenderer(w->win, 0, SDL_RENDERER_PRESENTVSYNC);
+
+ if (w->ren == NULL) {
+ failed = "SDL_CreateRenderer";
+ goto fail_ren;
+ }
+
+
+ fontmap = pango_ft2_font_map_new();
+ w->pc = pango_font_map_create_context(fontmap);
+ g_object_unref(fontmap);
+
+ luaL_getmetatable(L, UD_WINDOW);
+ lua_setmetatable(L, -2);
+
+ return 1;
+
+fail_ren: SDL_DestroyWindow(w->win);
+fail_win: release_sdl();
+ return sdl_error(L, failed);
+}
diff --git a/window.h b/window.h
new file mode 100644
index 0000000..3cde3cf
--- /dev/null
+++ b/window.h
@@ -0,0 +1,6 @@
+#ifndef WINDOW_H
+#define WINDOW_H
+
+int create_window(lua_State *L);
+
+#endif /* !WINDOW_H */
diff --git a/window_wait_event.c b/window_wait_event.c
new file mode 100644
index 0000000..9c06d72
--- /dev/null
+++ b/window_wait_event.c
@@ -0,0 +1,102 @@
+#include "xlua.h"
+
+/* window:wait_event() -> event, data */
+static int wait_event(lua_State *L)
+{
+ static const struct key mousebutton_keys[] = {
+ KEY_STR(button, VT_STRING),
+ KEY_STR(down, VT_BOOLEAN),
+ KEY_STR(x, VT_NUMBER),
+ KEY_STR(y, VT_NUMBER),
+ KEY_END,
+ };
+ static const struct key mousemotion_keys[] = {
+ KEY_STR(x, VT_NUMBER),
+ KEY_STR(y, VT_NUMBER),
+ KEY_STR(xrel, VT_NUMBER),
+ KEY_STR(yrel, VT_NUMBER),
+ KEY_END,
+ };
+ static const struct key key_keys[] = {
+ KEY_STR(down, VT_BOOLEAN),
+ KEY_STR(repeat, VT_BOOLEAN),
+ KEY_END,
+ };
+ static const struct key keysym_keys[] = {
+ KEY_STR(scancode, VT_STRING),
+ KEY_STR(sym, VT_STRING),
+ KEY_END,
+ };
+ struct window *w;
+ SDL_Event e;
+
+ w = luaL_checkudata(L, 1, UD_WINDOW);
+
+ while (1) {
+ if (SDL_WaitEvent(&e) == 0) return sdl_error(L, "SDL_WaitEvent");
+
+ switch (e.type) {
+ case SDL_QUIT:
+ lua_pushstring(L, "QUIT");
+ lua_pushnil(L);
+ break;
+ case SDL_WINDOWEVENT:
+ if (e.window.event != SDL_WINDOWEVENT_EXPOSED)
+ continue;
+ lua_pushstring(L, "REDRAW");
+ lua_pushnil(L);
+ break;
+ case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP:
+ lua_pushstring(L, "MOUSEBUTTON");
+ pushtable(L, mousebutton_keys,
+ strbutton(e.button.button),
+ e.button.state == SDL_PRESSED,
+ (lua_Number)e.button.x,
+ (lua_Number)e.button.y);
+ break;
+ case SDL_MOUSEMOTION:
+ lua_pushstring(L, "MOUSEMOTION");
+ pushtable(L, mousemotion_keys,
+ (lua_Number)e.motion.x,
+ (lua_Number)e.motion.y,
+ (lua_Number)e.motion.xrel,
+ (lua_Number)e.motion.yrel);
+ lua_pushstring(L, "state");
+ lua_createtable(L, 0, 5);
+ pushsetmember(L, -1, "LEFT", e.motion.state & SDL_BUTTON_LMASK);
+ pushsetmember(L, -1, "MIDDLE", e.motion.state & SDL_BUTTON_MMASK);
+ pushsetmember(L, -1, "RIGHT", e.motion.state & SDL_BUTTON_RMASK);
+ pushsetmember(L, -1, "X1", e.motion.state & SDL_BUTTON_X1MASK);
+ pushsetmember(L, -1, "X2", e.motion.state & SDL_BUTTON_X2MASK);
+ lua_settable(L, -3);
+ break;
+ case SDL_KEYDOWN: case SDL_KEYUP:
+ lua_pushstring(L, "KEY");
+ pushtable(L, key_keys,
+ e.key.state == SDL_PRESSED,
+ e.key.repeat);
+ lua_pushstring(L, "keysym");
+ pushtable(L, keysym_keys,
+ strscancode(e.key.keysym.scancode),
+ strsym(e.key.keysym.sym));
+ lua_pushstring(L, "mod");
+ lua_createtable(L, 0, 11);
+ pushsetmember(L, -1, "LSHIFT", e.key.keysym.mod & KMOD_LSHIFT);
+ pushsetmember(L, -1, "RSHIFT", e.key.keysym.mod & KMOD_RSHIFT);
+ pushsetmember(L, -1, "LCTRL", e.key.keysym.mod & KMOD_LCTRL);
+ pushsetmember(L, -1, "RCTRL", e.key.keysym.mod & KMOD_RCTRL);
+ pushsetmember(L, -1, "LALT", e.key.keysym.mod & KMOD_LALT);
+ pushsetmember(L, -1, "RALT", e.key.keysym.mod & KMOD_RALT);
+ pushsetmember(L, -1, "LGUI", e.key.keysym.mod & KMOD_LGUI);
+ pushsetmember(L, -1, "RGUI", e.key.keysym.mod & KMOD_RGUI);
+ pushsetmember(L, -1, "NUM", e.key.keysym.mod & KMOD_NUM);
+ pushsetmember(L, -1, "CAPS", e.key.keysym.mod & KMOD_CAPS);
+ pushsetmember(L, -1, "MODE", e.key.keysym.mod & KMOD_MODE);
+ lua_settable(L, -3);
+ lua_settable(L, -3);
+ break;
+ default: continue;
+ }
+ return 2;
+ }
+}
diff --git a/xlua.c b/xlua.c
new file mode 100644
index 0000000..bb1b2de
--- /dev/null
+++ b/xlua.c
@@ -0,0 +1,56 @@
+#include <lauxlib.h>
+#include <lua.h>
+#include <stdarg.h>
+#include <stdbool.h>
+
+#include "xlua.h"
+
+void pushtable(lua_State *L, const struct key *keys, ...)
+{
+ va_list ap;
+ int narr = 0, nrec = 0;
+
+ for (const struct key *key = keys; key->type != KT_NULL; key++) {
+ switch (key->type) {
+ case KT_STRING: nrec++; break;
+ case KT_NUMBER: narr++; break;
+ default:
+ luaL_error(L, "pusharray failed: key: invalid key type");
+ }
+ }
+
+ lua_createtable(L, narr, nrec);
+
+ va_start(ap, keys);
+
+ for (const struct key *key = keys; key->type != KT_NULL; key++) {
+ switch (key->type) {
+ case KT_STRING: lua_pushstring(L, key->key.str); break;
+ case KT_NUMBER: lua_pushnumber(L, key->key.num); break;
+ }
+ switch (key->vtype) {
+ case VT_STRING:
+ lua_pushstring(L, va_arg(ap, const char *));
+ break;
+ case VT_NUMBER:
+ lua_pushnumber(L, va_arg(ap, lua_Number));
+ break;
+ case VT_BOOLEAN:
+ lua_pushboolean(L, va_arg(ap, int));
+ break;
+ default:
+ luaL_error(L, "pusharray failed: key: invalid value type");
+ }
+ lua_settable(L, -3);
+ }
+
+ va_end(ap);
+}
+
+void pushsetmember(lua_State *L, int index, const char *name, bool in_set)
+{
+ if (!in_set) return;
+ if (index < 0) index -= 1;
+ lua_pushboolean(L, 1);
+ lua_setfield(L, index, name);
+}
diff --git a/xlua.h b/xlua.h
new file mode 100644
index 0000000..ac2838e
--- /dev/null
+++ b/xlua.h
@@ -0,0 +1,31 @@
+#ifndef XLUA_H
+#define XLUA_H
+
+#include <stdbool.h>
+
+struct key {
+ enum key_type {
+ KT_NULL,
+ KT_STRING,
+ KT_NUMBER,
+ } type;
+ union {
+ const char *str;
+ double num;
+ } key;
+ enum value_type {
+ VT_STRING,
+ VT_NUMBER,
+ VT_BOOLEAN,
+ } vtype;
+};
+#define _STR(s) #s
+#define STR(s) _STR(s)
+#define KEY_STR(k, vt) (struct key){ .type = KT_STRING, .key.str = _STR(k), .vtype = (vt)}
+#define KEY_NUM(k, vt) (struct key){ .type = KT_NUMBER, .key.mum = (k), .vtype = (vt)}
+#define KEY_END (struct key){ .type = KT_NULL }
+
+void pushtable(lua_State *L, const struct key *keys, ...);
+void pushsetmember(lua_State *L, int index, const char *name, bool in_set);
+
+#endif /* !XLUA_H */