info-beamer-1.0~pre4/0000755000175000017500000000000012456167031012336 5ustar nknkinfo-beamer-1.0~pre4/contrib/0000755000175000017500000000000012452774240014000 5ustar nknkinfo-beamer-1.0~pre4/contrib/remote/0000755000175000017500000000000012452774240015273 5ustar nknkinfo-beamer-1.0~pre4/contrib/remote/README.txt0000644000175000017500000000121512452774240016770 0ustar nknkRemote input example ==================== This directory contains a remote input example. The lua module remote.lua installs new event sources for mouse/keyboard input. All inputs will be sent to info-beamer using OSC. The client program remote.py uses python, pygame and pyOSC to send mouse/keyboard input to info-beamer. You can have multiple clients sending to the same info-beamer node. To tell them apart, you can use prefix them. node.lua is a minimal example. `install_remote_input` adds new OSC listeners for the given prefix and dispatches incoming input events to 5 new event handlers: keydown, keyup, mousedown, mouseup and mousemotion. info-beamer-1.0~pre4/contrib/remote/mouse.png0000644000175000017500000000024712452774240017134 0ustar nknkPNG  IHDR 'sRGB PLTE#ctRNS@f?IDAT%1 Aj"! hDCLc`"8b!q LOO~+NO IENDB`info-beamer-1.0~pre4/contrib/remote/node.lua0000644000175000017500000000101312452774240016716 0ustar nknkgl.setup(320, 200) remote = require "remote" remote.install_remote_input("player1") remote.install_remote_input("player2") node.event("keydown", function(...) print(...) end) node.event("mousedown", function(...) print(...) end) local mice = {} node.event("mousemotion", function(x, y, source) mice[source] = { x = x, y = y } end) util.auto_loader(_G) function node.render() gl.clear(1,1,1,1) for player, pos in pairs(mice) do mouse:draw(pos.x, pos.y, pos.x+12, pos.y+21, 1.0) end end info-beamer-1.0~pre4/contrib/remote/remote.lua0000644000175000017500000001460212452774240017274 0ustar nknkmodule("remote", package.seeall) local KEYMAP = { [ 0] = "UNKNOWN", [ 8] = "BACKSPACE", [ 9] = "TAB", [ 12] = "CLEAR", [ 13] = "RETURN", [ 19] = "PAUSE", [ 27] = "ESCAPE", [ 32] = "SPACE", [ 33] = "EXCLAIM", [ 34] = "QUOTEDBL", [ 35] = "HASH", [ 36] = "DOLLAR", [ 38] = "AMPERSAND", [ 39] = "QUOTE", [ 40] = "LEFTPAREN", [ 41] = "RIGHTPAREN", [ 42] = "ASTERISK", [ 43] = "PLUS", [ 44] = "COMMA", [ 45] = "MINUS", [ 46] = "PERIOD", [ 47] = "SLASH", [ 48] = "0", [ 49] = "1", [ 50] = "2", [ 51] = "3", [ 52] = "4", [ 53] = "5", [ 54] = "6", [ 55] = "7", [ 56] = "8", [ 57] = "9", [ 58] = "COLON", [ 59] = "SEMICOLON", [ 60] = "LESS", [ 61] = "EQUALS", [ 62] = "GREATER", [ 63] = "QUESTION", [ 64] = "AT", [ 91] = "LEFTBRACKET", [ 92] = "BACKSLASH", [ 93] = "RIGHTBRACKET", [ 94] = "CARET", [ 95] = "UNDERSCORE", [ 96] = "BACKQUOTE", [ 97] = "a", [ 98] = "b", [ 99] = "c", [100] = "d", [101] = "e", [102] = "f", [103] = "g", [104] = "h", [105] = "i", [106] = "j", [107] = "k", [108] = "l", [109] = "m", [110] = "n", [111] = "o", [112] = "p", [113] = "q", [114] = "r", [115] = "s", [116] = "t", [117] = "u", [118] = "v", [119] = "w", [120] = "x", [121] = "y", [122] = "z", [127] = "DELETE", [160] = "WORLD_0", [161] = "WORLD_1", [162] = "WORLD_2", [163] = "WORLD_3", [164] = "WORLD_4", [165] = "WORLD_5", [166] = "WORLD_6", [167] = "WORLD_7", [168] = "WORLD_8", [169] = "WORLD_9", [170] = "WORLD_10", [171] = "WORLD_11", [172] = "WORLD_12", [173] = "WORLD_13", [174] = "WORLD_14", [175] = "WORLD_15", [176] = "WORLD_16", [177] = "WORLD_17", [178] = "WORLD_18", [179] = "WORLD_19", [180] = "WORLD_20", [181] = "WORLD_21", [182] = "WORLD_22", [183] = "WORLD_23", [184] = "WORLD_24", [185] = "WORLD_25", [186] = "WORLD_26", [187] = "WORLD_27", [188] = "WORLD_28", [189] = "WORLD_29", [190] = "WORLD_30", [191] = "WORLD_31", [192] = "WORLD_32", [193] = "WORLD_33", [194] = "WORLD_34", [195] = "WORLD_35", [196] = "WORLD_36", [197] = "WORLD_37", [198] = "WORLD_38", [199] = "WORLD_39", [200] = "WORLD_40", [201] = "WORLD_41", [202] = "WORLD_42", [203] = "WORLD_43", [204] = "WORLD_44", [205] = "WORLD_45", [206] = "WORLD_46", [207] = "WORLD_47", [208] = "WORLD_48", [209] = "WORLD_49", [210] = "WORLD_50", [211] = "WORLD_51", [212] = "WORLD_52", [213] = "WORLD_53", [214] = "WORLD_54", [215] = "WORLD_55", [216] = "WORLD_56", [217] = "WORLD_57", [218] = "WORLD_58", [219] = "WORLD_59", [220] = "WORLD_60", [221] = "WORLD_61", [222] = "WORLD_62", [223] = "WORLD_63", [224] = "WORLD_64", [225] = "WORLD_65", [226] = "WORLD_66", [227] = "WORLD_67", [228] = "WORLD_68", [229] = "WORLD_69", [230] = "WORLD_70", [231] = "WORLD_71", [232] = "WORLD_72", [233] = "WORLD_73", [234] = "WORLD_74", [235] = "WORLD_75", [236] = "WORLD_76", [237] = "WORLD_77", [238] = "WORLD_78", [239] = "WORLD_79", [240] = "WORLD_80", [241] = "WORLD_81", [242] = "WORLD_82", [243] = "WORLD_83", [244] = "WORLD_84", [245] = "WORLD_85", [246] = "WORLD_86", [247] = "WORLD_87", [248] = "WORLD_88", [249] = "WORLD_89", [250] = "WORLD_90", [251] = "WORLD_91", [252] = "WORLD_92", [253] = "WORLD_93", [254] = "WORLD_94", [255] = "WORLD_95", [256] = "KP0", [257] = "KP1", [258] = "KP2", [259] = "KP3", [260] = "KP4", [261] = "KP5", [262] = "KP6", [263] = "KP7", [264] = "KP8", [265] = "KP9", [266] = "KP_PERIOD", [267] = "KP_DIVIDE", [268] = "KP_MULTIPLY", [269] = "KP_MINUS", [270] = "KP_PLUS", [271] = "KP_ENTER", [272] = "KP_EQUALS", [273] = "UP", [274] = "DOWN", [275] = "RIGHT", [276] = "LEFT", [277] = "INSERT", [278] = "HOME", [279] = "END", [280] = "PAGEUP", [281] = "PAGEDOWN", [282] = "F1", [283] = "F2", [284] = "F3", [285] = "F4", [286] = "F5", [287] = "F6", [288] = "F7", [289] = "F8", [290] = "F9", [291] = "F10", [292] = "F11", [293] = "F12", [294] = "F13", [295] = "F14", [296] = "F15", [300] = "NUMLOCK", [301] = "CAPSLOCK", [302] = "SCROLLOCK", [303] = "RSHIFT", [304] = "LSHIFT", [305] = "RCTRL", [306] = "LCTRL", [307] = "RALT", [308] = "LALT", [309] = "RMETA", [310] = "LMETA", [311] = "LSUPER", [312] = "RSUPER", [313] = "MODE", [314] = "COMPOSE", [315] = "HELP", [316] = "PRINT", [317] = "SYSREQ", [318] = "BREAK", [319] = "MENU", [320] = "POWER", [321] = "EURO", [322] = "UNDO", } events.keydown = {} events.keyup = {} events.mousedown = {} events.mouseup = {} events.mousemotion = {} function install_remote_input(raw_prefix) if not raw_prefix then prefix = "" else prefix = raw_prefix .. "/" end print("====================") print("you might now start remote.py using this commandline:") print() print("remote.py /" .. PATH .. "/" .. prefix .. " " .. WIDTH .. " " .. HEIGHT) print("====================") util.osc_mapper{ [prefix .. "keyup"] = function(code) node.dispatch("keyup", KEYMAP[code], raw_prefix) end; [prefix .. "keydown"] = function(code) node.dispatch("keydown", KEYMAP[code], raw_prefix) end; [prefix .. "mousedown"] = function(button, x, y) node.dispatch("mousedown", button, x, y, raw_prefix) end; [prefix .. "mouseup"] = function(button, x, y) node.dispatch("mouseup", button, x, y, raw_prefix) end; [prefix .. "mousemotion"] = function(x, y) node.dispatch("mousemotion", x, y, raw_prefix) end; } end return { install_remote_input = install_remote_input; } info-beamer-1.0~pre4/contrib/remote/remote.py0000644000175000017500000000371712452774240017150 0ustar nknkimport sys try: import pygame from pygame.locals import * except ImportError: print "==========================" print "You have to install pygame" print "==========================" raise try: from OSC import OSCClient, OSCMessage # provided by pyOSC except ImportError: print "=========================" print "You have to install pyOSC" print "=========================" raise PORT = 4444 if len(sys.argv) != 5: print "usage: remote " sys.exit(1) addr, path, width, height = sys.argv[1:5] width, height = int(width), int(height) client = OSCClient() client.connect((addr, PORT)) pygame.init() screen = pygame.display.set_mode((width, height)) pygame.display.set_caption('Info Beamer Remote Control') font = pygame.font.Font(None, 16) text = font.render('Sending to info-beamer @ %s:%d' % (addr, PORT), True, (255, 255, 255)) screen.fill((255, 0, 0)) screen.blit(text, ( (width - text.get_width()) / 2, (height - text.get_height()) / 2 )) pygame.display.flip() while 1: event = pygame.event.wait() if event.type == KEYUP: msg = OSCMessage(path + "keyup") msg.append(event.key) client.send(msg) elif event.type == KEYDOWN: if event.key == K_ESCAPE: break msg = OSCMessage(path + "keydown") msg.append(event.key) client.send(msg) elif event.type == MOUSEBUTTONDOWN: msg = OSCMessage(path + "mousedown") msg.append(event.button) msg.append(event.pos[0]) msg.append(event.pos[1]) client.send(msg) elif event.type == MOUSEBUTTONUP: msg = OSCMessage(path + "mouseup") msg.append(event.button) msg.append(event.pos[0]) msg.append(event.pos[1]) client.send(msg) elif event.type == MOUSEMOTION: msg = OSCMessage(path + "mousemotion") msg.append(event.pos[0]) msg.append(event.pos[1]) client.send(msg) info-beamer-1.0~pre4/contrib/netheat.pl0000644000175000017500000000420212452774240015763 0ustar nknk#!/usr/bin/perl use 5.010; use strict; use warnings; use bignum; use Carp; use SNMP::Info; use Data::Dumper; use Net::OpenSoundControl::Client; use Time::HiRes qw(sleep); use Math::BigFloat; use List::Util qw(min); my $sleep_time = 1; my $snmp = SNMP::Info->new(AutoSpecify => 0, Debug => 0, DestHost => '10.0.0.254', Community => 'public', Version => 2, BigInt => 1) or carp "cannot connect to snmp service"; my $osc_client = Net::OpenSoundControl::Client->new( Host => '192.168.23.20', Port => 4444) or carp "could not initialize osc"; my @interface_whitelist = ('alicedsl','qscdsl','eth2'); my @interfaces = grep { $_->{name} ~~ @interface_whitelist and not exists $snmp->if_ignore()->{$_->{idx}} and $snmp->i_up()->{$_->{idx}} eq 'up' and $snmp->i_up_admin()->{$_->{idx}} eq 'up'; } map { {idx=>$_, name=>$snmp->i_name()->{$_}} } keys $snmp->interfaces(); my %override_max_speed = ( 'wan' => 17_000_000, 'alicedsl' => 17_000_000 ); my %speeds = map { ( $_->{name} => ($override_max_speed{$_->{name}} // $snmp->i_speed_raw()->{$_->{idx}} or croak("no speed for interface ".$_->{name}))/8 ) } @interfaces; sub derive_val { state %cache; my ($name, $idx) = @_; my $counter = $snmp->i_octet_in64()->{$idx} + $snmp->i_octet_out64()->{$idx}; my $r = ($counter - ($cache{$idx} // $counter)) / $sleep_time; $cache{$idx} = $counter; return min(1,$r/$speeds{$name}); } sub send_val { my $val = shift; $osc_client->send($val); say join ', ', @$val; } while (1) { eval { $snmp->load_i_octet_in64(); $snmp->load_i_octet_out64(); map { send_val(['/gauge/if/' . $_->{name}, 'f', derive_val($_->{name}, $_->{idx})]); } @interfaces; 1; } or do { print STDERR "error sending counter values: " . $@; }; sleep($sleep_time); } info-beamer-1.0~pre4/contrib/oscproxy.pl0000644000175000017500000000121412452774240016221 0ustar nknk#!/usr/bin/perl use 5.010; use strict; use warnings; use Carp; use Data::Dumper; use Net::OpenSoundControl::Server; use Net::OpenSoundControl::Client; my $client = Net::OpenSoundControl::Client->new( Host => $ARGV[0], Port => 4444) or carp "error creating osc client"; my $server = Net::OpenSoundControl::Server->new( Port => 4444, Handler => \&proxy) or carp "error creating osc server"; sub proxy { eval { my ($sender, $msg) = @_; $msg = [$ARGV[1] . $msg->[0], @$msg[1..2]]; say Dumper($msg); $client->send($msg); } or do { say STDERR "error: $@"; }; } $server->readloop(); info-beamer-1.0~pre4/contrib/test.js0000644000175000017500000000302312452774240015313 0ustar nknkloadedInterfaceName = "GPN News"; interfaceOrientation = "portrait"; send = function() { oscManager.sendOSC(["/news", "ff", multi.xvalue, multi.yvalue]); }; pages = [[ { "name": "refresh", "type": "Button", "bounds": [.6, .9, .2, .1], "startingValue": 0, "isLocal": true, "mode": "contact", "ontouchstart": "interfaceManager.refreshInterface()", "stroke": "#aaa", "label": "refresh", }, { "name": "tabButton", "type": "Button", "bounds": [.8, .9, .2, .1], "mode": "toggle", "stroke": "#aaa", "isLocal": true, "ontouchstart": "if(this.value == this.max) { control.showToolbar(); } else { control.hideToolbar(); }", "label": "menu", // }, { // "name":"myButton", // "type":"Button", // "x" : 0, "y" : 0, // "width" : .25, "height" : .25, // "mode" : "momentary", // "min":10, "max":20, // "midiMin":0, "midiMax":64, // "address" : "/news", // }, { // "name" : "mySlider", // "type" : "Slider", // "x" : 0.25, "y" : 0, // "width" : .25, "height" : .75, // "min" : -1, "max" : 1, // "address" : "/news", // "isVertical" : true, // "isXFader" : false, }, { "name" : "multi", "type" : "MultiTouchXY", "bounds": [0,0,1,1], "isMomentary": false, "maxTouches": 1, "isLocal": true, "ontouchmove": "send();" } ]]; info-beamer-1.0~pre4/doc/0000755000175000017500000000000012452774240013105 5ustar nknkinfo-beamer-1.0~pre4/doc/.gitignore0000644000175000017500000000001412452774240015070 0ustar nknkmanual.html info-beamer-1.0~pre4/doc/README.txt0000644000175000017500000000010612452774240014600 0ustar nknkDocumentation is available on https://info-beamer.com/doc/info-beamer info-beamer-1.0~pre4/samples/0000755000175000017500000000000012452774240014004 5ustar nknkinfo-beamer-1.0~pre4/samples/green/0000755000175000017500000000000012452774240015104 5ustar nknkinfo-beamer-1.0~pre4/samples/green/blue/0000755000175000017500000000000012452774240016033 5ustar nknkinfo-beamer-1.0~pre4/samples/green/blue/node.lua0000644000175000017500000000012012452774240017454 0ustar nknkgl.setup(640, 480) function node.render() gl.clear(0, 0, 1, 1) -- blue end info-beamer-1.0~pre4/samples/green/red/0000755000175000017500000000000012452774240015656 5ustar nknkinfo-beamer-1.0~pre4/samples/green/red/node.lua0000644000175000017500000000011712452774240017305 0ustar nknkgl.setup(100, 800) function node.render() gl.clear(1, 0, 0, 1) -- red end info-beamer-1.0~pre4/samples/green/README.txt0000644000175000017500000000013612452774240016602 0ustar nknkSee the documentation at http://info-beamer.org/doc/ for more information about this example. info-beamer-1.0~pre4/samples/green/node.lua0000644000175000017500000000047612452774240016543 0ustar nknkgl.setup(800, 600) function node.render() gl.clear(0, 1, 0, 1) -- green -- render to image object and draw local red = resource.render_child("red") red:draw(640, 20, 780, 580) -- render an draw without creating an intermediate value resource.render_child("blue"):draw(50, 200, 300, 380) end info-beamer-1.0~pre4/samples/hello/0000755000175000017500000000000012452774240015107 5ustar nknkinfo-beamer-1.0~pre4/samples/hello/README.txt0000644000175000017500000000011312452774240016600 0ustar nknkHello world example. See http://info-beamer.org/doc/ for more information. info-beamer-1.0~pre4/samples/hello/node.lua0000644000175000017500000000022312452774240016534 0ustar nknkgl.setup(1024, 768) font = resource.load_font("silkscreen.ttf") function node.render() font:write(120, 320, "Hello World", 100, 1,1,1,1) end info-beamer-1.0~pre4/samples/image/0000755000175000017500000000000012452774240015066 5ustar nknkinfo-beamer-1.0~pre4/samples/image/beamer.png0000644000175000017500000007605112452774240017040 0ustar nknkPNG  IHDR4c_PLTED,8BJLRRTYZ [[`bb bgii!npotq ut-z|{~*;+7D0<L$5KW.>G Q\,:MWmb1; Q ,th8^TG'~5v*FmQ:!Ȅ0OȎn<'̘4GЉ?vQ-Ղ[9ԕ։iC՜0xrקN݀=݇HXgݢaB8rLUly_gOYpxcTlbu~piWtRNS@fbKGDH pHYs  tIME 2NtEXtCommentCreated with GIMPW IDATxpxꎨU㎰: i5=uJuΝ~Hض3;L/UǗ6 K&Lۜ8N)*pQ2h``{hp-WR)\SH?<+'v8 }YL0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &L0a„ &LұӇOA3&䡇&&5&̐9QY2av{t\kqvzddM7 &4Ȝ9n!Sn4M,g&4\/Z޴,F:Up,O#&l.fޘ)ic&53*V׊Fvl,5| `! #~0„p3Hj T+g%&̟Ǥ4)3& ΀FT"*LL?ay4+& L!xʊF0;CVJ(7j&Nn@JΥ33T0RSrVH0w>CV+NٌcҒ*~B„y3Ļf(a\̼05s< iF0 H@#Lˎt0 7@ sgBݧlgɣ3G?5;{|TV&h8aĜx k'lھyc4[i'5z6/~–61_1ȶ)'nI:Yw2  ̩/=9H+ЊM%91l*M"4U R^ [{aD"Wab9FèER6feY1LөXUI'~–(2G>F-L :R+e)5G&%o "[%t8]`n&_l+̾wVu3dI+ IA`lVMR5}L=%~–(3OKjִ17*u%:mXP/ʎG+l^4[M`8=GO^WfŏV?#V0Pj+@c_vW`7&%uTC$ 2lh֤FY`_3սaKEi TyBuәR6~.DѠ.M٬p5=l1;#lP0;;d7X| KYЌᗞD4d}`MU 2Cϔ1tMЊq_{HS Y'1&KMlL9ircL2_FgQ--7shBj uO8qZnB4 LAc _>&h@1UT]D̼<.ASԉmq=Œ;)'EzIhoyyLxfFNT.gZЌJLբ/%a!Y֊X\ʪ:4;C7)}J>@W7Ւ3hҀ$F unӘa )OaNjfK~\7TiכmQFgˑ\}j 8mO:ω(4L!~*J4.Y}Rji9+mw>VÀ']M);Z}jLP/lI VװgR&ra 3k2h.7rAU*u o MEjQP{BcO?Lϳ^PO_UA*]vdBByV%GCfevqoYE>?ʊ/4pW9W3C3wBC=Mp7$v1N"3§:Ss{yhiִž#(<{[iF$$v0ͶL b؞$t5<"<v٬9&O&'݄3аMaEl&h^h `4[drs4M\  oGǥ% \W6? UР>-?c39d h]4v|}6/ uFi ʚo=}54騖54-:e xv px&А}j KOVx)4U1]мN]%@f݂&2hB2B})@C3yсJ4@='vA&8t!b=N*fSCė\\<4 ji|LMK}K@#ٱ5R-p2{FfX2FlvgފiT@i(tTo a.8{H1bKeMZ0_}LU 4hٴ9hx CQ=J/DxόoxZ,hu(Mј'ojU5yF &dS[.3kAibURb!ik50JB@#Sr8p$ r罟A>s[OJ3EnHAVKJ\@#쬲'N8*չԛ̝^iNzɮS7\`Oc_YU3ysC^GT,ӳag̳[7mڴuۓO=ȌMTX9Ah5_kj.54žDC6J2Y8`b%tY@#, Y,S-Ord[_Uk+=|k`V@ӝ` ueUe̵sL$ } K̔nYQ h-f&$P,N Bn͘d@chkqTJS_dlmLI۾Z23O$Q 9(YB@EJ̦1Ysg}E!W~Ӫ,_egpƂfOF& \b%?;(| *ӧt)mr+*Ic020eLlP@m1EE]hhJ. 4hQٷRhN>Ѻq-ykw] HT60xoG c`Xdt*: Ydʶ189Y qJmu=+?+.4 3:wvGU̩֮bGۼA$\Be}2)Rb02i "l\GO M{B鳊 =?W*014 1 c| nKJ{EY% ;dszh|QX&lx8x*/r;yT t\AsM㊔Պe,YhԪz.+rlԵDtO^h|?<ÅgL`h%|4Td Cl2{;$6341'(ü  n"FjCx*YYP(B@`zr@ӴgMv.ݩIjAǒK QTg|_HW 7&p踒+Cx=fc+hzVʪoh!78:f$4Ƴ,Idv9 &nX9<|mhz11alPw`@AoY7ۏ= `)Y{J,Wʕd Cs}\* 1d3H":48@o/<E#Qh=~Ab:zS(3p<>MMJUmjӢVf%5k93ni7 4?}4:ތA**RG4 3iז%g9&|'?}H4[2ayLl=AT^l妳4TY&92fOfL,kI~]4|Sc%5A73k:=d M,טLX].$144,.;ܒ* W^wۭ_!BLt%|ācYwG{x*A⬠ٯAu 9eXjfW^)eɯ= wiy66hNYf z5u-IIA/c02pU2斛 E+ fdΕtpXKAtuw"SӸ̳=;gϑt=Ec+_ ;BWtjc|!`Bj>uit~9]d60ц&b\ZɆA uk'1=]]x*/r;T ų=Ygɪ̱ ϶fgv:i}N@h +%y95Ee>Y&$׿:2>Y4`qFPs0ulh`Y:SK fv[hz +9ٛϪz՘|Yl[%ىC649H[HwPhL?1pp9fbQc 64ݝNL @,Ocfg;73u(yq* Fӂs*רAhFs'e%u b2$(e1βs`G(4=tvvZдr@3υW.-01=  IDATwf>f^^迪Ml7剷_*~R74dőn(Nt^tr Ok C\Lg6"ipM\4+iMw!4EY]ydꔷ8'x@ `G](J4R%H:Gˆt ):yf SŰ R4& !+|v1h.~hpgxP \!3ߏ<ճ鵲f20MdXj<4Kx ~VRĹDvt c70z% F#x,L:> 00C4(PoM=@JLemmCHI< t^b?i/2S0R ܎ Ɇ`A`a,,h2I~ґw5< Y!LOc`TGdzPh:;IӶ|y P.h@s=> fR%+ Wv<̬ u\q`!Po<9H.Or&j" YFV3"h~Z+ݘ="Ŏ;A@'c!c"a54 i倅Uy9EB<\o>I9JrF-aXf'2gs2C\;n  33WN{Dm,11ߊGr1@-6g 40W_ Z䠙ݮh5j,OY}3זhrrR_?=*&ky2N'e'fyjx?$|Εu9дSd1]?;)E`2c4AsdM&Q`b$٭UgPgK\VV+W` Y_Y.@hI~ {# . ۓ˴a|BWwq\<ӌSY\69Uv!dOzS'Yh|POˮNir|M4 1xJ&>0/x=L-;d?鴡Yfi3A4K]QVm;v'Sެ);;54e*8|+Т9 15VٙALi&,hiۇ0!w>bLAbz<$Ɍ|s9~wJ<-M>kwAU"Qu|3~y >DnC/ 4kBs+a朥7ا)r5F̾OfT3;SF[1k"#27UFGd}ae_b~ vL%m+Y.>htj=RҤ9CO|'2驿lV؊F !zX'VӔXht\].#ϼJALG\332izЏt0! 9gɟKcَ?3"kC/ b-44p)#5/g+gn/2e$Fp<N3ɛA^"9 <CVʥ&o> c L9$,hj,'Hpww2G#Ħ1=g}'1Nl>4DwT(NQtF"0Ohj~3jv'ٜf g<Ҁ9ل< p-G_IjtHn`b! qL2C<4'i0>7#\̕- ۄnCT+|7{V-ԁLe-bl,gyuF5rde@Y T_}xnm0ͲzBlǬ`|,htD7=N(UxRf7 sؗⅫl> 2UUnp*dDQ5]]9J28jeqs,!32ខdգ C2\m171˗wm AZ^M ITYAsك }.h1Luy܀ 4.;L+&!4^‰:>3zv`O?if*trceL7 ]t_ܝ궐 `R][f,'#3Čr$ŹA!ԏ u#&.@b#AlWaQє hƹZseQl3ԠH 4F"Řj* d*SNAw'aGz^AJ2.h΀~'1K"rUZhIfUj&p ר?.܇3O?n*+kPI<4IpDq, Nmy??>sAr3vhbf3wo vB430%[:gfZ |O0WKꗦioahLF[%9M"S(dA?9+'o:Rl(b$JYA3\ txYv: ({r[A̒iZ<%4~Y}nv!@U+xV֢̌giF.h.]ʉC 2U`ʢ=>a2DZoOG׽# wm>~%5ai #hOwA<_ <"槧e=YE;]JY"\gQЄVF }-h;߯dMawWԡi_^jQ@+3{ 3tC="K1 C2{gTlwWSeH[* ƝXi`u#hz-M;]_]7\qIFg\q?A̟AxyΦ3*fy)/@XΞO|Fxx49w*ځCcހ\[*`qy2&d1P,p@OA621 Abb==n#=~_;?3of2~yuy-p -3M6OI ҉Ԉ ,47`X'#_8H_e+W2l_4[+r#:,?hnn2~pLM=@C$5lh|4jKoTm1#c%Q3ʸLN~ixmc"_8H(k/ 14#!F/" csVu]՞4ix[3i)4 6 )z?l`j 4ѸɤPN>?;8ΏXtdhDr#)lJ &|zY POjWxIp4S(2JL!S9he01)C_8Pp] tt؝!Ȅ1)f `?I*^W)p[ϙpM1Z=3KO=.0ז1 4Y޽pľOfז 4G?ұ'#sn0lNŗiYEuk^h|#44liϟ/47fTxh !c_#@S_3'#J2}=t' a~tnjpsB=̞6:קyLKbabF>`AmAb4Ch@ca2P;[IFfC0ZY+{5VN㳄F<ߜMIYGi6kKP /+4fD.FuJKeP ~5 0K[y\М7}w%"O+:cmJuUDH=~LV9fy-R<7}sRu($h$-NPcwz{M 駺IZ$#T1΋:O/){2֩ 4hT@ts'v -.Rҁ71,1x_Rnhb́2{9&v5d1B~7/wcY frY0s~Q)6,B}c L>4Sgx ͞=xNYԜA̒׵UGnNdd4V6 <Ġ߂fW`K/.w2'0%+3~ZddUSӋ4y=tþYGoqNg)g}YR>\!K8TP,4Q{;f Bd-i3&vɫ>ه>%sk_SΤ%X1R]"\$0p{8|'0^9LjF>^ndu +b j7܄odmCCggaoGcލ`w_aV:?t]?O2O}L\HqA5`Qs Q0[O,OUAjj?dt50s9xp̍ $J2xldd$_m/*7BV?ՉyEL,ז1W%.ak eb>`Cc2kzXmwӾI|x6Y9r߯obٞd MJ:K؏5#hBC' @hBn Y-#8ǖXg'9v!95^-u MQ en"ҚE99i5F*2Se};FЌs#|x h0R^0ɨOg=m#$#.a7sgF*D=Bu*ۦT[4eZ3MOP)( 䏊eUZd e8DW?GL0ɭc4nltu_[\^;=4 ۍI kQRi ̑'ë~ǰGc;:>uB"U˖ 3fd>|Ρ9q򗯾c۸hs"4(= ~Wu盤qI*ԍSwNu6{0wS%]NlَUTnF&0%)9<|*<n_e "hrfdvLW{N=iJ~Lc4;'8½kPOG%B(|V3:߽h2nZ2o̞[x1SezQJ$T%u0e8Rpw3,sݘ@WʣX\n_K='EzXrV4GP,2-bAS9?~gn/:&:EieNVVÇ11β:r2xV[I,ß~eQ{%7V.MWl^,mmWSv1vgۣt]%gU~} X%_,W:jkdtE/'u5>& J?lbpb''s鑻Jĝ1\Y;˱pkh,go;0 ` L }v\"F\xojͲ\iD%糂f扻gd4seػ]ntMd 4%7᧥MShU5&gX^%3$)[y-&F3DSlr1 Ñb쩲A Mg{u1Jb/WeH/DcwfVI,ao{e4i0U'tCzO͜=4~MXSNvyBfN1.70*~2U3$Ә(^ 90p7Bfzvqr/-b+wţC+@fw. eU{1Rvs@ͿkdGBYU& gov ct'sc3_˽+#Io˟\椗`/a2E ]Xݿle߹+/{ji oRbtB-6Y]hCW%i󁳅fٲZ `h45z% 2-#sX^ktbptj ]/vuWHck[)6.[,4Pb.!2v'aNK e{ 4͹)֝N ;:@٬hoߎ!?/uzUb\,PKJOq.e/W0XwW|Y<,=bW-h=4ϣkH2Ɗ&LnY|;ak +Z ϊJN00cRY*V~kM8Hjelo.N<FeRIG.r]7[3~8FJ&l3nF4MrV7CO?5zЯ̖% gWE"nf~wO:62Rf9+ 4mx~Y_X;oAi_SKZe)>w+9Z3[߸&I֍Ndo4M)g FE\[+k$!yLv2\TP!pGyPbN'cd?Z1.[ブ\h/jr[ 2uI'"ag}8.6)B㷑i>Z@!ێJO%3swp'-v G?ot|o`i;ozt%kXZ8rv hgYIIyȕϯ (9eTLV)+Tx)N'"Cr'-9A2_Rn>̲_yUߙ혋$ 6q7s]W3gEi IDAT^;Ѷ41-Uv_ Yw4|*ǖj2x֥o毰GcVY*_N|zx0y~ą*t2wLj`R]Szg^,+zs(Jg.I{! @?}$s}M*d\846$4 ij<dKbKg=O'd,S>=<D|yyxO(L}L:ɜ'Na2(?%fd.*hԚ;7)zQ^™[Rt`2-U|'SdFAv%4ly e.TeФd: }rNed<:<> R%^︘cLv ).2dpS*)ky }xuFUݳyA(iܤ˸g fD*Lv7=w|:`0+8avvqCc\Ŵa%43g a6V?Ԋ2' sY- R?Sbh (ijPd5|zpEgcT=! re'gd?p,Hᗜ抲cCc\=c[bc(1jA'+ϝ90)4L<QYk,xue!f=GcAEoˎR+ӳiv&R_aOC#7^ &{2>3UD;"޹N@0c2XyJ׺i22aVSղ jPNÔ#IL N2/1~ՉafLCc4 ʜCp8@O:0.l}5֞\Uw?AEmiUєJhg3nMפ! 40h>7QA@j,hiTEw1mɢ}@OdNr.yl8ɪeL qrc64_+} YYPa/~ͱz;{q70]\oKPUu330}Y6? p% QP-F5(4k$s1c *8bu`gߜhktX1>Gc>*Eaw[ɀ 9VՉ38<)V1P܌IL,%bHt`bP'\Y&2Z`tꝻ>͍Egg?lHgLJ~>7_lAthKlH7%MV=IoavfڬԹhz,b zj4270am92Do{P,c Ld w]{G? IgRQM'^=|ئNwK~6&Y2œ=*z0E3-nY@x )X^.Z?Mr[=}kQؒ[N.HzhNh.{zJlv5ѮtQ3;/eL VOQY;O( moߞ5,-/+!8DeC;ڗмvxTIEQI)y= +A7\cƤgF^rvO^F'=~a$َru[GRflN.~##x!Fw&]+Э+V<ﵝ3MFSNk*=DA)#}+<5KɫdX/Ψϳl窢]h p֟3TLRN'$bdv?9 _H0;KhL&]bcږ{nԚiF%)m:?`OL2͜;e0MhUZtMe p_ȵ[4R8S\$8r,HÑ}Pڏ'㳘 e1ۖc fa=lɀYfD>l6O̘\b2y gG?[$dreuѦX奼-VNē&CJ2<<7J'4`e1z=|yxc{Uf9jMeɛz67'Fi&gҧ<}FG-UJ&qj.N&3|5\ :e'dBLw-' )~$0faxCg#4?'+U@ǔyJVͧF~`RO{r9lEd)0"#>fݎ2 HsɿN.= P x6&W{h99F}pHYVW:?)@3D2n0gX,K#uʘ-Kj.=R٪s.]عij˖sCd(;ً}kZj/异/d;^wcс[^ˬ,%}{|X<ﵥr8(iz#4hF>>U FRkKTet2/\z:@9KNTl|K{m79$W,Egۅ< hz7f9 lmZ_OX"M;hHFy?t0eNn݌rW}XK?O{o$ x2*mTu4@ם[jV &u Xٝ$5X<ͪg{gzH$2\|,adn^_Bطa ǰRSdhvJc0_ٻlg""O,D_#pɿAb.;OTf*MVAYι9 +GCY hǔTzCU~/:n<hbI:"C=x|c̒?ӏt~ѽp̤qS20;;F}} 4-Gc|B/4웖<K8#2DC֝Qi#-b{!wAIhu-HBjNffߝ O?=zW;u&/i^XƳJ# ИD2%WӤ149LN@3(狗vp;(/9hV13/{QMf$+[ቃ̞|1ߗiD9(e_^7oZq P!mD)Bf%9v}3z}6F>hBo?@I_mS0 'dE%T82"yӅgzfC-Y/뮊Qu* {VcQ MyjDb#)lK/E3C,y03}W|++>Ob. N1^(6fv;-b$%W+ajeXP4cnsf+h^U u>Z:8o} gd*{Diq9+bҝeO60 0{wu~,d.H&afC<SQev 5u\tfl\7.M϶E8 Gl?44Do Gv(ɐ4^Y >sXc44{Qs ;S w >YyRwHܝGYb___0ŃS< (ɫa= jzA$`b<<ý4i@_hvzKC߱.X9ezؒJf7BULVYv1D N?" M\*g6蛳4Fӫ 8s0Ɣ&^9{\2[O: ʨf?4CX4@ `=O[OHZAHΝ&?nYJiɇMgfYLЀic;O4MWKh*a/s dK? *!ȒAp ߊJ9T\;-z[HY,0eW@&>bJg4tzlzCӤgН]9 kk~s gupbeZzyC30eFbP!'.N'%Tv@n:ag4q@6=TV2XOS>JK[Q&,~]UJx Px'ؐ@ȧGg/4& hIYOIYtt2xhow]01$aY\h.-%Ocr)M%冞8Jӌ/poWc-߮/jӰf+O2)P&iͳKi*c>UM'70C9vXb@Jfb.;GO'ܹ}&OaOci3(JN/49g`{قaLSg,I, }Jݲu]OO%"Ѿ !䠁}*(ʺ?,IN4Mߟa]gSU3f9M]QyPU|S(j8uMۻΔd92߻795d:>% 4>o>EPJJ+eV7MFg5n4J Z _4&B&3[Hts݁`o/G c)[YEFQw>(< -Z4=!j6?H!ӌU\3˘ fÈ q\~L:BŸ 0z%Nx8һfCNr!Ҋ:zNuy I5|Įeu2Ռ{%+jAK۩1Zt~4hFKp?/PF"Ӿ|y"Y9CUE y:xhߦ51{wOA5GBfpl hLdb+bY-GM k;xjikEh4ó/;aEeyh0 :g# MjYK{P)4O9owHRQA ^ID/-?8 dگݙ̉G'g(FnK& +F37f8-gͭY)¡1.)L&cC,l/;;dXȼBoi7(ټN'6fǨjFC& >c ^s}mJ\gڅAsw|D8If MGfDCs&Iϊ ͳHף\X 3 9u ԦqCCh2Ă D<:xBIȴ}ZTmi::.hM && ASY|Iܫ΀4o-}X 1L9V ߱_ųdmu:hz5?[9 I ع'QO3٬jzAU5M{~U#A5\/04H'Dy)kS~+E/4(}.3Dٟ ߿ПڗK[-%mLnf4t\{iP54TSwݱhFd.e؂i[FYo՟4^->4o!Z3^hM^]7|gMowyN)V7Ģ |3nD4%}#np-o8 lR105>Z0yd%}aukjQ-kuUGɤ-̩nZP;%~_E`g$}@45fT(L՛Mm;"/4%H,hgk45$UV.Hs5ņ Űȫ ~y>ox\!*"Mg<Y2zR(2[U7GB:R3L1GV!6S)G70;fgcc4M%44f,(T0ZЬFi We%G'wfﶉ:(ԩ&*+j3M.4\=#a- ?IVu <=-!hkߚٳi Q364p^[1 PEF/ȩ3$MC8e P%6/hCqUï''g6piKhMn!lks$E)zQ1*eGyGh扑p3E?L'&m MwhXp; e4(:Y23&[0KTO'zr2~GAUazЯ3bE|} м4&آ|A`Hj 4ߤOz@镡V1fj#kNs;M < L|lL'vc1<,hfj8>~1Bn`S }c-PN!YhX#'<ꭱh9f4CoLؼGR wACn:HKiZ1y}޻!̦~fϲYuw&l\)19)<>*:=@8e!پpkM\IDATP0ƲAc_'}G=g[Ҭ :FB I;fn~ory>+~UFDdsm0pK~Gʾ_i3Y7 !:d` K?W4ZhCsC]z7[ / s?Z^e,VkP 1%b%cQr E$15@tJ֟m4?CɓTGJDUYv')M,{WVE qo36YciɰXcQ%lf@'5{/MŠ,_Bf)J3gZ^"[ X1$̨[t*dz~gh5FΤҼN44C Ck^Ǖ5Osl=b# bV+@dL/x;BIeRbb 1NxZ?Cs?/244gsq+ffSt(9̲ V1 +pd`.Idu`vtdzNh}05vi(wutwbsT{pG ,WR{nX/i/2Y\cİ͎Ae&`tth4lM9/ffEi/,@#50u2>LJi!y"/u2ݎ 2&蘑[::=g) 6C,b?,&5V@tBS.),ئQ y+#IOe͟2:w@~g}Qќ,-.{vT܆ޖ}bjURM+GW >3 s*~ +=et4Io$|s[$3͋w)V0M'攃zPd> /z:ӉOI"_%#srNևpbfEh&t<54l9ˀZ%Q9/{LnYpؗux,iµ_jGyCй̯2-y=:M)UqASG{f}OVs14ۭ?[?1t/v_Ex\ ucS"Q7xfh{Sgzli94)TsiaQ9+a6UCnU/l.|G|4>&/tcbp2B>d@g?=n JBH#̿hj!OVqj5 B)y`Qrh{i҇C I*L@ߺ@g˖mмՒmiמ"r^cvzپAcN㳂6Ȏz9tsq2ӡEy Pd@g҈EJfSl|d02s{k 2/XkAc;>(A2AaN|ͿF:F"3qd@g!@g'g4Y&7KTJk= Cc"XӴl[(1A.y.=z#p#Px( }Āj(\:wviW04rӿebJ!c ǵBYh9艧^t?{]7mLcrkoQ tơY -Mcv&ap'<*WFx͖dSN1;JAhXw|\(LrEt?O~k]5'^>z g+1ce!{dlU|h+{M9'#\u^=}wI$/̓Wv})]'|=3#W9n4[k4Œk׿A>ǑOY qɓ*K(EhjӘfD_'[ ATrǓ T|h|Ŕ0l:0РUU4جl̆w;tJy%\Cs.ZJlrM$W C~ h02Me v$@SE^[kEÖFEr=I<%4)l8ɠ24 4g/4sXnv9'.Et -1t2 }5k>4i4 9/ݪ:7x<Upv4Q˹L2y½)5O$Hr3ugכ,  c,g̬Ɋ|֟N%qW 4z\ |dleb\?^#cr$56Jjů_꘢^b#! 4(T[P&fZ4w+˚>4?-eGXKE'cv2׶̀Қ'תGdXNAϘ}h L.~Z"$AIE1ټ p C>2 R^)L[hAϧ14z2ɒf$Uu//H1y1f4ŖW7]٨LgS>]KdMTB~ v+v>,h\j ,{e!\ʂ&YDyH*ah'&"_Mh)=hBfΡyы|>Jwe>5R&J Mo끏 48nVse( ƺi|yqr:#btehX"'4M/Rb&8S cdF6Cc"=L*@ܛD% *fK& 24vY*F2eh޸< EaH&o)-~~2drܿ 4| 32X>44L:c?"d&hMBc<ʂ2]Csp45v~4#8.1lFD|eڂƲx?O kӎ2Md4730q;f&4nI0+9d0$C^Q5CsÈ"":gM-mb f\Jc&}j2dHW&0RA32tǠiX0|&+@c;u9硡)F6$-֜0%e`2t&BwӒ^*Io4< fE8a$4nBcI7&J0dxU:9w_?h i3035GG :L/!Cs2<&11Pc@kWW z Mb8UDY_S2˺ 1zƀִS`>2A&H4t4_9/{QTo<4:,1L<#ˤɄvR*23ɀZ]Ce| /U5HЖk%Y+qMYGF,Ck̵c b2XtƉb@42ɝ_ZOq5:vcaZz)Fb3*/?oԫ\/d3ĕURgL{0 P%` +#wTW̗L|86NK9U \̴yEdpŤ7!,.SyhuE\!^' 8@. / 9~K&yv{S Z)yY^NQ.a4iJ.]su-_(yZ'K IXnqS.I;Ӟ(V~䅠Qbھ [4)_ܑ3p/λv֋7C^XHd 5zޢL2QP˒ V'X!hy n]߱gͦIE4aA(tsc_KX@^mkg[OoetF%q-y ^Xhb7}V o!&O4eu>ײAH)ߚ0u]K<ϰ3k-ī!4@D#kO yQ XPjgݮ^t`,YWsOAxQR *3[T2пJ (^bp5;Mof6hn^R(#XUpMق~5E7=3,f,hED6XnV`;wώ`=4wbZszBlHXlZm6Ɣ^)iηEGS`!BX6t,Ŵ>Ɵc#żg3&"X_VPz<%[Tysg'Nq[GŦbY+*{5uL"^Ic5Gf0h)3X 2П3ÅNXD~pX$,Ҭ_█4?%cw T܉r5^t | :K`ED]KH{>" eT\8#t"+NSn_ T|hZ7{Du,ا[s k)Hz! bܖ'1LQ'xQX+2&1a:LXS?>[+IʶEQp5Fc\K%v &k65F`OtEFKi7AX_\d$wg=f8BQ^lPTYLB ?PqK,}%c}/nl֟ՠEee 袧-!#fυܜhb(/jkꪦhn^k׮6Xn#ۦ;b%dئ 8ZwG.>XC鳚i`RD0+@~Ñ55ց΃`1G |!!!/0C4KdRc~MuIIժne#}lo/lX]]G8ŕ~E jA#FUU19ОeS5A/XJt z5^+_v$sťO(ZB4*V[OA^ch8tǀwbGaSDx3YϨVşT]L r4z`ŊU N:Ο"vCl+;l|%76* }6I^ҲtQvv!VA5RX a/d([[\aBfgfI^}c5j v9 /4CӮ:oڈ?s+7&Oaz[:u4ë>~%Q-gZf#Nw4tܱm/ĦaT=8qga"{ ǶmR)jdx X4ܲuWa ܫ-A  /_܉*+-G+iozJ97?'u{$GoIQA_R1E9r*G //\{UMe!;K/IUABCr8vG۔ }YoN35Eh<u?G޵xַe=%&LRVvڣ={6>Sj$[aHd0lU\q,[&9\@#q,ߩ.W&CTϏU<*B0愀Q^0-a31wXq+wMě\4srs'N^^iXL "yjb;A[W&1@v[<&Z\3oy-I| x?eaQ1a swhKPt0c[]Lc g.,Kl:74{^)V$|GrZsOa(qGSY_CԫqO(miΒ2OFB=~A4q4ztI*PlN~X-u _I]Ic5>ղb{'=p\>7+;#*l%WҲ p;WsPy̏];#%]跞{(c#MUëN _x`j*κ\ߊkYR_"]dss"DƿHnAi]{aZR5q2)uim..bnS*>]ғ҉gh2 6.8vz李uLտi%(;mBPHFvD8AP$E? H*ņL,cc#3fwt4[ >shP"Y;]]l11Ȏevv;o0@0`q01(IJ4 1N PB`%UDHJIH_>TUUU< UU(!15o=s<w3_=|gsY׽{m,z!L?q?\P8h =߀Fv^ƪhrDx9QᓱMsμlēO^G ,;2Fu=v(|wU1Ƅ/`ar&J;1ɑ[<mLz"~'ѹ(8W[v,U/0[;tj=N|>iyr;6SN ڗ2]V5Ue%$ "awAIxuAxJ4F0-;l4@x:\gINKHCRhb Ǩ%kʢ.Ukńyl穨IYZYnWU}-cɪ4H zd>!F H;\mϵ@`#dz^<73ɫ^e,URt7A0l'qg26vN;|\UpOm A!`z CkpYR޿,cr&`.m.+4[T|e,1G349I?Te" tXW,H5$ cSɽxeQ[>+{  G)26Sh&;DAplR!Q&_1ӺgӁ:z -cbCOeQ4CM.$F^Oa&ɏRE#MŽw"%,+G,cD㞏n&z$u $kIuǟq\<_zؾS#ߪ*9lz?=qYs%o/k_~iXGUb}cTnRu4޶uEo%Y`'(DNu V/{/_d ^VߦX-/ qO#K5H5XqR ?'lK^A0!A(M3jnx꣺>Ŗ1a!qe~`,l,cWk%YʹX ;,b o}HHS :Hx&ֹ 8c֠tmdz~`a+ :c(4$}YU5J-k| |I;$j%Z1ԓ*kNsܙq<`EL,R#g4q e=[7~E_kZjhWR NiNnX6bu jX|C6lPʞ6@bHΖ,-cuϷt-U/*`HtDQ:5,V& YnVW9cH0u´Vw.a=fl#gzY֑c!(}fY-XLVSV3%Q`MjPuHQΡO7.V$LFB`շ 38" f:LN߹ :N1,xwW>V/Wn-Mqʈě XX0=Whlg I#yҭXI,HƙS!ӭI1n": |k:?I(Xb, z7Kxù?/>+ff9(+kRځUM^\=_ lywE_K]L-^uHD6wn``*+Ю:z?BSY]-4%n|PrtD /móM.vl2OJNk&G#5J&Kٶf%ᚬYNb5*^uW9h!P f􁋡յv=dٹ=3lLH 3t A\ S!X`; BzjA\ғÞR8{井T`'Sc6t TxѧE'Bm4RJo;;x`,#ޫo}ÞkG{-/hc%P9^i^t:e3A^m#B 'pp 6]K}]~,wtWY8dZMV2̌"LEYaW+YRorR7E+,ϱ#oA{EO` Bo[1ζI)ECPr!uJ}6g190U-]N5>gG7Nj)d% En {;kqO8V0A_t%EX5fAf/]C7>Ԙ^n-o@UJƨ .00أeq_)uʚF߁8?粸,rA]J~H~F9v3 U!^zdZBZ@JAnZ` gZ}ൔnW1[nvudb TT Ѿh; AF%bjլ愜)L-쿚 o"0v dAJ*i?𜕩ڐ}:dB/Bz 0sXqZ߳q`NC\h6핁 *1iRҜ X_FBae]=N[Y7NWAx?x:A:w>w,DRaZqU8 f,+P ~jen/ lОVned1 rGt[zuISЁDZ`^d> E.HQWUΎ|o{WcQ,x%54}aw2ӺU{ ñw8mI/^9ky,DGc>q+'3bEo**v*l(z͜wJv Z@CFUGdcZcg6ea. &H+(W"*wm4M4lGc6ffiq<~9p&{yx>{ޗN *;B^ݹZ<fڊq=?'Iѓɮoh 쏍\U<`'w#qUx(3ў+ *֕_џ"1,kD_ ϳ(Q|!@6ȜAJ;`XWL _[Uji E+,jodlF&*IL Q[6*xnr <#xt{,^t5C%&(]OAmz,+y#&T?Dm(b |ǹ=Ȇ42#ޠ*0:՗XZPL- C/VVԐK#Wdo!a9?Q8VC! =5n}ձކ=f`L.QDODpY:szӫFlFOvuv5M$>6${P\zijl&VDZIky ӘEBS`6 󀅝U[4Q| [$8WRK؅ fܠnI/f瑻uNLE+cάִ!(0:DhFRN1"?/=Gx+8V+i` HD5rR_@jkVܬ\Z|GΝ;w#BerIKɦdKMІ\H2lSu0y6# ߯yX}V `q=׌ؖX@ܽGVɸtvjdgVA 驙O򬌵\M_:c-1X؄׏R& [XXNE|Uau}#ԑ>aZm[SZ$)!f3 Q^ZAZOL7̇ \E::?v6d~W ##: 'om?6Du w%>|w>f"% td OLZZ\M*H[&"!{-ӿ d'NVBO$cJ(wo,o(Q_T")⇳ZRl2ScL<uS:Dq*R޵$Z&-UeVyD-plwX{xE/IP4tOZ_irb 9v(d$Q2nLգY+De ,<<2;9XXPC o7>cϻ&FkRmv__yͤ%Ȓ -×Z/@Թ VdƓkfylPb>v!5QŵV\)ơDVW`z. v{/20 ݥ$-.tF9f&#F# fm7VQ4|!s2,*(f{Mu[ hilX;EtW_)^`b9a̶ x_Ք) 5(IOx^d6(Enʁ[SQL^|` 3 <#΁@16StnT]e/c"F9={z*Rfp%D xE+`os˱ ۑÉ/j'DLT;Hn(LQꤛɶ,$դ[JcW)kRG]kAm:Uro*pҐJ? R S`l$!1IH50ܬz) r?H6> ,pK!ꥦ?R]fw(Cs (:(ʚ9$9O4ǟ#!jL@3:*RzThM@:t&*j)'O*]b%AR,QHgoEʌݯ tr2ew483M Jt2cy TE{b[b[ kClMgc>>F 2KÌUI<6՛&X ,VqH݃U8`ikck" ,^Idh+cnYx~ X֬imgKSH@%̑ ]1L+/Ǎ kg_.Sߜ= D2,K}[>#OOԚsyI-_ @&_罹u-_2y:hgòj,Kh!oȫd* H>Qc{GYSi!fFӡO /D\zr?i3`\96vD_F:+sU~ഈ\4ʹПBW[1⎯KE k1.I&HA/-&Gh˘k`a wě"? lv`kQ~,~%4HvZ<|79g3*x؆XgV9W"a&Μ ʟh^H:C DS/Œ`#IR1Y}H‰E 9J uN(MC)RXOY}Y\gԇ+[~ *:"EL΂肚^[mɾ[؅tޚqn?EVG$ ,I] _)ŽYʦsE.` PIgQW qehn-hX\ 0t]"g:{Mt C"EȭuQNZifޫHO05y WJe8 ü|5EpG7ᨆb  Hl%)3OEpj4[$] +Rv1 hk7b"E,$"OH'x ƕ E`ɩ 9/R@R³fs"E(`}-AU 9B|`6h);[GVe~bN@g`Sw9E4zQ ,h9y+Pߘ+8"ѹ¡W"*EV2uT"YMX1r r/kA HӛɭŪ&<8"Elφmid~O"h Ԥb vɮ38.|3b3VsZXvRN\O=+~_T]k՛sav1HC8kYL_k^g`Hn%;\\|E;0?hΜ93;غfZ.Ƕf-&fRFj%&e_"iv#Ig.E]DHDDt!Et]DHDDHq=yGٙbٗq{y+)E5}KJ"kZf6)ߝD6N~d XI9ٱ.1`~r) k3I j=JĀ5[QVޣ[B\Hi]%cu1`vF#`]2j=ynz>)` VNJ"+Bp@bQ/kJ˦L%jk,)oj 67o#Ub0V*bJ8Tޫ ]ύĨK 7cmD1U`\}{ Xټ/e`},)7]5+6ZFB'96Z`Iϻ`]0\U%`_cm쵸 e`a `n=^msѦwH"{ǿG^C D7M?:6y  g@"kL5q); SDրu0X#;$g %lԻ?Ȝ7QxΒVH<<{Hdl |/ԋ K`O";(66Fس)Ô %ȓ$Q=a&yFYsZ'U!r6M [EbϦ?!C~aZdnfng~m` yx2x)L>'޴Dvqry,ykESWJ{$,gyҰ؅[љYusE iwo[m}eڹkZ"湛c;u[ZZ&&'丏]}OS:>?G ibڰwdy)\ڸ?:GIJ)ln!Hdݱ.UVQ|<UX=V3VtטBCaX`x{ӻm.VDJ&Ǒ`ia YHT燹f񯍌4lR ~M09IO!wY) VqHIߞIy\i~y<:D_* t}{nWDU;rC`v6w5tj|> m&Bs=U`&p0^ІGE‘8A,??ү? :!SZH;0Pn HD8.dahX\oTopR p4S*XWL "xYD$Gt@~*+n:XC"эJ y~ʗ%Ѹ&P]F@$(O^4H$iݐRR0IENDB`info-beamer-1.0~pre4/samples/parrot/node.lua0000644000175000017500000000134412452774240016745 0ustar nknkgl.setup(400, 400) function readln() return coroutine.yield() end if not N.clients then N.clients = {} end node.event("connect", function(client) local handler = coroutine.wrap(echo) N.clients[client] = handler handler(function(...) sys.client_write(client, ...) end) end) node.event("input", function(line, client) N.clients[client](line) end) node.event("disconnect", function(client) N.clients[client] = nil end) function echo(print) print("I will repeat everything you send me") while true do local line = readln() print(line) end end util.auto_loader(_G) function node.render() gl.clear(1,1,1,1) util.draw_correct(blue_macaw, 0, 0, WIDTH, HEIGHT) end info-beamer-1.0~pre4/samples/shader/0000755000175000017500000000000012452774240015252 5ustar nknkinfo-beamer-1.0~pre4/samples/shader/lua.png0000644000175000017500000001221512452774240016542 0ustar nknkPNG  IHDR), pHYsHHFk> vpAg KPLTE""""3333DDDDUUUUffffwwwwÈđ˙̢ԪճܻQ^m2IDATxkc^tj 5PI 5{hp1_Y  5[)DZg?WXx:y1QRL)P$ŏ"((BʀbH)p? @B  ( E~,P $E}\ '@Kd!r$ + )H32TF$2 ~  u Mcl6KoF 4$L'(>*fYzɍ?cq!E"rξP!T!d8:q  A@qcH93ʊFOїH():RX:$"qNR@EAՍP"+.@NPDD1HVA>P@A_Q7vGSeT|7b1^@&˸'N PbTau 7G0w#S pbsQ0 C G򐈉P;! "d9Xf~*&θS$H $yP*?b݂ $feS!9i~ vٍጠx)* f uNfdADA@E5%'R-{K?qCӝ3JVK )ۖ"v0 qP)<3em!jB<P Xjew P!RRB^@0b9Qt(v@1, xGQ@DD~LT%V(deHKd&t3+H`%' ֎GW/=I@RJg/us? V֧DEta{ /j%gӾl@X|襋x}(h7m7x^IY)Sf:?K@JTէEUpn ަse{ <,۳X=[ӧ@;Te X?>v͗ *$@f}zm! QUة ]/`ϖ8G"P@LwOa}w:PBr&|zz KMDP_h z>j`SQ01{ 0j`@;'o1$mF2F$տ_?5g[y@NLVޠ,2s ! H(4|*%r-=AH@잼DV:bR%w%P,2"87x %,d3k`r)u1 _dƞyѵOzc"4}ujm𳡼K+]Y7VW(]R ߋ󦫚B4kˍDf8\>ŝMt붸\)h}b}re5fw}0(,C,1aߟ}.{{E 9TP@ 5}p uMTȄQlC(Ksdh1ğiC9\ Z6w"gD,DpAG13K 1Ocɧ%;[33NCR8c׶f ҵ{ @$ QS ^PΜ Z+5.a 6Dg9T‰EpػC%^@B:ڎI DDgEBs6Jn;ߎ$1m' QNud]`st7y+S.PKq]%yfP{d=qbǠw|t(yPh.$5p(= yT[à(3lQ|^=2 *| =vq9Q9\^DQ(bt`_x5]B *PD) %B&+L_ΓXn, '+ֈ“Jf9k6Pu(h_ ޝUf}g}U(`7f!͗uQyRs0m\":Ș3LS FP|_oW[]U`6fwn_ڴPY}k"Z ]F,͡qV''pe,N+870$Pm$UPDA dPkvղ@Fq  mS)e2 P8)xg#""9Bl60ApxJ ׯ5'eq6$ٺ"6Yr&O(& >h[ F0=l/ұA@D1H*}ןv\ 9pA_ş! AT[#U5xrf0}Za -F`L:!L~j'wsLa?O]O9JUGl6o6Mg6-]$md47`4"$>H.9Ⱥ1}CH $IDL\@wԋȀSB"P3@vU59rNu@h/bj@@9} p< " \BP)(4}Șz j BvE٧yuz^Pδb72k0'y( (1)g?X9V[ϭW9(2 Q˖*d*bhiŘp᷌r\V $w_QERT߽s All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Some files include external code. License information about these files in included in their header. video.c Includes code from ffmpeg example code http://bit.ly/w3iOvz misc.h Includes code based on lua-users wiki: http://lua-users.org/wiki/UserDataWithPointerExample Includes code based on http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ misc.c Includes code based on http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ userlib.lua Includes code based on lua-users wiki: http://lua-users.org/wiki/TableSerialization Included 3rd Party code: uthash.h utlist.h Hashtables/Lists for C structures http://uthash.sourceforge.net/ bin2c.c Convert file to C include struct.c Library for packing/unpacking structures. http://www.inf.puc-rio.br/~roberto/struct/ info-beamer-1.0~pre4/Makefile0000644000175000017500000000314612452774240014004 0ustar nknk# See Copyright Notice in LICENSE.txt RELEASE = 1.0pre3 VERSION = $(RELEASE).$(shell git rev-parse --short=6 HEAD) ifdef DEBUG CFLAGS ?= -ggdb -DDEBUG else CFLAGS ?= -O3 -DNDEBUG endif ifdef USE_LUAJIT LUA_CFLAGS ?= -I/usr/include/luajit-2.0 LUA_LDFLAGS ?= -lluajit-5.1 LUA_LUAC ?= luac CFLAGS += -DUSE_LUAJIT=1 else ################################################# # # If you have compile/link problems related to lua, try # setting these variables while running make. For example: # # $ LUA_LDFLAGS=-llua make # ################################################# LUA_CFLAGS ?= -I/usr/include/lua5.1 LUA_LDFLAGS ?= -L/usr/lib -llua5.1 LUA_LUAC ?= luac endif CFLAGS += -DVERSION='"$(VERSION)"' CFLAGS += $(LUA_CFLAGS) -I/usr/include/freetype2/ -I/usr/include/ffmpeg -std=c99 -Wall LDFLAGS += $(LUA_LDFLAGS) -levent -lglfw -lGL -lGLU -lGLEW -lftgl -lIL -lILU -lavformat -lavcodec -lavutil -lswscale -lz prefix ?= /usr/local exec_prefix ?= $(prefix) bindir ?= $(exec_prefix)/bin all: info-beamer info-beamer: main.o image.o font.o video.o shader.o vnc.o framebuffer.o misc.o struct.o $(CC) -o $@ $^ $(LDFLAGS) main.o: main.c kernel.h userlib.h module_json.h info-beamer.1: info-beamer.1.ronn ronn $< -r --pipe > $@ bin2c: bin2c.c $(CC) $^ -o $@ %.h: %.lua bin2c $(LUA_LUAC) -p $< ./bin2c $* < $< > $@ doc: markdown_py -x toc -x tables -x codehilite doc/manual.md > doc/manual.html install: info-beamer install -D -o root -g root -m 755 $< $(DESTDIR)$(bindir)/$< clean: rm -f *.o info-beamer kernel.h userlib.h module_*.h bin2c *.compiled doc/manual.html info-beamer.1 .PHONY: clean doc install info-beamer-1.0~pre4/README.md0000644000175000017500000000073412452774240013623 0ustar nknkAbout info-beamer ================= info-beamer allows you to develop interactive information displays using the Lua programming language. Read more about it on the [main website](http://info-beamer.org/) Documentation ------------- The complete documentation is available on [info-beamer.com](https://info-beamer.com/doc/info-beamer) Installation ------------ See the [documentation](https://info-beamer.com/doc/info-beamer#installing-info-beamer) for more information. info-beamer-1.0~pre4/bin2c.c0000644000175000017500000000127612452774240013507 0ustar nknk/* * Unloved program to convert a binary on stdin to a C include on stdout * * Jan 1999 Matt Mackall * * This software may be used and distributed according to the terms * of the GNU General Public License, incorporated herein by reference. */ #include int main(int argc, char *argv[]) { int ch, total=0; if (argc > 1) printf("const char %s[] %s=\n", argv[1], argc > 2 ? argv[2] : ""); do { printf("\t\""); while ((ch = getchar()) != EOF) { total++; printf("\\x%02x",ch); if (total % 16 == 0) break; } printf("\"\n"); } while (ch != EOF); if (argc > 1) printf("\t;\n\nconst int %s_size = %d;\n", argv[1], total); return 0; } info-beamer-1.0~pre4/font.c0000644000175000017500000000522712452774240013460 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #include #include #include #include #include #include #include #include #include "misc.h" #include "shader.h" typedef struct { FTGLfont *font; } font_t; LUA_TYPE_DECL(font) /* Instance methods */ static int font_write(lua_State *L) { font_t *font = checked_font(L, 1); GLfloat x = luaL_checknumber(L, 2); GLfloat y = luaL_checknumber(L, 3); const char *text = luaL_checkstring(L, 4); // Protect FTGL if (!check_utf8(text)) return luaL_error(L, "invalid utf8"); GLfloat size = luaL_checknumber(L, 5) / 1000.0; int type = lua_type(L, 6); if (type == LUA_TNUMBER) { GLfloat r = luaL_checknumber(L, 6); GLfloat g = luaL_checknumber(L, 7); GLfloat b = luaL_checknumber(L, 8); GLfloat a = luaL_optnumber(L, 9, 1.0); shader_set_gl_color(r, g, b, a); glBindTexture(GL_TEXTURE_2D, default_tex); } else if (type == LUA_TUSERDATA || type == LUA_TTABLE) { lua_pushliteral(L, "texid"); lua_gettable(L, 6); if (lua_type(L, -1) != LUA_TFUNCTION) return luaL_argerror(L, 6, "no texid() function"); lua_pushvalue(L, 6); lua_call(L, 1, 1); if (lua_type(L, -1) != LUA_TNUMBER) return luaL_argerror(L, 6, "texid() did not return number"); int tex_id = lua_tonumber(L, -1); lua_pop(L, 1); shader_set_gl_color(1.0, 1.0, 1.0, 1.0); glBindTexture(GL_TEXTURE_2D, tex_id); } else { return luaL_argerror(L, 6, "unsupported value. must be RGBA or texturelike"); } glPushMatrix(); glTranslatef(x, y, 0); glTranslatef(0, size * 800, 0); glScalef(size, -size, 1.0); ftglRenderFont(font->font, text, FTGL_RENDER_ALL); glPopMatrix(); lua_pushnumber(L, ftglGetFontAdvance(font->font, text) * size); return 1; } static const luaL_reg font_methods[] = { {"write", font_write}, {0,0} }; /* Lifecycle */ int font_new(lua_State *L, const char *path, const char *name) { FTGLfont *ftgl_font = ftglCreatePolygonFont(path); if (!ftgl_font) return luaL_error(L, "cannot load font file %s", path); ftglSetFontDisplayList(ftgl_font, 1); ftglSetFontFaceSize(ftgl_font, 1000, 1000); ftglSetFontCharMap(ftgl_font, ft_encoding_unicode); font_t *font = push_font(L); font->font = ftgl_font; return 1; } static int font_gc(lua_State *L) { font_t *font = to_font(L, 1); ftglDestroyFont(font->font); fprintf(stderr, INFO("gc'ing font\n")); return 0; } LUA_TYPE_IMPL(font) info-beamer-1.0~pre4/font.h0000644000175000017500000000026412452774240013461 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #ifndef FONT_H #define FONT_H int font_register (lua_State *L); int font_new(lua_State *L, const char *path, const char *name); #endif info-beamer-1.0~pre4/framebuffer.c0000644000175000017500000000525212452774240014774 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #include #include #include #include #include #include "utlist.h" #include "misc.h" #define MAX_CACHED 30 typedef struct framebuffer { GLuint fbo; GLuint tex; int width; int height; struct framebuffer *prev; struct framebuffer *next; } framebuffer_t; static framebuffer_t *framebuffers = NULL; static int num_framebuffers = 0; static void unlink_framebuffer(framebuffer_t *framebuffer) { DL_DELETE(framebuffers, framebuffer); free(framebuffer); num_framebuffers--; } void make_framebuffer(int width, int height, GLuint *tex, GLuint *fbo) { framebuffer_t *framebuffer, *tmp; DL_FOREACH_SAFE(framebuffers, framebuffer, tmp) { // Same size? if (framebuffer->height == height && framebuffer->width == width) { *tex = framebuffer->tex; *fbo = framebuffer->fbo; glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->fbo); glBindTexture(GL_TEXTURE_2D, framebuffer->tex); unlink_framebuffer(framebuffer); return; } } glGenFramebuffers(1, fbo); glBindFramebuffer(GL_FRAMEBUFFER, *fbo); fprintf(stderr, INFO("new framebuffer (%dx%d): %u\n"), width, height, *fbo); glGenTextures(1, tex); glBindTexture(GL_TEXTURE_2D, *tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_INT, NULL); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *tex, 0); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) die("cannot initialize new framebuffer"); } void recycle_framebuffer(int width, int height, GLuint tex, GLuint fbo) { framebuffer_t *framebuffer = xmalloc(sizeof(framebuffer_t)); framebuffer->width = width; framebuffer->height = height; framebuffer->tex = tex; framebuffer->fbo = fbo; // fprintf(stderr, "added recyleable framebuffer %dx%d %d %d\n", framebuffer->width, framebuffer->height, // framebuffer->tex, framebuffer->fbo); DL_APPEND(framebuffers, framebuffer); num_framebuffers++; if (num_framebuffers > MAX_CACHED) { fprintf(stderr, ERROR("too many framebuffers in use\n")); glDeleteFramebuffers(1, &framebuffers->fbo); glDeleteTextures(1, &framebuffers->tex); unlink_framebuffer(framebuffers); } } info-beamer-1.0~pre4/framebuffer.h0000644000175000017500000000040512452774240014774 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #ifndef FRAMEBUFFER_H #define FRAMEBUFFER_H #include void make_framebuffer(int width, int height, GLuint *tex, GLuint *fbo); void recycle_framebuffer(int width, int height, GLuint tex, GLuint fbo); #endif info-beamer-1.0~pre4/image.c0000644000175000017500000001244512452774240013574 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #include #include #include #include #include #include #include #include #include #include "framebuffer.h" #include "misc.h" #include "shader.h" typedef struct { GLuint tex; GLuint fbo; int width; int height; } image_t; LUA_TYPE_DECL(image) /* Instance methods */ static int image_state(lua_State *L) { image_t *image = checked_image(L, 1); lua_pushliteral(L, "loaded"); lua_pushnumber(L, image->width); lua_pushnumber(L, image->height); return 3; } static int image_size(lua_State *L) { image_t *image = checked_image(L, 1); lua_pushnumber(L, image->width); lua_pushnumber(L, image->height); return 2; } static int image_draw(lua_State *L) { image_t *image = checked_image(L, 1); GLfloat x1 = luaL_checknumber(L, 2); GLfloat y1 = luaL_checknumber(L, 3); GLfloat x2 = luaL_checknumber(L, 4); GLfloat y2 = luaL_checknumber(L, 5); GLfloat alpha = luaL_optnumber(L, 6, 1.0); glBindTexture(GL_TEXTURE_2D, image->tex); shader_set_gl_color(1.0, 1.0, 1.0, alpha); glBegin(GL_QUADS); glTexCoord2f(0.0, 1.0); glVertex3f(x1, y1, 0); glTexCoord2f(1.0, 1.0); glVertex3f(x2, y1, 0); glTexCoord2f(1.0, 0.0); glVertex3f(x2, y2, 0); glTexCoord2f(0.0, 0.0); glVertex3f(x1, y2, 0); glEnd(); return 0; } static int image_texid(lua_State *L) { image_t *image = checked_image(L, 1); lua_pushnumber(L, image->tex); return 1; } static int image_dispose(lua_State *L) { return 0; } static const luaL_reg image_methods[] = { {"state", image_state}, {"draw", image_draw}, {"size", image_size}, {"texid", image_texid}, {"dispose", image_dispose}, {0,0} }; /* Lifecycle */ int image_create(lua_State *L, GLuint tex, GLuint fbo, int width, int height) { image_t *image = push_image(L); image->tex = tex; image->fbo = fbo; image->width = width; image->height = height; return 1; } int image_from_current_framebuffer(lua_State *L, int x, int y, int width, int height, int mipmap) { GLuint tex; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); GLint filter = mipmap ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, x, y, width, height); if (mipmap) glGenerateMipmap(GL_TEXTURE_2D); return image_create(L, tex, 0, width, height); } int image_from_color(lua_State *L, GLfloat r, GLfloat g, GLfloat b, GLfloat a) { GLuint tex; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); unsigned char buf[4] = {r * 255, g * 255, b * 255, a * 255 }; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf); return image_create(L, tex, 0, 1, 1); } int image_load(lua_State *L, const char *path, const char *name) { ILuint imageID; ilGenImages(1, &imageID); ilBindImage(imageID); if (!ilLoadImage(path)) { ilDeleteImages(1, &imageID); return luaL_error(L, "loading %s failed: %s", path, iluErrorString(ilGetError())); } ILinfo ImageInfo; iluGetImageInfo(&ImageInfo); if (ImageInfo.Origin == IL_ORIGIN_UPPER_LEFT) iluFlipImage(); if (!ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE)) { ilDeleteImages(1, &imageID); return luaL_error(L, "converting %s failed: %s", path, iluErrorString(ilGetError())); } int width = ilGetInteger(IL_IMAGE_WIDTH); int height = ilGetInteger(IL_IMAGE_HEIGHT); GLuint tex; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, ilGetInteger(IL_IMAGE_BPP), width, height, 0, ilGetInteger(IL_IMAGE_FORMAT), GL_UNSIGNED_BYTE, ilGetData()); glGenerateMipmap(GL_TEXTURE_2D); ilDeleteImages(1, &imageID); return image_create(L, tex, 0, width, height); } static int image_gc(lua_State *L) { image_t *image = to_image(L, 1); if (image->fbo) { // If images has attached Framebuffer, put the // texture and framebuffer into the recycler. // Allocations for new framebuffers can then // reuse these => Better performance. recycle_framebuffer(image->width, image->height, image->tex, image->fbo); } else { // No Framebuffer? Just remove the texture. glDeleteTextures(1, &image->tex); } return 0; } LUA_TYPE_IMPL(image) info-beamer-1.0~pre4/image.h0000644000175000017500000000066412452774240013601 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #ifndef IMAGE_H #define IMAGE_H int image_register(lua_State *L); int image_create(lua_State *L, int tex, int fbo, int width, int height); int image_from_current_framebuffer(lua_State *L, int x, int y, int width, int height, int mipmap); int image_from_color(lua_State *L, GLfloat r, GLfloat g, GLfloat b, GLfloat a); int image_load(lua_State *L, const char *path, const char *name); #endif info-beamer-1.0~pre4/info-beamer.1.ronn0000644000175000017500000000332612452774240015565 0ustar nknkinfo-beamer(1) -- An interactive multimedia presenter ===================================================== ## SYNOPSIS `info-beamer` ## DESCRIPTION **info-beamer** uses the files and subdirectories in the specified directory and displays them according to the Lua script `node.lua`. **info-beamer** provides a simple yet powerful API to display fonts, images, videos and more. The full documentation is available on http://info-beamer.org/doc/ ## ENVIRONMENT * `INFOBEAMER_FULLSCREEN`: If set to 1, **info-beamer** will start in fullscreen mode. * `INFOBEAMER_ADDR`: If specified, only binds to this address. Defaults to `0.0.0.0`. * `INFOBEAMER_PORT`: Specifies a port for both the TCP and UDP interface. Defaults to `4444`. * `INFOBEAMER_PRECOMPILED`: If set to `1`, **info-beamer** will also load precompiled Lua files. Only use this option if you trust the files being loaded, as precompiled Lua files should be considered unsafe. * `INFOBEAMER_FULLSCALE`: If set to `1`, **info-beamer** will ignore the aspect ratio of the screen and scale the root content to the complete size of the screen. * `INFOBEAMER_WIDTH`: Sets the width of the initial screen. Useful when using the fullscreen option above. Defaults to 1024. * `INFOBEAMER_HEIGHT`: Sets the height of the initial screen. Useful when using the fullscreen option above. Defaults to 768. ## SECURITY CONSIDERATIONS By default, **info-beamer** will bind to `0.0.0.0`. Use `INFOBEAMER_ADDR` to bind to another address. ## AUTHOR Written by Florian Wesch ## COPYRIGHT **info-beamer** is Copyright (c) 2013, Florian Wesch . The code is licensed under the BSD 2-Clause License. ## SEE ALSO lua(1) info-beamer-1.0~pre4/kernel.lua0000644000175000017500000003255612452774240014336 0ustar nknk-- See Copyright Notice in LICENSE.txt function kprint(msg) print("kernel: " .. msg) end function safe_loadstring(code, chunkname, allow_precompiled) if not allow_precompiled and string.byte(code, 1) == 27 then return nil, string.format( "precompiled code not allowed for chunk '%s'", chunkname ) else return loadstring(code, chunkname) end end local seen_warnings = {} function deprecation_warning(tag, warn, level) if not seen_warnings[tag] then seen_warnings[tag] = true print(debug.traceback("deprecation warning: " .. warn, level)) end end local DEFAULT_VERTEX_SHADER = [[ varying vec2 TexCoord; void main() { TexCoord = gl_MultiTexCoord0.st; gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; } ]] --============= -- Sandboxing --============= -- list of childs/contents for this node. local CHILDS = {} local CONTENTS = {} -- "persistent" table for this node. survives reloads local N = {} function create_sandbox() local sandbox = { error = error; assert = assert; ipairs = ipairs; next = next; pairs = pairs; pcall = pcall; rawequal = rawequal; rawget = rawget; rawset = rawset; select = select; tonumber = tonumber; tostring = tostring; type = type; unpack = unpack; xpcall = xpcall; setmetatable = setmetatable; getmetatable = getmetatable; module = function(name, ...) local module = sandbox.package.loaded[name] if not module then module = sandbox._G[name] end if not module then module = { _NAME = name; _PACKAGE = name; } module._M = module end -- Make sure setfenv won't change the outer -- environment. if getfenv(2) == _G then error("cannot modify outer environment") end setfenv(2, module) for _, func in ipairs({...}) do module = func(module) end sandbox._G[name] = module sandbox.package.loaded[name] = module return module end; struct = { unpack = struct.unpack; }; _BUNDLED_MODULES = { ["json.lua"] = MODULE_JSON; }; coroutine = { create = coroutine.create; resume = coroutine.resume; running = coroutine.running; status = coroutine.status; wrap = coroutine.wrap; yield = coroutine.yield; }; debug = { traceback = function(message, level) local message = tostring(message or "") local level = tonumber(level) or 1 assert(level >= 0, "level is negative") assert(level < 256, "level too large") return debug.traceback(message, level) end; }; math = { abs = math.abs; acos = math.acos; asin = math.asin; atan = math.atan; atan2 = math.atan2; ceil = math.ceil; cos = math.cos; cosh = math.cosh; deg = math.deg; exp = math.exp; floor = math.floor; fmod = math.fmod; frexp= math.frexp; ldexp = math.ldexp; log = math.log; log10 = math.log10; max = math.max; min = math.min; modf = math.modf; pi = math.pi; pow = math.pow; rad = math.rad; sin = math.sin; sinh = math.sinh; sqrt = math.sqrt; tan = math.tan; tanh = math.tanh; random = math.random; randomseed = math.randomseed; }; string = { byte = string.byte; char = string.char; find = function(s, pattern, init, plain) if #s > 32768 then error("s too large") elseif #pattern > 4096 then error("pattern too large") end return string.find(s, pattern, init, plain) end; format = string.format; gmatch = string.gmatch; gsub = string.gsub; len = string.len; lower = string.lower; match = string.match; rep = function(s, n) if n > 8192 then error("n too large") elseif n < 0 then error("n cannot be negative") end return string.rep(s, n) end; reverse = string.reverse; sub = string.sub; upper = string.upper; }; table = { insert = table.insert; concat = table.concat; maxn = table.maxn; remove = table.remove; sort = table.sort; }; print = print; loadstring = function(code, chunkname) local func, err = safe_loadstring(code, chunkname, false) if func then return setfenv(func, sandbox) else return nil, err end end; resource = { render_child = render_child; load_image = load_image; load_image_async = load_image; load_video = load_video; load_font = load_font; load_file = load_file; create_shader = function(vertex, fragment) if fragment == nil then fragment = vertex vertex = DEFAULT_VERTEX_SHADER elseif vertex == nil then vertex = DEFAULT_VERTEX_SHADER deprecation_warning( "shader_nil_vertex", "Using nil vertex argument for create_shader is deprecated. Only specify a single fragment argument.", 3 ) else deprecation_warning( "create_shader", "Specifying both vertex and fragment shader is deprecated. Only specify a single fragment argument.", 3 ) end return create_shader(vertex, fragment) end; create_vnc = create_vnc; create_snapshot = create_snapshot; create_colored_texture = create_colored_texture; }; gl = { setup = function(width, height) setup(width, height) sandbox.WIDTH = width sandbox.HEIGHT = height end; clear = glClear; pushMatrix = glPushMatrix; popMatrix = glPopMatrix; rotate = glRotate; translate = glTranslate; scale = glScale; ortho = glOrtho; perspective = glPerspective; }; sys = { now = now; set_flag = function(...) kprint("set_flag() call ignored") end; get_env = function(key) return NODE_ENVIRON[key] end; platform = "desktop"; client_write = client_write; }; events = { child_add = {}; child_remove = {}; content_update = {}; content_remove = {}; osc = {}; data = {}; connect = {}; input = {}; disconnect = {}; raw_data = { function(data, is_osc, suffix) if is_osc then if string.byte(data, 1, 1) ~= 44 then kprint("no osc type tag string") return end local typetags, offset = struct.unpack(">!4s", data) local tags = {string.byte(typetags, 1, offset)} local fmt = ">!4" for idx, tag in ipairs(tags) do if tag == 44 then -- , fmt = fmt .. "s" elseif tag == 105 then -- i fmt = fmt .. "i4" elseif tag == 102 then -- f fmt = fmt .. "f" elseif tag == 98 then -- b kprint("no blob support") return else kprint("unknown type tag " .. string.char(tag)) return end end local unpacked = {struct.unpack(fmt, data)} table.remove(unpacked, 1) -- remove typetags table.remove(unpacked, #unpacked) -- remove trailing offset sandbox.node.dispatch("osc", suffix, unpack(unpacked)) else sandbox.node.dispatch("data", data, suffix) end end; }; render = { function() sandbox.node.render() end }; }; node = { alias = set_alias; event = function(event, handler) if not sandbox.events[event] then sandbox.events[event] = {} end table.insert(sandbox.events[event], handler) end; dispatch = function(event, ...) for _, handler in ipairs(sandbox.events[event] or {}) do handler(...) end end; render = function() end; }; NAME = NAME; PATH = PATH; CHILDS = CHILDS; CONTENTS = CONTENTS; N = N; } -- There is only one metatable for strings. Reset it -- to the sandbox controlled version. local string_mt = getmetatable("") for k, v in pairs(string_mt) do string_mt[k] = nil end string_mt.__index = sandbox.string sandbox._G = sandbox return sandbox end function load_into_sandbox(code, chunkname, allow_precompiled) setfenv( assert(safe_loadstring(code, chunkname, allow_precompiled)), sandbox )() end function reload(...) sandbox = create_sandbox() reset_error() -- load userlib load_into_sandbox( USERLIB, "=userlib.lua", true ) -- load all given files into the sandbox for _, usercode_file in ipairs({...}) do load_into_sandbox( load_file(usercode_file), "=" .. PATH .. "/" .. usercode_file, os.getenv("INFOBEAMER_PRECOMPILED") ) end -- send child / content events for name, added in pairs(CHILDS) do sandbox.node.dispatch("child_add", name) end for name, added in pairs(CONTENTS) do sandbox.node.dispatch("content_update", name) end end -- Einige Funktionen in der registry speichern, -- so dass der C Teil dran kommt. do local registry = debug.getregistry() local full_scale = os.getenv("INFOBEAMER_FULLSCALE") registry.traceback = debug.traceback registry.execute = function(cmd, ...) if cmd == "boot" then kprint("booting node") reload(NODE_CODE_FILE) elseif cmd == "event" then sandbox.node.dispatch(...) elseif cmd == "child_update" then local name, added = ... if added then CHILDS[name] = now() sandbox.node.dispatch("child_add", name) else CHILDS[name] = nil sandbox.node.dispatch("child_remove", name) end elseif cmd == "content_update" then local name, added = ... if name == NODE_CODE_FILE then if added then kprint("node code updated. reloading...") reload(NODE_CODE_FILE) else kprint("node code removed. resetting...") reload() end else if added then CONTENTS[name] = now() sandbox.node.dispatch("content_update", name) else CONTENTS[name] = nil sandbox.node.dispatch("content_remove", name) end end elseif cmd == "render_self" then local screen_width, screen_height = ... if full_scale then render_self():draw(0, 0, screen_width, screen_height) else sandbox.util.draw_correct( render_self(), 0, 0, screen_width, screen_height ) end end end registry.alarm = function() error("CPU usage too high") end end io = nil require = nil loadfile = nil load = nil package = nil module = nil os = { getenv = os.getenv; } dofile = nil debug = { traceback = debug.traceback; getinfo = debug.getinfo; } reload() info-beamer-1.0~pre4/main.c0000644000175000017500000013517512452774240013444 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #define _BSD_SOURCE #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "uthash.h" #include "utlist.h" #include "misc.h" #include "image.h" #include "video.h" #include "font.h" #include "shader.h" #include "vnc.h" #include "framebuffer.h" #include "struct.h" #include "kernel.h" #include "userlib.h" #include "module_json.h" #if USE_LUAJIT #include #define VERSION_STRING "Info Beamer " VERSION "+" LUA_VERSION "+" LUAJIT_VERSION #else #define VERSION_STRING "Info Beamer " VERSION "+" LUA_VERSION #endif #define INFO_URL "http://info-beamer.org/" #define NODE_CODE_FILE "node.lua" #define ENVIRONMENT_PREFIX "INFOBEAMER_ENV_" #define ENVIRONMENT_PREFIX_SIZE (sizeof(ENVIRONMENT_PREFIX)-1) #define MAX_MEM 2000000 // KB #define MAX_GL_PUSH 20 // glPushMatrix depth #define MAX_CHILD_RENDERS 20 // maximum childs rendered per node #define MAX_SNAPSHOTS 5 // maximum number of snapshots per render // Default host/port (both udp & tcp) #define LISTEN_ADDR "0.0.0.0" #define DEFAULT_PORT 4444 #ifdef DEBUG #define MAX_RUNAWAY_TIME 10 // sec #define MAX_PCALL_TIME 5000000 // usec #else #define MAX_RUNAWAY_TIME 1 // sec #define MAX_PCALL_TIME 500000 // usec #endif #define NO_GL_PUSHPOP -1 #define NODE_INACTIVITY 2.0 // node considered idle after x seconds #define NODE_CPU_BLACKLIST 60.0 // seconds a node is blacklisted if it exceeds cpu usage typedef enum { PROFILE_BOOT, PROFILE_UPDATE, PROFILE_EVENT } profiling_bins; typedef struct node_s { int wd; // inotify watch descriptor char *name; // local node name char *path; // full path (including node name) char *alias; // alias path lua_State *L; UT_hash_handle by_wd; // global handle for search by watch descriptor UT_hash_handle by_name; // childs by name UT_hash_handle by_path; // node by path UT_hash_handle by_alias; // node by alias struct node_s *parent; struct node_s *childs; int width; int height; int gl_matrix_depth; struct client_s *clients; int child_render_quota; int snapshot_quota; double profiling[3]; double last_profile; int num_frames; int num_resource_inits; int num_allocs; double last_activity; double blacklisted; } node_t; static node_t *nodes_by_wd = NULL; static node_t *nodes_by_path = NULL; static node_t *nodes_by_alias = NULL; static node_t root = {0}; typedef struct client_s { int fd; node_t *node; struct bufferevent *buf_ev; struct client_s *next; struct client_s *prev; } client_t; static int inotify_fd; static double now; static int running = 1; static int listen_port; GLuint default_tex; // white default texture struct event_base *event_base; struct evdns_base *dns_base; /*=== Forward declarations =====*/ static void client_write(client_t *client, const char *data, size_t data_size); static void client_close(client_t *client); static void node_printf(node_t *node, const char *fmt, ...); static void node_blacklist(node_t *node, double time); static void node_remove_alias(node_t *node); static void node_reset_quota(node_t *node); static int node_render_to_image(lua_State *L, node_t *node); static void node_init(node_t *node, node_t *parent, const char *path, const char *name); static void node_free(node_t *node); /*======= Lua Sandboxing =======*/ #ifndef USE_LUAJIT static void *lua_alloc(void *ud, void *ptr, size_t osize, size_t nsize) { node_t *node = ud; node->num_allocs++; (void)osize; /* not used */ if (nsize == 0) { free(ptr); return NULL; } else { return realloc(ptr, nsize); } } #endif /* execution time limiting for pcalls */ static node_t *global_node = NULL; static int timers_expired = 0; static void deadline_stop(lua_State *L, lua_Debug *ar) { lua_sethook(L, NULL, 0, 0); lua_pushliteral(L, "alarm"); lua_gettable(L, LUA_REGISTRYINDEX); lua_call(L, 0, 0); } static void deadline_signal(int i) { if (!global_node) die("urg. timer expired and no global_node"); fprintf(stderr, RED("[%s]") " timeout\n", global_node->path); if (timers_expired == 0) { // timer expired once? Try to solve it inside of // lua: set a hook that will execute deadline_stop. lua_sethook(global_node->L, deadline_stop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT, 1); node_blacklist(global_node, NODE_CPU_BLACKLIST); } else { // timer expired again without lua being stopped? die("unstoppable runaway code in %s", global_node->path); } timers_expired++; } static int lua_timed_pcall(node_t *node, int in, int out, int error_handler_pos) { node_t *old_global_node = global_node; struct itimerval old_timer; struct itimerval deadline; deadline.it_interval.tv_sec = MAX_RUNAWAY_TIME; deadline.it_interval.tv_usec = 0; deadline.it_value.tv_sec = MAX_PCALL_TIME / 1000000; deadline.it_value.tv_usec = MAX_PCALL_TIME % 1000000; setitimer(ITIMER_VIRTUAL, &deadline, &old_timer); global_node = node; timers_expired = 0; int ret = lua_pcall(node->L, in, out, error_handler_pos); setitimer(ITIMER_VIRTUAL, &old_timer, NULL); global_node = old_global_node; return ret; } static int lua_panic(lua_State *L) { die("node panic!"); return 0; } static const char *lua_safe_dedup_error_message(lua_State *L) { const char *message = lua_tostring(L, -1); if (!message) die(" error message"); lua_pushliteral(L, "last_error"); lua_rawget(L, LUA_REGISTRYINDEX); int same_as_last_time = lua_equal(L, -1, -2); lua_pop(L, 1); // remove value of last_error if (same_as_last_time) { message = NULL; } else { lua_pushliteral(L, "last_error"); lua_pushvalue(L, -2); lua_rawset(L, LUA_REGISTRYINDEX); } return message; } static void lua_node_enter(node_t *node, int args, profiling_bins bin) { node_reset_quota(node); lua_State *L = node->L; lua_pushliteral(L, "execute"); // [args] "execute" lua_rawget(L, LUA_REGISTRYINDEX); // [args] execute lua_insert(L, -1 - args); // execute [args] lua_pushliteral(L, "traceback"); // execute [args] "traceback" lua_rawget(L, LUA_REGISTRYINDEX); // execute [args] traceback const int error_handler_pos = lua_gettop(L) - 1 - args; lua_insert(L, error_handler_pos); // traceback execute [args] struct timeval before, after; gettimeofday(&before, NULL); int status = lua_timed_pcall(node, args, 0, error_handler_pos); if (status == 0) { // success // traceback lua_remove(L, error_handler_pos); // } else { // error // traceback "error" char *err = status == LUA_ERRRUN ? "runtime error" : status == LUA_ERRMEM ? "memory error" : status == LUA_ERRERR ? "error handling error" : NULL; assert(err); const char *message = lua_safe_dedup_error_message(L); if (message) node_printf(node, "%s: %s\n", err, message); lua_pop(L, 2); // } gettimeofday(&after, NULL); lua_gc(node->L, LUA_GCSTEP, 5); node->profiling[bin] += time_delta(&before, &after); node->last_activity = now; } /*======= Lua entry points =======*/ // reinit sandbox, load usercode and user code static void node_boot(node_t *node) { lua_pushliteral(node->L, "boot"); lua_node_enter(node, 1, PROFILE_BOOT); } // notify of child update static void node_child_update(node_t *node, const char *name, int added) { lua_pushliteral(node->L, "child_update"); lua_pushstring(node->L, name); lua_pushboolean(node->L, added); lua_node_enter(node, 3, PROFILE_UPDATE); } // notify of content update static void node_content_update(node_t *node, const char *name, int added) { fprintf(stderr, YELLOW("[%s]")" update %c%s\n", node->path, added ? '+' : '-', name); lua_pushliteral(node->L, "content_update"); lua_pushstring(node->L, name); lua_pushboolean(node->L, added); if (!strcmp(name, NODE_CODE_FILE)) { // reset blacklisted flag node->blacklisted = 0; // reset node dimensions node->width = 0; node->height = 0; // remove existing node alias node_remove_alias(node); } lua_node_enter(node, 3, PROFILE_UPDATE); } // event.(args...) static void node_event(node_t *node, const char *name, int args) { lua_pushliteral(node->L, "event"); // [args] "event_name" lua_pushstring(node->L, name); // [args] "event_name" name lua_insert(node->L, -2 - args); // name [args] "event_name" lua_insert(node->L, -2 - args); // "event_name" name [args] lua_node_enter(node, 2 + args, PROFILE_EVENT); } // render node static void node_render_self(node_t *node, int width, int height) { lua_pushliteral(node->L, "render_self"); lua_pushnumber(node->L, width); lua_pushnumber(node->L, height); lua_node_enter(node, 3, PROFILE_EVENT); } /*===== node macros =======*/ #define node_setup_completed(node) ((node)->width != 0) #define node_is_idle(node) (now > (node)->last_activity + NODE_INACTIVITY) #define node_is_blacklisted(node) (now < (node)->blacklisted) #define node_is_rendering(node) ((node)->gl_matrix_depth != NO_GL_PUSHPOP) /*===== Lua bindings ======*/ static node_t *get_rendering_node(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); if (!node_is_rendering(node)) luaL_error(L, "only callable in node.render"); return node; } static int luaResetError(lua_State *L) { lua_pushliteral(L, "last_error"); lua_pushnil(L); lua_rawset(L, LUA_REGISTRYINDEX); return 0; } static int luaRenderSelf(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); return node_render_to_image(L, node); } static int luaRenderChild(lua_State *L) { node_t *node = get_rendering_node(L); if (node->child_render_quota-- <= 0) return luaL_error(L, "too many childs rendered"); const char *name = luaL_checkstring(L, 1); node_t *child; HASH_FIND(by_name, node->childs, name, strlen(name), child); if (!child) return luaL_error(L, "child %s not found", name); return node_render_to_image(L, child); } static int luaSetup(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); if (node_is_rendering(node)) return luaL_error(L, "cannot change width or height while rendering"); int width = (int)luaL_checknumber(L, 1); int height = (int)luaL_checknumber(L, 2); if (width < 32 || width > 2048) luaL_argerror(L, 1, "invalid width. must be within [32,2048]"); if (height < 32 || height > 2048) luaL_argerror(L, 2, "invalid height. must be within [32,2048]"); node->width = width; node->height = height; return 0; } static int luaGlOrtho(lua_State *L) { node_t *node = get_rendering_node(L); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, node->width, node->height, 0, -1000, 1000); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); node->gl_matrix_depth = 0; return 0; } static int luaGlPerspective(lua_State *L) { node_t *node = get_rendering_node(L); double fov = luaL_checknumber(L, 1); double eye_x = luaL_checknumber(L, 2); double eye_y = luaL_checknumber(L, 3); double eye_z = luaL_checknumber(L, 4); double center_x = luaL_checknumber(L, 5); double center_y = luaL_checknumber(L, 6); double center_z = luaL_checknumber(L, 7); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(fov, (float)node->width / (float)node->height, 0.1, 10000); gluLookAt(eye_x, eye_y, eye_z, center_x, center_y, center_z, 0, -1, 0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); node->gl_matrix_depth = 0; return 0; } static int luaSetAlias(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); const char *alias = luaL_checkstring(L, 1); // already exists? node_t *existing_node; HASH_FIND(by_alias, nodes_by_alias, alias, strlen(alias), existing_node); if (existing_node) { if (existing_node == node) { return 0; } else { return luaL_error(L, "alias already taken by %s", existing_node->path); } } // remove old alias if (node->alias) { HASH_DELETE(by_alias, nodes_by_alias, node); free(node->alias); } // set new alias node->alias = strdup(alias); HASH_ADD_KEYPTR(by_alias, nodes_by_alias, node->alias, strlen(node->alias), node); return 0; } static int luaLoadImage(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); const char *name = luaL_checkstring(L, 1); if (index(name, '/')) luaL_argerror(L, 1, "invalid resource name"); char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", node->path, name); node->num_resource_inits++; return image_load(L, path, name); } static int luaLoadVideo(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); const char *name = luaL_checkstring(L, 1); if (index(name, '/')) luaL_argerror(L, 1, "invalid resource name"); char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", node->path, name); node->num_resource_inits++; return video_load(L, path, name); } static int luaLoadFont(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); const char *name = luaL_checkstring(L, 1); if (index(name, '/')) luaL_argerror(L, 1, "invalid resource name"); char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", node->path, name); node->num_resource_inits++; return font_new(L, path, name); } static int luaLoadFile(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); const char *name = luaL_checkstring(L, 1); if (index(name, '/')) luaL_argerror(L, 1, "invalid resource name"); char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", node->path, name); int fd = open(path, O_RDONLY); if (fd == -1) return luaL_error(L, "cannot open file '%s'", path); luaL_Buffer b; luaL_buffinit(L, &b); while (1) { char *data = luaL_prepbuffer(&b); ssize_t data_size = read(fd, data, LUAL_BUFFERSIZE); if (data_size < 0) return luaL_error(L, "cannot read %s: %s", name, strerror(errno)); if (data_size == 0) break; luaL_addsize(&b, data_size); } close(fd); luaL_pushresult(&b); node->num_resource_inits++; return 1; } static int luaCreateColoredTexture(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); GLfloat r = luaL_checknumber(L, 1); GLfloat g = luaL_checknumber(L, 2); GLfloat b = luaL_checknumber(L, 3); GLfloat a = luaL_optnumber(L, 4, 1.0); node->num_resource_inits++; return image_from_color(L, CLAMP(r, 0, 1), CLAMP(g, 0, 1), CLAMP(b, 0, 1), CLAMP(a, 0, 1) ); } static int luaCreateSnapshot(lua_State *L) { node_t *node = get_rendering_node(L); if (node->snapshot_quota-- <= 0) return luaL_error(L, "too many snapshots"); node->num_resource_inits++; int mipmap = 0; int x = 0; int y = 0; int width = node->width; int height = node->height; if (lua_gettop(L) <= 1) { mipmap = lua_toboolean(L, 1); } else if (lua_gettop(L) == 4) { x = luaL_checknumber(L, 1); y = luaL_checknumber(L, 2); width = luaL_checknumber(L, 3); height = luaL_checknumber(L, 4); if (x < 0 || y < 0 || width < 0 || height < 0 || x + width > node->width || y + height > node->height) { return luaL_error(L, "snapshot out of bounds"); } } else { return luaL_error(L, "invalid number of arguments"); } return image_from_current_framebuffer( L, x, node->height - y - height, width, height, mipmap ); } static int luaCreateShader(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); const char *vertex = luaL_checkstring(L, 1); const char *fragment = luaL_checkstring(L, 2); node->num_resource_inits++; return shader_new(L, vertex, fragment); } static int luaCreateVnc(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); const char *host = luaL_checkstring(L, 1); int port = luaL_optnumber(L, 2, 5900); node->num_resource_inits++; return vnc_create(L, host, port); } static int luaPushFormattedArgs(lua_State *L) { luaL_Buffer b; luaL_buffinit(L, &b); int n = lua_gettop(L); lua_getglobal(L, "tostring"); for (int i = 1; i <= n; i++) { lua_pushvalue(L, n + 1); lua_pushvalue(L, i); lua_call(L, 1, 1); if (!lua_isstring(L, -1)) return luaL_error(L, "tostring must return a string to print"); if (i > 1) luaL_addchar(&b, '\t'); luaL_addvalue(&b); } luaL_addchar(&b, '\n'); luaL_pushresult(&b); return 1; } static int luaPrint(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); luaPushFormattedArgs(L); node_printf(node, "%s", lua_tostring(L, -1)); return 0; } static int luaClientWrite(lua_State *L) { node_t *node = lua_touserdata(L, lua_upvalueindex(1)); client_t *client = lua_touserdata(L, 1); lua_remove(L, 1); luaPushFormattedArgs(L); size_t string_len; const char *string = lua_tolstring(L, -1, &string_len); client_t *current_client; DL_FOREACH(node->clients, current_client) { if (current_client == client) { client_write(current_client, string, string_len); } } return 0; } static int luaGlClear(lua_State *L) { get_rendering_node(L); GLdouble r = luaL_checknumber(L, 1); GLdouble g = luaL_checknumber(L, 2); GLdouble b = luaL_checknumber(L, 3); GLdouble a = luaL_checknumber(L, 4); glClearColor(r, g, b, a); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(0); return 0; } static int luaGlPushMatrix(lua_State *L) { node_t *node = get_rendering_node(L); if (node->gl_matrix_depth > MAX_GL_PUSH) return luaL_error(L, "Too may pushes"); glPushMatrix(); node->gl_matrix_depth++; return 0; } static int luaGlPopMatrix(lua_State *L) { node_t *node = get_rendering_node(L); if (node->gl_matrix_depth == 0) return luaL_error(L, "Nothing to pop"); glPopMatrix(); node->gl_matrix_depth--; return 0; } static int luaGlRotate(lua_State *L) { get_rendering_node(L); double angle = luaL_checknumber(L, 1); double x = luaL_checknumber(L, 2); double y = luaL_checknumber(L, 3); double z = luaL_checknumber(L, 4); glRotated(angle, x, y, z); return 0; } static int luaGlTranslate(lua_State *L) { get_rendering_node(L); double x = luaL_checknumber(L, 1); double y = luaL_checknumber(L, 2); double z = luaL_optnumber(L, 3, 0.0); glTranslated(x, y, z); return 0; } static int luaGlScale(lua_State *L) { get_rendering_node(L); double x = luaL_checknumber(L, 1); double y = luaL_checknumber(L, 2); double z = luaL_optnumber(L, 3, 1.0); glScaled(x, y, z); return 0; } static int luaNow(lua_State *L) { lua_pushnumber(L, now); return 1; } /*==== Node functions =====*/ static int node_render_to_image(lua_State *L, node_t *node) { // save current gl state int prev_fbo, prev_prog; GLdouble prev_projection[16]; GLdouble prev_modelview[16]; glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo); glGetIntegerv(GL_CURRENT_PROGRAM, &prev_prog); glGetDoublev(GL_PROJECTION_MATRIX, prev_projection); glGetDoublev(GL_MODELVIEW_MATRIX, prev_modelview); glPushAttrib(GL_ALL_ATTRIB_BITS); int width = 1, height = 1; if (node_setup_completed(node)) width = node->width, height = node->height; // get new framebuffer and associated texture from recycler unsigned int fbo, tex; make_framebuffer(width, height, &tex, &fbo); // initialize gl state glUseProgram(0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glViewport(0, 0, width, height); glOrtho(0, width, height, 0, -1000, 1000); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if (!node_setup_completed(node)) { node_printf(node, "node not initialized with gl.setup()\n"); glClearColor(0.5, 0.5, 0.5, 1); glClear(GL_COLOR_BUFFER_BIT); } else if (node_is_blacklisted(node)) { node_printf(node, "node is blacklisted\n"); glClearColor(0.5, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); } else { // clear with transparent color glClearColor(1, 1, 1, 0); glClear(GL_COLOR_BUFFER_BIT); // render node node->gl_matrix_depth = 0; node->num_frames++; node_event(node, "render", 0); while (node->gl_matrix_depth-- > 0) glPopMatrix(); node->gl_matrix_depth = NO_GL_PUSHPOP; } // rebind to framebuffer texture glBindTexture(GL_TEXTURE_2D, tex); glGenerateMipmap(GL_TEXTURE_2D); // restore previous state glPopAttrib(); glMatrixMode(GL_PROJECTION); glLoadMatrixd(prev_projection); glMatrixMode(GL_MODELVIEW); glLoadMatrixd(prev_modelview); glUseProgram(prev_prog); glBindFramebuffer(GL_FRAMEBUFFER, prev_fbo); return image_create(L, tex, fbo, width, height); } static void node_printf(node_t *node, const char *fmt, ...) { char buffer[16384]; va_list ap; va_start(ap, fmt); vsnprintf(buffer, sizeof(buffer), fmt, ap); va_end(ap); fprintf(stderr, GREEN("[%s]")" %s", node->path, buffer); } static void node_blacklist(node_t *node, double time) { node->blacklisted = now + time; node_printf(node, "blacklisted for %.0f seconds\n", time); } static void node_remove_alias(node_t *node) { if (node->alias) { HASH_DELETE(by_alias, nodes_by_alias, node); free(node->alias); node->alias = NULL; } } static void node_tree_gc(node_t *node) { if (!node_is_idle(node)) lua_gc(node->L, LUA_GCSTEP, 30); node_t *child, *tmp; HASH_ITER(by_name, node->childs, child, tmp) { node_tree_gc(child); }; } static node_t *node_add_child(node_t* node, const char *path, const char *name) { fprintf(stderr, YELLOW("[%s]")" adding new child node %s\n", node->name, name); node_t *child = xmalloc(sizeof(node_t)); node_init(child, node, path, name); HASH_ADD_KEYPTR(by_name, node->childs, child->name, strlen(child->name), child); return child; } static void node_remove_child(node_t* node, node_t* child) { fprintf(stderr, YELLOW("[%s]")" removing child node %s\n", node->name, child->name); node_child_update(node, child->name, 0); HASH_DELETE(by_name, node->childs, child); node_free(child); free(child); } static void node_remove_child_by_name(node_t* node, const char *name) { node_t *child; HASH_FIND(by_name, node->childs, name, strlen(name), child); if (!child) die("child not found: %s", name); node_remove_child(node, child); } static void node_reset_quota(node_t *node) { node->child_render_quota = MAX_CHILD_RENDERS; node->snapshot_quota = MAX_SNAPSHOTS; } static void node_reset_profiler(node_t *node) { node->last_profile = now; node->profiling[PROFILE_BOOT] = 0.0; node->profiling[PROFILE_UPDATE] = 0.0; node->profiling[PROFILE_EVENT] = 0.0; node->num_frames = 0; node->num_resource_inits = 0; node->num_allocs = 0; } #define lua_register_node_func(node,name,func) \ (lua_pushliteral((node)->L, name), \ lua_pushlightuserdata((node)->L, node), \ lua_pushcclosure((node)->L, func, 1), \ lua_settable((node)->L, LUA_GLOBALSINDEX)) static void node_init(node_t *node, node_t *parent, const char *path, const char *name) { // add directory watcher node->wd = inotify_add_watch(inotify_fd, path, IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF| IN_MOVE); if (node->wd == -1) die("cannot start watching directory %s: %s", path, strerror(errno)); node->parent = parent; node->path = strdup(path); node->name = strdup(name); node->alias = NULL; node->width = 0; node->height = 0; node_reset_profiler(node); node->last_activity = now; node->gl_matrix_depth = NO_GL_PUSHPOP; // link by watch descriptor & path HASH_ADD(by_wd, nodes_by_wd, wd, sizeof(int), node); HASH_ADD_KEYPTR(by_path, nodes_by_path, node->path, strlen(node->path), node); // create lua state #ifdef USE_LUAJIT node->L = luaL_newstate(); #else node->L = lua_newstate(lua_alloc, node); #endif if (!node->L) die("cannot create lua"); lua_atpanic(node->L, lua_panic); luaL_openlibs(node->L); image_register(node->L); video_register(node->L); font_register(node->L); shader_register(node->L); vnc_register(node->L); luaopen_struct(node->L); lua_register_node_func(node, "reset_error", luaResetError); lua_register_node_func(node, "setup", luaSetup); lua_register_node_func(node, "print", luaPrint); lua_register_node_func(node, "set_alias", luaSetAlias); lua_register_node_func(node, "client_write", luaClientWrite); lua_register_node_func(node, "render_self", luaRenderSelf); lua_register_node_func(node, "render_child", luaRenderChild); lua_register_node_func(node, "load_image", luaLoadImage); lua_register_node_func(node, "load_video", luaLoadVideo); lua_register_node_func(node, "load_font", luaLoadFont); lua_register_node_func(node, "load_file", luaLoadFile); lua_register_node_func(node, "create_colored_texture", luaCreateColoredTexture); lua_register_node_func(node, "create_snapshot", luaCreateSnapshot); lua_register_node_func(node, "create_shader", luaCreateShader); lua_register_node_func(node, "create_vnc", luaCreateVnc); lua_register_node_func(node, "glClear", luaGlClear); lua_register_node_func(node, "glPushMatrix", luaGlPushMatrix); lua_register_node_func(node, "glPopMatrix", luaGlPopMatrix); lua_register_node_func(node, "glRotate", luaGlRotate); lua_register_node_func(node, "glTranslate", luaGlTranslate); lua_register_node_func(node, "glScale", luaGlScale); lua_register_node_func(node, "glOrtho", luaGlOrtho); lua_register_node_func(node, "glPerspective", luaGlPerspective); lua_register(node->L, "now", luaNow); lua_pushstring(node->L, path); lua_setglobal(node->L, "PATH"); lua_pushstring(node->L, name); lua_setglobal(node->L, "NAME"); lua_pushlstring(node->L, userlib, userlib_size); lua_setglobal(node->L, "USERLIB"); lua_pushlstring(node->L, module_json, module_json_size); lua_setglobal(node->L, "MODULE_JSON"); lua_pushliteral(node->L, NODE_CODE_FILE); lua_setglobal(node->L, "NODE_CODE_FILE"); // get variables from environment lua_newtable(node->L); for (const char **cur = (const char**)environ; *cur; cur++) { if (strstr(*cur, ENVIRONMENT_PREFIX) != *cur) continue; const char *equals = strstr(*cur + ENVIRONMENT_PREFIX_SIZE, "="); if (!equals) continue; const char *name = *cur + ENVIRONMENT_PREFIX_SIZE; int name_len = equals - name; const char *value = equals+1; // printf("%d %.*s %s\n", name_len, name_len, name, value); lua_pushlstring(node->L, name, name_len); lua_pushstring(node->L, value); lua_rawset(node->L, -3); } lua_setglobal(node->L, "NODE_ENVIRON"); if (luaL_loadbuffer(node->L, kernel, kernel_size, "=kernel.lua") != 0) { const char *error = lua_tostring(node->L, -1); // If kernel.lua was procompiled with an incompatible lua // version, loading the embedded code fail here. Try to // detect this... die("cannot load kernel.lua: %s%s", error, strstr(error, "bad header") ? " (See 'kernel load error' in the docs)" : "" ); } if (lua_pcall(node->L, 0, 0, 0) != 0) die("kernel run %s", lua_tostring(node->L, -1)); } static void node_free(node_t *node) { node_t *child, *tmp; HASH_ITER(by_name, node->childs, child, tmp) { node_remove_child(node, child); } HASH_DELETE(by_wd, nodes_by_wd, node); HASH_DELETE(by_path, nodes_by_path, node); free(node->path); free(node->name); node_remove_alias(node); client_t *client, *tmp_client; DL_FOREACH_SAFE(node->clients, client, tmp_client) { client_close(client); } assert(node->clients == NULL); lua_close(node->L); } static void node_search_and_boot(node_t *node) { DIR *dp = opendir(node->path); if (!dp) die("cannot open directory %s: %s", node->path, strerror(errno)); struct dirent *ep; while ((ep = readdir(dp))) { if (ep->d_name[0] == '.') continue; const char *child_name = ep->d_name; char child_path[PATH_MAX]; snprintf(child_path, sizeof(child_path), "%s/%s", node->path, child_name); enum { CHILD_FILE, CHILD_DIR, CHILD_UNKNOWN } type = CHILD_UNKNOWN; if (ep->d_type == DT_UNKNOWN) { struct stat sb; if (stat(child_path, &sb) == -1) die("cannot stat %s", child_path); if (S_ISDIR(sb.st_mode)) { type = CHILD_DIR; } else if (S_ISREG(sb.st_mode)) { type = CHILD_FILE; } } else if (ep->d_type == DT_DIR) { type = CHILD_DIR; } else if (ep->d_type == DT_REG) { type = CHILD_FILE; } if (type == CHILD_DIR) { node_t *child = node_add_child(node, child_path, child_name); node_search_and_boot(child); node_child_update(node, child->name, 1); } else if (type == CHILD_FILE && strcmp(child_name, NODE_CODE_FILE)) { node_content_update(node, child_name, 1); } } closedir(dp); node_boot(node); } static void node_init_root(node_t *root, const char *base_path) { node_init(root, NULL, base_path, base_path); node_search_and_boot(root); } static node_t *node_find_by_path_or_alias(const char *needle) { size_t needle_size = strlen(needle); node_t *node; HASH_FIND(by_path, nodes_by_path, needle, needle_size, node); if (node) return node; HASH_FIND(by_alias, nodes_by_alias, needle, needle_size, node); return node; } static void node_print_profile(node_t *node, int depth) { node_t *child, *tmp; double delta = (now - node->last_profile) * 1000; fprintf(stderr, "%c%4dkb %3.0f %5.1f %6.1f %5d %5d %5.1lf%% %5.1lf%% %5.1lf%% %*s '- %s (%s)\n", node_is_blacklisted(node) ? 'X' : node_is_idle(node) ? ' ' : '*', lua_gc(node->L, LUA_GCCOUNT, 0), node->num_frames * 1000 / delta, (double)node->num_resource_inits * 1000 / delta, node->num_frames ? (double)node->num_allocs / node->num_frames : 0.0, node->width, node->height, 100 / delta * node->profiling[PROFILE_BOOT], 100 / delta * node->profiling[PROFILE_UPDATE], 100 / delta * node->profiling[PROFILE_EVENT], depth*3, "", node->name, node->alias ? node->alias : "-" ); node_reset_profiler(node); HASH_ITER(by_name, node->childs, child, tmp) { node_print_profile(child, depth+1); }; } static void node_profiler() { fprintf(stderr, " mem fps rps allocs width height boot update event name (alias)\n"); fprintf(stderr, "---------------------------------------------------------------------------\n"); node_print_profile(&root, 0); fprintf(stderr, "---------------------------------------------------------------------------\n"); } /*======= inotify ==========*/ static void check_inotify() { static char inotify_buffer[sizeof(struct inotify_event) + PATH_MAX + 1]; while (1) { size_t size = read(inotify_fd, &inotify_buffer, sizeof(inotify_buffer)); if (size == -1) { if (errno == EAGAIN) break; die("error reading from inotify fd"); } char *pos = inotify_buffer; char *end = pos + size; while (pos < end) { struct inotify_event *event = (struct inotify_event*)pos; pos += sizeof(struct inotify_event) + event->len; // printf("%s %08x %d\n", event->name, event->mask, event->wd); // ignore dot-files (including parent and current directory) if (event->len && event->name[0] == '.') continue; // ignore dot files // notifies, that wd was removed from kernel. // can be ignored (since it is handled in // IN_DELETE_SELF). if (event->mask & IN_IGNORED) continue; node_t *node; HASH_FIND(by_wd, nodes_by_wd, &event->wd, sizeof(int), node); if (!node) die("node not found: %s", event->name); char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", node->path, event->name); // fprintf(stderr, "event for %s (%s), mask: %08x\n", path, event->name, event->mask); if (event->mask & IN_CREATE) { struct stat stat_buf; if (stat(path, &stat_buf) == -1) { // file/path can be gone (race between inotify and // user actions) fprintf(stderr, "cannot stat %s\n", path); continue; } if (S_ISDIR(stat_buf.st_mode)) { node_t *child = node_add_child(node, path, event->name); node_search_and_boot(child); node_child_update(node, child->name, 1); } else if (S_ISREG(stat_buf.st_mode)) { node_content_update(node, event->name, 1); } } else if (event->mask & IN_CLOSE_WRITE) { node_content_update(node, event->name, 1); } else if (event->mask & IN_DELETE_SELF) { if (!node->parent) die("root node deleted. cannot continue"); node_remove_child(node->parent, node); } else if (event->mask & IN_DELETE && !(event->mask & IN_ISDIR)) { node_content_update(node, event->name, 0); } else if (event->mask & IN_MOVED_FROM) { if (event->mask & IN_ISDIR) { node_remove_child_by_name(node, event->name); } else { node_content_update(node, event->name, 0); } } else if (event->mask & IN_MOVED_TO) { if (event->mask & IN_ISDIR) { node_t *child = node_add_child(node, path, event->name); node_search_and_boot(child); node_child_update(node, child->name, 1); } else { node_content_update(node, event->name, 1); } } } } } /*============ GUI ===========*/ static int win_w, win_h; static void GLFWCALL reshape(int width, int height) { win_w = width; win_h = height; fprintf(stderr, INFO("resized to %dx%d\n"), width, height); } static void GLFWCALL keypressed(int key, int action) { if (action == GLFW_PRESS) { switch (key) { case GLFW_KEY_SPACE: node_profiler(); break; case GLFW_KEY_ESC: running = 0; break; } } } /*===== Util ========*/ static int create_socket(int type) { int one = 1; struct sockaddr_in sin; int fd = socket(AF_INET, type, 0); if (fd < 0) die("socket failed: %s", strerror(errno)); if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) < 0) die("setsockopt reuse failed: %s", strerror(errno)); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; const char *addr = getenv("INFOBEAMER_ADDR"); if (!addr) addr = LISTEN_ADDR; if (!inet_aton(addr, &sin.sin_addr)) die("invalid address %s", addr); sin.sin_port = htons(listen_port); if (bind(fd, (struct sockaddr *)&sin, sizeof(struct sockaddr)) < 0) die("binding to %s port %d failed: %s", type == SOCK_DGRAM ? "udp" : "tcp", listen_port, strerror(errno) ); return fd; } /*===== UDP (osc) Handling ========*/ static void udp_read(int fd, short event, void *arg) { char buf[1500]; int len; unsigned int size = sizeof(struct sockaddr); struct sockaddr_in client_addr; len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &size); if (len == -1) die("recvfrom"); assert(len > 0); // own format: : int is_osc = 0; char payload_separator = ':'; int initial_offset = 0; // If data starts with /, assume it's osc // format: /0x00 if (*buf == '/') { is_osc = 1; payload_separator = '\0'; initial_offset = 1; }; char *sep = memchr(buf, payload_separator, len); if (!sep) { sendto(fd, LITERAL_AND_SIZE("fmt\n"), 0, (struct sockaddr *)&client_addr, size); return; } // Terminate by NUL *sep = '\0'; char *path = buf + initial_offset; char *data = sep + 1; if (is_osc) { // round up to next multiple of 4 data += 3 - (data - buf - 1) % 4; } int data_len = buf + len - data; if (data_len < 0) { sendto(fd, LITERAL_AND_SIZE("wtf\n"), 0, (struct sockaddr *)&client_addr, size); return; } // split a/b/c into first matching prefix: // a/b -> suffix: c if node a/b exists // fprintf(stderr, "udp event: %s: %*s\n", path, data_len, data); char *suffix = sep; node_t *node; while (1) { node = node_find_by_path_or_alias(path); if (node) break; char *next_split = memrchr(path, '/', suffix - path); if (!next_split) { sendto(fd, LITERAL_AND_SIZE("404\n"), 0, (struct sockaddr *)&client_addr, size); return; } if (suffix != sep) *suffix = '/'; suffix = next_split; *next_split = '\0'; } if (suffix != sep) suffix++; lua_pushlstring(node->L, data, data_len); lua_pushboolean(node->L, is_osc); lua_pushstring(node->L, suffix); node_event(node, "raw_data", 3); } static void open_udp(struct event *event) { int fd = create_socket(SOCK_DGRAM); event_set(event, fd, EV_READ | EV_PERSIST, &udp_read, NULL); if (event_add(event, NULL) == -1) die("event_add failed"); } /*===== TCP Handler ========*/ static void client_write(client_t *client, const char *data, size_t data_size) { bufferevent_write(client->buf_ev, data, data_size); } static void client_close(client_t *client) { if (client->node) { lua_pushlightuserdata(client->node->L, client); node_event(client->node, "disconnect", 1); // unlink client & node DL_DELETE(client->node->clients, client); client->node = NULL; } bufferevent_free(client->buf_ev); close(client->fd); free(client); } static void client_read(struct bufferevent *bev, void *arg) { client_t *client = arg; while (1) { char *line = evbuffer_readln(bev->input, NULL, EVBUFFER_EOL_CRLF); if (!line) break; if (client->node) { lua_pushstring(client->node->L, line); lua_pushlightuserdata(client->node->L, client); node_event(client->node, "input", 2); } else { node_t *node = node_find_by_path_or_alias(line); if (!node) { client_write(client, LITERAL_AND_SIZE("404\n")); } else { // link client & node DL_APPEND(node->clients, client); client->node = node; client_write(client, LITERAL_AND_SIZE("ok!\n")); lua_pushlightuserdata(node->L, client); node_event(client->node, "connect", 1); } } free(line); } } static void client_error(struct bufferevent *bev, short what, void *arg) { client_t *client = arg; client_close(client); } static void client_create(int fd) { client_t *client = xmalloc(sizeof(client_t)); client->fd = fd; client->buf_ev = bufferevent_new( fd, client_read, NULL, client_error, client); bufferevent_enable(client->buf_ev, EV_READ); client_write(client, LITERAL_AND_SIZE(VERSION_STRING)); client_write(client, LITERAL_AND_SIZE(" (")); client_write(client, LITERAL_AND_SIZE(INFO_URL)); client_write(client, LITERAL_AND_SIZE(") [")); char status[64]; snprintf(status, sizeof(status), "pid %d/uptime %d", getpid(), (int)now); client_write(client, status, strlen(status)); client_write(client, LITERAL_AND_SIZE("]. Select your channel!\n")); } static void accept_callback(int fd, short ev, void *arg) { int client_fd; struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_len); if (client_fd < 0) { fprintf(stderr, "accept() failed\n"); return; } evutil_make_socket_nonblocking(client_fd); client_create(client_fd); } static void open_tcp(struct event *event) { int fd = create_socket(SOCK_STREAM); if (listen(fd, 5) < 0) die("listen failed: %s", strerror(errno)); evutil_make_socket_nonblocking(fd); event_set(event, fd, EV_READ | EV_PERSIST, accept_callback, NULL); if (event_add(event, NULL) == -1) die("event_add failed"); } static void tick() { now = glfwGetTime(); check_inotify(); event_loop(EVLOOP_NONBLOCK); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFuncSeparate( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_ONE ); glBindFramebuffer(GL_FRAMEBUFFER, 0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glViewport(0, 0, win_w, win_h); glOrtho(0, win_w, win_h, 0, -1000, 1000); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glClearColor(0.05, 0.05, 0.05, 1); glClear(GL_COLOR_BUFFER_BIT); node_render_self(&root, win_w, win_h); glfwSwapBuffers(); node_tree_gc(&root); if (!glfwGetWindowParam(GLFW_OPENED)) running = 0; } static void init_default_texture() { glGenTextures(1, &default_tex); glBindTexture(GL_TEXTURE_2D, default_tex); unsigned char white_pixel[] = {255, 255, 255, 255}; glTexImage2D(GL_TEXTURE_2D, 0, 4, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, white_pixel); } int main(int argc, char *argv[]) { fprintf(stdout, VERSION_STRING " (" INFO_URL ")\n"); fprintf(stdout, "Copyright (c) 2014 Florian Wesch \n\n"); if (argc != 2 || (argc == 2 && !strcmp(argv[1], "-h"))) { fprintf(stderr, "Usage: %s \n" "\n" "Optional environment variables:\n" "\n" " INFOBEAMER_FULLSCREEN=1 # Fullscreen mode\n" " INFOBEAMER_ADDR= # Bind to specified ip (default %s)\n" " INFOBEAMER_PORT= # Listen on alternative port (tcp & udp, default %d)\n" " INFOBEAMER_PRECOMPILED=1 # Allow precompiled code\n" " Warning: unsafe for untrusted code\n" " INFOBEAMER_FULLSCALE=1 # Scale root node to full screen size\n" " INFOBEAMER_WIDTH= # Width (default 1024)\n" " INFOBEAMER_HEIGHT= # Height (default 768)\n" "\n", argv[0], LISTEN_ADDR, DEFAULT_PORT); exit(1); } char *root_name = realpath(argv[1], NULL); if (!root_name) die("cannot canonicalize path: %s", strerror(errno)); char *split = rindex(root_name, '/'); if (!split) die("no slashes in target path. cannot continue"); *split = '\0'; if (*root_name == '\0') root_name = "/"; fprintf(stderr, INFO("chdir %s\n"), root_name); if (chdir(root_name) == -1) die("cannot chdir(%s): %s", root_name, strerror(errno)); root_name = split+1; fprintf(stderr, INFO("root node is %s\n"), root_name); inotify_fd = inotify_init1(IN_NONBLOCK); if (inotify_fd == -1) die("cannot open inotify: %s", strerror(errno)); av_register_all(); event_base = event_init(); dns_base = evdns_base_new(event_base, 1); const char *port = getenv("INFOBEAMER_PORT"); listen_port = port ? atoi(port) : DEFAULT_PORT; fprintf(stderr, INFO("tcp/udp port is %d\n"), listen_port); struct event udp_event; open_udp(&udp_event); struct event tcp_event; open_tcp(&tcp_event); glfwInit(); glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4); int mode = getenv("INFOBEAMER_FULLSCREEN") ? GLFW_FULLSCREEN : GLFW_WINDOW; int width = 1024; int height = 768; const char *new_width = getenv("INFOBEAMER_WIDTH"); if (new_width) width = atoi(new_width); const char *new_height = getenv("INFOBEAMER_HEIGHT"); if (new_height) height = atoi(new_height); fprintf(stderr, INFO("initial size is %dx%d\n"), width, height); if(!glfwOpenWindow(width, height, 8,8,8,8, 0,0, mode)) die("cannot open window"); GLenum err = glewInit(); if (err != GLEW_OK) die("cannot initialize glew"); if (!glewIsSupported("GL_VERSION_3_0")) die("need opengl 3.0 support\n"); glfwSetWindowTitle(VERSION_STRING); glfwSwapInterval(1); glfwSetWindowSizeCallback(reshape); glfwSetKeyCallback(keypressed); if (mode == GLFW_FULLSCREEN) glfwDisable(GLFW_MOUSE_CURSOR); ilInit(); iluInit(); signal(SIGVTALRM, deadline_signal); init_default_texture(); now = glfwGetTime(); node_init_root(&root, root_name); fprintf(stderr, INFO("initialization completed\n")); while (running) { tick(); } // no cleanup :-} return 0; } info-beamer-1.0~pre4/misc.c0000644000175000017500000000505212452774240013441 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #include #include #include #include #include #include "misc.h" void die(const char *fmt, ...) { va_list ap; va_start(ap, fmt); printf("CRITICAL ERROR: "); vprintf(fmt, ap); printf("\n"); va_end(ap); exit(1); } double time_delta(struct timeval *before, struct timeval *after) { double delta_seconds = after->tv_sec - before->tv_sec; double delta_milliseconds = (after->tv_usec - before->tv_usec) / 1000; if (delta_milliseconds < 0) { delta_milliseconds += 1000; delta_seconds--; } return delta_seconds * 1000 + delta_milliseconds; } void *xmalloc(size_t size) { void *ptr = calloc(1, size); if (!ptr) die("cannot malloc"); return ptr; } // Copyright (c) 2008-2009 Bjoern Hoehrmann // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. // // Modified for info-beamer static const uint8_t utf8d[] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8 }; #define UTF8_ACCEPT 0 #define UTF8_REJECT 1 uint32_t decode_utf8(uint32_t* state, uint32_t* codep, uint32_t byte) { uint32_t type = utf8d[byte]; *codep = (*state != UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte); *state = utf8d[256 + *state*16 + type]; return *state; } int check_utf8(const char* s) { uint32_t codepoint, state = 0; while (*s) decode_utf8(&state, &codepoint, *(uint8_t*)s++); return state == UTF8_ACCEPT; } info-beamer-1.0~pre4/misc.h0000644000175000017500000001062012452774240013443 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #ifndef MISC_H #define MISC_H #include #include #include #include #define LITERAL_SIZE(x) (sizeof(x) - 1) #define LITERAL_AND_SIZE(x) x, LITERAL_SIZE(x) #define RED(string) "" string "" #define GREEN(string) "" string "" #define YELLOW(string) "" string "" #define BLUE(string) "" string "" #define CYAN(string) "" string "" #define WHITE(string) "" string "" #define INFO(str) WHITE("[" __FILE__ "]") " " str #define ERROR(str) RED("[" __FILE__ "]") " " str #define CLAMP(val, min, max) ((val) > (max) ? (max) : ((val) < (min) ? (min) : (val))) void die(const char *fmt, ...); void *xmalloc(size_t size); double time_delta(struct timeval *before, struct timeval *after); int check_utf8(const char* s); extern GLuint default_tex; extern struct event_base *event_base; extern struct evdns_base *dns_base; // Simple Lua binder // Based on http://lua-users.org/wiki/UserDataWithPointerExample #define LUA_TYPE_DECL(type) \ static type##_t *to_##type(lua_State *L, int index); \ static type##_t *checked_##type(lua_State *L, int index); \ static type##_t *push_##type(lua_State *L); #define LUA_TYPE_IMPL(type) \ static type##_t *to_##type(lua_State *L, int index) { \ type##_t *obj = (type##_t *)lua_touserdata(L, index); \ if (!obj) luaL_typerror(L, index, #type); \ return obj; \ } \ \ static type##_t *checked_##type(lua_State *L, int index) { \ luaL_checktype(L, index, LUA_TUSERDATA); \ type##_t *obj = (type##_t *)luaL_checkudata(L, index, #type); \ if (!obj) luaL_typerror(L, index, #type); \ return obj; \ } \ \ static type##_t *push_##type(lua_State *L) { \ type##_t *obj = (type##_t *)lua_newuserdata(L, sizeof(type##_t)); \ luaL_getmetatable(L, #type); \ lua_setmetatable(L, -2); \ return obj; \ } \ \ static int type##_tostring(lua_State *L) { \ lua_pushfstring(L, "<" #type " %p>", lua_touserdata(L, 1)); \ return 1; \ } \ \ static const luaL_reg type##_meta[] = { \ {"__gc", type##_gc}, \ {"__tostring", type##_tostring}, \ {0, 0} \ }; \ \ int type##_register(lua_State *L) { \ luaL_openlib(L, #type, type##_methods, 0); \ luaL_newmetatable(L, #type); \ luaL_openlib(L, 0, type##_meta, 0); \ lua_pushliteral(L, "__index"); \ lua_pushvalue(L, -3); \ lua_rawset(L, -3); \ lua_pushliteral(L, "__metatable"); \ lua_pushvalue(L, -3); \ lua_rawset(L, -3); \ lua_pop(L, 1); \ return 1; \ } #endif info-beamer-1.0~pre4/module_json.lua0000644000175000017500000003672412452774240015375 0ustar nknk----------------------------------------------------------------------------- -- JSON4Lua: JSON encoding / decoding support for the Lua language. -- json Module. -- Author: Craig Mason-Jones -- Homepage: http://json.luaforge.net/ -- Version: 0.9.40 -- This module is released under the MIT License (MIT). -- Please see LICENCE.txt for details. -- -- USAGE: -- This module exposes two functions: -- encode(o) -- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string. -- decode(json_string) -- Returns a Lua object populated with the data encoded in the JSON string json_string. -- -- REQUIREMENTS: -- compat-5.1 if using Lua 5.0 -- -- CHANGELOG -- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix). -- Fixed Lua 5.1 compatibility issues. -- Introduced json.null to have null values in associative arrays. -- encode() performance improvement (more than 50%) through table.concat rather than .. -- Introduced decode ability to ignore /**/ comments in the JSON string. -- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays. ----------------------------------------------------------------------------- ----------------------------------------------------------------------------- -- Imports and dependencies ----------------------------------------------------------------------------- local math = require('math') local string = require("string") local table = require("table") local base = _G ----------------------------------------------------------------------------- -- Module declaration ----------------------------------------------------------------------------- module("json") -- Public functions -- Private functions local decode_scanArray local decode_scanComment local decode_scanConstant local decode_scanNumber local decode_scanObject local decode_scanString local decode_scanWhitespace local encodeString local isArray local isEncodable ----------------------------------------------------------------------------- -- PUBLIC FUNCTIONS ----------------------------------------------------------------------------- --- Encodes an arbitrary Lua object / variable. -- @param v The Lua object / variable to be JSON encoded. -- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode) function encode (v) -- Handle nil values if v==nil then return "null" end local vtype = base.type(v) -- Handle strings if vtype=='string' then return '"' .. encodeString(v) .. '"' -- Need to handle encoding in string end -- Handle booleans if vtype=='number' or vtype=='boolean' then return base.tostring(v) end -- Handle tables if vtype=='table' then local rval = {} -- Consider arrays separately local bArray, maxCount = isArray(v) if bArray then for i = 1,maxCount do table.insert(rval, encode(v[i])) end else -- An object, not an array for i,j in base.pairs(v) do if isEncodable(i) and isEncodable(j) then table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j)) end end end if bArray then return '[' .. table.concat(rval,',') ..']' else return '{' .. table.concat(rval,',') .. '}' end end -- Handle null values if vtype=='function' and v==null then return 'null' end base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v)) end --- Decodes a JSON string and returns the decoded value as a Lua data structure / value. -- @param s The string to scan. -- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1. -- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil, -- and the position of the first character after -- the scanned JSON object. function decode(s, startPos) startPos = startPos and startPos or 1 startPos = decode_scanWhitespace(s,startPos) base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') local curChar = string.sub(s,startPos,startPos) -- Object if curChar=='{' then return decode_scanObject(s,startPos) end -- Array if curChar=='[' then return decode_scanArray(s,startPos) end -- Number if string.find("+-0123456789.e", curChar, 1, true) then return decode_scanNumber(s,startPos) end -- String if curChar==[["]] or curChar==[[']] then return decode_scanString(s,startPos) end if string.sub(s,startPos,startPos+1)=='/*' then return decode(s, decode_scanComment(s,startPos)) end -- Otherwise, it must be a constant return decode_scanConstant(s,startPos) end --- The null function allows one to specify a null value in an associative array (which is otherwise -- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null } function null() return null -- so json.null() will also return null ;-) end ----------------------------------------------------------------------------- -- Internal, PRIVATE functions. -- Following a Python-like convention, I have prefixed all these 'PRIVATE' -- functions with an underscore. ----------------------------------------------------------------------------- --- Scans an array from JSON into a Lua object -- startPos begins at the start of the array. -- Returns the array and the next starting position -- @param s The string being scanned. -- @param startPos The starting position for the scan. -- @return table, int The scanned array as a table, and the position of the next character to scan. function decode_scanArray(s,startPos) local array = {} -- The return value local stringLen = string.len(s) base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) startPos = startPos + 1 -- Infinite loop for array elements repeat startPos = decode_scanWhitespace(s,startPos) base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') local curChar = string.sub(s,startPos,startPos) if (curChar==']') then return array, startPos+1 end if (curChar==',') then startPos = decode_scanWhitespace(s,startPos+1) end base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') object, startPos = decode(s,startPos) table.insert(array,object) until false end --- Scans a comment and discards the comment. -- Returns the position of the next character following the comment. -- @param string s The JSON string to scan. -- @param int startPos The starting position of the comment function decode_scanComment(s, startPos) base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) local endPos = string.find(s,'*/',startPos+2) base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos) return endPos+2 end --- Scans for given constants: true, false or null -- Returns the appropriate Lua type, and the position of the next character to read. -- @param s The string being scanned. -- @param startPos The position in the string at which to start scanning. -- @return object, int The object (true, false or nil) and the position at which the next character should be -- scanned. function decode_scanConstant(s, startPos) local consts = { ["true"] = true, ["false"] = false, ["null"] = nil } local constNames = {"true","false","null"} for i,k in base.pairs(constNames) do --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k) if string.sub(s,startPos, startPos + string.len(k) -1 )==k then return consts[k], startPos + string.len(k) end end base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) end --- Scans a number from the JSON encoded string. -- (in fact, also is able to scan numeric +- eqns, which is not -- in the JSON spec.) -- Returns the number, and the position of the next character -- after the number. -- @param s The string being scanned. -- @param startPos The position at which to start scanning. -- @return number, int The extracted number and the position of the next character to scan. function decode_scanNumber(s,startPos) local endPos = startPos+1 local stringLen = string.len(s) local acceptableChars = "+-0123456789.e" while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) and endPos<=stringLen ) do endPos = endPos + 1 end local stringValue = 'return ' .. string.sub(s,startPos, endPos-1) local stringEval = base.loadstring(stringValue) base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) return stringEval(), endPos end --- Scans a JSON object into a Lua object. -- startPos begins at the start of the object. -- Returns the object and the next starting position. -- @param s The string being scanned. -- @param startPos The starting position of the scan. -- @return table, int The scanned object as a table and the position of the next character to scan. function decode_scanObject(s,startPos) local object = {} local stringLen = string.len(s) local key, value base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s) startPos = startPos + 1 repeat startPos = decode_scanWhitespace(s,startPos) base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') local curChar = string.sub(s,startPos,startPos) if (curChar=='}') then return object,startPos+1 end if (curChar==',') then startPos = decode_scanWhitespace(s,startPos+1) end base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') -- Scan the key key, startPos = decode(s,startPos) base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) startPos = decode_scanWhitespace(s,startPos) base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) startPos = decode_scanWhitespace(s,startPos+1) base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) value, startPos = decode(s,startPos) object[key]=value until false -- infinite loop while key-value pairs are found end --- Scans a JSON string from the opening inverted comma or single quote to the -- end of the string. -- Returns the string extracted as a Lua string, -- and the position of the next non-string character -- (after the closing inverted comma or single quote). -- @param s The string being scanned. -- @param startPos The starting position of the scan. -- @return string, int The extracted string as a Lua string, and the next character to parse. function decode_scanString(s,startPos) base.assert(startPos, 'decode_scanString(..) called without start position') local startChar = string.sub(s,startPos,startPos) base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string') local escaped = false local endPos = startPos + 1 local bEnded = false local stringLen = string.len(s) repeat local curChar = string.sub(s,endPos,endPos) -- Character escaping is only used to escape the string delimiters if not escaped then if curChar==[[\]] then escaped = true else bEnded = curChar==startChar end else -- If we're escaped, we accept the current character come what may escaped = false end endPos = endPos + 1 base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos) until bEnded local stringValue = 'return ' .. string.sub(s, startPos, endPos-1) local stringEval = base.loadstring(stringValue) base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos) return stringEval(), endPos end --- Scans a JSON string skipping all whitespace from the current start position. -- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached. -- @param s The string being scanned -- @param startPos The starting position where we should begin removing whitespace. -- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string -- was reached. function decode_scanWhitespace(s,startPos) local whitespace=" \n\r\t" local stringLen = string.len(s) while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do startPos = startPos + 1 end return startPos end --- Encodes a string to be JSON-compatible. -- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-) -- @param s The string to return as a JSON encoded (i.e. backquoted string) -- @return The string appropriately escaped. function encodeString(s) s = string.gsub(s,'\\','\\\\') s = string.gsub(s,'"','\\"') s = string.gsub(s,"'","\\'") s = string.gsub(s,'\n','\\n') s = string.gsub(s,'\t','\\t') return s end -- Determines whether the given Lua type is an array or a table / dictionary. -- We consider any table an array if it has indexes 1..n for its n items, and no -- other data in the table. -- I think this method is currently a little 'flaky', but can't think of a good way around it yet... -- @param t The table to evaluate as an array -- @return boolean, number True if the table can be represented as an array, false otherwise. If true, -- the second returned value is the maximum -- number of indexed elements in the array. function isArray(t) -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable -- (with the possible exception of 'n') local maxIndex = 0 for k,v in base.pairs(t) do if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair if (not isEncodable(v)) then return false end -- All array elements must be encodable maxIndex = math.max(maxIndex,k) else if (k=='n') then if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements else -- Else of (k=='n') if isEncodable(v) then return false end end -- End of (k~='n') end -- End of k,v not an indexed pair end -- End of loop across all pairs return true, maxIndex end --- Determines whether the given Lua object / table / variable can be JSON encoded. The only -- types that are JSON encodable are: string, boolean, number, nil, table and json.null. -- In this implementation, all other types are ignored. -- @param o The object to examine. -- @return boolean True if the object should be JSON encoded, false if it should be ignored. function isEncodable(o) local t = base.type(o) return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null) end info-beamer-1.0~pre4/shader.c0000644000175000017500000001357212452774240013762 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #include #include #include #include #include #include #include #include #include #include "misc.h" typedef struct { GLuint fs; GLuint vs; GLuint po; } shader_t; LUA_TYPE_DECL(shader) /* Instance methods */ static int shader_use(lua_State *L) { shader_t *shader = checked_shader(L, 1); glUseProgram(shader->po); // No variables? if (lua_gettop(L) == 1) return 0; int num_textures = 1; luaL_checktype(L, 2, LUA_TTABLE); lua_pushnil(L); while (lua_next(L, -2)) { // copy the name and convert it to a string // (thereby changing the stack slot) // => [name] [value] [converted name] lua_pushvalue(L, -2); const char *name = lua_tostring(L, -1); GLint loc = glGetUniformLocation(shader->po, name); if (loc == -1) { // return luaL_error(L, "unknown uniform name %s. " // "maybe it is not used in the shader?", name); lua_pop(L, 2); continue; } int type = lua_type(L, -2); int len = lua_objlen(L, -2); if (type == LUA_TNUMBER) { GLfloat value = lua_tonumber(L, -2); glUniform1f(loc, value); } else if (type == LUA_TTABLE && 2 <= len && len <= 4) { GLfloat values[4]; for (int idx = 1; idx <= len; idx++) { lua_rawgeti(L, -2, idx); if (lua_type(L, -1) != LUA_TNUMBER) return luaL_error(L, "only numbers supported in %s at index %d", name, idx); values[idx -1] = lua_tonumber(L, -1); lua_pop(L, 1); } switch (len) { case 4: glUniform4f(loc, values[0], values[1], values[2], values[3]); break; case 3: glUniform3f(loc, values[0], values[1], values[2]); break; case 2: glUniform2f(loc, values[0], values[1]); break; } } else if (type == LUA_TUSERDATA || type == LUA_TTABLE) { lua_pushliteral(L, "texid"); lua_gettable(L, -3); // texid aus metatable holen if (lua_type(L, -1) != LUA_TFUNCTION) return luaL_error(L, "value %s has no texid() function", name); lua_pushvalue(L, -3); // value kopieren (als self) lua_call(L, 1, 1); // obj:texid() if (lua_type(L, -1) != LUA_TNUMBER) return luaL_error(L, "%s's texid() did not return number", name); int tex_id = lua_tonumber(L, -1); lua_pop(L, 1); glActiveTexture(GL_TEXTURE0 + num_textures); glBindTexture(GL_TEXTURE_2D, tex_id); glUniform1i(loc, num_textures); num_textures++; } else { return luaL_error(L, "unsupported value for %s. " "must be number, vector or texturelike", name); } // if (glGetError() == GL_INVALID_OPERATION) // return luaL_error(L, "unsupported assignment to %s " // "incompatible values?", name); lua_pop(L, 2); } lua_pop(L, 1); glActiveTexture(GL_TEXTURE0); GLint texloc = glGetUniformLocation(shader->po, "Texture"); if (texloc != -1) glUniform1i(texloc, 0); return 0; } static int shader_deactivate(lua_State *L) { glUseProgram(0); return 0; } static const luaL_reg shader_methods[] = { {"use", shader_use}, {"deactivate", shader_deactivate}, {0,0} }; /* Lifecycle */ int shader_new(lua_State *L, const char *vertex, const char *fragment) { char *fault = ""; char log[1024]; const char *define = "#define INFOBEAMER\n#define INFOBEAMER_PLAT_DESKTOP\n"; GLint status; GLsizei log_len; GLuint fs = 0, vs = 0, po = 0; // Pixel vs = glCreateShader(GL_VERTEX_SHADER); const char *vertex_sources[] = { define, vertex }; glShaderSource(vs, 2, vertex_sources, NULL); glCompileShader(vs); glGetObjectParameterivARB(vs, GL_COMPILE_STATUS, &status); if (!status) { fault = "compiling vertex shader"; glGetShaderInfoLog(vs, sizeof(log), &log_len, log); if (log_len > 0) goto error; } // Fragment fs = glCreateShader(GL_FRAGMENT_SHADER); const char *fragment_sources[] = { define, fragment }; glShaderSource(fs, 2, fragment_sources, NULL); glCompileShader(fs); glGetObjectParameterivARB(fs, GL_COMPILE_STATUS, &status); if (!status) { fault = "compiling fragment shader"; glGetShaderInfoLog(fs, sizeof(log), &log_len, log); if (log_len > 0) goto error; } // Program Object po = glCreateProgram(); glAttachShader(po, vs); glAttachShader(po, fs); glLinkProgram(po); glGetProgramiv(po, GL_LINK_STATUS, &status); if (!status) { fault = "linking program"; glGetProgramInfoLog(po, sizeof(log), &log_len, log); if (log_len > 0) goto error; } shader_t *shader = push_shader(L); shader->fs = fs; shader->vs = vs; shader->po = po; return 1; error: if (po) glDeleteProgram(po); if (vs) glDeleteShader(vs); if (fs) glDeleteShader(fs); return luaL_error(L, "While %s: %s", fault, log); } static int shader_gc(lua_State *L) { shader_t *shader = to_shader(L, 1); glDeleteProgram(shader->po); glDeleteShader(shader->vs); glDeleteShader(shader->fs); return 0; } LUA_TYPE_IMPL(shader) void shader_set_gl_color(GLfloat r, GLfloat g, GLfloat b, GLfloat a) { glColor4f(r, g, b, a); GLint prog, color; glGetIntegerv(GL_CURRENT_PROGRAM, &prog); color = glGetUniformLocation(prog, "Color"); if (color != -1) glUniform4f(color, r, g, b, a); } info-beamer-1.0~pre4/shader.h0000644000175000017500000000040712452774240013760 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #ifndef SHADER_H #define SHADER_H int shader_register(lua_State *L); int shader_new(lua_State *L, const char *vertex, const char *fragment); void shader_set_gl_color(GLfloat r, GLfloat g, GLfloat b, GLfloat a); #endif info-beamer-1.0~pre4/struct.c0000644000175000017500000002257212452774240014040 0ustar nknk #include #include #include #include #include "lua.h" #include "lauxlib.h" /* ** {====================================================== ** Library for packing/unpacking structures. ** $Id: struct.c,v 1.2 2008/04/18 20:06:01 roberto Exp $ ** See Copyright Notice at the end of this file ** ======================================================= */ /* ** Valid formats: ** > - big endian ** < - little endian ** ![num] - alignment ** x - pading ** b/B - signed/unsigned byte ** h/H - signed/unsigned short ** l/L - signed/unsigned long ** i/In - signed/unsigned integer with size `n' (default is size of int) ** cn - sequence of `n' chars (from/to a string); when packing, n==0 means the whole string; when unpacking, n==0 means use the previous read number as the string length ** s - zero-terminated string ** f - float ** d - doulbe ** ' ' - ignored */ /* is 'x' a power of 2? */ #define isp2(x) ((x) > 0 && ((x) & ((x) - 1)) == 0) /* dummy structure to get alignment requirements */ struct cD { char c; double d; }; #define PADDING (sizeof(struct cD) - sizeof(double)) #define MAXALIGN (PADDING > sizeof(int) ? PADDING : sizeof(int)) /* endian options */ #define BIG 0 #define LITTLE 1 static union { int dummy; char endian; } const native = {1}; typedef struct Header { int endian; int align; } Header; static size_t getnum (const char **fmt, size_t df) { if (!isdigit(**fmt)) /* no number? */ return df; /* return default value */ else { size_t a = 0; do { a = a*10 + *((*fmt)++) - '0'; } while (isdigit(**fmt)); return a; } } #define defaultoptions(h) ((h)->endian = native.endian, (h)->align = 1) static size_t optsize (lua_State *L, char opt, const char **fmt) { switch (opt) { case 'B': case 'b': return sizeof(char); case 'H': case 'h': return sizeof(short); case 'L': case 'l': return sizeof(long); case 'f': return sizeof(float); case 'd': return sizeof(double); case 'x': return 1; case 'c': return getnum(fmt, 1); case 's': case ' ': case '<': case '>': case '!': return 0; case 'i': case 'I': { int sz = getnum(fmt, sizeof(int)); if (!isp2(sz)) luaL_error(L, "integral size %d is not a power of 2", sz); return sz; } default: { const char *msg = lua_pushfstring(L, "invalid format option [%c]", opt); return luaL_argerror(L, 1, msg); } } } static int gettoalign (size_t len, Header *h, int opt, size_t size) { if (size == 0 || opt == 'c') return 0; if (size > (size_t)h->align) size = h->align; /* respect max. alignment */ return (size - (len & (size - 1))) & (size - 1); } static void commoncases (lua_State *L, int opt, const char **fmt, Header *h) { switch (opt) { case ' ': return; /* ignore white spaces */ case '>': h->endian = BIG; return; case '<': h->endian = LITTLE; return; case '!': { int a = getnum(fmt, MAXALIGN); if (!isp2(a)) luaL_error(L, "alignment %d is not a power of 2", a); h->align = a; return; } default: assert(0); } } static void putinteger (lua_State *L, luaL_Buffer *b, int arg, int endian, int size) { lua_Number n = luaL_checknumber(L, arg); unsigned long value; if (n < (lua_Number)LONG_MAX) value = (long)n; else value = (unsigned long)n; if (endian == LITTLE) { int i; for (i = 0; i < size; i++) luaL_addchar(b, (value >> 8*i) & 0xff); } else { int i; for (i = size - 1; i >= 0; i--) luaL_addchar(b, (value >> 8*i) & 0xff); } } static void correctbytes (char *b, int size, int endian) { if (endian != native.endian) { int i = 0; while (i < --size) { char temp = b[i]; b[i++] = b[size]; b[size] = temp; } } } static int b_pack (lua_State *L) { luaL_Buffer b; const char *fmt = luaL_checkstring(L, 1); Header h; int arg = 2; size_t totalsize = 0; defaultoptions(&h); lua_pushnil(L); /* mark to separate arguments from string buffer */ luaL_buffinit(L, &b); while (*fmt != '\0') { int opt = *fmt++; size_t size = optsize(L, opt, &fmt); int toalign = gettoalign(totalsize, &h, opt, size); totalsize += toalign; while (toalign-- > 0) luaL_putchar(&b, '\0'); switch (opt) { case 'b': case 'B': case 'h': case 'H': case 'l': case 'L': case 'i': case 'I': { /* integer types */ putinteger(L, &b, arg++, h.endian, size); break; } case 'x': { luaL_putchar(&b, '\0'); break; } case 'f': { float f = (float)luaL_checknumber(L, arg++); correctbytes((char *)&f, size, h.endian); luaL_addlstring(&b, (char *)&f, size); break; } case 'd': { double d = luaL_checknumber(L, arg++); correctbytes((char *)&d, size, h.endian); luaL_addlstring(&b, (char *)&d, size); break; } case 'c': case 's': { size_t l; const char *s = luaL_checklstring(L, arg++, &l); if (size == 0) size = l; luaL_argcheck(L, l >= (size_t)size, arg, "string too short"); luaL_addlstring(&b, s, size); if (opt == 's') { luaL_putchar(&b, '\0'); /* add zero at the end */ size++; } break; } default: commoncases(L, opt, &fmt, &h); } totalsize += size; } luaL_pushresult(&b); return 1; } static lua_Number getinteger (const char *buff, int endian, int issigned, int size) { unsigned long l = 0; if (endian == BIG) { int i; for (i = 0; i < size; i++) l |= (unsigned long)(unsigned char)buff[size - i - 1] << (i*8); } else { int i; for (i = 0; i < size; i++) l |= (unsigned long)(unsigned char)buff[i] << (i*8); } if (!issigned) return (lua_Number)l; else { /* signed format */ unsigned long mask = ~(0UL) << (size*8 - 1); if (l & mask) /* negative value? */ l |= mask; /* signal extension */ return (lua_Number)(long)l; } } static int b_unpack (lua_State *L) { Header h; const char *fmt = luaL_checkstring(L, 1); size_t ld; const char *data = luaL_checklstring(L, 2, &ld); size_t pos = luaL_optinteger(L, 3, 1) - 1; defaultoptions(&h); lua_settop(L, 2); while (*fmt) { int opt = *fmt++; size_t size = optsize(L, opt, &fmt); pos += gettoalign(pos, &h, opt, size); luaL_argcheck(L, pos+size <= ld, 2, "data string too short"); switch (opt) { case 'b': case 'B': case 'h': case 'H': case 'l': case 'L': case 'i': case 'I': { /* integer types */ int issigned = islower(opt); lua_Number res = getinteger(data+pos, h.endian, issigned, size); lua_pushnumber(L, res); break; } case 'x': { break; } case 'f': { float f; memcpy(&f, data+pos, size); correctbytes((char *)&f, sizeof(f), h.endian); lua_pushnumber(L, f); break; } case 'd': { double d; memcpy(&d, data+pos, size); correctbytes((char *)&d, sizeof(d), h.endian); lua_pushnumber(L, d); break; } case 'c': { if (size == 0) { if (!lua_isnumber(L, -1)) luaL_error(L, "format `c0' needs a previous size"); size = lua_tonumber(L, -1); lua_pop(L, 1); luaL_argcheck(L, pos+size <= ld, 2, "data string too short"); } lua_pushlstring(L, data+pos, size); break; } case 's': { const char *e = (const char *)memchr(data+pos, '\0', ld - pos); if (e == NULL) luaL_error(L, "unfinished string in data"); size = (e - (data+pos)) + 1; lua_pushlstring(L, data+pos, size - 1); break; } default: commoncases(L, opt, &fmt, &h); } pos += size; } lua_pushinteger(L, pos + 1); return lua_gettop(L) - 2; } /* }====================================================== */ static const struct luaL_reg thislib[] = { {"pack", b_pack}, {"unpack", b_unpack}, {NULL, NULL} }; LUALIB_API int luaopen_struct (lua_State *L) { luaL_register(L, "struct", thislib); return 1; } /****************************************************************************** * Copyright (C) 2010 Lua.org, PUC-Rio. 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 OR COPYRIGHT HOLDERS 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. ******************************************************************************/ info-beamer-1.0~pre4/struct.h0000644000175000017500000000022612452774240014035 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #ifndef STRUCT_H #define STRUCT_H #include LUALIB_API int luaopen_struct (lua_State *L); #endif info-beamer-1.0~pre4/userlib.lua0000644000175000017500000005421312452774240014515 0ustar nknk-- See Copyright Notice in LICENSE.txt util = {} function util.shader_loader(filename) return resource.create_shader(resource.load_file(filename)) end function util.videoplayer(name, opt) local stream, start, fps, frame, width, height local function open_stream() stream = resource.load_video(name) start = sys.now() fps = stream:fps() frame = 0 width, height = stream:size() end open_stream() opt = opt or {} local speed = opt.speed or 1 fps = fps * speed local loop = true if opt.loop ~= nil then loop = opt.loop end local done = false return { draw = function(self, x1, y1, x2, y2, alpha) if done then return end local now = sys.now() local target_frame = (now - start) * fps if target_frame > frame + 10 then print(string.format( "slow player for '%s'. missed %d frames since last call", name, target_frame - frame )) -- too slow to decode. rebase time start = now - frame * 1/fps else while frame < target_frame do if not stream:next() then if loop then print("player: looping") open_stream() stream:next() break else -- stream completed done = true return false end end frame = frame + 1 end end stream:draw(x1, y1, x2, y2, alpha) return true end; texid = function(self) return stream:texid() end; next = function(self) return not done end; size = function(self) return stream:size() end; } end util.loaders = { png = resource.load_image; jpg = resource.load_image; jpeg = resource.load_image; gif = resource.load_image; bmp = resource.load_image; ttf = resource.load_font; otf = resource.load_font; avi = util.videoplayer; mpg = util.videoplayer; ogg = util.videoplayer; flv = util.videoplayer; mkv = util.videoplayer; mp4 = util.videoplayer; mov = util.videoplayer; frag = util.shader_loader; } function util.auto_loader(container, filter) container = container or {} filter = filter or function() return true end local loaded_version = {} local function auto_load(name) if filter and not filter(name) then return end if loaded_version[name] == CONTENTS[name] then -- print("auto_loader: already loaded " .. name) return end local target, suffix = name:match("(.*)[.]([^.]+)$") if not target then print("loader: invalid resource name " .. name .. ". ignoring " .. name) return end local loader = util.loaders[suffix] if not loader then print("loader: no resource loader for suffix " .. suffix .. ". ignoring " .. name) return end local success, res = pcall(loader, name) if not success then print("loader: cannot load " .. name .. ": " .. res) else print("loader: updated " .. target .. " (triggered by " .. name .. ")") container[target] = res loaded_version[name] = CONTENTS[name] end end print("loader: loading known resources") for name, added in pairs(CONTENTS) do auto_load(name) end node.event("content_update", auto_load) node.event("content_remove", function(name) local target, suffix = name:match("(.*)[.]([^.]+)$") if target and util.loaders[suffix] and container[target] then print("loader: unloaded " .. target .. " (triggered by " .. name .. ")") container[target] = nil loaded_version[name] = nil end end) return container end function util.resource_loader(resources) local whitelist = {} for _, name in ipairs(resources) do whitelist[name] = true end util.auto_loader(_G, function(name) return whitelist[name] end) end function util.file_watch(filename, handler) local loaded_version = nil local function updated(name) if name ~= filename then return end if loaded_version == CONTENTS[filename] then return end loaded_version = CONTENTS[filename] handler(resource.load_file(filename)) end node.event("content_update", updated) updated(filename) end local function handle_suffix_match(suffix, pattern, callback, ...) local data = {...} return (function(s, e, ...) if s == nil then return false end local args = {...} for n = 1, #data do args[#args+1] = data[n] end callback(unpack(args)) return true end)(suffix:find(pattern)) end function util.osc_mapper(routes) node.event("osc", function(suffix, ...) for pattern, callback in pairs(routes) do if handle_suffix_match(suffix, pattern, callback, ...) then return end end end) end function util.data_mapper(routes) node.event("data", function(data, suffix) for pattern, callback in pairs(routes) do if handle_suffix_match(suffix, pattern, callback, data) then return end end end) end function util.generator(refiller) local items = {} return { next = function(self) local next_item = next(items) if not next_item then for _, value in ipairs(refiller()) do items[value] = 1 end next_item = next(items) if not next_item then error("no items available") end end items[next_item] = nil return next_item end; add = function(self, value) items[value] = 1 end; remove = function(self, value) items[value] = nil end; } end function util.set_interval(interval, callback) local next_call = sys.now() + interval node.event("render", function() local now = sys.now() if now > next_call then next_call = now + interval callback() end end) callback() end function util.post_effect(shader, shader_opt) local surface = resource.create_snapshot() gl.ortho() gl.clear(0,0,0,1) shader:use(shader_opt) surface:draw(0, 0, WIDTH, HEIGHT) shader:deactivate() end function util.running_text(opt) local current_idx = 1 local current_left = 0 local last = sys.now() local generator = opt.generator local font = opt.font local size = opt.size or 10 local speed = opt.speed or 10 local color = opt.color or {1,1,1,1} local texts = {} return { draw = function(self, y) local now = sys.now() local xoff = current_left local idx = 1 while xoff < WIDTH do if #texts < idx then table.insert(texts, generator.next()) end local width = font:write(xoff, y, texts[idx] .. " - ", size, unpack(color)) xoff = xoff + width if xoff < 0 then current_left = xoff table.remove(texts, idx) else idx = idx + 1 end end local delta = now - last last = now current_left = current_left - delta * speed end; add = function(self, text) generator:add(text) end; } end function util.scale_into(target_width, target_height, source_width, source_height) local prop_height = source_height * target_width / source_width local prop_width = source_width * target_height / source_height local x1, y1, x2, y2 if prop_height > target_height then local x_center = target_width / 2 local half_width = prop_width / 2 x1 = x_center - half_width y1 = 0 x2 = x_center + half_width y2 = target_height else local y_center = target_height / 2 local half_height = prop_height / 2 x1 = 0 y1 = y_center - half_height x2 = target_width y2 = y_center + half_height end return x1, y1, x2, y2 end function util.draw_correct(obj, x1, y1, x2, y2, ...) local ox1, oy1, ox2, oy2 = util.scale_into( x2 - x1, y2 - y1, obj:size() ) obj:draw(x1 + ox1, y1 + oy1, x1 + ox2, y1 + oy2, ...) end function table.filter(t, predicate) local j = 1 for i, v in ipairs(t) do if predicate(v) then t[j] = v j = j + 1 end end while t[j] ~= nil do t[j] = nil j = j + 1 end return t end -- Based on http://lua-users.org/wiki/TableSerialization -- Modified to *not* use debug.getinfo --[[ Author: Julio Manuel Fernandez-Diaz Date: January 12, 2007 (For Lua 5.1) Modified slightly by RiciLake to avoid the unnecessary table traversal in tablecount() Formats tables with cycles recursively to any depth. The output is returned as a string. References to other tables are shown as values. Self references are indicated. The string returned is "Lua code", which can be procesed (in the case in which indent is composed by spaces or "--"). Userdata and function keys and values are shown as strings, which logically are exactly not equivalent to the original code. This routine can serve for pretty formating tables with proper indentations, apart from printing them: print(table.show(t, "t")) -- a typical use Heavily based on "Saving tables with cycles", PIL2, p. 113. Arguments: t is the table. name is the name of the table (optional) indent is a first indentation (optional). --]] function table.show(t, name, indent) local cart -- a container local autoref -- for self references -- (RiciLake) returns true if the table is empty local function isemptytable(t) return next(t) == nil end local function basicSerialize (o) local so = tostring(o) if type(o) == "function" or type(o) == "number" or type(o) == "boolean" then return so else return string.format("%q", so) end end local function addtocart (value, name, indent, saved, field) indent = indent or "" saved = saved or {} field = field or name cart = cart .. indent .. field if type(value) ~= "table" then cart = cart .. " = " .. basicSerialize(value) .. ";\n" else if saved[value] then cart = cart .. " = {...}; -- " .. saved[value] .. " (self reference)\n" autoref = autoref .. name .. " = " .. saved[value] .. ";\n" else saved[value] = name --if tablecount(value) == 0 then if isemptytable(value) then cart = cart .. " = {};\n" else cart = cart .. " = {\n" for k, v in pairs(value) do k = basicSerialize(k) local fname = string.format("%s[%s]", name, k) field = string.format("[%s]", k) -- three spaces between levels addtocart(v, fname, indent .. " ", saved, field) end cart = cart .. indent .. "};\n" end end end end name = name or "__unnamed__" if type(t) ~= "table" then return name .. " = " .. basicSerialize(t) end cart, autoref = "", "" addtocart(t, name, indent) return cart .. autoref end function table.keys(t) local ret = {} for k, v in pairs(t) do ret[#ret+1] = k end return ret end function pp(t) print(table.show(t)) end -- Sandboxed package loader package = { loadlib = function(libname, funcname) error("no native linking") end; seeall = function(module) return setmetatable(module, { __index = _G }) end; loaded = { table = table; string = string; math = math; table = table; coroutine = coroutine; debug = debug; struct = struct; util = util; sys = sys; gl = gl; resource = resource; }; loaders = { function(modname) local filename = modname .. ".lua" local status, content = pcall(resource.load_file, filename) if not status then return "no file " .. filename .. ": " .. content else return function(loader_modname) assert(loader_modname == modname) local filename = PATH .. "/" .. modname .. ".lua" return assert(loadstring(content, "=" .. filename))(modname) end, filename end end; -- bundled moduls loader function(modname) local filename = modname .. ".lua" local content = _BUNDLED_MODULES[filename] if not content then return "no file " .. filename else return function(loader_modname) print("loading bundled module '" .. loader_modname .. "'") assert(loader_modname == modname) return assert(loadstring(content, "=" .. filename))(modname) end, filename end end }; } package.loaded['package'] = package function require(modname) local loaded = package.loaded[modname] if loaded then return loaded end -- find loader local loader local errors = {"module '" .. modname .. "' not found:"} for _, searcher in ipairs(package.loaders) do local searcher_val = searcher(modname) if type(searcher_val) == "function" then loader = searcher_val break elseif type(searcher_val) == "string" then errors[#errors + 1] = "\t" .. searcher_val end end if not loader then error(table.concat(errors, "\n")) end -- load module local value = loader(modname) if value then package.loaded[modname] = value elseif not package.loaded[modname] then package.loaded[modname] = true end return package.loaded[modname] end function hosted_init() local json = require "json" local hosted = nil local config_json = nil local node_json = nil local reload_config = function() print "[hosted] reloading config" -- pp(hosted) -- pp(node_json) -- pp(config_json) if hosted and node_json and config_json then local parsed = hosted.parse_config(node_json.options, config_json) _G['CONFIG'] = parsed node.dispatch("config_update", parsed) end end util.file_watch("hosted.lua", function(content) print "[hosted] loading hosted.lua" local filename = PATH .. "/hosted.lua" hosted = assert(loadstring(content, "=" .. filename))() reload_config() end) util.file_watch("node.json", function(content) print("[hosted] loading node.json") node_json = json.decode(content) _G['NODE'] = node_json reload_config() node.dispatch("node_update", node_json) end) util.file_watch("config.json", function(content) print("[hosted] loading config.json") config_json = json.decode(content) reload_config() end) util.file_watch("package.json", function(content) print("[hosted] loading package.json") local package_json = json.decode(content) _G['PACKAGE'] = package_json node.dispatch("package_update", package_json) end) end do local function red(str) return "" .. str .. "" end local function green(str) return "" .. str .. "" end local function yellow(str) return "" .. str .. "" end local handlers = { ["boolean"] = function(cmd, info, target) local function setup() target[cmd] = info.value end local function info() return string.format("(%s)", tostring(target[cmd])) end local function call(arg) local value = ({ ["true"] = true; ["1"] = true; ["y"] = true; ["false"] = false; ["0"] = false; ["n"] = false; })[arg] if value == nil then print(red("invalid value: true/false expected")) else target[cmd] = value print(green("value updated")) end end return { setup = setup; param = ""; info = info; call = call; } end; ["string"] = function(cmd, info, target) local function setup() target[cmd] = info.value end local function info() return string.format("(\"%s\")", target[cmd]) end local function call(arg) target[cmd] = arg print(green("value updated")) end return { setup = setup; param = "<\"new value\">"; info = info; call = call; } end; ["number"] = function(cmd, info, target) local function setup() target[cmd] = info.value end local function info() return string.format("(%f)", target[cmd]) end local function call(arg) local value = tonumber(arg) if value == nil then print(red("invalid value: number expected")) else target[cmd] = value print(green("value updated")) end end return { setup = setup; param = ""; info = info; call = call; } end; ["function"] = function(cmd, info, target) local function call(arg) return info.value(target, readln, arg) end return { setup = function() end; param = ""; info = function() return "" end; call = call; } end; } local function create_menu_interface(name, target, options, readln) if not target then target = _G end for cmd, info in pairs(options) do local type = info.type or type(info.value) info.handler = handlers[type](cmd, info, target) info.handler.setup() end local function print_help() local max_size = 0 local cmds = {} for cmd, info in pairs(options) do max_size = math.max(max_size, #cmd + 1 + #info.handler.param) cmds[#cmds+1] = cmd end table.sort(cmds) print() print(green("Available commands/values:")) for idx, cmd in ipairs(cmds) do local info = options[cmd] print(string.format("%-" .. tostring(max_size) .. "s - %s %s", cmd .. " " .. info.handler.param, info.help, info.handler.info())) end print() end return function() while true do print() print(yellow(name .. " - your command")) local line = readln() if line == "?" or line == "help" then print_help() elseif line == "" or line == "exit" then break else local cmd, arg = string.match(line, "^([^%s]+) (.*)$") if not cmd then cmd = line end local option = options[cmd] if option then option.handler.call(arg) else print(red("invalid command line \"" .. line .. "\". type '?' for help")) end end end end end local function create_submenu(name, options) return { value = function(target, readln) return create_menu_interface(name, target, options, readln)() end; help = name; } end local function create_variable(value, help) return { value = value; help = help; } end local function tcp_export(options, target) local main_menu = create_menu_interface("main menu", target, options, function() return coroutine.yield() end) if not N.clients then N.clients = {} end node.event("connect", function(client) local handler = coroutine.wrap(function() print(green("configuration interface for " .. PATH)) print(green("-----------------------------------------")) while true do main_menu() end end) N.clients[client] = handler handler() end) node.event("input", function(line, client) N.clients[client](line) end) node.event("disconnect", function(client) N.clients[client] = nil end) end util.menu = { tcp = tcp_export; sub = create_submenu; var = create_variable; } end info-beamer-1.0~pre4/uthash.h0000644000175000017500000016257112452774240014021 0ustar nknk/* Copyright (c) 2003-2011, Troy D. Hanson http://uthash.sourceforge.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTHASH_H #define UTHASH_H #include /* memcmp,strlen */ #include /* ptrdiff_t */ #include /* exit() */ /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ source) this code uses whatever method is needed or, for VS2008 where neither is available, uses casting workarounds. */ #ifdef _MSC_VER /* MS compiler */ #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ #define DECLTYPE(x) (decltype(x)) #else /* VS2008 or older (or VS2010 in C mode) */ #define NO_DECLTYPE #define DECLTYPE(x) #endif #else /* GNU, Sun and other compilers */ #define DECLTYPE(x) (__typeof(x)) #endif #ifdef NO_DECLTYPE #define DECLTYPE_ASSIGN(dst,src) \ do { \ char **_da_dst = (char**)(&(dst)); \ *_da_dst = (char*)(src); \ } while(0) #else #define DECLTYPE_ASSIGN(dst,src) \ do { \ (dst) = DECLTYPE(dst)(src); \ } while(0) #endif /* a number of the hash function use uint32_t which isn't defined on win32 */ #ifdef _MSC_VER typedef unsigned int uint32_t; typedef unsigned char uint8_t; #else #include /* uint32_t */ #endif #define UTHASH_VERSION 1.9.4 #define uthash_fatal(msg) exit(-1) /* fatal error (out of memory,etc) */ #define uthash_malloc(sz) malloc(sz) /* malloc fcn */ #define uthash_free(ptr,sz) free(ptr) /* free fcn */ #define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ #define uthash_expand_fyi(tbl) /* can be defined to log expands */ /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS 32 /* initial number of buckets */ #define HASH_INITIAL_NUM_BUCKETS_LOG2 5 /* lg2 of initial number of buckets */ #define HASH_BKT_CAPACITY_THRESH 10 /* expand when bucket count reaches */ /* calculate the element whose hash handle address is hhe */ #define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) #define HASH_FIND(hh,head,keyptr,keylen,out) \ do { \ unsigned _hf_bkt,_hf_hashv; \ out=NULL; \ if (head) { \ HASH_FCN(keyptr,keylen, (head)->hh.tbl->num_buckets, _hf_hashv, _hf_bkt); \ if (HASH_BLOOM_TEST((head)->hh.tbl, _hf_hashv)) { \ HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], \ keyptr,keylen,out); \ } \ } \ } while (0) #ifdef HASH_BLOOM #define HASH_BLOOM_BITLEN (1ULL << HASH_BLOOM) #define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8) + ((HASH_BLOOM_BITLEN%8) ? 1:0) #define HASH_BLOOM_MAKE(tbl) \ do { \ (tbl)->bloom_nbits = HASH_BLOOM; \ (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ if (!((tbl)->bloom_bv)) { uthash_fatal( "out of memory"); } \ memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN); \ (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ } while (0); #define HASH_BLOOM_FREE(tbl) \ do { \ uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ } while (0); #define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8] |= (1U << ((idx)%8))) #define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8] & (1U << ((idx)%8))) #define HASH_BLOOM_ADD(tbl,hashv) \ HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1))) #define HASH_BLOOM_TEST(tbl,hashv) \ HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1))) #else #define HASH_BLOOM_MAKE(tbl) #define HASH_BLOOM_FREE(tbl) #define HASH_BLOOM_ADD(tbl,hashv) #define HASH_BLOOM_TEST(tbl,hashv) (1) #endif #define HASH_MAKE_TABLE(hh,head) \ do { \ (head)->hh.tbl = (UT_hash_table*)uthash_malloc( \ sizeof(UT_hash_table)); \ if (!((head)->hh.tbl)) { uthash_fatal( "out of memory"); } \ memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \ (head)->hh.tbl->tail = &((head)->hh); \ (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); } \ memset((head)->hh.tbl->buckets, 0, \ HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_MAKE((head)->hh.tbl); \ (head)->hh.tbl->signature = HASH_SIGNATURE; \ } while(0) #define HASH_ADD(hh,head,fieldname,keylen_in,add) \ HASH_ADD_KEYPTR(hh,head,&((add)->fieldname),keylen_in,add) #define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ do { \ unsigned _ha_bkt; \ (add)->hh.next = NULL; \ (add)->hh.key = (char*)keyptr; \ (add)->hh.keylen = keylen_in; \ if (!(head)) { \ head = (add); \ (head)->hh.prev = NULL; \ HASH_MAKE_TABLE(hh,head); \ } else { \ (head)->hh.tbl->tail->next = (add); \ (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ (head)->hh.tbl->tail = &((add)->hh); \ } \ (head)->hh.tbl->num_items++; \ (add)->hh.tbl = (head)->hh.tbl; \ HASH_FCN(keyptr,keylen_in, (head)->hh.tbl->num_buckets, \ (add)->hh.hashv, _ha_bkt); \ HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt],&(add)->hh); \ HASH_BLOOM_ADD((head)->hh.tbl,(add)->hh.hashv); \ HASH_EMIT_KEY(hh,head,keyptr,keylen_in); \ HASH_FSCK(hh,head); \ } while(0) #define HASH_TO_BKT( hashv, num_bkts, bkt ) \ do { \ bkt = ((hashv) & ((num_bkts) - 1)); \ } while(0) /* delete "delptr" from the hash table. * "the usual" patch-up process for the app-order doubly-linked-list. * The use of _hd_hh_del below deserves special explanation. * These used to be expressed using (delptr) but that led to a bug * if someone used the same symbol for the head and deletee, like * HASH_DELETE(hh,users,users); * We want that to work, but by changing the head (users) below * we were forfeiting our ability to further refer to the deletee (users) * in the patch-up process. Solution: use scratch space to * copy the deletee pointer, then the latter references are via that * scratch pointer rather than through the repointed (users) symbol. */ #define HASH_DELETE(hh,head,delptr) \ do { \ unsigned _hd_bkt; \ struct UT_hash_handle *_hd_hh_del; \ if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) ) { \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ head = NULL; \ } else { \ _hd_hh_del = &((delptr)->hh); \ if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) { \ (head)->hh.tbl->tail = \ (UT_hash_handle*)((char*)((delptr)->hh.prev) + \ (head)->hh.tbl->hho); \ } \ if ((delptr)->hh.prev) { \ ((UT_hash_handle*)((char*)((delptr)->hh.prev) + \ (head)->hh.tbl->hho))->next = (delptr)->hh.next; \ } else { \ DECLTYPE_ASSIGN(head,(delptr)->hh.next); \ } \ if (_hd_hh_del->next) { \ ((UT_hash_handle*)((char*)_hd_hh_del->next + \ (head)->hh.tbl->hho))->prev = \ _hd_hh_del->prev; \ } \ HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ (head)->hh.tbl->num_items--; \ } \ HASH_FSCK(hh,head); \ } while (0) /* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ #define HASH_FIND_STR(head,findstr,out) \ HASH_FIND(hh,head,findstr,strlen(findstr),out) #define HASH_ADD_STR(head,strfield,add) \ HASH_ADD(hh,head,strfield,strlen(add->strfield),add) #define HASH_FIND_INT(head,findint,out) \ HASH_FIND(hh,head,findint,sizeof(int),out) #define HASH_ADD_INT(head,intfield,add) \ HASH_ADD(hh,head,intfield,sizeof(int),add) #define HASH_FIND_PTR(head,findptr,out) \ HASH_FIND(hh,head,findptr,sizeof(void *),out) #define HASH_ADD_PTR(head,ptrfield,add) \ HASH_ADD(hh,head,ptrfield,sizeof(void *),add) #define HASH_DEL(head,delptr) \ HASH_DELETE(hh,head,delptr) /* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. */ #ifdef HASH_DEBUG #define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) #define HASH_FSCK(hh,head) \ do { \ unsigned _bkt_i; \ unsigned _count, _bkt_count; \ char *_prev; \ struct UT_hash_handle *_thh; \ if (head) { \ _count = 0; \ for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \ _bkt_count = 0; \ _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ _prev = NULL; \ while (_thh) { \ if (_prev != (char*)(_thh->hh_prev)) { \ HASH_OOPS("invalid hh_prev %p, actual %p\n", \ _thh->hh_prev, _prev ); \ } \ _bkt_count++; \ _prev = (char*)(_thh); \ _thh = _thh->hh_next; \ } \ _count += _bkt_count; \ if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ HASH_OOPS("invalid bucket count %d, actual %d\n", \ (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ } \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("invalid hh item count %d, actual %d\n", \ (head)->hh.tbl->num_items, _count ); \ } \ /* traverse hh in app order; check next/prev integrity, count */ \ _count = 0; \ _prev = NULL; \ _thh = &(head)->hh; \ while (_thh) { \ _count++; \ if (_prev !=(char*)(_thh->prev)) { \ HASH_OOPS("invalid prev %p, actual %p\n", \ _thh->prev, _prev ); \ } \ _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ _thh = ( _thh->next ? (UT_hash_handle*)((char*)(_thh->next) + \ (head)->hh.tbl->hho) : NULL ); \ } \ if (_count != (head)->hh.tbl->num_items) { \ HASH_OOPS("invalid app item count %d, actual %d\n", \ (head)->hh.tbl->num_items, _count ); \ } \ } \ } while (0) #else #define HASH_FSCK(hh,head) #endif /* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to * the descriptor to which this macro is defined for tuning the hash function. * The app can #include to get the prototype for write(2). */ #ifdef HASH_EMIT_KEYS #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ do { \ unsigned _klen = fieldlen; \ write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ write(HASH_EMIT_KEYS, keyptr, fieldlen); \ } while (0) #else #define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) #endif /* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ #ifdef HASH_FUNCTION #define HASH_FCN HASH_FUNCTION #else #define HASH_FCN HASH_JEN #endif /* The Bernstein hash function, used in Perl prior to v5.6 */ #define HASH_BER(key,keylen,num_bkts,hashv,bkt) \ do { \ unsigned _hb_keylen=keylen; \ char *_hb_key=(char*)(key); \ (hashv) = 0; \ while (_hb_keylen--) { (hashv) = ((hashv) * 33) + *_hb_key++; } \ bkt = (hashv) & (num_bkts-1); \ } while (0) /* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ #define HASH_SAX(key,keylen,num_bkts,hashv,bkt) \ do { \ unsigned _sx_i; \ char *_hs_key=(char*)(key); \ hashv = 0; \ for(_sx_i=0; _sx_i < keylen; _sx_i++) \ hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ bkt = hashv & (num_bkts-1); \ } while (0) #define HASH_FNV(key,keylen,num_bkts,hashv,bkt) \ do { \ unsigned _fn_i; \ char *_hf_key=(char*)(key); \ hashv = 2166136261UL; \ for(_fn_i=0; _fn_i < keylen; _fn_i++) \ hashv = (hashv * 16777619) ^ _hf_key[_fn_i]; \ bkt = hashv & (num_bkts-1); \ } while(0); #define HASH_OAT(key,keylen,num_bkts,hashv,bkt) \ do { \ unsigned _ho_i; \ char *_ho_key=(char*)(key); \ hashv = 0; \ for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ hashv += _ho_key[_ho_i]; \ hashv += (hashv << 10); \ hashv ^= (hashv >> 6); \ } \ hashv += (hashv << 3); \ hashv ^= (hashv >> 11); \ hashv += (hashv << 15); \ bkt = hashv & (num_bkts-1); \ } while(0) #define HASH_JEN_MIX(a,b,c) \ do { \ a -= b; a -= c; a ^= ( c >> 13 ); \ b -= c; b -= a; b ^= ( a << 8 ); \ c -= a; c -= b; c ^= ( b >> 13 ); \ a -= b; a -= c; a ^= ( c >> 12 ); \ b -= c; b -= a; b ^= ( a << 16 ); \ c -= a; c -= b; c ^= ( b >> 5 ); \ a -= b; a -= c; a ^= ( c >> 3 ); \ b -= c; b -= a; b ^= ( a << 10 ); \ c -= a; c -= b; c ^= ( b >> 15 ); \ } while (0) #define HASH_JEN(key,keylen,num_bkts,hashv,bkt) \ do { \ unsigned _hj_i,_hj_j,_hj_k; \ char *_hj_key=(char*)(key); \ hashv = 0xfeedbeef; \ _hj_i = _hj_j = 0x9e3779b9; \ _hj_k = keylen; \ while (_hj_k >= 12) { \ _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + ( (unsigned)_hj_key[2] << 16 ) \ + ( (unsigned)_hj_key[3] << 24 ) ); \ _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + ( (unsigned)_hj_key[6] << 16 ) \ + ( (unsigned)_hj_key[7] << 24 ) ); \ hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + ( (unsigned)_hj_key[10] << 16 ) \ + ( (unsigned)_hj_key[11] << 24 ) ); \ \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ \ _hj_key += 12; \ _hj_k -= 12; \ } \ hashv += keylen; \ switch ( _hj_k ) { \ case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); \ case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); \ case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); \ case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); \ case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); \ case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); \ case 5: _hj_j += _hj_key[4]; \ case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); \ case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); \ case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); \ case 1: _hj_i += _hj_key[0]; \ } \ HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ bkt = hashv & (num_bkts-1); \ } while(0) /* The Paul Hsieh hash function */ #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif #define HASH_SFH(key,keylen,num_bkts,hashv,bkt) \ do { \ char *_sfh_key=(char*)(key); \ uint32_t _sfh_tmp, _sfh_len = keylen; \ \ int _sfh_rem = _sfh_len & 3; \ _sfh_len >>= 2; \ hashv = 0xcafebabe; \ \ /* Main loop */ \ for (;_sfh_len > 0; _sfh_len--) { \ hashv += get16bits (_sfh_key); \ _sfh_tmp = (get16bits (_sfh_key+2) << 11) ^ hashv; \ hashv = (hashv << 16) ^ _sfh_tmp; \ _sfh_key += 2*sizeof (uint16_t); \ hashv += hashv >> 11; \ } \ \ /* Handle end cases */ \ switch (_sfh_rem) { \ case 3: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 16; \ hashv ^= _sfh_key[sizeof (uint16_t)] << 18; \ hashv += hashv >> 11; \ break; \ case 2: hashv += get16bits (_sfh_key); \ hashv ^= hashv << 11; \ hashv += hashv >> 17; \ break; \ case 1: hashv += *_sfh_key; \ hashv ^= hashv << 10; \ hashv += hashv >> 1; \ } \ \ /* Force "avalanching" of final 127 bits */ \ hashv ^= hashv << 3; \ hashv += hashv >> 5; \ hashv ^= hashv << 4; \ hashv += hashv >> 17; \ hashv ^= hashv << 25; \ hashv += hashv >> 6; \ bkt = hashv & (num_bkts-1); \ } while(0); #ifdef HASH_USING_NO_STRICT_ALIASING /* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads. * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. * MurmurHash uses the faster approach only on CPU's where we know it's safe. * * Note the preprocessor built-in defines can be emitted using: * * gcc -m64 -dM -E - < /dev/null (on gcc) * cc -## a.c (where a.c is a simple test file) (Sun Studio) */ #if (defined(__i386__) || defined(__x86_64__)) #define MUR_GETBLOCK(p,i) p[i] #else /* non intel */ #define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 0x3) == 0) #define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 0x3) == 1) #define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 0x3) == 2) #define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 0x3) == 3) #define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL)) #if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__)) #define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24)) #define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16)) #define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8)) #else /* assume little endian non-intel */ #define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24)) #define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16)) #define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8)) #endif #define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \ (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \ (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \ MUR_ONE_THREE(p)))) #endif #define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) #define MUR_FMIX(_h) \ do { \ _h ^= _h >> 16; \ _h *= 0x85ebca6b; \ _h ^= _h >> 13; \ _h *= 0xc2b2ae35l; \ _h ^= _h >> 16; \ } while(0) #define HASH_MUR(key,keylen,num_bkts,hashv,bkt) \ do { \ const uint8_t *_mur_data = (const uint8_t*)(key); \ const int _mur_nblocks = (keylen) / 4; \ uint32_t _mur_h1 = 0xf88D5353; \ uint32_t _mur_c1 = 0xcc9e2d51; \ uint32_t _mur_c2 = 0x1b873593; \ const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+_mur_nblocks*4); \ int _mur_i; \ for(_mur_i = -_mur_nblocks; _mur_i; _mur_i++) { \ uint32_t _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \ _mur_k1 *= _mur_c1; \ _mur_k1 = MUR_ROTL32(_mur_k1,15); \ _mur_k1 *= _mur_c2; \ \ _mur_h1 ^= _mur_k1; \ _mur_h1 = MUR_ROTL32(_mur_h1,13); \ _mur_h1 = _mur_h1*5+0xe6546b64; \ } \ const uint8_t *_mur_tail = (const uint8_t*)(_mur_data + _mur_nblocks*4); \ uint32_t _mur_k1=0; \ switch((keylen) & 3) { \ case 3: _mur_k1 ^= _mur_tail[2] << 16; \ case 2: _mur_k1 ^= _mur_tail[1] << 8; \ case 1: _mur_k1 ^= _mur_tail[0]; \ _mur_k1 *= _mur_c1; \ _mur_k1 = MUR_ROTL32(_mur_k1,15); \ _mur_k1 *= _mur_c2; \ _mur_h1 ^= _mur_k1; \ } \ _mur_h1 ^= (keylen); \ MUR_FMIX(_mur_h1); \ hashv = _mur_h1; \ bkt = hashv & (num_bkts-1); \ } while(0) #endif /* HASH_USING_NO_STRICT_ALIASING */ /* key comparison function; return 0 if keys equal */ #define HASH_KEYCMP(a,b,len) memcmp(a,b,len) /* iterate over items in a known bucket to find desired item */ #define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,out) \ do { \ if (head.hh_head) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,head.hh_head)); \ else out=NULL; \ while (out) { \ if (out->hh.keylen == keylen_in) { \ if ((HASH_KEYCMP(out->hh.key,keyptr,keylen_in)) == 0) break; \ } \ if (out->hh.hh_next) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,out->hh.hh_next)); \ else out = NULL; \ } \ } while(0) /* add an item to a bucket */ #define HASH_ADD_TO_BKT(head,addhh) \ do { \ head.count++; \ (addhh)->hh_next = head.hh_head; \ (addhh)->hh_prev = NULL; \ if (head.hh_head) { (head).hh_head->hh_prev = (addhh); } \ (head).hh_head=addhh; \ if (head.count >= ((head.expand_mult+1) * HASH_BKT_CAPACITY_THRESH) \ && (addhh)->tbl->noexpand != 1) { \ HASH_EXPAND_BUCKETS((addhh)->tbl); \ } \ } while(0) /* remove an item from a given bucket */ #define HASH_DEL_IN_BKT(hh,head,hh_del) \ (head).count--; \ if ((head).hh_head == hh_del) { \ (head).hh_head = hh_del->hh_next; \ } \ if (hh_del->hh_prev) { \ hh_del->hh_prev->hh_next = hh_del->hh_next; \ } \ if (hh_del->hh_next) { \ hh_del->hh_next->hh_prev = hh_del->hh_prev; \ } /* Bucket expansion has the effect of doubling the number of buckets * and redistributing the items into the new buckets. Ideally the * items will distribute more or less evenly into the new buckets * (the extent to which this is true is a measure of the quality of * the hash function as it applies to the key domain). * * With the items distributed into more buckets, the chain length * (item count) in each bucket is reduced. Thus by expanding buckets * the hash keeps a bound on the chain length. This bounded chain * length is the essence of how a hash provides constant time lookup. * * The calculation of tbl->ideal_chain_maxlen below deserves some * explanation. First, keep in mind that we're calculating the ideal * maximum chain length based on the *new* (doubled) bucket count. * In fractions this is just n/b (n=number of items,b=new num buckets). * Since the ideal chain length is an integer, we want to calculate * ceil(n/b). We don't depend on floating point arithmetic in this * hash, so to calculate ceil(n/b) with integers we could write * * ceil(n/b) = (n/b) + ((n%b)?1:0) * * and in fact a previous version of this hash did just that. * But now we have improved things a bit by recognizing that b is * always a power of two. We keep its base 2 log handy (call it lb), * so now we can write this with a bit shift and logical AND: * * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) * */ #define HASH_EXPAND_BUCKETS(tbl) \ do { \ unsigned _he_bkt; \ unsigned _he_bkt_i; \ struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ if (!_he_new_buckets) { uthash_fatal( "out of memory"); } \ memset(_he_new_buckets, 0, \ 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ tbl->ideal_chain_maxlen = \ (tbl->num_items >> (tbl->log2_num_buckets+1)) + \ ((tbl->num_items & ((tbl->num_buckets*2)-1)) ? 1 : 0); \ tbl->nonideal_items = 0; \ for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++) \ { \ _he_thh = tbl->buckets[ _he_bkt_i ].hh_head; \ while (_he_thh) { \ _he_hh_nxt = _he_thh->hh_next; \ HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2, _he_bkt); \ _he_newbkt = &(_he_new_buckets[ _he_bkt ]); \ if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) { \ tbl->nonideal_items++; \ _he_newbkt->expand_mult = _he_newbkt->count / \ tbl->ideal_chain_maxlen; \ } \ _he_thh->hh_prev = NULL; \ _he_thh->hh_next = _he_newbkt->hh_head; \ if (_he_newbkt->hh_head) _he_newbkt->hh_head->hh_prev = \ _he_thh; \ _he_newbkt->hh_head = _he_thh; \ _he_thh = _he_hh_nxt; \ } \ } \ uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ tbl->num_buckets *= 2; \ tbl->log2_num_buckets++; \ tbl->buckets = _he_new_buckets; \ tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ? \ (tbl->ineff_expands+1) : 0; \ if (tbl->ineff_expands > 1) { \ tbl->noexpand=1; \ uthash_noexpand_fyi(tbl); \ } \ uthash_expand_fyi(tbl); \ } while(0) /* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ /* Note that HASH_SORT assumes the hash handle name to be hh. * HASH_SRT was added to allow the hash handle name to be passed in. */ #define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) #define HASH_SRT(hh,head,cmpfcn) \ do { \ unsigned _hs_i; \ unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ if (head) { \ _hs_insize = 1; \ _hs_looping = 1; \ _hs_list = &((head)->hh); \ while (_hs_looping) { \ _hs_p = _hs_list; \ _hs_list = NULL; \ _hs_tail = NULL; \ _hs_nmerges = 0; \ while (_hs_p) { \ _hs_nmerges++; \ _hs_q = _hs_p; \ _hs_psize = 0; \ for ( _hs_i = 0; _hs_i < _hs_insize; _hs_i++ ) { \ _hs_psize++; \ _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ ((void*)((char*)(_hs_q->next) + \ (head)->hh.tbl->hho)) : NULL); \ if (! (_hs_q) ) break; \ } \ _hs_qsize = _hs_insize; \ while ((_hs_psize > 0) || ((_hs_qsize > 0) && _hs_q )) { \ if (_hs_psize == 0) { \ _hs_e = _hs_q; \ _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ ((void*)((char*)(_hs_q->next) + \ (head)->hh.tbl->hho)) : NULL); \ _hs_qsize--; \ } else if ( (_hs_qsize == 0) || !(_hs_q) ) { \ _hs_e = _hs_p; \ _hs_p = (UT_hash_handle*)((_hs_p->next) ? \ ((void*)((char*)(_hs_p->next) + \ (head)->hh.tbl->hho)) : NULL); \ _hs_psize--; \ } else if (( \ cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \ DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \ ) <= 0) { \ _hs_e = _hs_p; \ _hs_p = (UT_hash_handle*)((_hs_p->next) ? \ ((void*)((char*)(_hs_p->next) + \ (head)->hh.tbl->hho)) : NULL); \ _hs_psize--; \ } else { \ _hs_e = _hs_q; \ _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ ((void*)((char*)(_hs_q->next) + \ (head)->hh.tbl->hho)) : NULL); \ _hs_qsize--; \ } \ if ( _hs_tail ) { \ _hs_tail->next = ((_hs_e) ? \ ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL); \ } else { \ _hs_list = _hs_e; \ } \ _hs_e->prev = ((_hs_tail) ? \ ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \ _hs_tail = _hs_e; \ } \ _hs_p = _hs_q; \ } \ _hs_tail->next = NULL; \ if ( _hs_nmerges <= 1 ) { \ _hs_looping=0; \ (head)->hh.tbl->tail = _hs_tail; \ DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ } \ _hs_insize *= 2; \ } \ HASH_FSCK(hh,head); \ } \ } while (0) /* This function selects items from one hash into another hash. * The end result is that the selected items have dual presence * in both hashes. There is no copy of the items made; rather * they are added into the new hash through a secondary hash * hash handle that must be present in the structure. */ #define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ do { \ unsigned _src_bkt, _dst_bkt; \ void *_last_elt=NULL, *_elt; \ UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ if (src) { \ for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ _src_hh; \ _src_hh = _src_hh->hh_next) { \ _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ if (cond(_elt)) { \ _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ _dst_hh->key = _src_hh->key; \ _dst_hh->keylen = _src_hh->keylen; \ _dst_hh->hashv = _src_hh->hashv; \ _dst_hh->prev = _last_elt; \ _dst_hh->next = NULL; \ if (_last_elt_hh) { _last_elt_hh->next = _elt; } \ if (!dst) { \ DECLTYPE_ASSIGN(dst,_elt); \ HASH_MAKE_TABLE(hh_dst,dst); \ } else { \ _dst_hh->tbl = (dst)->hh_dst.tbl; \ } \ HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh); \ (dst)->hh_dst.tbl->num_items++; \ _last_elt = _elt; \ _last_elt_hh = _dst_hh; \ } \ } \ } \ } \ HASH_FSCK(hh_dst,dst); \ } while (0) #define HASH_CLEAR(hh,head) \ do { \ if (head) { \ uthash_free((head)->hh.tbl->buckets, \ (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ HASH_BLOOM_FREE((head)->hh.tbl); \ uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ (head)=NULL; \ } \ } while(0) #ifdef NO_DECLTYPE #define HASH_ITER(hh,head,el,tmp) \ for((el)=(head), (*(char**)(&(tmp)))=(char*)((head)?(head)->hh.next:NULL); \ el; (el)=(tmp),(*(char**)(&(tmp)))=(char*)((tmp)?(tmp)->hh.next:NULL)) #else #define HASH_ITER(hh,head,el,tmp) \ for((el)=(head),(tmp)=DECLTYPE(el)((head)?(head)->hh.next:NULL); \ el; (el)=(tmp),(tmp)=DECLTYPE(el)((tmp)?(tmp)->hh.next:NULL)) #endif /* obtain a count of items in the hash */ #define HASH_COUNT(head) HASH_CNT(hh,head) #define HASH_CNT(hh,head) ((head)?((head)->hh.tbl->num_items):0) typedef struct UT_hash_bucket { struct UT_hash_handle *hh_head; unsigned count; /* expand_mult is normally set to 0. In this situation, the max chain length * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If * the bucket's chain exceeds this length, bucket expansion is triggered). * However, setting expand_mult to a non-zero value delays bucket expansion * (that would be triggered by additions to this particular bucket) * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. * (The multiplier is simply expand_mult+1). The whole idea of this * multiplier is to reduce bucket expansions, since they are expensive, in * situations where we know that a particular bucket tends to be overused. * It is better to let its chain length grow to a longer yet-still-bounded * value, than to do an O(n) bucket expansion too often. */ unsigned expand_mult; } UT_hash_bucket; /* random signature used only to find hash tables in external analysis */ #define HASH_SIGNATURE 0xa0111fe1 #define HASH_BLOOM_SIGNATURE 0xb12220f2 typedef struct UT_hash_table { UT_hash_bucket *buckets; unsigned num_buckets, log2_num_buckets; unsigned num_items; struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ /* in an ideal situation (all buckets used equally), no bucket would have * more than ceil(#items/#buckets) items. that's the ideal chain length. */ unsigned ideal_chain_maxlen; /* nonideal_items is the number of items in the hash whose chain position * exceeds the ideal chain maxlen. these items pay the penalty for an uneven * hash distribution; reaching them in a chain traversal takes >ideal steps */ unsigned nonideal_items; /* ineffective expands occur when a bucket doubling was performed, but * afterward, more than half the items in the hash had nonideal chain * positions. If this happens on two consecutive expansions we inhibit any * further expansion, as it's not helping; this happens when the hash * function isn't a good fit for the key domain. When expansion is inhibited * the hash will still work, albeit no longer in constant time. */ unsigned ineff_expands, noexpand; uint32_t signature; /* used only to find hash tables in external analysis */ #ifdef HASH_BLOOM uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ uint8_t *bloom_bv; char bloom_nbits; #endif } UT_hash_table; typedef struct UT_hash_handle { struct UT_hash_table *tbl; void *prev; /* prev element in app order */ void *next; /* next element in app order */ struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ struct UT_hash_handle *hh_next; /* next hh in bucket order */ void *key; /* ptr to enclosing struct's key */ unsigned keylen; /* enclosing struct's key len */ unsigned hashv; /* result of hash-fcn(key) */ } UT_hash_handle; #endif /* UTHASH_H */ info-beamer-1.0~pre4/utlist.h0000644000175000017500000011502612452774240014042 0ustar nknk/* Copyright (c) 2007-2011, Troy D. Hanson http://uthash.sourceforge.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef UTLIST_H #define UTLIST_H #define UTLIST_VERSION 1.9.4 #include /* * This file contains macros to manipulate singly and doubly-linked lists. * * 1. LL_ macros: singly-linked lists. * 2. DL_ macros: doubly-linked lists. * 3. CDL_ macros: circular doubly-linked lists. * * To use singly-linked lists, your structure must have a "next" pointer. * To use doubly-linked lists, your structure must "prev" and "next" pointers. * Either way, the pointer to the head of the list must be initialized to NULL. * * ----------------.EXAMPLE ------------------------- * struct item { * int id; * struct item *prev, *next; * } * * struct item *list = NULL: * * int main() { * struct item *item; * ... allocate and populate item ... * DL_APPEND(list, item); * } * -------------------------------------------------- * * For doubly-linked lists, the append and delete macros are O(1) * For singly-linked lists, append and delete are O(n) but prepend is O(1) * The sort macro is O(n log(n)) for all types of single/double/circular lists. */ /* These macros use decltype or the earlier __typeof GNU extension. As decltype is only available in newer compilers (VS2010 or gcc 4.3+ when compiling c++ code), this code uses whatever method is needed or, for VS2008 where neither is available, uses casting workarounds. */ #ifdef _MSC_VER /* MS compiler */ #if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ #define LDECLTYPE(x) decltype(x) #else /* VS2008 or older (or VS2010 in C mode) */ #define NO_DECLTYPE #define LDECLTYPE(x) char* #endif #else /* GNU, Sun and other compilers */ #define LDECLTYPE(x) __typeof(x) #endif /* for VS2008 we use some workarounds to get around the lack of decltype, * namely, we always reassign our tmp variable to the list head if we need * to dereference its prev/next pointers, and save/restore the real head.*/ #ifdef NO_DECLTYPE #define _SV(elt,list) _tmp = (char*)(list); {char **_alias = (char**)&(list); *_alias = (elt); } #define _NEXT(elt,list) ((char*)((list)->next)) #define _NEXTASGN(elt,list,to) { char **_alias = (char**)&((list)->next); *_alias=(char*)(to); } #define _PREV(elt,list) ((char*)((list)->prev)) #define _PREVASGN(elt,list,to) { char **_alias = (char**)&((list)->prev); *_alias=(char*)(to); } #define _RS(list) { char **_alias = (char**)&(list); *_alias=_tmp; } #define _CASTASGN(a,b) { char **_alias = (char**)&(a); *_alias=(char*)(b); } #else #define _SV(elt,list) #define _NEXT(elt,list) ((elt)->next) #define _NEXTASGN(elt,list,to) ((elt)->next)=(to) #define _PREV(elt,list) ((elt)->prev) #define _PREVASGN(elt,list,to) ((elt)->prev)=(to) #define _RS(list) #define _CASTASGN(a,b) (a)=(b) #endif /****************************************************************************** * The sort macro is an adaptation of Simon Tatham's O(n log(n)) mergesort * * Unwieldy variable names used here to avoid shadowing passed-in variables. * *****************************************************************************/ #define LL_SORT(list, cmp) \ do { \ LDECLTYPE(list) _ls_p; \ LDECLTYPE(list) _ls_q; \ LDECLTYPE(list) _ls_e; \ LDECLTYPE(list) _ls_tail; \ LDECLTYPE(list) _ls_oldhead; \ LDECLTYPE(list) _tmp; \ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \ if (list) { \ _ls_insize = 1; \ _ls_looping = 1; \ while (_ls_looping) { \ _CASTASGN(_ls_p,list); \ _CASTASGN(_ls_oldhead,list); \ list = NULL; \ _ls_tail = NULL; \ _ls_nmerges = 0; \ while (_ls_p) { \ _ls_nmerges++; \ _ls_q = _ls_p; \ _ls_psize = 0; \ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \ _ls_psize++; \ _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list); _RS(list); \ if (!_ls_q) break; \ } \ _ls_qsize = _ls_insize; \ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \ if (_ls_psize == 0) { \ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list); _RS(list); _ls_qsize--; \ } else if (_ls_qsize == 0 || !_ls_q) { \ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = _NEXT(_ls_p,list); _RS(list); _ls_psize--; \ } else if (cmp(_ls_p,_ls_q) <= 0) { \ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = _NEXT(_ls_p,list); _RS(list); _ls_psize--; \ } else { \ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list); _RS(list); _ls_qsize--; \ } \ if (_ls_tail) { \ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_ls_e); _RS(list); \ } else { \ _CASTASGN(list,_ls_e); \ } \ _ls_tail = _ls_e; \ } \ _ls_p = _ls_q; \ } \ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,NULL); _RS(list); \ if (_ls_nmerges <= 1) { \ _ls_looping=0; \ } \ _ls_insize *= 2; \ } \ } else _tmp=NULL; /* quiet gcc unused variable warning */ \ } while (0) #define DL_SORT(list, cmp) \ do { \ LDECLTYPE(list) _ls_p; \ LDECLTYPE(list) _ls_q; \ LDECLTYPE(list) _ls_e; \ LDECLTYPE(list) _ls_tail; \ LDECLTYPE(list) _ls_oldhead; \ LDECLTYPE(list) _tmp; \ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \ if (list) { \ _ls_insize = 1; \ _ls_looping = 1; \ while (_ls_looping) { \ _CASTASGN(_ls_p,list); \ _CASTASGN(_ls_oldhead,list); \ list = NULL; \ _ls_tail = NULL; \ _ls_nmerges = 0; \ while (_ls_p) { \ _ls_nmerges++; \ _ls_q = _ls_p; \ _ls_psize = 0; \ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \ _ls_psize++; \ _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list); _RS(list); \ if (!_ls_q) break; \ } \ _ls_qsize = _ls_insize; \ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \ if (_ls_psize == 0) { \ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list); _RS(list); _ls_qsize--; \ } else if (_ls_qsize == 0 || !_ls_q) { \ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = _NEXT(_ls_p,list); _RS(list); _ls_psize--; \ } else if (cmp(_ls_p,_ls_q) <= 0) { \ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = _NEXT(_ls_p,list); _RS(list); _ls_psize--; \ } else { \ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list); _RS(list); _ls_qsize--; \ } \ if (_ls_tail) { \ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_ls_e); _RS(list); \ } else { \ _CASTASGN(list,_ls_e); \ } \ _SV(_ls_e,list); _PREVASGN(_ls_e,list,_ls_tail); _RS(list); \ _ls_tail = _ls_e; \ } \ _ls_p = _ls_q; \ } \ _CASTASGN(list->prev, _ls_tail); \ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,NULL); _RS(list); \ if (_ls_nmerges <= 1) { \ _ls_looping=0; \ } \ _ls_insize *= 2; \ } \ } else _tmp=NULL; /* quiet gcc unused variable warning */ \ } while (0) #define CDL_SORT(list, cmp) \ do { \ LDECLTYPE(list) _ls_p; \ LDECLTYPE(list) _ls_q; \ LDECLTYPE(list) _ls_e; \ LDECLTYPE(list) _ls_tail; \ LDECLTYPE(list) _ls_oldhead; \ LDECLTYPE(list) _tmp; \ LDECLTYPE(list) _tmp2; \ int _ls_insize, _ls_nmerges, _ls_psize, _ls_qsize, _ls_i, _ls_looping; \ if (list) { \ _ls_insize = 1; \ _ls_looping = 1; \ while (_ls_looping) { \ _CASTASGN(_ls_p,list); \ _CASTASGN(_ls_oldhead,list); \ list = NULL; \ _ls_tail = NULL; \ _ls_nmerges = 0; \ while (_ls_p) { \ _ls_nmerges++; \ _ls_q = _ls_p; \ _ls_psize = 0; \ for (_ls_i = 0; _ls_i < _ls_insize; _ls_i++) { \ _ls_psize++; \ _SV(_ls_q,list); \ if (_NEXT(_ls_q,list) == _ls_oldhead) { \ _ls_q = NULL; \ } else { \ _ls_q = _NEXT(_ls_q,list); \ } \ _RS(list); \ if (!_ls_q) break; \ } \ _ls_qsize = _ls_insize; \ while (_ls_psize > 0 || (_ls_qsize > 0 && _ls_q)) { \ if (_ls_psize == 0) { \ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list); _RS(list); _ls_qsize--; \ if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \ } else if (_ls_qsize == 0 || !_ls_q) { \ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = _NEXT(_ls_p,list); _RS(list); _ls_psize--; \ if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \ } else if (cmp(_ls_p,_ls_q) <= 0) { \ _ls_e = _ls_p; _SV(_ls_p,list); _ls_p = _NEXT(_ls_p,list); _RS(list); _ls_psize--; \ if (_ls_p == _ls_oldhead) { _ls_p = NULL; } \ } else { \ _ls_e = _ls_q; _SV(_ls_q,list); _ls_q = _NEXT(_ls_q,list); _RS(list); _ls_qsize--; \ if (_ls_q == _ls_oldhead) { _ls_q = NULL; } \ } \ if (_ls_tail) { \ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_ls_e); _RS(list); \ } else { \ _CASTASGN(list,_ls_e); \ } \ _SV(_ls_e,list); _PREVASGN(_ls_e,list,_ls_tail); _RS(list); \ _ls_tail = _ls_e; \ } \ _ls_p = _ls_q; \ } \ _CASTASGN(list->prev,_ls_tail); \ _CASTASGN(_tmp2,list); \ _SV(_ls_tail,list); _NEXTASGN(_ls_tail,list,_tmp2); _RS(list); \ if (_ls_nmerges <= 1) { \ _ls_looping=0; \ } \ _ls_insize *= 2; \ } \ } else _tmp=NULL; /* quiet gcc unused variable warning */ \ } while (0) /****************************************************************************** * singly linked list macros (non-circular) * *****************************************************************************/ #define LL_PREPEND(head,add) \ do { \ (add)->next = head; \ head = add; \ } while (0) #define LL_CONCAT(head1,head2) \ do { \ LDECLTYPE(head1) _tmp; \ if (head1) { \ _tmp = head1; \ while (_tmp->next) { _tmp = _tmp->next; } \ _tmp->next=(head2); \ } else { \ (head1)=(head2); \ } \ } while (0) #define LL_APPEND(head,add) \ do { \ LDECLTYPE(head) _tmp; \ (add)->next=NULL; \ if (head) { \ _tmp = head; \ while (_tmp->next) { _tmp = _tmp->next; } \ _tmp->next=(add); \ } else { \ (head)=(add); \ } \ } while (0) #define LL_DELETE(head,del) \ do { \ LDECLTYPE(head) _tmp; \ if ((head) == (del)) { \ (head)=(head)->next; \ } else { \ _tmp = head; \ while (_tmp->next && (_tmp->next != (del))) { \ _tmp = _tmp->next; \ } \ if (_tmp->next) { \ _tmp->next = ((del)->next); \ } \ } \ } while (0) /* Here are VS2008 replacements for LL_APPEND and LL_DELETE */ #define LL_APPEND_VS2008(head,add) \ do { \ if (head) { \ (add)->next = head; /* use add->next as a temp variable */ \ while ((add)->next->next) { (add)->next = (add)->next->next; } \ (add)->next->next=(add); \ } else { \ (head)=(add); \ } \ (add)->next=NULL; \ } while (0) #define LL_DELETE_VS2008(head,del) \ do { \ if ((head) == (del)) { \ (head)=(head)->next; \ } else { \ char *_tmp = (char*)(head); \ while (head->next && (head->next != (del))) { \ head = head->next; \ } \ if (head->next) { \ head->next = ((del)->next); \ } \ { \ char **_head_alias = (char**)&(head); \ *_head_alias = _tmp; \ } \ } \ } while (0) #ifdef NO_DECLTYPE #undef LL_APPEND #define LL_APPEND LL_APPEND_VS2008 #undef LL_DELETE #define LL_DELETE LL_DELETE_VS2008 #undef LL_CONCAT /* no LL_CONCAT_VS2008 */ #undef DL_CONCAT /* no DL_CONCAT_VS2008 */ #endif /* end VS2008 replacements */ #define LL_FOREACH(head,el) \ for(el=head;el;el=el->next) #define LL_FOREACH_SAFE(head,el,tmp) \ for((el)=(head);(el) && (tmp = (el)->next, 1); (el) = tmp) #define LL_SEARCH_SCALAR(head,out,field,val) \ do { \ LL_FOREACH(head,out) { \ if ((out)->field == (val)) break; \ } \ } while(0) #define LL_SEARCH(head,out,elt,cmp) \ do { \ LL_FOREACH(head,out) { \ if ((cmp(out,elt))==0) break; \ } \ } while(0) /****************************************************************************** * doubly linked list macros (non-circular) * *****************************************************************************/ #define DL_PREPEND(head,add) \ do { \ (add)->next = head; \ if (head) { \ (add)->prev = (head)->prev; \ (head)->prev = (add); \ } else { \ (add)->prev = (add); \ } \ (head) = (add); \ } while (0) #define DL_APPEND(head,add) \ do { \ if (head) { \ (add)->prev = (head)->prev; \ (head)->prev->next = (add); \ (head)->prev = (add); \ (add)->next = NULL; \ } else { \ (head)=(add); \ (head)->prev = (head); \ (head)->next = NULL; \ } \ } while (0); #define DL_CONCAT(head1,head2) \ do { \ LDECLTYPE(head1) _tmp; \ if (head2) { \ if (head1) { \ _tmp = (head2)->prev; \ (head2)->prev = (head1)->prev; \ (head1)->prev->next = (head2); \ (head1)->prev = _tmp; \ } else { \ (head1)=(head2); \ } \ } \ } while (0); #define DL_DELETE(head,del) \ do { \ assert((del)->prev != NULL); \ if ((del)->prev == (del)) { \ (head)=NULL; \ } else if ((del)==(head)) { \ (del)->next->prev = (del)->prev; \ (head) = (del)->next; \ } else { \ (del)->prev->next = (del)->next; \ if ((del)->next) { \ (del)->next->prev = (del)->prev; \ } else { \ (head)->prev = (del)->prev; \ } \ } \ } while (0); #define DL_FOREACH(head,el) \ for(el=head;el;el=el->next) /* this version is safe for deleting the elements during iteration */ #define DL_FOREACH_SAFE(head,el,tmp) \ for((el)=(head);(el) && (tmp = (el)->next, 1); (el) = tmp) /* these are identical to their singly-linked list counterparts */ #define DL_SEARCH_SCALAR LL_SEARCH_SCALAR #define DL_SEARCH LL_SEARCH /****************************************************************************** * circular doubly linked list macros * *****************************************************************************/ #define CDL_PREPEND(head,add) \ do { \ if (head) { \ (add)->prev = (head)->prev; \ (add)->next = (head); \ (head)->prev = (add); \ (add)->prev->next = (add); \ } else { \ (add)->prev = (add); \ (add)->next = (add); \ } \ (head)=(add); \ } while (0) #define CDL_DELETE(head,del) \ do { \ if ( ((head)==(del)) && ((head)->next == (head))) { \ (head) = 0L; \ } else { \ (del)->next->prev = (del)->prev; \ (del)->prev->next = (del)->next; \ if ((del) == (head)) (head)=(del)->next; \ } \ } while (0); #define CDL_FOREACH(head,el) \ for(el=head;el;el=(el->next==head ? 0L : el->next)) #define CDL_FOREACH_SAFE(head,el,tmp1,tmp2) \ for((el)=(head), ((tmp1)=(head)?((head)->prev):NULL); \ (el) && ((tmp2)=(el)->next, 1); \ ((el) = (((el)==(tmp1)) ? 0L : (tmp2)))) #define CDL_SEARCH_SCALAR(head,out,field,val) \ do { \ CDL_FOREACH(head,out) { \ if ((out)->field == (val)) break; \ } \ } while(0) #define CDL_SEARCH(head,out,elt,cmp) \ do { \ CDL_FOREACH(head,out) { \ if ((cmp(out,elt))==0) break; \ } \ } while(0) #endif /* UTLIST_H */ info-beamer-1.0~pre4/video.c0000644000175000017500000002566012452774240013623 0ustar nknk/* See Copyright Notice in LICENSE.txt */ /* * Includes from code by Michael Meeuwisse * https://docs.google.com/leaf?id=0B_dz2NwhjXB-NDQ0NWNjOWEtMzJiNy00ZjcwLWJjMjYtZTU2YmQzMWMzYmU0 * * License: * * (C) Copyright 2010 Michael Meeuwisse * * Adapted from avcodec_sample.0.5.0.c, license unknown * * ffmpeg_test is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * ffmpeg_test is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with ffmpeg_test. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include "misc.h" #include "shader.h" typedef struct { AVFormatContext *format_context; AVCodecContext *codec_context; AVCodec *codec; AVFrame *raw_frame; AVFrame *scaled_frame; uint8_t *buffer; struct SwsContext *scaler; int stream_idx, format; int width, height; int buffer_width, buffer_height; double par; GLuint tex; double fps; int finished; } video_t; LUA_TYPE_DECL(video) /* Helper functions */ static void video_free(video_t *video) { if (video->scaler) sws_freeContext(video->scaler); if (video->raw_frame) av_free(video->raw_frame); if (video->scaled_frame) av_free(video->scaled_frame); if (video->codec_context) avcodec_close(video->codec_context); if (video->format_context) avformat_close_input(&video->format_context); av_free(video->buffer); } static int video_open(video_t *video, const char *filename) { video->finished = 0; video->format = PIX_FMT_RGB24; if (avformat_open_input(&video->format_context, filename, NULL, NULL) || avformat_find_stream_info(video->format_context, NULL) < 0) { fprintf(stderr, ERROR("cannot open video stream %s\n"), filename); goto failed; } video->stream_idx = -1; for (int i = 0; i < video->format_context->nb_streams; i++) { if (video->format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { video->stream_idx = i; break; } } if (video->stream_idx == -1) { fprintf(stderr, ERROR("cannot find video stream\n")); goto failed; } AVStream *stream = video->format_context->streams[video->stream_idx]; video->codec_context = stream->codec; video->codec = avcodec_find_decoder(video->codec_context->codec_id); /* Save Width/Height */ video->width = video->codec_context->width; video->height = video->codec_context->height; if (!video->codec || avcodec_open2(video->codec_context, video->codec, NULL) < 0) { fprintf(stderr, ERROR("cannot open codec\n")); goto failed; } video->buffer_width = video->codec_context->width; video->buffer_height = video->codec_context->height; fprintf(stderr, INFO("pixel aspect ratio: %d/%d, size: %dx%d buffer size: %dx%d\n"), video->codec_context->sample_aspect_ratio.num, video->codec_context->sample_aspect_ratio.den, video->width, video->height, video->buffer_width, video->buffer_height ); video->par = (float)video->codec_context->sample_aspect_ratio.num / video->codec_context->sample_aspect_ratio.den; if (video->par == 0) video->par = 1; /* Frame rate fix for some codecs */ if (video->codec_context->time_base.num > 1000 && video->codec_context->time_base.den == 1) video->codec_context->time_base.den = 1000; /* Get FPS */ // http://libav-users.943685.n4.nabble.com/Retrieving-Frames-Per-Second-FPS-td946533.html if ((stream->time_base.den != stream->avg_frame_rate.num) || (stream->time_base.num != stream->avg_frame_rate.den)) { video->fps = 1.0 / stream->avg_frame_rate.den * stream->avg_frame_rate.num; } else { video->fps = 1.0 / stream->time_base.num * stream->time_base.den; } fprintf(stderr, INFO("fps: %lf\n"), video->fps); /* Get framebuffers */ video->raw_frame = avcodec_alloc_frame(); video->scaled_frame = avcodec_alloc_frame(); if (!video->raw_frame || !video->scaled_frame) { fprintf(stderr, ERROR("cannot preallocate frames\n")); goto failed; } /* Create data buffer */ video->buffer = av_malloc(avpicture_get_size( video->format, video->buffer_width, video->buffer_height )); /* Init buffers */ avpicture_fill( (AVPicture *) video->scaled_frame, video->buffer, video->format, video->buffer_width, video->buffer_height ); /* Init scale & convert */ video->scaler = sws_getContext( video->buffer_width, video->buffer_height, video->codec_context->pix_fmt, video->buffer_width, video->buffer_height, video->format, SWS_BICUBIC, NULL, NULL, NULL ); if (!video->scaler) { fprintf(stderr, ERROR("scale context init failed\n")); goto failed; } /* Give some info on stderr about the file & stream */ av_dump_format(video->format_context, 0, filename, 0); return 1; failed: video_free(video); return 0; } static int video_next_frame(video_t *video) { AVPacket packet; av_init_packet(&packet); again: /* Can we read a frame? */ if (av_read_frame(video->format_context, &packet)) { fprintf(stderr, "no next frame\n"); video->finished = 1; av_free_packet(&packet); return 0; } /* Is it what we're trying to parse? */ if (packet.stream_index != video->stream_idx) { // fprintf(stderr, "not video\n"); av_free_packet(&packet); goto again; } /* Decode it! */ int complete_frame = 0; avcodec_decode_video2(video->codec_context, video->raw_frame, &complete_frame, &packet); /* Success? If not, drop packet. */ if (!complete_frame) { fprintf(stderr, ERROR("incomplete video packet\n")); av_free_packet(&packet); goto again; } /* Flip vertically * XXX: This feels wrong. What's the right way to do this? */ int heights[] = { video->buffer_height - 1, video->buffer_height / 2 - 1, video->buffer_height / 2 - 1, 0, }; for (int i = 0; i < 4; i++) { video->raw_frame->data[i] += video->raw_frame->linesize[i] * heights[i]; video->raw_frame->linesize[i] = -video->raw_frame->linesize[i]; // fprintf(stderr, "%d -> %d\n", video->raw_frame->linesize[i], video->scaled_frame->linesize[i]); } sws_scale( video->scaler, (const uint8_t* const *)video->raw_frame->data, video->raw_frame->linesize, 0, video->buffer_height, video->scaled_frame->data, video->scaled_frame->linesize ); av_free_packet(&packet); return 1; } /* Instance methods */ static int video_size(lua_State *L) { video_t *video = checked_video(L, 1); lua_pushnumber(L, video->width); lua_pushnumber(L, video->height / video->par); return 2; } static int video_fps(lua_State *L) { video_t *video = checked_video(L, 1); lua_pushnumber(L, video->fps); return 1; } static int video_next(lua_State *L) { video_t *video = checked_video(L, 1); if (!video_next_frame(video)) { lua_pushboolean(L, 0); return 1; } glBindTexture(GL_TEXTURE_2D, video->tex); glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT); glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_TRUE); glPixelStorei(GL_UNPACK_LSB_FIRST, GL_TRUE); glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); glPixelStorei(GL_UNPACK_SKIP_ROWS, video->buffer_height - video->height); glPixelStorei(GL_UNPACK_ROW_LENGTH, video->buffer_width); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, video->width, video->height, GL_RGB, GL_UNSIGNED_BYTE, video->buffer ); glGenerateMipmap(GL_TEXTURE_2D); glPopClientAttrib(); lua_pushboolean(L, 1); return 1; } static int video_state(lua_State *L) { video_t *video = checked_video(L, 1); lua_pushstring(L, video->finished ? "finished" : "loaded"); lua_pushnumber(L, video->width); lua_pushnumber(L, video->height / video->par); lua_pushnumber(L, video->fps); return 4; } static int video_draw(lua_State *L) { video_t *video = checked_video(L, 1); GLfloat x1 = luaL_checknumber(L, 2); GLfloat y1 = luaL_checknumber(L, 3); GLfloat x2 = luaL_checknumber(L, 4); GLfloat y2 = luaL_checknumber(L, 5); GLfloat alpha = luaL_optnumber(L, 6, 1.0); glBindTexture(GL_TEXTURE_2D, video->tex); shader_set_gl_color(1.0, 1.0, 1.0, alpha); glBegin(GL_QUADS); glTexCoord2f(0.0, 1.0); glVertex3f(x1, y1, 0); glTexCoord2f(1.0, 1.0); glVertex3f(x2, y1, 0); glTexCoord2f(1.0, 0.0); glVertex3f(x2, y2, 0); glTexCoord2f(0.0, 0.0); glVertex3f(x1, y2, 0); glEnd(); return 0; } static int video_texid(lua_State *L) { video_t *video = checked_video(L, 1); lua_pushnumber(L, video->tex); return 1; } static int video_dispose(lua_State *L) { return 0; } static const luaL_reg video_methods[] = { {"state", video_state}, {"draw", video_draw}, {"next", video_next}, {"size", video_size}, {"fps", video_fps}, {"texid", video_texid}, {"dispose", video_dispose}, {0,0} }; /* Lifecycle */ int video_load(lua_State *L, const char *path, const char *name) { video_t video; memset(&video, 0, sizeof(video_t)); if (!video_open(&video, path)) return luaL_error(L, "cannot open video %s", path); glGenTextures(1, &video.tex); glBindTexture(GL_TEXTURE_2D, video.tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, video.width, video.height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL ); *push_video(L) = video; return 1; } static int video_gc(lua_State *L) { video_t *video = to_video(L, 1); fprintf(stderr, INFO("gc'ing video: tex id: %d\n"), video->tex); glDeleteTextures(1, &video->tex); video_free(video); return 0; } LUA_TYPE_IMPL(video) info-beamer-1.0~pre4/video.h0000644000175000017500000000027012452774240013616 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #ifndef VIDEO_H #define VIDEO_H int video_register(lua_State *L); int video_load(lua_State *L, const char *path, const char *name); #endif info-beamer-1.0~pre4/vnc.c0000644000175000017500000003316412452774240013301 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #define _BSD_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "misc.h" #include "shader.h" typedef struct vnc_s vnc_t; typedef void(*protocol_handler)(vnc_t *); typedef struct { uint8_t bpp; uint8_t depth; uint8_t bigendian; uint8_t truecolor; uint16_t red_max; uint16_t green_max; uint16_t blue_max; uint8_t red_shift; uint8_t green_shift; uint8_t blue_shift; uint8_t padding[3]; } pixelformat_t; struct vnc_s { GLuint tex; int width; int height; struct bufferevent *buf_ev; char *host; int port; int alive; protocol_handler handler; int num_bytes; pixelformat_t pixelformat; // state for rect update int num_rects; int rect_x; int rect_y; int rect_w; int rect_h; }; LUA_TYPE_DECL(vnc) /* Instance methods */ static int vnc_size(lua_State *L) { vnc_t *vnc = checked_vnc(L, 1); lua_pushnumber(L, vnc->width); lua_pushnumber(L, vnc->height); return 2; } static int vnc_draw(lua_State *L) { vnc_t *vnc = checked_vnc(L, 1); GLfloat x1 = luaL_checknumber(L, 2); GLfloat y1 = luaL_checknumber(L, 3); GLfloat x2 = luaL_checknumber(L, 4); GLfloat y2 = luaL_checknumber(L, 5); GLfloat alpha = luaL_optnumber(L, 6, 1.0); glBindTexture(GL_TEXTURE_2D, vnc->tex); shader_set_gl_color(1.0, 1.0, 1.0, alpha); glBegin(GL_QUADS); glTexCoord2f(0.0, 1.0); glVertex3f(x1, y1, 0); glTexCoord2f(1.0, 1.0); glVertex3f(x2, y1, 0); glTexCoord2f(1.0, 0.0); glVertex3f(x2, y2, 0); glTexCoord2f(0.0, 0.0); glVertex3f(x1, y2, 0); glEnd(); return 0; } static int vnc_alive(lua_State *L) { vnc_t *vnc = checked_vnc(L, 1); lua_pushboolean(L, vnc->alive); return 1; } static int vnc_texid(lua_State *L) { vnc_t *vnc = checked_vnc(L, 1); lua_pushnumber(L, vnc->tex); return 1; } static const luaL_reg vnc_methods[] = { {"draw", vnc_draw}, {"size", vnc_size}, {"alive", vnc_alive}, {"texid", vnc_texid}, {0,0} }; /* Protocol utils */ static void vnc_printf(vnc_t *vnc, const char *fmt, ...) { char buffer[16384]; va_list ap; va_start(ap, fmt); vsnprintf(buffer, sizeof(buffer), fmt, ap); va_end(ap); fprintf(stderr, CYAN("[vnc@%s:%d]")" %s", vnc->host, vnc->port, buffer); } static void vnc_set_handler(vnc_t *vnc, protocol_handler handler, int num_bytes) { vnc->handler = handler; vnc->num_bytes = num_bytes; if (evbuffer_get_length(vnc->buf_ev->input) >= vnc->num_bytes) vnc->handler(vnc); } static void vnc_close(vnc_t *vnc) { if (vnc->buf_ev) { vnc_printf(vnc, "connection closed\n"); bufferevent_free(vnc->buf_ev); vnc->buf_ev = NULL; } if (vnc->tex) { glDeleteTextures(1, &vnc->tex); vnc->tex = 0; } vnc->alive = 0; } static void vnc_read(struct bufferevent *bev, void *arg) { vnc_t *vnc = arg; if (evbuffer_get_length(bev->input) >= vnc->num_bytes) vnc->handler(vnc); } static void vnc_event(struct bufferevent *bev, short events, void *arg) { vnc_t *vnc = arg; if (events & BEV_EVENT_CONNECTED) { vnc_printf(vnc, "connected!\n"); } else if (events & BEV_EVENT_ERROR) { int err = bufferevent_socket_get_dns_error(bev); if (err) { vnc_printf(vnc, "dns error: %s\n", evutil_gai_strerror(err)); } else { vnc_printf(vnc, "connection error!\n"); } return vnc_close(vnc); } else if (events & BEV_EVENT_EOF) { vnc_printf(vnc, "eof!\n"); return vnc_close(vnc); } } static const int endian_test = 1; #define is_bigendian() ((*(char*)&endian_test) == 0) #define swap32(v) ((((v) & 0xff000000) >> 24) | \ (((v) & 0x00ff0000) >> 8) | \ (((v) & 0x0000ff00) << 8) | \ (((v) & 0x000000ff) << 24)) static int vnc_decode(vnc_t *vnc, const unsigned char *pixels) { unsigned char *converted = malloc(vnc->rect_w * vnc->rect_h * 4); assert(vnc->pixelformat.bpp == 32); int row_size = vnc->rect_w * 4; for (int row = 0; row < vnc->rect_h; row++) { uint32_t *src = (uint32_t*)(pixels + row * row_size); uint32_t *dest = (uint32_t*)(converted + (vnc->rect_h - row - 1) * row_size); for (int col = 0; col < vnc->rect_w; col++) { uint32_t raw = *src; if (is_bigendian() ^ vnc->pixelformat.bigendian) { raw = swap32(raw); } uint32_t r = (raw >> vnc->pixelformat.red_shift) & vnc->pixelformat.red_max; uint32_t g = (raw >> vnc->pixelformat.green_shift) & vnc->pixelformat.green_max; uint32_t b = (raw >> vnc->pixelformat.blue_shift) & vnc->pixelformat.blue_max; *dest = 255 << 24 | b << 16 | g << 8 | r; dest++, src++; } } glBindTexture(GL_TEXTURE_2D, vnc->tex); glTexSubImage2D( GL_TEXTURE_2D, 0, vnc->rect_x, vnc->height - vnc->rect_y - vnc->rect_h, vnc->rect_w, vnc->rect_h, GL_RGBA, GL_UNSIGNED_BYTE, converted ); glGenerateMipmap(GL_TEXTURE_2D); free(converted); return 1; } /* Packet definitions */ typedef struct { uint16_t x; uint16_t y; uint16_t w; uint16_t h; uint32_t encoding; } pkt_server_rect; typedef struct { uint8_t msg_type; #define SERVER_MSG_TYPE_FRAMEBUFFER_UPDATE 0 #define SERVER_MSG_TYPE_BELL 2 #define SERVER_MSG_TYPE_CUT_TEXT 3 } pkt_server_base_msg; typedef struct { /* extends pkt_server_base_msg */ uint8_t msg_type; uint8_t padding[1]; uint16_t num_rects; } pkt_server_frameupdate; typedef struct { /* extends pkt_server_base_msg */ uint8_t msg_type; uint8_t padding[3]; uint32_t text_len; } pkt_server_cut_text; typedef struct { uint16_t width; uint16_t height; pixelformat_t pixelformat; uint32_t name_len; } pkt_server_init; typedef struct { uint32_t security_type; #define SERVER_SECURITY_NO_AUTH 1 } pkt_server_auth; typedef struct { uint8_t R; uint8_t F; uint8_t B; uint8_t handshake[9]; } pkt_server_handshake; typedef struct { uint8_t msg_type; #define CLIENT_MSG_TYPE_UPDATE_REQUEST 3 uint8_t incremental; uint16_t x; uint16_t y; uint16_t w; uint16_t h; } pkt_client_update_request; typedef struct { uint8_t shared; } pkt_client_init; /* Protocol */ static void vnc_read_msg_header(vnc_t *vnc); static void vnc_read_rect(vnc_t *vnc); static void vnc_send_update_request(vnc_t *vnc, int x, int y, int w, int h, int incremental); static void vnc_read_cut_text(vnc_t *vnc) { evbuffer_drain(vnc->buf_ev->input, vnc->num_bytes); return vnc_set_handler(vnc, vnc_read_msg_header, sizeof(pkt_server_base_msg)); } static void vnc_read_cut(vnc_t *vnc) { pkt_server_cut_text in_pkt; evbuffer_remove(vnc->buf_ev->input, &in_pkt, sizeof(in_pkt)); uint32_t text_len = ntohl(in_pkt.text_len); if (text_len > 2048) { vnc_printf(vnc, "too large server cut text\n"); return vnc_close(vnc); } return vnc_set_handler(vnc, vnc_read_cut_text, text_len); } static void vnc_read_rect_data(vnc_t *vnc) { unsigned char *pixels = evbuffer_pullup(vnc->buf_ev->input, vnc->num_bytes); if (!vnc_decode(vnc, pixels)) { vnc_printf(vnc, "decoding failed\n"); return vnc_close(vnc); } evbuffer_drain(vnc->buf_ev->input, vnc->num_bytes); if (--vnc->num_rects == 0) { return vnc_send_update_request(vnc, 0, 0, vnc->width, vnc->height, 1); } else { return vnc_set_handler(vnc, vnc_read_rect, sizeof(pkt_server_rect)); } } static void vnc_read_rect(vnc_t *vnc) { pkt_server_rect in_pkt; evbuffer_remove(vnc->buf_ev->input, &in_pkt, sizeof(in_pkt)); vnc->rect_x = ntohs(in_pkt.x); vnc->rect_y = ntohs(in_pkt.y); vnc->rect_w = ntohs(in_pkt.w); vnc->rect_h = ntohs(in_pkt.h); if ((vnc->rect_x + vnc->rect_w > vnc->width) || (vnc->rect_y + vnc->rect_h > vnc->height)) { vnc_printf(vnc, "invalid rect (out of bound)\n"); return vnc_close(vnc); } return vnc_set_handler(vnc, vnc_read_rect_data, vnc->pixelformat.bpp / 8 * vnc->rect_w * vnc->rect_h); } static void vnc_read_rects(vnc_t *vnc) { pkt_server_frameupdate in_pkt; evbuffer_remove(vnc->buf_ev->input, &in_pkt, sizeof(in_pkt)); vnc->num_rects = ntohs(in_pkt.num_rects); if (vnc->num_rects == 0) { vnc_printf(vnc, "zero rect update\n"); return vnc_close(vnc); } return vnc_set_handler(vnc, vnc_read_rect, sizeof(pkt_server_rect)); } static void vnc_read_msg_header(vnc_t *vnc) { // peek into header without removing it pkt_server_base_msg in_pkt = *(pkt_server_base_msg *)evbuffer_pullup( vnc->buf_ev->input, sizeof(pkt_server_base_msg)); if (in_pkt.msg_type == SERVER_MSG_TYPE_FRAMEBUFFER_UPDATE) { return vnc_set_handler(vnc, vnc_read_rects, sizeof(pkt_server_frameupdate)); } else if (in_pkt.msg_type == SERVER_MSG_TYPE_BELL) { // ignore the bell evbuffer_drain(vnc->buf_ev->input, sizeof(in_pkt)); return vnc_set_handler(vnc, vnc_read_msg_header, sizeof(pkt_server_base_msg)); } else if (in_pkt.msg_type == SERVER_MSG_TYPE_CUT_TEXT) { return vnc_set_handler(vnc, vnc_read_cut, sizeof(pkt_server_cut_text)); } else { vnc_printf(vnc, "unexpected msg_type\n"); return vnc_close(vnc); } } static void vnc_send_update_request(vnc_t *vnc, int x, int y, int w, int h, int incremental) { pkt_client_update_request out_pkt = { .msg_type = CLIENT_MSG_TYPE_UPDATE_REQUEST, .incremental = incremental, .x = htons(x), .y = htons(y), .w = htons(w), .h = htons(h), }; bufferevent_write(vnc->buf_ev, &out_pkt, sizeof(out_pkt)); return vnc_set_handler(vnc, vnc_read_msg_header, sizeof(pkt_server_base_msg)); } static void vnc_read_server_name(vnc_t *vnc) { evbuffer_drain(vnc->buf_ev->input, vnc->num_bytes); return vnc_send_update_request(vnc, 0, 0, vnc->width, vnc->height, 0); } static void vnc_read_server_init(vnc_t *vnc) { pkt_server_init in_pkt; evbuffer_remove(vnc->buf_ev->input, &in_pkt, sizeof(in_pkt)); uint32_t name_len = ntohl(in_pkt.name_len); if (name_len > 512) { vnc_printf(vnc, "name too long\n"); return vnc_close(vnc); } if (in_pkt.pixelformat.bpp != 32) { vnc_printf(vnc, "invalid bpp (only 32bit supported)\n"); return vnc_close(vnc); } vnc->width = ntohs(in_pkt.width); vnc->height = ntohs(in_pkt.height); vnc->pixelformat = in_pkt.pixelformat; vnc->pixelformat.red_max = ntohs(vnc->pixelformat.red_max); vnc->pixelformat.green_max = ntohs(vnc->pixelformat.green_max); vnc->pixelformat.blue_max = ntohs(vnc->pixelformat.blue_max); if (vnc->width > 1920 || vnc->height > 1080) { vnc_printf(vnc, "screen too large\n"); return vnc_close(vnc); } glGenTextures(1, &vnc->tex); glBindTexture(GL_TEXTURE_2D, vnc->tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, vnc->width, vnc->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); vnc_printf(vnc, "got screen: %dx%d\n", vnc->width, vnc->height); return vnc_set_handler(vnc, vnc_read_server_name, name_len); } static void vnc_read_server_auth(vnc_t *vnc) { pkt_server_auth in_pkt; evbuffer_remove(vnc->buf_ev->input, &in_pkt, sizeof(in_pkt)); if (ntohl(in_pkt.security_type) != SERVER_SECURITY_NO_AUTH) { vnc_printf(vnc, "unexpected security type\n"); return vnc_close(vnc); } pkt_client_init out_pkt = { .shared = 1 }; bufferevent_write(vnc->buf_ev, &out_pkt, sizeof(out_pkt)); return vnc_set_handler(vnc, vnc_read_server_init, sizeof(pkt_server_init)); } static void vnc_read_handshake(vnc_t *vnc) { pkt_server_handshake in_pkt; evbuffer_remove(vnc->buf_ev->input, &in_pkt, sizeof(in_pkt)); if (in_pkt.R != 'R' || in_pkt.F != 'F' || in_pkt.B != 'B') { vnc_printf(vnc, "unexpected handshake packet\n"); return vnc_close(vnc); } bufferevent_write(vnc->buf_ev, LITERAL_AND_SIZE("RFB 003.003\n")); return vnc_set_handler(vnc, vnc_read_server_auth, sizeof(pkt_server_auth)); } /* Lifecycle */ int vnc_create(lua_State *L, const char *host, int port) { vnc_t *vnc = push_vnc(L); vnc->tex = 0; vnc->width = 0; vnc->height = 0; vnc->buf_ev = NULL; vnc->alive = 1; vnc->host = strdup(host); vnc->port = port; vnc_printf(vnc, "connecting...\n"); vnc->buf_ev = bufferevent_socket_new(event_base, -1, BEV_OPT_CLOSE_ON_FREE); vnc_set_handler(vnc, vnc_read_handshake, sizeof(pkt_server_handshake)); bufferevent_setcb(vnc->buf_ev, vnc_read, NULL, vnc_event, vnc); bufferevent_enable(vnc->buf_ev, EV_READ | EV_WRITE); bufferevent_socket_connect_hostname(vnc->buf_ev, dns_base, AF_UNSPEC, host, port); return 1; } static int vnc_gc(lua_State *L) { vnc_t *vnc = to_vnc(L, 1); vnc_close(vnc); free(vnc->host); return 0; } LUA_TYPE_IMPL(vnc) info-beamer-1.0~pre4/vnc.h0000644000175000017500000000025312452774240013277 0ustar nknk/* See Copyright Notice in LICENSE.txt */ #ifndef VNC_H #define VNC_H int vnc_create(lua_State *L, const char *host, int port); int vnc_register (lua_State *L); #endif