lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


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