From b2372ff8f73a72b375976578d0758ba0d993b6a2 Mon Sep 17 00:00:00 2001 From: Peter Harris Date: Mon, 5 Nov 2007 14:59:44 -0500 Subject: [PATCH] Initial checkin of luaxcb --- COPYING | 29 ++ build | 4 + examples/DumpTree.lua | 80 +++++ examples/DumpTreeSlow.lua | 83 +++++ examples/FullDumpTree.lua | 137 ++++++++ examples/ShowProps.lua | 156 +++++++++ lua-binding.pl | 813 ++++++++++++++++++++++++++++++++++++++++++++++ lxcb.c | 240 ++++++++++++++ 8 files changed, 1542 insertions(+) create mode 100644 COPYING create mode 100755 build create mode 100644 examples/DumpTree.lua create mode 100644 examples/DumpTreeSlow.lua create mode 100644 examples/FullDumpTree.lua create mode 100644 examples/ShowProps.lua create mode 100755 lua-binding.pl create mode 100644 lxcb.c diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..00c426c --- /dev/null +++ b/COPYING @@ -0,0 +1,29 @@ +Copyright (C) 2007 Hummingbird Ltd. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, +sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall +be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the names of the authors +or their institutions shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this +Software without prior written authorization from the +authors. diff --git a/build b/build new file mode 100755 index 0000000..af0847d --- /dev/null +++ b/build @@ -0,0 +1,4 @@ +#!/bin/sh + +./lua-binding.pl +gcc *.c -O2 -fvisibility=hidden -shared -fpic -lxcb -llua5.1 -o lxcb.so diff --git a/examples/DumpTree.lua b/examples/DumpTree.lua new file mode 100644 index 0000000..3c8877b --- /dev/null +++ b/examples/DumpTree.lua @@ -0,0 +1,80 @@ +require("lxcb") + +local INDENT_PER_LEVEL=2 +local no_name = "" + +local atom = {} + +function internatom(this, name) + this[name] = dpy:intern_atom_reply(dpy:intern_atom(false, string.len(name), name)).atom + return this[name] +end + +setmetatable(atom, { __index = internatom }) + +function printf(...) + io.output():write(string.format(...)) +end + +function DumpTree(id, indent) + -- Show the information for this window + local mapped + + local attrcookie = dpy:get_window_attributes(id) + local geomcookie = dpy:get_geometry(id) + local namecookie = dpy:get_property(false, id, atom.WM_NAME, dpy.GET_PROPERTY_TYPE.ANY, 0, 80) + local treecookie = dpy:query_tree(id) + + local xwa = dpy:get_window_attributes_reply(attrcookie) + if (xwa) then + mapped = (xwa.map_state == dpy.MAP_STATE.VIEWABLE) + end + + if (mapped) then + printf("%s0x%08X ", string.rep(" ", indent), id); + else + printf("%s(0x%08X)", string.rep(" ", indent), id); + end + + local geom = dpy:get_geometry_reply(geomcookie) + printf(" %d: %d,%d [%dx%d]", geom.depth, geom.x, geom.y, geom.width, geom.height) + + local name = '' + local prop = dpy:get_property_reply(namecookie) + if (prop) then + name = prop.value + end + + printf(" %s\n", name); + + -- Recursively show the information for the rest of the tree + local tree = dpy:query_tree_reply(treecookie) + for k, v in ipairs(tree.children) do + DumpTree(v, indent + INDENT_PER_LEVEL) + end +end + + +local root +dpy, screen = lxcb.connect("") + +if (not dpy) then + print("Error: Cannot open default display.") + return 1; +end + +if (#arg >= 1) then + root = tonumber(arg[1]) + if (not root) then + print("Usage: DumpTree [starting window]") + return 1; + end +else + root = dpy:get_setup().roots[screen+1].root +end + +DumpTree(root, 0) + +dpy = nil + +return 0 diff --git a/examples/DumpTreeSlow.lua b/examples/DumpTreeSlow.lua new file mode 100644 index 0000000..4ba0aa2 --- /dev/null +++ b/examples/DumpTreeSlow.lua @@ -0,0 +1,83 @@ +require("lxcb") + +local INDENT_PER_LEVEL=2 +local no_name = "" + +local WM_NAME + +function GetWMName(id) + if (WM_NAME == nil) then + local name = 'WM_NAME' + WM_NAME = dpy:intern_atom_reply(dpy:intern_atom(false, string.len(name), name)).atom + end + local prop, propreq + propreq = dpy:get_property(false, id, WM_NAME, dpy.GET_PROPERTY_TYPE.ANY, 0, 80) + prop = dpy:get_property_reply(propreq) + if (prop) then + return string.char(unpack(prop.value)) + else + return nil + end +end + +function printf(...) + io.output():write(string.format(...)) +end + +function DumpTree(id, indent) + local root, parent, children + + -- Show the information for this window + local mapped = true + local name + + local xwa = dpy:get_window_attributes_reply(dpy:get_window_attributes(id)) + if (xwa) then + mapped = (xwa.map_state == dpy.MAP_STATE.VIEWABLE) + end + + local tp = GetWMName(id) + name = tp or "" + + if (mapped) then + printf("%s0x%08X ", string.rep(" ", indent), id); + else + printf("%s(0x%08X)", string.rep(" ", indent), id); + end + + local geom = dpy:get_geometry_reply(dpy:get_geometry(id)) + printf(" %d: %d,%d [%dx%d]", geom.depth, geom.x, geom.y, geom.width, geom.height) + + printf(" %s\n", name); + + -- Recursively show the information for the rest of the tree + local tree = dpy:query_tree_reply(dpy:query_tree(id)) + for k, v in ipairs(tree.children) do + DumpTree(v, indent + INDENT_PER_LEVEL) + end +end + + +local root +dpy, screen = lxcb.connect("") + +if (not dpy) then + print("Error: Cannot open default display.") + return 1; +end + +if (#arg >= 1) then + root = tonumber(arg[1]) + if (not root) then + print("Usage: DumpTree [starting window]") + return 1; + end +else + root = dpy:get_setup().roots[screen+1].root +end + +DumpTree(root, 0) + +dpy = nil + +return 0 diff --git a/examples/FullDumpTree.lua b/examples/FullDumpTree.lua new file mode 100644 index 0000000..dd211d2 --- /dev/null +++ b/examples/FullDumpTree.lua @@ -0,0 +1,137 @@ +require("lxcb") + +local INDENT_PER_LEVEL=2 +local no_name = "" + +local queue = {}; +local window = {}; +local atom = {}; + +function internatom(this, name) + this[name] = dpy:intern_atom_reply(dpy:intern_atom(false, string.len(name), name)).atom + return this[name] +end + +function printf(...) + io.output():write(string.format(...)) +end + +function DumpTree(id, indent) + -- Show the information for this window + local win = window[id] + + if (win.mapped) then + printf("%s0x%08X ", string.rep(" ", indent), id); + else + printf("%s(0x%08X)", string.rep(" ", indent), id); + end + + local geom = win.geom + printf(" %d: %d,%d [%dx%d]", geom.depth, geom.x, geom.y, geom.width, geom.height) + + printf(" %s\n", win.name or ""); + + -- Recursively show the information for the rest of the tree + for k, v in ipairs(win.children) do + DumpTree(v, indent + INDENT_PER_LEVEL) + end +end + +-- Queueing functions +function QueueInternAtom(name) + local cookie = dpy:intern_atom(false, string.len(name), name) + table.insert(queue, function() + atom[name] = dpy:intern_atom_reply(cookie).atom + end) +end + +function QueueWindowName(id) + local cookie = dpy:get_property(false, id, atom.WM_NAME, dpy.GET_PROPERTY_TYPE.ANY, 0, 80) + table.insert(queue, function() + local prop = dpy:get_property_reply(cookie) + if (prop) then + window[id].name = string.char(unpack(prop.value)) + end + end) +end + +function QueueWindowGeometry(id) + local cookie = dpy:get_geometry(id) + table.insert(queue, function() + window[id].geom = dpy:get_geometry_reply(cookie) + end) +end + +function QueueWindowMapped(id) + local cookie = dpy:get_window_attributes(id) + table.insert(queue, function() + local xwa = dpy:get_window_attributes_reply(cookie) + window[id].mapped = (xwa.map_state == dpy.MAP_STATE.VIEWABLE) + end) +end + +function QueueWindowChildren(id) + local cookie = dpy:query_tree(id) + table.insert(queue, function() + local tree = dpy:query_tree_reply(cookie) + window[id].children = tree.children + + -- For each window, query the children first + -- (since that's the one that'll cause recursive + -- queries), and then query incidental per-window data + -- (hopefully overlapping with the child queries) + + for k, v in ipairs(tree.children) do + window[v] = {} + QueueWindowChildren(v) + end + + for k, v in ipairs(tree.children) do + QueueWindowName(v) + QueueWindowGeometry(v) + QueueWindowMapped(v) + end + end) +end + +function ProcessQueue() + local next = 1 + while (#queue >= next) do + queue[next]() + next = next + 1 + end +end + +-- Atom cache setup +setmetatable(atom, { __index = internatom }) + +-- Main +local root +dpy, screen = lxcb.connect("") + +if (not dpy) then + print("Error: Cannot open default display.") + return 1; +end + +if (#arg >= 1) then + root = tonumber(arg[1]) + if (not root) then + print("Usage: DumpTree [starting window]") + return 1; + end +else + root = dpy:get_setup().roots[screen+1].root +end + +window[root] = { mapped = true } +QueueWindowChildren(root) +QueueInternAtom('WM_NAME') +QueueWindowGeometry(root) +ProcessQueue() + +DumpTree(root, 0) + +dpy = nil + +return 0 diff --git a/examples/ShowProps.lua b/examples/ShowProps.lua new file mode 100644 index 0000000..e17cd68 --- /dev/null +++ b/examples/ShowProps.lua @@ -0,0 +1,156 @@ +require("lxcb") + +function printf(...) + io.output():write(string.format(...)) +end + +local atom = {} +local atomname = {} + +function internatom(this, name) + local id = c:intern_atom_reply(c:intern_atom(false, string.len(name), name)).atom + this[name] = id + atomname[id] = name + return id +end + +function getatomname(this, id) + local name = string.char(unpack(c:get_atom_name_reply(c:get_atom_name(id)).name)) + this[id] = name + atom[name] = id + return name +end + +setmetatable(atom, { __index = internatom }) +setmetatable(atomname, { __index = getatomname }) + + +function FontCursor(c, id) + local font = c:generate_id() + local cursor = c:generate_id() + local fn = "cursor" + c:open_font(font, string.len(fn), fn) + c:create_glyph_cursor(cursor, font, font, id, id+1, 0xFFFF, 0xFFFF, 0xFFFF, 0, 0, 0) + c:close_font(font) + + return cursor +end + + -- Routine to let user select a window using the mouse +function Select_Window(c) + local status + local cursor + local event + local target_win = 0 + local root = c:get_setup().roots[screen+1].root + local buttons = 0 + + -- Make the target cursor + cursor = FontCursor(c, 34); -- XC_crosshair + + -- Grab the pointer using target cursor, letting it room all over + local grabreq = c:grab_pointer(false, root, + c.EVENT_MASK.BUTTON_PRESS + c.EVENT_MASK.BUTTON_RELEASE, + c.GRAB_MODE.SYNC, c.GRAB_MODE.ASYNC, + root, cursor, 0) -- CurrentTime) + status = c:grab_pointer_reply(grabreq).status + if (status ~= c.GRAB_STATUS.SUCCESS) then + printf("Can't grab the mouse.\n") + return 0; + end + + -- Let the user select a window... + while ((target_win == 0) or (buttons ~= 0)) do + -- allow one more event + c:allow_events(c.ALLOW.SYNC_POINTER, 0) -- CurrentTime) + + c:flush() -- wait_for_event doesn't automatically flush. + event = c:wait_for_event() + + if (event.response_type == 4) -- "ButtonPress") + then + if (target_win == 0) then + target_win = event.child -- window selected + if (target_win == 0) then target_win = event.event end + if (target_win == 0) then target_win = event.root end + end + buttons = buttons + 1 + elseif (event.response_type == 5) -- "ButtonRelease") + then + if (buttons > 0) then -- there may have been some down before we started + buttons = buttons - 1 + end + end + end + + c:ungrab_pointer(0) -- CurrentTime) -- Done with pointer + + printf("target_win = 0x%08X\n", target_win) + return target_win +end + +function string2int(s) + -- assume we're running on an LSB machine + local multiplier = 1; + local value = 0; + local bytes = { string.byte(s, 1, #s) } + for k,v in ipairs(bytes) do + value = value + v * multiplier + multiplier = multiplier * 0x100 + end + return value +end + +local dumper = { + STRING = function(prop) printf(" = %s", prop) end, + UTF8_STRING = function(prop) printf(" = %s", prop) end, + CARDINAL = function(prop) printf(" = 0x%X", string2int(prop)) end, + ATOM = function(prop) + printf(" = ") + while (#prop >= 4) do + local atom = string2int(string.sub(prop, 1, 4)) + prop = string.sub(prop, 5); + printf("%s, ", atomname[atom]) + end + end, +} + + +function DumpProps(c, id) + local list = c:list_properties_reply(c:list_properties(id)).atoms; + for k, v in ipairs(list) do + local name = atomname[v] + printf(" %s: ",name); + local prop = c:get_property_reply(c:get_property(false, id, v, c.GET_PROPERTY_TYPE.ANY, 0, 100)) + if (prop) then + local typename = atomname[prop.type] + printf("%s", typename) + if (dumper[typename]) then dumper[typename](prop.value) end + end + printf("\n"); + end +end + +local root +c, screen = lxcb.connect() + +if (not c) then + print("Error: Cannot open default display.") + return 1; +end + +if (#arg >= 1) then + root = tonumber(arg[1]) + if (not root) then + print("Usage: ShowProps [window]") + return 1; + end +else + root = Select_Window(c) +end + +DumpProps(c, root) + +c = nil + +return 0 diff --git a/lua-binding.pl b/lua-binding.pl new file mode 100755 index 0000000..4664f0c --- /dev/null +++ b/lua-binding.pl @@ -0,0 +1,813 @@ +#!/usr/bin/perl + +# Known bugs: +# - Does not support _checked or _unchecked variants of function calls +# - Allows Lua to underflow and (maybe) crash C, when it should lua_error instead (so pcall can catch it) +# - ChangeProperty is limited to the 8-bit datatype + +# Known warts: +# - Should get string lengths (and other lengths) from Lua, instead of requiring the length to be passed from the script + +use warnings; +use strict; + +use XML::Simple qw(:strict); + + +my %xcbtype = ( + BOOL => 'uint8_t', + BYTE => 'uint8_t', + CARD8 => 'uint8_t', + CARD16 => 'uint16_t', + CARD32 => 'uint32_t', + INT8 => 'int8_t', + INT16 => 'int16_t', + INT32 => 'int32_t', + + char => 'const char', + void => 'const void', # Hack, to partly support ChangeProperty, until we can reverse 'op'. + float => 'float', + double => 'double', +); + +my %luatype = ( + BOOL => 'boolean', + BYTE => 'integer', + CARD8 => 'integer', + CARD16 => 'integer', + CARD32 => 'integer', + INT8 => 'integer', + INT16 => 'integer', + INT32 => 'integer', + + char => 'integer', + void => 'integer', # Hack, to partly support ChangeProperty, until we can reverse 'op'. + float => 'number', + double => 'number', +); + +my %luachecktype = ( + BOOL => 'LUA_TBOOLEAN', + BYTE => 'LUA_TNUMBER', + CARD8 => 'LUA_TNUMBER', + CARD16 => 'LUA_TNUMBER', + CARD32 => 'LUA_TNUMBER', + INT8 => 'LUA_TNUMBER', + INT16 => 'LUA_TNUMBER', + INT32 => 'LUA_TNUMBER', + + char => 'LUA_TNUMBER', + void => 'LUA_TNIL', + float => 'LUA_TNUMBER', + double => 'LUA_TNUMBER', +); + +sub mangle($;$) +{ + my %simple = ( + CHAR2B => 1, + INT64 => 1, + FLOAT32 => 1, + FLOAT64 => 1, + BOOL32 => 1, + STRING8 => 1, + Family_DECnet => 1 + ); + my $name = shift; + my $clean = shift; + my $mangled = ''; + + $mangled = 'xcb_' unless ($clean); + + if ($simple{$name}) { + $mangled .= lc($name); + } else { + while (length ($name)) { + $name =~ /^(.)(.*)$/; + my $char = $1; + my $next = $2; + + $mangled .= lc($char); + + if ( + $name =~ /^[[:lower:]][[:upper:]]/ || + $name =~ /^\d[[:alpha:]]/ || + $name =~ /^[[:alpha:]]\d/ || + $name =~ /^[[:upper:]][[:upper:]][[:lower:]]/) + { + $mangled .= '_'; + } + + $name = $next; + } + } + return $mangled; +} + +sub cname($) +{ + my %bad = ( + new => 1, + delete => 1, + class => 1, + operator => 1 ); + my $name = shift; + return "_$name" if ($bad{$name}); + return $name; +} + +sub do_push($$;$) +{ + my $indent = ' ' x ((shift) * 4); + my $type = shift; + my $name = shift; + + my $base; + + if (defined($name)) { + $base = "x->".cname($name); + } else { + $base = "i.data"; + } + + if ($luatype{$type}) { + # elemental type + $base = '*'.$base if (!defined($name)); + print OUT $indent."lua_push".$luatype{$type}."(L, $base);\n"; + } else { + # complex type + $base = '&'.$base if (defined($name)); + print OUT $indent."push_$type(L, $base);\n"; + } +} + +sub do_push_list($$$$;$) +{ + my $indent = shift; + my $name = shift; + my $type = shift; + my $fn = shift; + my $const = shift; + + my $spaces = ' ' x (($indent) * 4); + my $mtype = mangle($type); + + if ($type eq 'char') { + # String + my $get = mangle($name)."_".$fn; + print OUT $spaces."lua_pushlstring(L, "; + print OUT "$get(x), "; + print OUT $get."_length(x) );\n"; + print OUT $spaces."lua_setfield(L, -2, \"$fn\");\n"; + } elsif ($type eq 'void') { + # Evil hack for GetProperty + my $get = mangle($name)."_".$fn; + print OUT $spaces."lua_pushlstring(L, "; + print OUT "$get(x), "; + print OUT $get."_length(x) * (x->format / 8) );\n"; + print OUT $spaces."lua_setfield(L, -2, \"$fn\");\n"; + } elsif (defined($luatype{$type})) { + # Array of elemental type + my $get = mangle($name)."_".$fn; + print OUT $spaces."{\n"; + print OUT "$spaces const ".$xcbtype{$type}." *i;\n"; + print OUT "$spaces int len, j;\n\n"; + if (defined($const)) { + my $cfn = cname($fn); + my $len = $const->[0]; + print OUT "$spaces i = x->$cfn;\n"; + print OUT "$spaces len = $len;\n"; + } else { + print OUT "$spaces i = $get(x);\n"; + print OUT "$spaces len = $get"."_length(x);\n"; + } + print OUT "$spaces lua_newtable(L);\n"; + print OUT "$spaces for (j=0; j{'struct'}}) { + my $name = $struct->{'name'}; + my $xcbname = mangle($name).'_t'; + my $dogetter = 1; + + my %nostatic = ( # These structs are used from the base protocol + xcb_setup_t => 1, + ); + + print OUT "static " unless ($nostatic{$xcbname}); + + print OUT "void push_$name (lua_State *L, const $xcbname *x)\n"; + print OUT "{\n"; + print OUT " lua_newtable(L);\n"; + foreach my $field (@{$struct->{'field'}}) { + my $fn = $field->{'name'}; + do_push(1, $field->{'type'}, $fn); + print OUT " lua_setfield(L, -2, \"$fn\");\n"; + } + if ($struct->{'list'}) { + $dogetter = 0; # If it has a list, the get half shouldn't (can't?) be needed. + foreach my $list (@{$struct->{'list'}}) { + do_push_list(1, $name, $list->{'type'}, $list->{'name'}, $list->{'value'}); + } + } + print OUT "}\n\n"; + + if ($dogetter) { + print OUT "static void get_$name (lua_State *L, int pos, $xcbname *x)\n"; + print OUT "{\n"; + print OUT " luaL_checktype(L, pos, LUA_TTABLE);\n"; + foreach my $field (@{$struct->{'field'}}) { + my $fn = $field->{'name'}; + my $type = $luatype{$field->{'type'}}; + my $checktype = $luachecktype{$field->{'type'}}; + my $cfn = cname($fn); + print OUT " lua_getfield(L, pos, \"$fn\");\n"; + print OUT " luaL_checktype(L, -1, $checktype);\n"; + print OUT " x->$cfn = lua_to$type(L, -1);\n"; + print OUT " lua_pop(L, 1);\n"; + } + print OUT "}\n\n"; + } + } + foreach my $union (@{$xcb->{'union'}}) { + my $name = $union->{'name'}; + my $xcbname = mangle($name).'_t'; + + print OUT "static "; + print OUT "void push_$name (lua_State *L, const $xcbname *x)\n"; + print OUT "{\n"; + print OUT " lua_newtable(L);\n"; + foreach my $field (@{$union->{'field'}}) { + my $fn = $field->{'name'}; + do_push(1, $field->{'type'}, $fn); + print OUT " lua_setfield(L, -2, \"$fn\");\n"; + } + if ($union->{'list'}) { + foreach my $list (@{$union->{'list'}}) { + do_push_list(1, $name, $list->{'type'}, $list->{'name'}, $list->{'value'}); + } + } + print OUT "}\n\n"; + } +} + +sub do_typedefs($) +{ + my $xcb = shift; + foreach my $tdef (@{$xcb->{'typedef'}}) { + $xcbtype{$tdef->{'newname'}} = $xcbtype{$tdef->{'oldname'}}; + $luatype{$tdef->{'newname'}} = $luatype{$tdef->{'oldname'}}; + $luachecktype{$tdef->{'newname'}} = $luachecktype{$tdef->{'oldname'}}; + } + foreach my $tdef (@{$xcb->{'xidtype'}}) { + $xcbtype{$tdef->{'name'}} = $xcbtype{'CARD32'}; + $luatype{$tdef->{'name'}} = $luatype{'CARD32'}; + $luachecktype{$tdef->{'name'}} = $luachecktype{'CARD32'}; + } + foreach my $tdef (@{$xcb->{'xidunion'}}) { + $xcbtype{$tdef->{'name'}} = $xcbtype{'CARD32'}; + $luatype{$tdef->{'name'}} = $luatype{'CARD32'}; + $luachecktype{$tdef->{'name'}} = $luachecktype{'CARD32'}; + } +} + +sub get_vartype($) +{ + my $type = shift; + return $xcbtype{$type} if (defined ($xcbtype{$type})); + return mangle($type)."_t"; +} + +sub do_get($$$;$) +{ + my $index = shift; + my $type = shift; + my $name = shift; + my $indent = shift; + + $indent = 1 unless (defined($indent)); + $indent = ' ' x (($indent) * 4); + + if ($luatype{$type}) { + # elemental type + print OUT $indent.$name." = lua_to".$luatype{$type}."(L, $index);\n"; + } else { + # complex type + print OUT $indent."get_$type(L, $index, &$name);\n"; + } +} + +sub do_get_list($$$;$) +{ + my $index = shift; + my $type = shift; + my $name = shift; + my $indent; + my $autolen = shift; + + $indent = 1 unless (defined($indent)); + my $spaces = ' ' x (($indent) * 4); + + if ($type eq 'char' or $type eq 'void') { + # Simple case: String + print OUT $spaces.$name." = lua_tostring(L, $index);\n"; + return; + } + + print OUT $spaces."{\n"; + print OUT "$spaces size_t i, count;\n"; + print OUT "$spaces int idx = lua_gettop(L) + 1;\n"; + print OUT "$spaces luaL_checktype(L, $index, LUA_TTABLE);\n"; + if ($autolen) { + print OUT "$spaces ${name}_len = lua_objlen(L, $index);\n"; + print OUT "$spaces count = ${name}_len;\n"; + } else { + print OUT "$spaces count = lua_objlen(L, $index);\n"; + } + print OUT "$spaces $name = malloc(count * sizeof("; + + if ($xcbtype{$type}) { + # elemental type + print OUT $xcbtype{$type}; + } else { + # complex type + print OUT mangle($type)."_t"; + } + print OUT "));\n"; + print OUT "$spaces for (i=0; i{'request'}}) { + # Function header + print OUT "static int "; + print OUT $req->{'name'}; + print OUT "(lua_State *L)\n{\n"; + + my $cookie = mangle($req->{'name'})."_cookie_t"; + + # Declare variables + if (defined($req->{'reply'})) { + print OUT " $cookie *cookie;\n"; + } + print OUT " xcb_connection_t *c;\n"; + foreach my $var (@{$req->{'field'}}) { + print OUT " ".get_vartype($var->{'type'})." "; + print OUT $var->{'name'}.";\n"; + } + if (defined($req->{'list'})) { + foreach my $var (@{$req->{'list'}}) { + if (!defined($var->{'fieldref'}) && !defined($var->{'op'}) && !defined($var->{'value'})) { + print OUT " uint32_t "; + print OUT $var->{'name'}."_len;\n"; + } + print OUT " ".get_vartype($var->{'type'})." *"; + print OUT $var->{'name'}.";\n"; + } + } + if (defined($req->{'valueparam'})) { + foreach my $var (@{$req->{'valueparam'}}) { + print OUT " ".get_vartype($var->{'value-mask-type'})." "; + print OUT $var->{'value-mask-name'}.";\n"; + print OUT " uint32_t *".$var->{'value-list-name'}.";\n"; + } + } + print OUT "\n"; + + # Set up userdata + if (defined($req->{'reply'})) { + print OUT " lua_newuserdata(L, sizeof(*cookie));\n"; + print OUT " luaL_getmetatable(L, \"$cookie\");\n"; + print OUT " lua_setmetatable(L, -2);\n"; + print OUT " cookie = ($cookie *)lua_touserdata(L, -1);\n"; + print OUT " lua_createtable(L, 0, 2);\n"; + print OUT " lua_pushvalue(L, 1);\n"; + print OUT " lua_setfield(L, -2, \"display\");\n"; + print OUT " lua_pushboolean(L, 1);\n"; + print OUT " lua_setfield(L, -2, \"collect\");\n"; + print OUT " lua_setfenv(L, -2);\n\n"; + } + + # Read variables from lua + print OUT " c = ((xcb_connection_t **)luaL_checkudata(L, 1, \"XCB.display\"))[0];\n"; + my $index = 1; + foreach my $var (@{$req->{'field'}}) { + do_get(++$index, $var->{'type'}, $var->{'name'}); + } + if (defined($req->{'list'})) { + foreach my $var (@{$req->{'list'}}) { + if (!defined($var->{'fieldref'}) && !defined($var->{'op'}) && !defined($var->{'value'})) { + # do_get(++$index, 'CARD32', $var->{'name'}."_len"); + do_get_list(++$index, $var->{'type'}, $var->{'name'}, 1); + } else { + do_get_list(++$index, $var->{'type'}, $var->{'name'}); + } + } + } + if (defined($req->{'valueparam'})) { + foreach my $var (@{$req->{'valueparam'}}) { + do_get(++$index, $var->{'value-mask-type'}, $var->{'value-mask-name'}); + do_get_list(++$index, 'CARD32', $var->{'value-list-name'}); + } + } + print OUT "\n"; + + # Function call + print OUT " "; + if (defined($req->{'reply'})) { + print OUT "*cookie = "; + } + print OUT mangle($req->{'name'})."("; + my $glob = 'c, '; + foreach my $var (@{$req->{'field'}}) { + $glob .= $var->{'name'}; + $glob .= ", "; + } + if (defined($req->{'list'})) { + foreach my $var (@{$req->{'list'}}) { + if (!defined($var->{'fieldref'}) && !defined($var->{'op'}) && !defined($var->{'value'})) { + $glob .= $var->{'name'}.'_len'; + $glob .= ", "; + } + $glob .= $var->{'name'}; + $glob .= ", "; + } + } + if (defined($req->{'valueparam'})) { + foreach my $var (@{$req->{'valueparam'}}) { + $glob .= $var->{'value-mask-name'}; + $glob .= ", "; + $glob .= $var->{'value-list-name'}; + $glob .= ", "; + } + } + chop $glob; chop $glob; # removing trailing comma + print OUT "$glob);\n\n"; + + # Cleanup + if (defined($req->{'list'})) { + foreach my $var (@{$req->{'list'}}) { + if ($var->{'type'} ne 'char' and $var->{'type'} ne 'void') { + print OUT " free(". $var->{'name'}.");\n"; + } + } + } + + if (defined($req->{'valueparam'})) { + foreach my $var (@{$req->{'valueparam'}}) { + print OUT " free(". $var->{'value-list-name'}.");\n"; + } + } + + my $retcount = 0; + $retcount = 1 if (defined($req->{'reply'})); + print OUT " return $retcount;\n}\n\n"; + + my $manglefunc = mangle($req->{'name'}, 1); + $func->{$manglefunc} = $req->{'name'}; + } +} + +sub do_events($) +{ + my $xcb = shift; + my %events; + + foreach my $event (@{$xcb->{'event'}}) { + my $xcbev = mangle($event->{'name'})."_event_t"; + print OUT "/* This function adds the remaining fields into the table\n that is on the top of the stack */\n"; + print OUT "static void set_"; + print OUT $event->{'name'}; + print OUT "(lua_State *L, xcb_generic_event_t *event)\n{\n"; + print OUT " $xcbev *x = ($xcbev *)event;\n"; + foreach my $var (@{$event->{'field'}}) { + my $name = $var->{'name'}; + do_push(1, $var->{'type'}, $name); + print OUT " lua_setfield(L, -2, \"$name\");\n"; + } + print OUT "}\n\n"; + $events{$event->{'number'}} = 'set_'.$event->{'name'}; + } + + foreach my $event (@{$xcb->{'eventcopy'}}) { + $events{$event->{'number'}} = 'set_'.$event->{'ref'}; + } + + print OUT "static void init_events()\n{\n"; + foreach my $i (sort { $a <=> $b } keys %events) { + print OUT " RegisterEvent($i, $events{$i});\n"; + } + print OUT "}\n\n"; +} + +sub do_replies($\%\%) +{ + my $xcb = shift; + my $func = shift; + my $collect = shift; + + foreach my $req (@{$xcb->{'request'}}) { + my $rep = $req->{'reply'}; + next unless defined($rep); + + my $name = mangle($req->{'name'}); + my $cookie = $name."_cookie_t"; + + $collect->{$cookie} = $req->{'name'}."_gc"; + + # Garbage collection function + print OUT "static int "; + print OUT $req->{'name'}.'_gc'; + print OUT "(lua_State *L)\n{\n"; + print OUT " $cookie *cookie = ($cookie *)luaL_checkudata(L, 1, \"$cookie\");\n"; + print OUT " lua_getfenv(L, 1);\n"; + print OUT " lua_getfield(L, -1, \"collect\");\n"; + print OUT " if (lua_toboolean(L, -1)) {\n"; + print OUT " xcb_connection_t *c;\n"; + print OUT " xcb_generic_error_t *e = NULL;\n"; + print OUT " $name"."_reply_t *x = NULL;\n"; + print OUT " lua_getfield(L, -2, \"display\");\n"; + print OUT " c = ((xcb_connection_t **)luaL_checkudata(L, -1, \"XCB.display\"))[0];\n\n"; + + print OUT " lua_getfenv(L, -1);\n"; + print OUT " lua_getfield(L, -1, \"closed\");\n"; + print OUT " if (!lua_toboolean(L, -1))\n"; + print OUT " x = $name"."_reply(c, *cookie, &e);\n\n"; + + print OUT " if (x) free(x);\n"; + # TODO: Needs a way to report errors in GC'd replies + print OUT " if (e) free(e);\n"; + print OUT " }\n"; + print OUT " return 0;\n"; + print OUT "}\n\n"; + + # Function header + print OUT "static int "; + print OUT $req->{'name'}.'_reply'; + print OUT "(lua_State *L)\n{\n"; + + # Declare variables + print OUT " xcb_connection_t *c;\n"; + print OUT " xcb_generic_error_t *e = NULL;\n"; + print OUT " $name"."_reply_t *x;\n"; + print OUT " $cookie *cookie;\n"; + print OUT "\n"; + + # Read variables from lua + print OUT " c = ((xcb_connection_t **)luaL_checkudata(L, 1, \"XCB.display\"))[0];\n"; + print OUT " lua_getfenv(L, 1);\n"; + print OUT " lua_getfield(L, -1, \"closed\");\n"; + print OUT " if (lua_toboolean(L, -1))\n"; + print OUT " luaL_error(L, \"Error: already disconnected\");\n"; + print OUT " lua_pop(L, 2);\n\n"; + + print OUT " cookie = ($cookie *)luaL_checkudata(L, 2, \"$cookie\");\n"; + print OUT "\n"; + + # Function call + print OUT " x = $name"."_reply(c, *cookie, &e);\n\n"; + + # Mark reply as not collectable + print OUT " lua_getfenv(L, 2);\n"; + print OUT " lua_getfield(L, -1, \"collect\");\n"; + print OUT " if (!lua_toboolean(L, -1)) {\n"; + print OUT " luaL_error(L, \"Error: Attempted to wait for the same %s cookie twice\", \"$name\");\n"; + print OUT " }\n"; + print OUT " lua_pushboolean(L, 0);\n"; + print OUT " lua_setfield(L, -3, \"collect\");\n"; + print OUT " lua_pop(L, 2);\n\n"; + + # Push reply to lua + print OUT " if (x) {\n"; + print OUT " lua_newtable(L);\n"; + foreach my $var (@{$rep->[0]->{'field'}}) { + my $name = $var->{'name'}; + do_push(2, $var->{'type'}, $name); + print OUT " lua_setfield(L, -2, \"$name\");\n"; + } + if (defined $rep->[0]->{'list'}) { + foreach my $list (@{$rep->[0]->{'list'}}) { + do_push_list(2, $req->{'name'}, $list->{'type'}, $list->{'name'}, $list->{'value'}); + } + } + + print OUT " free(x);\n"; + print OUT " } else\n"; + print OUT " lua_pushnil(L);\n\n"; + + print OUT " if (e) {\n"; + print OUT " push_ERROR(L, e);\n"; + print OUT " free(e);\n"; + print OUT " } else\n"; + print OUT " lua_pushnil(L);\n\n"; + + print OUT " return 2;\n}\n\n"; + + my $manglefunc = mangle($req->{'name'}, 1); + $func->{$manglefunc.'_reply'} = $req->{'name'}.'_reply'; + } +} + +sub do_enums($) +{ + my $xcb = shift; + + print OUT "/* This function adds enums into the table that is on the top of the stack */\n"; + print OUT "static void set_enums(lua_State *L)\n{\n"; + + foreach my $enum (@{$xcb->{'enum'}}) { + print OUT " lua_newtable(L);\n"; + foreach my $item (@{$enum->{'item'}}) { + my $value = $item->{'value'}; + my $bit = $item->{'bit'}; + if ($value) { + print OUT " lua_pushinteger(L, $value->[0]);\n"; + } elsif ($bit) { + print OUT " lua_pushinteger(L, 1u << $bit->[0]);\n"; + } else { + die ("Unexpected enum type on ".$item->{'name'}."\n"); + } + + my $name = mangle($item->{'name'}, 1); + $name = uc($name); + print OUT " lua_setfield(L, -2, \"$name\");\n"; + } + my $name = mangle($enum->{'name'}, 1); + $name = uc($name); + print OUT " lua_setfield(L, -2, \"$name\");\n"; + } + + # Events + print OUT "\n lua_newtable(L);\n"; + foreach my $event (@{$xcb->{'event'}}) { + my $name = $event->{'name'}; + my $number = $event->{'number'}; + print OUT " lua_pushinteger(L, $number);\n"; + print OUT " lua_setfield(L, -2, \"$name\");\n"; + } + + foreach my $event (@{$xcb->{'eventcopy'}}) { + my $name = $event->{'name'}; + my $number = $event->{'number'}; + print OUT " lua_pushinteger(L, $number);\n"; + print OUT " lua_setfield(L, -2, \"$name\");\n"; + } + print OUT " lua_setfield(L, -2, \"event\");\n"; + + # Errors + print OUT "\n lua_newtable(L);\n"; + foreach my $error (@{$xcb->{'error'}}) { + my $name = $error->{'name'}; + my $number = $error->{'number'}; + print OUT " lua_pushinteger(L, $number);\n"; + print OUT " lua_setfield(L, -2, \"$name\");\n"; + } + + foreach my $error (@{$xcb->{'errorcopy'}}) { + my $name = $error->{'name'}; + my $number = $error->{'number'}; + print OUT " lua_pushinteger(L, $number);\n"; + print OUT " lua_setfield(L, -2, \"$name\");\n"; + } + print OUT " lua_setfield(L, -2, \"error\");\n"; + + print OUT "}\n\n"; +} + +sub do_gcs($\%) +{ + my $xcb = shift; + my $collect = shift; + + print OUT "static void init_gcs(lua_State *L)\n{\n"; + foreach my $cookie (keys(%$collect)) { + my $func = $collect->{$cookie}; + print OUT " luaL_newmetatable(L, \"$cookie\");\n"; + print OUT " lua_pushcfunction(L, $func);\n"; + print OUT " lua_setfield(L, -2, \"__gc\");\n"; + print OUT " lua_pop(L, 1);\n"; + } + print OUT "}\n\n"; +} + +sub do_init($\%) +{ + my $xcb = shift; + my $func = shift; + + print OUT "/* This function adds function calls into the table\n that is on the top of the stack */\n"; + print OUT "void init_".$xcb->{'header'}."(lua_State *L)\n{\n"; + print OUT " init_events(L);\n"; + print OUT " init_gcs(L);\n"; + print OUT " set_enums(L);\n"; + foreach my $name (keys %{$func}) { + my $funcname = $func->{$name}; + print OUT " lua_pushcfunction(L, $funcname);\n"; + print OUT " lua_setfield(L, -2, \"$name\");\n"; + } + print OUT "}\n"; +} + + +my @files; + +opendir(DIR, '.'); +@files = grep { /\.xml$/ } readdir(DIR); +closedir DIR; + +foreach my $name (@files) { + $name =~ s/\.xml$//; + + my $xcb = XMLin("$name.xml", KeyAttr => undef, ForceArray => 1); + + open(OUT, ">$name.c") or die ("Cannot open $name.c for writing"); + +print OUT < +#include + +#include +#include + +#include +#include +#include + +typedef void (*eventFunc)(lua_State *L, xcb_generic_event_t *); +extern void RegisterEvent(int index, eventFunc func); + +void push_ERROR(lua_State *L, xcb_generic_error_t *e) +{ + xcb_value_error_t *ve = (xcb_value_error_t *)e; + lua_newtable(L); + + lua_pushinteger(L, ve->response_type); + lua_setfield(L, -2, "response_type"); + lua_pushinteger(L, ve->error_code); + lua_setfield(L, -2, "error_code"); + lua_pushinteger(L, ve->sequence); + lua_setfield(L, -2, "sequence"); + lua_pushinteger(L, ve->bad_value); + lua_setfield(L, -2, "bad_value"); + lua_pushinteger(L, ve->minor_opcode); + lua_setfield(L, -2, "minor_opcode"); + lua_pushinteger(L, ve->major_opcode); + lua_setfield(L, -2, "major_opcode"); + + lua_pushinteger(L, e->full_sequence); + lua_setfield(L, -2, "full_sequence"); +} + +eot + ; + + my %functions; + my %collectors; + do_typedefs($xcb); + do_structs($xcb); + do_events($xcb); + do_requests($xcb, %functions); + do_replies($xcb, %functions, %collectors); + do_enums($xcb); + do_gcs($xcb, %collectors); + do_init($xcb, %functions); + close OUT; +} diff --git a/lxcb.c b/lxcb.c new file mode 100644 index 0000000..b7d35d8 --- /dev/null +++ b/lxcb.c @@ -0,0 +1,240 @@ +#include +#include +#include +#include + +#include + +#define MT_DISPLAY "XCB.display" + + +extern void push_ERROR(lua_State *L, xcb_generic_error_t *e); + +typedef void (*eventFunc)(lua_State *L, xcb_generic_event_t *); +eventFunc push_event[256] = { NULL }; + +void RegisterEvent(int index, eventFunc func) +{ + push_event[index] = func; +} + +static void PushEvent(lua_State *L, xcb_generic_event_t *event) +{ + lua_newtable(L); + lua_pushinteger(L, event->response_type); + lua_setfield(L, -2, "response_type"); + lua_pushinteger(L, event->sequence); + lua_setfield(L, -2, "sequence"); + lua_pushinteger(L, event->full_sequence); + lua_setfield(L, -2, "full_sequence"); + + if (push_event[event->response_type]) + push_event[event->response_type](L, event); +} + +static int Connect(lua_State *L) +{ + xcb_connection_t *c; + int screen; + const char *name = NULL; + + if (!lua_isnil(L, 1)) + name = lua_tostring(L, 1); + c = xcb_connect(name, &screen); + if (!c) + return 0; + + lua_newuserdata(L, sizeof(xcb_connection_t *)); + ((xcb_connection_t **)lua_touserdata(L, -1))[0] = c; + luaL_getmetatable(L, MT_DISPLAY); + lua_setmetatable(L, -2); + + lua_newtable(L); + lua_setfenv(L, -2); + + lua_pushinteger(L, screen); + return 2; +} + +static int gc_connection(lua_State *L) +{ + xcb_connection_t *c; + c = ((xcb_connection_t **)luaL_checkudata(L, 1, MT_DISPLAY))[0]; + + lua_getfenv(L, 1); + lua_getfield(L, -1, "closed"); + if (!lua_toboolean(L, -1)) + xcb_disconnect(c); + + return 0; +} + +static int Disconnect(lua_State *L) +{ + xcb_connection_t *c; + c = ((xcb_connection_t **)luaL_checkudata(L, 1, MT_DISPLAY))[0]; + + lua_getfenv(L, 1); + lua_getfield(L, -1, "closed"); + if (lua_toboolean(L, -1)) + luaL_error(L, "Error: already disconnected"); + + lua_pushboolean(L, 1); + lua_setfield(L, -3, "closed"); + + xcb_disconnect(c); + return 0; +} + +static int Flush(lua_State *L) +{ + xcb_connection_t *c; + c = ((xcb_connection_t **)luaL_checkudata(L, 1, MT_DISPLAY))[0]; + + lua_pushinteger(L, xcb_flush(c)); + return 1; +} + +static int GenerateID(lua_State *L) +{ + lua_pushinteger(L, xcb_generate_id(((xcb_connection_t **)luaL_checkudata(L, 1, MT_DISPLAY))[0])); + + return 1; +} + + +extern void push_Setup(lua_State *L, const xcb_setup_t *x); //Temporary until I get headers sorted out + +static int GetSetup(lua_State *L) +{ + xcb_connection_t *c; + c = ((xcb_connection_t **)luaL_checkudata(L, 1, MT_DISPLAY))[0]; + + lua_getfenv(L, 1); + lua_getfield(L, -1, "closed"); + if (lua_toboolean(L, -1)) + luaL_error(L, "Error: already disconnected"); + + push_Setup(L, xcb_get_setup(c)); + return 1; +} + +static int PollForEvent(lua_State *L) +{ + xcb_connection_t *c; + xcb_generic_event_t *e; + + c = ((xcb_connection_t **)luaL_checkudata(L, 1, MT_DISPLAY))[0]; + + lua_getfenv(L, 1); + lua_getfield(L, -1, "closed"); + if (lua_toboolean(L, -1)) + luaL_error(L, "Error: already disconnected"); + + e = xcb_poll_for_event(c); + if (!e) + lua_pushnil(L); + else if (!e->response_type) + push_ERROR(L, (xcb_generic_error_t *)e); + else + PushEvent(L, e); + + if (e) + free(e); + + return 1; +} + +static int WaitForEvent(lua_State *L) +{ + xcb_connection_t *c; + xcb_generic_event_t *e; + + c = ((xcb_connection_t **)luaL_checkudata(L, 1, MT_DISPLAY))[0]; + + lua_getfenv(L, 1); + lua_getfield(L, -1, "closed"); + if (lua_toboolean(L, -1)) + luaL_error(L, "Error: already disconnected"); + + e = xcb_wait_for_event(c); + if (!e) + lua_pushnil(L); + else if (!e->response_type) + push_ERROR(L, (xcb_generic_error_t *)e); + else + PushEvent(L, e); + + if (e) + free(e); + + return 1; +} + +static int HasError(lua_State *L) +{ + xcb_connection_t *c; + c = ((xcb_connection_t **)luaL_checkudata(L, 1, MT_DISPLAY))[0]; + + lua_getfenv(L, 1); + lua_getfield(L, -1, "closed"); + if (lua_toboolean(L, -1)) + luaL_error(L, "Error: already disconnected"); + + lua_pushboolean(L, xcb_connection_has_error(c)); + return 1; +} + + + +static luaL_Reg lxcb[] = { + { "connect", Connect }, + { NULL, NULL } +}; + +static luaL_Reg display_m[] = { + { "__gc", gc_connection }, + { "disconnect", Disconnect }, + { "flush", Flush }, + { "generate_id", GenerateID }, + { "get_setup", GetSetup }, + { "poll_for_event", PollForEvent }, + { "wait_for_event", WaitForEvent }, + { "has_error", HasError }, + /* TODO: + { "connect_to_display_with_auth_info", ConnectToDisplayWithAuthInfo }, + { "connect_to_fd", ConnectToFD }, + { "get_file_descriptor", GetFileDescriptor }, + { "get_maximum_request_length", GetMaximumRequestLength }, + { "parse_display", ParseDisplay }, + { "prefetch_extension_data", PrefetchExtensionData }, + { "prefetch_maximum_request_length", PrefetchMaximumRequestLength }, + { "query_extension_reply", QueryExtensionReply }, + { "request_check", RequestCheck }, + */ + { NULL, NULL } +}; + +#ifdef _MSC_VER +#define DLLEXPORT __declspec(dllexport) +#else +/* Must be gcc if not MSC */ +#define DLLEXPORT __attribute__((visibility("default"))) +#endif + +extern void init_xproto(lua_State *L); // Temporary, until I get headers sorted. + +DLLEXPORT int luaopen_lxcb(lua_State *L) +{ + luaL_register(L, "lxcb", lxcb); + + luaL_newmetatable(L, MT_DISPLAY); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_register(L, NULL, display_m); + + init_xproto(L); + lua_pop(L, 1); + + return 1; +} -- 2.11.4.GIT