[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: How to approach this?
- From: Sean Conner <sean@...>
- Date: Tue, 16 Sep 2014 12:30:05 -0400
It was thus said that the Great Dirk Schippers once stated:
> Hi,
>
> I am creating a test project (C++) where I have this telnet server which
> creates a thread for every connected client that has its own Lua
> environment to play around in.
> But I want to do more than: print 5 + 2
> So I created some functions: createWindow({...}) which creates a window.
> I can call it multiple times to create multiple windows.
>
> At this time, the function has become the constructor for an object,
> which returns a light userdata pointing to a C-struct containing the
> window handle and some other stuff.
>
> But I want to expand this: I want the return value to become a table
> where you can put your own events handlers in, like this:
I have code that does this for X Windows [1]. Here's how I create the
window [2]:
static int Xdisplay_window(lua_State *const L)
{
Display **display;
Window parent;
Window *window;
int x;
int y;
int width;
int height;
int border;
unsigned int class;
unsigned long mask;
XSetWindowAttributes attr;
assert(L != NULL);
display = XL_checkudata(L,1,XLUA_DISPLAY);
parent = XL_optwindow(L,2,RootWindow(*display,DefaultScreen(*display)));
x = luaL_optint(L,3,0);
y = luaL_optint(L,4,0);
width = luaL_optint(L,5,512);
height = luaL_optint(L,6,342);
border = luaL_optint(L,7,0);
class = luaL_checkoption(L,8,"InputOutput",c_classenum);
mask = XL_optsetwinattr(L,9,&attr,*display);
window = lua_newuserdata(L,sizeof(Window));
*window = XCreateWindow(
*display,
parent,
x,
y,
width,
height,
border,
DefaultDepth(*display,DefaultScreen(*display)),
class,
DefaultVisual(*display,DefaultScreen(*display)),
mask,
&attr
);
lua_createtable(L,0,0);
lua_pushliteral(L,XLUA_WINDOW);
lua_setfield(L,-2,"_TYPE");
lua_pushvalue(L,-1);
lua_setfield(L,-2,"__index");
lua_pushvalue(L,-1);
lua_setfield(L,-2,"__newindex");
lua_pushvalue(L,1);
lua_setfield(L,-2,"_DISPLAY");
lua_pushvalue(L,1);
luaI_openlib(L,NULL,Xwindow_metatable,1); /* [3] */
lua_setmetatable(L,-2);
luaL_getmetafield(L,1,"_CACHE");
lua_pushinteger(L,*window);
lua_pushvalue(L,-3);
lua_settable(L,-3);
lua_pop(L,1);
return 1;
}
I use regular userdata since this is written for Lua 5.1, and userdata can
have metatables; lightuserdata can't. I think Lua 5.2 and 5.3 is the same,
but don't quote me on that (check the manual).
Anyway, each window gets its own metatable [4] which is where the __index
and __newindex fields point to. Not only does this allow me to do things
like:
window:seticon(icon)
window:show()
but I can attach my own data to the window:
window.mycolor = display:color('olive green')
This is nice, because of how I handle events. It's simply:
X = require "X"
-- code to create window eluded
window.events = X.default_events {
Expose = function(event) ... end,
ButtonPress = function(event) ... end,
ButtonRelease = function(event) ... end,
KeyPress = function(event) ... end,
KeyRelease = function(event) ... end,
ConfigureNotify = function(event) ... end,
}
These are X Window specific events [1], and X.default_events() will create
a function that does nothing if a function for a given event is not passed
in. The main event loop is then:
repeat
local event = display:event()
local rc = event.window.events[event.type](event)
until rc == "QUIT"
At any time, I can update the event code by a simple assignment:
window.events['KeyPress'] = function(event) ... end
The .events field is just a plain Lua table.
> How can I call
> the correct event function in the correct object when I receive the
> events in my message loop? If I use tables, I don't know how to find
> them back... How can I keep an index between the window handle and a lua
> table?
In my case, that's what the _CACHE field (it's a table in the display
metatable) is for. When I push an event, I look up the value in the _CACHE
table. It's a table with weak keys, so when the windows go away, the
reference in the _CACHE table also goes away. For example (this is the
catch-all event handler for events I haven't gotten around to full support):
static void XL_pushevent(lua_State *const L,const XEvent *const e)
{
assert(L != NULL);
assert(e != NULL);
lua_createtable(L,0,0);
lua_pushstring(L,m_pushevent[e->xany.type].name);
lua_setfield(L,-2,"type");
lua_pushnumber(L,e->xany.serial);
lua_setfield(L,-2,"serial");
lua_pushboolean(L,e->xany.send_event);
lua_setfield(L,-2,"send_event");
lua_pushlightuserdata(L,e->xany.display);
lua_gettable(L,LUA_REGISTRYINDEX);
lua_pushvalue(L,-1);
lua_setfield(L,-3,"display");
luaL_getmetafield(L,-1,"_CACHE");
lua_pushinteger(L,e->xany.window);
lua_gettable(L,-2);
lua_setfield(L,-4,"window");
lua_pop(L,2);
}
One thing to note---when I create the display, I save it (as a
lightuserdata) in the LUA_REGISTRYINDEX table, so I can get to the full
userdata for the display (which contains the windows _CACHE table). In your
case, you can probably do this trick with windows directly (depending on how
your GUI is programmed).
I'd offer the code, but a) it's *very* Xlib specific, b) embeds a C
compiler [5] so I can fake C closures to handle Xlib errors (no, really!)
and c) is some 5,000 lines of C code (and around 250 lines of Lua, excluding
some UI stuff).
-spc (But if you have further questions, please feel free to ask)
[1] It calls Xlib, the lowest level API in X; any lower and I'd be
talking the X protocol.
[2] All Xlib functions take a pointer to the current display, which
means you need to first open the display.
[3] luaI_openlib() is used here so that each function dealing with
windows has the current display as an upvalue. It's not documented
in the Lua 5.1 manual, but it is public, so I opted to use this.
[4] This was written before I realized each userobject could have an
environment attached to it. Had I known that, I probably would have
done that, but it would end up looking the same anyway.
[5] https://github.com/spc476/lua-conmanorg/blob/master/src/tcc.c