├── COPYRIGHT ├── build-docs.bat ├── build-gcc-52.bat ├── build-gcc-lfw.bat ├── build-gcc.bat ├── build-lc.bat ├── build-msvc.bat ├── build-readme.bat ├── clean.bat ├── config.ld ├── doc.css ├── event_callback.lua ├── examples ├── caption.lua ├── drives.lua ├── event.lua ├── files.lua ├── greek.txt ├── input.lua ├── message.lua ├── multiple.lua ├── pipe-server.lua ├── process-wait.lua ├── read-console.lua ├── readserial.lua ├── setenv.lua ├── slow.lua ├── start_time.lua ├── test-processes.lua ├── test-reg.lua ├── test-sleep.lua ├── test-spawn.lua ├── test-timer.lua ├── test-times.lua ├── test-uninterrupted.lua ├── test-watcher.lua ├── testshort.lua ├── testu.lua ├── thread-test.lua ├── with spaces │ └── dir.txt └── without_spaces │ └── dir.txt ├── lakefile ├── lc.lua ├── makefile ├── markdown.lua ├── processt_callback.lua ├── readme.md ├── winapi.c ├── winapi.l.c ├── wutils.c └── wutils.h /COPYRIGHT: -------------------------------------------------------------------------------- 1 | winapi License 2 | ----------- 3 | Copyright (C) 2011 Steve Donovan. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /build-docs.bat: -------------------------------------------------------------------------------- 1 | REM building documentation with ldoc 2 | REM http://github.com/stevedonovan/LDoc 3 | ldoc winapi.l.c -o api && lua markdown.lua readme.md -s doc.css -l 4 | -------------------------------------------------------------------------------- /build-gcc-52.bat: -------------------------------------------------------------------------------- 1 | REM compiling for mingw against msvcrt 2 | set LUA_DIR=D:\dev\lua\lua-5.2.0\src 3 | set CFLAGS=-c -g -DPSAPI_VERSION=1 -I"%LUA_DIR%" 4 | gcc %CFLAGS% winapi.c 5 | gcc %CFLAGS% wutils.c 6 | gcc -g -shared winapi.o wutils.o "%LUA_DIR%\lua52.dll" -lpsapi -lMpr -o winapi.dll -------------------------------------------------------------------------------- /build-gcc-lfw.bat: -------------------------------------------------------------------------------- 1 | REM building with mingw for LfW 2 | set LUA_DIR=C:\Program Files\Lua\5.1 3 | set CFLAGS=-Os -DPSAPI_VERSION=1 -I"%LUA_DIR%\include" 4 | gcc -c %CFLAGS% winapi.c 5 | gcc -c %CFLAGS% wutils.c 6 | gcc -Wl,-s -shared winapi.o wutils.o -L"%LUA_DIR%/lib" -lpsapi -lMpr -llua5.1 -lmsvcr80 -o winapi.dll -------------------------------------------------------------------------------- /build-gcc.bat: -------------------------------------------------------------------------------- 1 | REM compiling for mingw against msvcrt 2 | # set LUA_DIR=D:\dev\lua\luajit-2.0\src 3 | set LUA_INCLUDE=c:\users\steve\luadist\include 4 | set LUA_LIB=c:\users\steve\luadist\lib\liblua51.dll.a 5 | set CFLAGS=-c -O1 -DPSAPI_VERSION=1 -I"%LUA_INCLUDE%" 6 | gcc %CFLAGS% winapi.c 7 | gcc %CFLAGS% wutils.c 8 | gcc -Wl,-s -shared winapi.o wutils.o "%LUA_LIB%" -lpsapi -lMpr -o winapi.dll -------------------------------------------------------------------------------- /build-lc.bat: -------------------------------------------------------------------------------- 1 | luam -C -llc winapi.l.c > winapi.c 2 | -------------------------------------------------------------------------------- /build-msvc.bat: -------------------------------------------------------------------------------- 1 | set LUA_DIR=C:\Program Files\Lua\5.1 2 | set CFLAGS= /O1 /DPSAPI_VERSION=1 /I"%LUA_DIR%\include" 3 | cl /nologo -c %CFLAGS% winapi.c 4 | cl /nologo -c %CFLAGS% wutils.c 5 | link /nologo winapi.obj wutils.obj /EXPORT:luaopen_winapi /LIBPATH:"%LUA_DIR%\lib" msvcrt.lib kernel32.lib user32.lib psapi.lib advapi32.lib shell32.lib Mpr.lib lua5.1.lib /DLL /OUT:winapi.dll 6 | -------------------------------------------------------------------------------- /build-readme.bat: -------------------------------------------------------------------------------- 1 | REM convert the markdown to HTML 2 | lua markdown.lua readme.md -s doc.css -l 3 | copy readme.html docs\index.html 4 | copy doc.css docs\default.css 5 | del readme.html 6 | 7 | -------------------------------------------------------------------------------- /clean.bat: -------------------------------------------------------------------------------- 1 | del *.manifest *.o *.obj *.exp *.lib *.d *.spec 2 | -------------------------------------------------------------------------------- /config.ld: -------------------------------------------------------------------------------- 1 | -- ldoc configuration file 2 | file = "winapi.l.c" 3 | output = "api" 4 | title = "Winapi documentation" 5 | project = "winapi" 6 | readme = "readme.md" 7 | --one = true 8 | -- no_summary = true 9 | examples = {'examples', exclude = {'examples/slow.lua'}} 10 | description = [[ 11 | A minimal but useful binding to the Windows API. 12 | ]] 13 | 14 | --manual_url 'file:///D:/dev/lua/projects/lua-5.1.4/doc/manual.html' 15 | 16 | format = 'discount' 17 | 18 | 19 | -------------------------------------------------------------------------------- /doc.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-left: 2em; 3 | } 4 | pre { 5 | background-color: #eeeeff 6 | } 7 | a:link { font-weight:bold; color: #004080; text-decoration: none; } 8 | a:visited { font-weight:bold; color: #006699; text-decoration: none; } 9 | a:link:hover { text-decoration:underline; } 10 | -------------------------------------------------------------------------------- /event_callback.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | 3 | fprintf = require 'pl.utils'.fprintf 4 | stderr = io.stderr 5 | 6 | e = winapi.event() 7 | 8 | winapi.make_timer(500,function() 9 | fprintf(stderr,'signal!\n') 10 | e:signal() 11 | end) 12 | 13 | --[[ 14 | while true do 15 | e:wait() 16 | print 'ok' 17 | end 18 | --]] 19 | 20 | e:wait_async(function(s) 21 | fprintf (stderr,'finis %s\n',s) 22 | os.exit() 23 | end) 24 | 25 | fprintf(stderr,'sleeping\n') 26 | 27 | winapi.sleep(-1) 28 | -------------------------------------------------------------------------------- /examples/caption.lua: -------------------------------------------------------------------------------- 1 | local W = require 'winapi' 2 | local console = W.get_foreground_window() 3 | console:set_text 'e???????' 4 | W.set_clipboard 'e???????' 5 | print 'Press enter' 6 | io.read() 7 | -------------------------------------------------------------------------------- /examples/drives.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | 3 | drives = winapi.get_logical_drives() 4 | for _,drive in ipairs(drives) do 5 | local free,avail = winapi.get_disk_free_space(drive) 6 | if not free then -- call failed, avail is error 7 | free = '('..avail..')' 8 | else 9 | free = math.ceil(free/1024) -- get Mb 10 | end 11 | local rname = '' 12 | local dtype = winapi.get_drive_type(drive) 13 | if dtype == 'remote' then 14 | rname = winapi.get_disk_network_name(drive:gsub('\\$','')) 15 | end 16 | print(drive,dtype,free,rname) 17 | end 18 | 19 | --[[ output: 20 | C:\ fixed 1785 21 | D:\ fixed 49916 22 | E:\ cdrom (The device is not ready.) 23 | G:\ remote 33823 \\CARL-VFILE\SYS 24 | I:\ remote 433682 \\CARL-VFILE\GROUPS 25 | X:\ remote 12160 \\CARL-VFILE\APPS 26 | Y:\ remote 33823 \\CARL-VFILE\SYS\PUBLIC 27 | Z:\ remote 33823 \\CARL-VFILE\SYS\PUBLIC 28 | ]] 29 | -------------------------------------------------------------------------------- /examples/event.lua: -------------------------------------------------------------------------------- 1 | local W = require 'winapi' 2 | local e = W.event() 3 | local count = 1 4 | local finished 5 | 6 | W.make_timer(500,function() 7 | print 'tick' 8 | if count == 5 then 9 | print 'finished!' 10 | finished = true 11 | end 12 | e:signal() 13 | count = count + 1 14 | end) 15 | 16 | while not finished do 17 | e:wait() 18 | print 'gotcha' 19 | end 20 | -------------------------------------------------------------------------------- /examples/files.lua: -------------------------------------------------------------------------------- 1 | -- iterating over all files matching some pattern. 2 | -- (this handles Unicode file names correctly) 3 | require 'winapi' 4 | winapi.set_encoding(winapi.CP_UTF8) 5 | 6 | files,err = winapi.files ('*.txt',false) 7 | if not files then return print(err) end 8 | 9 | for f in files do 10 | print(f) 11 | end 12 | 13 | -------------------------------------------------------------------------------- /examples/greek.txt: -------------------------------------------------------------------------------- 1 | Τη γλώσσα μου έδωσαν ελληνική 2 | το σπίτι φτωχικό στις αμμουδιές του Ομήρου. 3 | Μονάχη έγνοια η γλώσσα μου στις αμμουδιές του Ομήρου. 4 | -------------------------------------------------------------------------------- /examples/input.lua: -------------------------------------------------------------------------------- 1 | -- this shows how @{Process:wait_for_input_idle} means that there's no need for 2 | -- a random wait until an application is ready to go. 3 | -- Note, if we use @{spawn_process} then the window is initially invisible, 4 | -- and needs to be shown explicitly. 5 | require 'winapi' 6 | P = winapi.spawn_process 'notepad' 7 | P:wait_for_input_idle() 8 | w = winapi.find_window_match 'Untitled' 9 | w:show() 10 | w:set_foreground() 11 | winapi.send_to_window 'hello dammit' 12 | 13 | -------------------------------------------------------------------------------- /examples/message.lua: -------------------------------------------------------------------------------- 1 | 2 | local W = require 'winapi' 3 | 4 | 5 | print(W.show_message("Message","stuff")) 6 | print(W.show_message("Message","stuff\nand nonsense","yes-no","warning")) 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/multiple.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | 3 | function printer(msec,msg) 4 | local i = 1 5 | return winapi.make_timer(500,function() 6 | print (msg,i) 7 | i = i + 1 8 | end) 9 | end 10 | 11 | printer(500,'bob') 12 | printer(500,'june') 13 | printer(500,'alice') 14 | printer(500,'jim') 15 | 16 | winapi.sleep(-1) 17 | -------------------------------------------------------------------------------- /examples/pipe-server.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | 3 | --[[ -- blocking version 4 | winapi.make_pipe_server(function(f) 5 | local res = f:read() 6 | f:write(res:upper()) 7 | end) 8 | -- ]] 9 | 10 | --[[ 'node.js' style 11 | winapi.make_pipe_server(function(f) 12 | f:read_async(function(res) 13 | f:write(res:upper()) 14 | end) 15 | end) 16 | --]] 17 | 18 | local wrap, yield, resume = coroutine.wrap, coroutine.yield, coroutine.resume 19 | 20 | --[[ 21 | winapi.make_pipe_server(function(f) 22 | local fun = function(f) 23 | while true do 24 | local res = f:read() 25 | if res == 'close' then break end 26 | f:write(res:upper()) 27 | end 28 | end 29 | local co = coroutine.create(fun) 30 | resume(co,fwrap(f,co)) 31 | end) 32 | ]] 33 | 34 | --~ f:read_async(function(txt) 35 | --~ resume(co,txt) 36 | --~ end) 37 | 38 | function fwrap (f,co) 39 | local obj = {} 40 | local started 41 | function obj:read () 42 | if not started then 43 | f:read_async(co) 44 | started = true 45 | end 46 | return yield() 47 | end 48 | function obj:write (s) 49 | return f:write(s) 50 | end 51 | return obj 52 | end 53 | 54 | function winapi.make_pipe_server_async(fun) 55 | winapi.make_pipe_server(function(f) 56 | local co = coroutine.wrap(fun) 57 | co(fwrap(f,co)) 58 | end) 59 | end 60 | 61 | winapi.make_pipe_server_async(function(f) 62 | while true do 63 | local res = f:read() 64 | if res == 'close' then break end 65 | f:write(res:upper()) 66 | end 67 | print 'finis' 68 | end) 69 | 70 | 71 | winapi.sleep(-1) 72 | 73 | -------------------------------------------------------------------------------- /examples/process-wait.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | t = os.clock() 3 | n = tonumber(arg[1] or 2) 4 | local P = {} 5 | for i = 1,n do 6 | P[i],f = winapi.spawn_process ('lua slow.lua '..i) 7 | f:read_async(print) 8 | end 9 | winapi.wait_for_processes(P,true) 10 | print(os.clock() - t) 11 | 12 | -------------------------------------------------------------------------------- /examples/read-console.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | 3 | f = winapi.get_console() 4 | f:read_async(function(line) 5 | f:write(line) 6 | if line:match '^quit' then 7 | os.exit() 8 | end 9 | end) 10 | 11 | winapi.sleep(-1) 12 | -------------------------------------------------------------------------------- /examples/readserial.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | local f,e = winapi.open_serial 'COM4 baud=19' 3 | if not f then return print('error',e) end 4 | local sub = f:read() 5 | local line = {} 6 | local append = table.insert 7 | while sub ~= '+' do 8 | f:write(sub) 9 | append(line,sub) 10 | if sub == '\r' then 11 | f:write '\n' 12 | print('gotcha',table.concat(line)) 13 | line = {} 14 | end 15 | --print(sub,sub:byte(1)) 16 | sub = f:read() 17 | end 18 | f:close() 19 | -------------------------------------------------------------------------------- /examples/setenv.lua: -------------------------------------------------------------------------------- 1 | -- You will only get nice output from this script (like other unicode examples) 2 | -- if executed in a properly multilingual environment like SciTE. 3 | -- To get UTF-8 support in SciTE, edit your global properties like so: 4 | -- # Internationalisation 5 | -- # Japanese input code page 932 and ShiftJIS character set 128 6 | -- #code.page=932 7 | -- #character.set=128 8 | -- # Unicode 9 | -- code.page=65001 # uncomment out this line 10 | -- #code.page=0 # and comment out this line 11 | -- 12 | -- And restart SciTE. 13 | 14 | require 'winapi' 15 | 16 | winapi.setenv('greek','ελληνική') 17 | 18 | print(os.getenv 'greek') -- this will still be nil 19 | 20 | -- but child processes can see this variable ... 21 | os.execute [[lua -e "print(os.getenv('greek'))"]] 22 | -------------------------------------------------------------------------------- /examples/slow.lua: -------------------------------------------------------------------------------- 1 | print(arg[1]) 2 | for i = 1,1e8 do end 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/start_time.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | p = winapi.get_current_process() 3 | print(os.date('%c',os.time(p:get_start_time()))) 4 | 5 | -------------------------------------------------------------------------------- /examples/test-processes.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | pids = winapi.get_processes() 3 | 4 | for _,pid in ipairs(pids) do 5 | local P = winapi.process_from_id(pid) 6 | local name = P:get_process_name(true) 7 | if name then print(pid,name) end 8 | P:close() 9 | end 10 | -------------------------------------------------------------------------------- /examples/test-reg.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | k,err = winapi.open_reg_key [[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\DateTime\Servers]] 3 | if not k then return print('bad key',err) end 4 | 5 | print(k:get_value("1")) 6 | k:close() 7 | 8 | k,err = winapi.open_reg_key [[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion]] 9 | 10 | t = k:get_keys() 11 | --for _,k in ipairs(t) do print(_,k) end 12 | k:close() 13 | 14 | k,err = winapi.open_reg_key ([[HKEY_CURRENT_USER\Environment]],true) 15 | path = k:get_value("PATH") 16 | print(path) 17 | print(k:get_value("TEMP")) 18 | if #arg > 0 then 19 | local type = winapi.REG_SZ 20 | if arg[3] then 21 | type = winapi[arg[3]] 22 | end 23 | k:set_value(arg[1],arg[2],type) 24 | print(k:get_value(arg[1],type)) 25 | end 26 | k:close() 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/test-sleep.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | 3 | l = 1 4 | winapi.make_timer(400,function() 5 | print 'bonzo' 6 | l = l + 1 7 | if l > 10 then os.exit() end 8 | end) 9 | 10 | k = 1 11 | winapi.make_timer(300,function() 12 | print 'alice' 13 | k = k +1 14 | if k > 5 then return true end 15 | end) 16 | 17 | winapi.sleep(-1) 18 | -------------------------------------------------------------------------------- /examples/test-spawn.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | P,W = winapi.spawn_process 'lua test-timer.lua' 3 | stuff = W:read() 4 | k = 1 5 | while stuff do 6 | io.write(stuff); 7 | stuff = W:read() 8 | k = k + 1 9 | if k > 15 then P:kill() end 10 | end 11 | -------------------------------------------------------------------------------- /examples/test-timer.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | io.stdout:setvbuf 'no' 3 | local t1,t2 4 | t1 = winapi.make_timer(500,function() 5 | print 'gotcha' 6 | end) 7 | 8 | local k = 1 9 | t2 = winapi.make_timer(400,function() 10 | k = k + 1 11 | print(k) 12 | if k > 5 then 13 | print 'killing' 14 | t1:kill() -- kill the first timer 15 | t2 = nil 16 | return true -- and we will end now 17 | end 18 | end) 19 | 20 | winapi.make_timer(1000,function() 21 | print 'doo' 22 | if not t2 then os.exit(0) end -- we all finish 23 | end) 24 | 25 | -- sleep forever... 26 | winapi.sleep(-1) 27 | -------------------------------------------------------------------------------- /examples/test-times.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | t = os.clock() 3 | winapi.sleep(200) 4 | for i = 1,1e8 do end 5 | P = winapi.get_current_process() 6 | print(P:get_working_size()) 7 | print(P:get_run_times()) 8 | print((os.clock() - t)*1000) 9 | -------------------------------------------------------------------------------- /examples/test-uninterrupted.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | 3 | l = 1 4 | winapi.make_timer(400,function() 5 | print 'bonzo' 6 | l = l + 1 7 | --if l > 10 then os.exit() end 8 | end) 9 | 10 | winapi.make_timer(300,function() 11 | print 'woo' 12 | end) 13 | 14 | function protected(val) 15 | print(val) 16 | end 17 | 18 | -- --[[ 19 | while true do 20 | winapi.sleep(200,true) 21 | print 'gotcha' 22 | end 23 | --]] 24 | 25 | --[[ 26 | while true do 27 | winapi.sleep(200) 28 | winapi.locked_eval(protected,'hello') 29 | --protected 'hello' --> this will cause nonsense 30 | end 31 | --]] 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/test-watcher.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | io.stdout:setvbuf 'no' 3 | local dir = '.' 4 | local dir2 = dir .. '\\without_spaces' 5 | local LAST_WRITE,FILE_NAME = 6 | winapi.FILE_NOTIFY_CHANGE_LAST_WRITE, 7 | winapi.FILE_NOTIFY_CHANGE_FILE_NAME 8 | 9 | w1,err = winapi.watch_for_file_changes(dir,LAST_WRITE+FILE_NAME,false,print) 10 | if not w1 then return print(err) end 11 | w2,err = winapi.watch_for_file_changes(dir2,LAST_WRITE+FILE_NAME,false,print) 12 | if not w2 then return print(err) end 13 | 14 | -- ok, our watchers are in the background 15 | winapi.sleep(200) 16 | 17 | function writefile (name,text) 18 | local f = io.open(name,'w') 19 | f:write(text) 20 | f:close() 21 | end 22 | 23 | writefile('without_spaces/out.txt','hello') 24 | 25 | os.execute 'cd without_spaces && del frodo.txt && ren out.txt frodo.txt' 26 | 27 | writefile ('mobo.txt','freaky') 28 | 29 | winapi.sleep(200) 30 | 31 | -- can stop a watcher by killing its thread 32 | w1:kill() 33 | 34 | writefile ('doof.txt','freaky') 35 | 36 | winapi.sleep(200) 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/testshort.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | local U = winapi.uexpand 3 | local encode = winapi.encode 4 | local UTF8 = winapi.CP_UTF8 5 | 6 | winapi.set_encoding(UTF8) 7 | 8 | local short = winapi.short_path 9 | local name = short 'ελληνική.txt' 10 | os.remove(name) 11 | print(name) 12 | local f,err = io.open(name,'w') 13 | if not f then return print(err) end 14 | f:write 'a new file\n' 15 | f:close() 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/testu.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | local U = winapi.utf8_expand 3 | local UTF8 = winapi.CP_UTF8 4 | 5 | winapi.set_encoding(UTF8) 6 | 7 | txt = U '#03BB + #03BC + C' 8 | print(txt) 9 | 10 | print(U '#03BD') 11 | -------------------------------------------------------------------------------- /examples/thread-test.lua: -------------------------------------------------------------------------------- 1 | local W = require 'winapi' 2 | local r,w = W.pipe() 3 | local m = W.mutex() 4 | 5 | function lprint(...) 6 | m:lock() 7 | print(...) 8 | m:release() 9 | end 10 | 11 | function long(name) 12 | lprint('hello',name) 13 | for i = 1,2 do 14 | m:lock() 15 | w:write(name..i) 16 | m:release() 17 | for i = 1,1e8 do end 18 | end 19 | end 20 | 21 | r:read_async(function(s) 22 | lprint(s) 23 | end) 24 | 25 | T = {} 26 | T[1] = W.thread(long,'john') 27 | T[2] = W.thread(long,'jane') 28 | T[3] = W.thread(long,'june') 29 | 30 | W.wait_for_processes(T,true) 31 | print 'finish' 32 | 33 | -------------------------------------------------------------------------------- /examples/with spaces/dir.txt: -------------------------------------------------------------------------------- 1 | D:\dev\lua\winapi\examples\dir.txt 2 | D:\dev\lua\winapi\examples\greek.txt 3 | D:\dev\lua\winapi\examples\with spaces\dir.txt 4 | -------------------------------------------------------------------------------- /examples/without_spaces/dir.txt: -------------------------------------------------------------------------------- 1 | D:\dev\lua\winapi\examples\dir.txt 2 | D:\dev\lua\winapi\examples\greek.txt 3 | D:\dev\lua\winapi\examples\with spaces\dir.txt 4 | D:\dev\lua\winapi\examples\without_spaces\dir.txt 5 | -------------------------------------------------------------------------------- /lakefile: -------------------------------------------------------------------------------- 1 | c.shared{'examples/winapi',src='winapi wutils',needs='lua', 2 | defines='PSAPI_VERSION=1', 3 | libs = 'kernel32 user32 psapi advapi32 shell32 Mpr', 4 | dynamic = true, 5 | strip = true 6 | } 7 | 8 | target('winapi.c',{'winapi.l.c'},'luam -C -llc winapi.l.c 1>winapi.c') 9 | 10 | -------------------------------------------------------------------------------- /lc.lua: -------------------------------------------------------------------------------- 1 | -- Simplifying writing C extensions for Lua 2 | -- Adds new module and class constructs; 3 | -- see class1.lc and str.lc for examples. 4 | local M = require 'macro' 5 | 6 | function dollar_subst(s,tbl) 7 | return (s:gsub('%$%((%a+)%)',tbl)) 8 | end 9 | 10 | -- reuse some machinery from the C-skin experiments 11 | local push,pop = table.insert,table.remove 12 | local bstack,btop = {},{} 13 | 14 | local function push_brace_stack (newv) 15 | newv = newv or {} 16 | newv.lev = 0 17 | push(bstack,btop) 18 | btop = newv 19 | end 20 | 21 | M.define('{',function() 22 | if btop.lev then 23 | btop.lev = btop.lev + 1 24 | end 25 | return nil,true --> pass-through macro 26 | end) 27 | 28 | M.define('}',function(get,put) 29 | if not btop.lev then 30 | return nil,true 31 | elseif btop.lev == 0 then 32 | local res 33 | if btop.handler then res = btop.handler(get,put) end 34 | if not res then res = put:space() '}' end 35 | btop = pop(bstack) 36 | return res 37 | else 38 | btop.lev = btop.lev - 1 39 | return nil,true --> pass-through macro 40 | end 41 | end) 42 | 43 | --------- actual implementation begins ------- 44 | 45 | local append = table.insert 46 | local module 47 | 48 | local function register_functions (names,cnames) 49 | local out = {} 50 | for i = 1,#names do 51 | append(out,(' {"%s",l_%s},'):format(names[i],cnames[i])) 52 | end 53 | return table.concat(out,'\n') 54 | end 55 | 56 | local function finalizers (names) 57 | local out = {} 58 | for i = 1,#names do 59 | append(out,names[i].."(L);") 60 | end 61 | return table.concat(out,'\n') 62 | end 63 | 64 | local typedefs 65 | 66 | local preamble = [[ 67 | #include 68 | #include 69 | #include 70 | #ifdef WIN32 71 | #define EXPORT __declspec(dllexport) 72 | #else 73 | #define EXPORT 74 | #endif 75 | #if LUA_VERSION_NUM > 501 76 | #define lua_objlen lua_rawlen 77 | #endif 78 | ]] 79 | 80 | local finis = [[ 81 | static const luaL_Reg $(cname)_funs[] = { 82 | $(funs) 83 | {NULL,NULL} 84 | }; 85 | 86 | EXPORT int luaopen_$(cname) (lua_State *L) { 87 | #if LUA_VERSION_NUM > 501 88 | lua_newtable(L); 89 | luaL_setfuncs (L,$(cname)_funs,0); 90 | lua_pushvalue(L,-1); 91 | lua_setglobal(L,"$(cname)"); 92 | #else 93 | luaL_register(L,"$(cname)",$(cname)_funs); 94 | #endif 95 | $(finalizers) 96 | return 1; 97 | } 98 | ]] 99 | 100 | M.define('module',function(get) 101 | local name = get:string() 102 | local cname = name:gsub('%.','_') 103 | get:expecting '{' 104 | local out = preamble .. typedefs 105 | push_brace_stack{ 106 | name = name, cname = cname, 107 | names = {}, cnames = {}, finalizers = {}, 108 | handler = function() 109 | local out = {} 110 | local funs = register_functions(btop.names,btop.cnames) 111 | local final = finalizers(btop.finalizers) 112 | append(out,dollar_subst(finis, { 113 | cname = cname, 114 | name = name, 115 | funs = funs, 116 | finalizers = final 117 | })) 118 | return table.concat(out,'\n') 119 | end } 120 | module = btop 121 | return out 122 | end) 123 | 124 | 125 | M.define('def',function(get) 126 | local fname = get:name() 127 | local cname = (btop.ns and btop.ns..'_' or '')..fname 128 | append(btop.names,fname) 129 | append(btop.cnames,cname) 130 | get:expecting '(' 131 | local args = get:list():strip_spaces() 132 | get:expecting '{' 133 | local t,space = get() 134 | indent = space:gsub('^%s*[\n\r]',''):gsub('%s$','') 135 | local out = {"static int l_"..cname.."(lua_State *L) {"} 136 | if btop.massage_arg then 137 | btop.massage_arg(args) 138 | end 139 | for i,arg in ipairs(args) do 140 | local mac = arg[1][2]..'_init' 141 | if arg[3] and arg[3][1] == '=' then 142 | mac = mac .. 'o' 143 | i = i .. ',' .. arg[4][2] 144 | end 145 | if not arg[2] then M.error("parameter must be TYPE NAME [= VALUE]") end 146 | append(out,indent..mac..'('..arg[2][2]..','..i..');') 147 | end 148 | --append(out,space) 149 | return table.concat(out,'\n')..space 150 | end) 151 | 152 | M.define('constants',function(get,put) 153 | get:expecting '{' 154 | local consts = get:list '}' :strip_spaces() 155 | --for k,v in pairs(btop) do io.stderr:write(k,'=',tostring(v),'\n') end 156 | -- os.exit() 157 | local fname = 'set_'..btop.cname..'_constants' 158 | local out = { 'static void '..fname..'(lua_State *L) {'} 159 | if not btop.finalizers then M.error("not inside a module") end 160 | append(btop.finalizers,fname) 161 | for _,c in ipairs(consts) do 162 | local type,value,name 163 | if #c == 1 then -- a simple int constant: CONST 164 | name = c:pick(1) 165 | type = 'Int' 166 | value = name 167 | else -- Type CONST [ = VALUE ] 168 | type = c:pick(1) 169 | name = c:pick(2) 170 | if #c == 2 then 171 | value = name 172 | else 173 | value = c:pick(4) 174 | end 175 | end 176 | append(out,('%s_set("%s",%s);'):format(type,name,value )) 177 | end 178 | append(out,'}') 179 | return table.concat(out,'\n') 180 | end) 181 | 182 | M.define('assign',function(get) 183 | get:expecting '{' 184 | local asses = get:list '}' :strip_spaces() 185 | local out = {} 186 | for _,c in ipairs(asses) do 187 | append(out,('%s_set("%s",%s);\n'):format(c:pick(1),c:pick(2),c:pick(4)) ) 188 | end 189 | return table.concat(out,'\n') 190 | end) 191 | 192 | local load_lua = [[ 193 | static void load_lua_code (lua_State *L) { 194 | luaL_dostring(L,lua_code_block); 195 | } 196 | ]] 197 | 198 | M.define('lua',function(get) 199 | get:expecting '{' 200 | local block = tostring(get:upto '}') 201 | local code_name = 'lua_code_block' 202 | local out = {'static const char *'.. code_name .. ' = ""\\'} 203 | for line in block:gmatch('([^\r\n]+)') do 204 | line = line:gsub('\\','\\\\'):gsub('"','\\"') 205 | append(out,' "'..line..'\\n"\\') 206 | end 207 | append(out,';') 208 | append(out,load_lua) 209 | out = table.concat(out,'\n') 210 | append(module.finalizers,'load_lua_code') 211 | return out 212 | end) 213 | 214 | M.define('initial',function(get) 215 | local name = get:name() 216 | append(module.finalizers,name) 217 | get:expecting '{' 218 | local body = tostring(get:upto '}') 219 | return ('int %s(lua_State *L) {\n%s}\n'):format(name,body) 220 | end) 221 | 222 | typedefs = [[ 223 | typedef const char *Str; 224 | typedef const char *StrNil; 225 | typedef int Int; 226 | typedef double Number; 227 | typedef int Boolean; 228 | ]] 229 | 230 | 231 | M.define 'Str_init(var,idx) const char *var = luaL_checklstring(L,idx,NULL)' 232 | M.define 'Str_inito(var,idx,val) const char *var = luaL_optlstring(L,idx,val,NULL)' 233 | M.define 'Str_set(name,value) lua_pushstring(L,value); lua_setfield(L,-2,name)' 234 | M.define 'Str_get(var,name) lua_getfield(L,-1,name); var=lua_tostring(L,-1); lua_pop(L,1)' 235 | M.define 'Str_geti(var,idx) lua_rawgeti(L,-1,idx); var=lua_tostring(L,-1); lua_pop(L,1)' 236 | 237 | M.define 'StrNil_init(var,idx) const char *var = lua_tostring(L,idx)' 238 | 239 | M.define 'Int_init(var,idx) int var = luaL_checkinteger(L,idx)' 240 | M.define 'Int_inito(var,idx,val) int var = luaL_optinteger(L,idx,val)' 241 | M.define 'Int_set(name,value) lua_pushinteger(L,value); lua_setfield(L,-2,name)' 242 | M.define 'Int_get(var,name) lua_getfield(L,-1,name); var=lua_tointeger(L,-1); lua_pop(L,1)' 243 | M.define 'Int_geti(var,idx) lua_rawgeti(L,-1,idx); var=lua_tointeger(L,-1); lua_pop(L,1)' 244 | 245 | M.define 'Number_init(var,idx) double var = luaL_checknumber(L,idx)' 246 | M.define 'Number_inito(var,idx,val) double var = luaL_optnumber(L,idx,val)' 247 | M.define 'NUmber_set(name,value) lua_pushnumber(L,value); lua_setfield(L,-2,name)' 248 | M.define 'Number_get(var,name) lua_getfield(L,-1,name); var=lua_tonumber(L,-1); lua_pop(L,1)' 249 | M.define 'Number_geti(var,idx) lua_rawgeti(L,-1,idx); var=lua_tonumber(L,-1); lua_pop(L,1)' 250 | 251 | M.define 'Boolean_init(var,idx) int var = lua_toboolean(L,idx)' 252 | M.define 'Boolean_set(name,value) lua_pushboolean(L,value); lua_setfield(L,-2,name)' 253 | M.define 'Boolean_get(var,name) lua_getfield(L,-1,name); var=lua_toboolean(L,-1); lua_pop(L,1)' 254 | M.define 'Boolean_geti(var,idx) lua_rawgeti(L,-1,idx); var=lua_toboolean(L,-1); lua_pop(L,1)' 255 | 256 | M.define 'Value_init(var,idx) int var = idx' 257 | 258 | M.define('lua_tests',function(get) 259 | get:expecting '{' 260 | local body = get:upto '}' 261 | local f = io.open(M.filename..'.lua','w') 262 | f:write(tostring(body)) 263 | f:close() 264 | end) 265 | 266 | ------ class support ---------------------- 267 | 268 | local klass_ctor = "static void $(klass)_ctor(lua_State *L, $(klass) *this, $(fargs))" 269 | 270 | local begin_klass = [[ 271 | 272 | typedef struct { 273 | $(fields) 274 | } $(klass); 275 | 276 | define_ $(klass)_init(var,idx) $(klass) *var = $(klass)_arg(L,idx) 277 | 278 | #define $(klass)_MT "$(klass)" 279 | 280 | $(klass) * $(klass)_arg(lua_State *L,int idx) { 281 | $(klass) *this = ($(klass) *)luaL_checkudata(L,idx,$(klass)_MT); 282 | luaL_argcheck(L, this != NULL, idx, "$(klass) expected"); 283 | return this; 284 | } 285 | 286 | $(ctor); 287 | 288 | static int push_new_$(klass)(lua_State *L,$(fargs)) { 289 | $(klass) *this = ($(klass) *)lua_newuserdata(L,sizeof($(klass))); 290 | luaL_getmetatable(L,$(klass)_MT); 291 | lua_setmetatable(L,-2); 292 | $(klass)_ctor(L,this,$(aargs)); 293 | return 1; 294 | } 295 | 296 | ]] 297 | 298 | local end_klass = [[ 299 | 300 | static const struct luaL_Reg $(klass)_methods [] = { 301 | $(methods) 302 | {NULL, NULL} /* sentinel */ 303 | }; 304 | 305 | static void $(klass)_register (lua_State *L) { 306 | luaL_newmetatable(L,$(klass)_MT); 307 | #if LUA_VERSION_NUM > 501 308 | luaL_setfuncs(L,$(klass)_methods,0); 309 | #else 310 | luaL_register(L,NULL,$(klass)_methods); 311 | #endif 312 | lua_pushvalue(L,-1); 313 | lua_setfield(L,-2,"__index"); 314 | lua_pop(L,1); 315 | } 316 | ]] 317 | 318 | M.define('class',function(get) 319 | local name = get:name() 320 | get:expecting '{' 321 | local fields = get:upto (function(t,v) 322 | return t == 'iden' and v == 'constructor' 323 | end) 324 | fields = tostring(fields):gsub('%s+$','\n') 325 | get:expecting '(' 326 | local out = {} 327 | local args = get:list() 328 | local f_args = args:strip_spaces() 329 | local a_args = f_args:pick(2) 330 | f_args = table.concat(args:__tostring(),',') 331 | a_args = table.concat(a_args,',') 332 | local subst = {klass=name,fields=fields,fargs=f_args,aargs=a_args } 333 | local proto = dollar_subst(klass_ctor,subst) 334 | subst.ctor = proto 335 | append(out,dollar_subst(begin_klass,subst)) 336 | append(out,proto) 337 | local pp = {{'iden',name},{'iden','this'}} 338 | push_brace_stack{ 339 | names = {}, cnames = {}, ns = name, cname = name, 340 | massage_arg = function(args) 341 | table.insert(args,1,pp) 342 | end, 343 | handler = function(get,put) 344 | append(module.finalizers,name.."_register") 345 | local methods = register_functions(btop.names,btop.cnames) 346 | return dollar_subst(end_klass,{methods=methods,klass=name,fargs=f_args,aargs=a_args}) 347 | end 348 | } 349 | return table.concat(out,'\n') 350 | end) 351 | 352 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | lake 3 | build: 4 | build-lc 5 | -------------------------------------------------------------------------------- /markdown.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | --[[ 4 | # markdown.lua -- version 0.32 5 | 6 | 7 | 8 | **Author:** Niklas Frykholm, 9 | **Date:** 31 May 2008 10 | 11 | This is an implementation of the popular text markup language Markdown in pure Lua. 12 | Markdown can convert documents written in a simple and easy to read text format 13 | to well-formatted HTML. For a more thourough description of Markdown and the Markdown 14 | syntax, see . 15 | 16 | The original Markdown source is written in Perl and makes heavy use of advanced 17 | regular expression techniques (such as negative look-ahead, etc) which are not available 18 | in Lua's simple regex engine. Therefore this Lua port has been rewritten from the ground 19 | up. It is probably not completely bug free. If you notice any bugs, please report them to 20 | me. A unit test that exposes the error is helpful. 21 | 22 | ## Usage 23 | 24 | require "markdown" 25 | markdown(source) 26 | 27 | ``markdown.lua`` exposes a single global function named ``markdown(s)`` which applies the 28 | Markdown transformation to the specified string. 29 | 30 | ``markdown.lua`` can also be used directly from the command line: 31 | 32 | lua markdown.lua test.md 33 | 34 | Creates a file ``test.html`` with the converted content of ``test.md``. Run: 35 | 36 | lua markdown.lua -h 37 | 38 | For a description of the command-line options. 39 | 40 | ``markdown.lua`` uses the same license as Lua, the MIT license. 41 | 42 | ## License 43 | 44 | Copyright © 2008 Niklas Frykholm. 45 | 46 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 47 | software and associated documentation files (the "Software"), to deal in the Software 48 | without restriction, including without limitation the rights to use, copy, modify, merge, 49 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 50 | to whom the Software is furnished to do so, subject to the following conditions: 51 | 52 | The above copyright notice and this permission notice shall be included in all copies 53 | or substantial portions of the Software. 54 | 55 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 56 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 57 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 58 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 59 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 60 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 61 | THE SOFTWARE. 62 | 63 | ## Version history 64 | 65 | - **0.32** -- 31 May 2008 66 | - Fix for links containing brackets 67 | - **0.31** -- 1 Mar 2008 68 | - Fix for link definitions followed by spaces 69 | - **0.30** -- 25 Feb 2008 70 | - Consistent behavior with Markdown when the same link reference is reused 71 | - **0.29** -- 24 Feb 2008 72 | - Fix for
 blocks with spaces in them
  73 | -	**0.28** -- 18 Feb 2008
  74 | 	-	Fix for link encoding
  75 | -	**0.27** -- 14 Feb 2008
  76 | 	-	Fix for link database links with ()
  77 | -	**0.26** -- 06 Feb 2008
  78 | 	-	Fix for nested italic and bold markers
  79 | -	**0.25** -- 24 Jan 2008
  80 | 	-	Fix for encoding of naked <
  81 | -	**0.24** -- 21 Jan 2008
  82 | 	-	Fix for link behavior.
  83 | -	**0.23** -- 10 Jan 2008
  84 | 	-	Fix for a regression bug in longer expressions in italic or bold.
  85 | -	**0.22** -- 27 Dec 2007
  86 | 	-	Fix for crash when processing blocks with a percent sign in them.
  87 | -	**0.21** -- 27 Dec 2007
  88 | 	- 	Fix for combined strong and emphasis tags
  89 | -	**0.20** -- 13 Oct 2007
  90 | 	-	Fix for < as well in image titles, now matches Dingus behavior
  91 | -	**0.19** -- 28 Sep 2007
  92 | 	-	Fix for quotation marks " and ampersands & in link and image titles.
  93 | -	**0.18** -- 28 Jul 2007
  94 | 	-	Does not crash on unmatched tags (behaves like standard markdown)
  95 | -	**0.17** -- 12 Apr 2007
  96 | 	-	Fix for links with %20 in them.
  97 | -	**0.16** -- 12 Apr 2007
  98 | 	-	Do not require arg global to exist.
  99 | -	**0.15** -- 28 Aug 2006
 100 | 	-	Better handling of links with underscores in them.
 101 | -	**0.14** -- 22 Aug 2006
 102 | 	-	Bug for *`foo()`*
 103 | -	**0.13** -- 12 Aug 2006
 104 | 	-	Added -l option for including stylesheet inline in document.
 105 | 	-	Fixed bug in -s flag.
 106 | 	-	Fixed emphasis bug.
 107 | -	**0.12** -- 15 May 2006
 108 | 	-	Fixed several bugs to comply with MarkdownTest 1.0 
 109 | -	**0.11** -- 12 May 2006
 110 | 	-	Fixed bug for escaping `*` and `_` inside code spans.
 111 | 	-	Added license terms.
 112 | 	-	Changed join() to table.concat().
 113 | -	**0.10** -- 3 May 2006
 114 | 	-	Initial public release.
 115 | 
 116 | // Niklas
 117 | ]]
 118 | 
 119 | 
 120 | -- Set up a table for holding local functions to avoid polluting the global namespace
 121 | local M = {}
 122 | local MT = {__index = _G}
 123 | setmetatable(M, MT)
 124 | setfenv(1, M)
 125 | 
 126 | ----------------------------------------------------------------------
 127 | -- Utility functions
 128 | ----------------------------------------------------------------------
 129 | 
 130 | -- Locks table t from changes, writes an error if someone attempts to change the table.
 131 | -- This is useful for detecting variables that have "accidently" been made global. Something
 132 | -- I tend to do all too much.
 133 | function lock(t)
 134 | 	function lock_new_index(t, k, v)
 135 | 		error("module has been locked -- " .. k .. " must be declared local", 2)
 136 | 	end
 137 | 
 138 | 	local mt = {__newindex = lock_new_index}
 139 | 	if getmetatable(t) then mt.__index = getmetatable(t).__index end
 140 | 	setmetatable(t, mt)
 141 | end
 142 | 
 143 | -- Returns the result of mapping the values in table t through the function f
 144 | function map(t, f)
 145 | 	local out = {}
 146 | 	for k,v in pairs(t) do out[k] = f(v,k) end
 147 | 	return out
 148 | end
 149 | 
 150 | -- The identity function, useful as a placeholder.
 151 | function identity(text) return text end
 152 | 
 153 | -- Functional style if statement. (NOTE: no short circuit evaluation)
 154 | function iff(t, a, b) if t then return a else return b end end
 155 | 
 156 | -- Splits the text into an array of separate lines.
 157 | function split(text, sep)
 158 | 	sep = sep or "\n"
 159 | 	local lines = {}
 160 | 	local pos = 1
 161 | 	while true do
 162 | 		local b,e = text:find(sep, pos)
 163 | 		if not b then table.insert(lines, text:sub(pos)) break end
 164 | 		table.insert(lines, text:sub(pos, b-1))
 165 | 		pos = e + 1
 166 | 	end
 167 | 	return lines
 168 | end
 169 | 
 170 | -- Converts tabs to spaces
 171 | function detab(text)
 172 | 	local tab_width = 4
 173 | 	local function rep(match)
 174 | 		local spaces = -match:len()
 175 | 		while spaces<1 do spaces = spaces + tab_width end
 176 | 		return match .. string.rep(" ", spaces)
 177 | 	end
 178 | 	text = text:gsub("([^\n]-)\t", rep)
 179 | 	return text
 180 | end
 181 | 
 182 | -- Applies string.find for every pattern in the list and returns the first match
 183 | function find_first(s, patterns, index)
 184 | 	local res = {}
 185 | 	for _,p in ipairs(patterns) do
 186 | 		local match = {s:find(p, index)}
 187 | 		if #match>0 and (#res==0 or match[1] < res[1]) then res = match end
 188 | 	end
 189 | 	return unpack(res)
 190 | end
 191 | 
 192 | -- If a replacement array is specified, the range [start, stop] in the array is replaced
 193 | -- with the replacement array and the resulting array is returned. Without a replacement
 194 | -- array the section of the array between start and stop is returned.
 195 | function splice(array, start, stop, replacement)
 196 | 	if replacement then
 197 | 		local n = stop - start + 1
 198 | 		while n > 0 do
 199 | 			table.remove(array, start)
 200 | 			n = n - 1
 201 | 		end
 202 | 		for i,v in ipairs(replacement) do
 203 | 			table.insert(array, start, v)
 204 | 		end
 205 | 		return array
 206 | 	else
 207 | 		local res = {}
 208 | 		for i = start,stop do
 209 | 			table.insert(res, array[i])
 210 | 		end
 211 | 		return res
 212 | 	end
 213 | end
 214 | 
 215 | -- Outdents the text one step.
 216 | function outdent(text)
 217 | 	text = "\n" .. text
 218 | 	text = text:gsub("\n  ? ? ?", "\n")
 219 | 	text = text:sub(2)
 220 | 	return text
 221 | end
 222 | 
 223 | -- Indents the text one step.
 224 | function indent(text)
 225 | 	text = text:gsub("\n", "\n    ")
 226 | 	return text
 227 | end
 228 | 
 229 | -- Does a simple tokenization of html data. Returns the data as a list of tokens. 
 230 | -- Each token is a table with a type field (which is either "tag" or "text") and
 231 | -- a text field (which contains the original token data).
 232 | function tokenize_html(html)
 233 | 	local tokens = {}
 234 | 	local pos = 1
 235 | 	while true do
 236 | 		local start = find_first(html, {"", start)
 246 | 		elseif html:match("^<%?", start) then
 247 | 			_,stop = html:find("?>", start)
 248 | 		else
 249 | 			_,stop = html:find("%b<>", start)
 250 | 		end
 251 | 		if not stop then
 252 | 			-- error("Could not match html tag " .. html:sub(start,start+30)) 
 253 | 		 	table.insert(tokens, {type="text", text=html:sub(start, start)})
 254 | 			pos = start + 1
 255 | 		else
 256 | 			table.insert(tokens, {type="tag", text=html:sub(start, stop)})
 257 | 			pos = stop + 1
 258 | 		end
 259 | 	end
 260 | 	return tokens
 261 | end
 262 | 
 263 | ----------------------------------------------------------------------
 264 | -- Hash
 265 | ----------------------------------------------------------------------
 266 | 
 267 | -- This is used to "hash" data into alphanumeric strings that are unique
 268 | -- in the document. (Note that this is not cryptographic hash, the hash
 269 | -- function is not one-way.) The hash procedure is used to protect parts
 270 | -- of the document from further processing.
 271 | 
 272 | local HASH = {
 273 | 	-- Has the hash been inited.
 274 | 	inited = false,
 275 | 	
 276 | 	-- The unique string prepended to all hash values. This is to ensure
 277 | 	-- that hash values do not accidently coincide with an actual existing
 278 | 	-- string in the document.
 279 | 	identifier = "",
 280 | 	
 281 | 	-- Counter that counts up for each new hash instance.
 282 | 	counter = 0,
 283 | 	
 284 | 	-- Hash table.
 285 | 	table = {}
 286 | }
 287 | 
 288 | -- Inits hashing. Creates a hash_identifier that doesn't occur anywhere
 289 | -- in the text.
 290 | function init_hash(text)
 291 | 	HASH.inited = true
 292 | 	HASH.identifier = ""
 293 | 	HASH.counter = 0
 294 | 	HASH.table = {}
 295 | 	
 296 | 	local s = "HASH"
 297 | 	local counter = 0
 298 | 	local id
 299 | 	while true do
 300 | 		id  = s .. counter
 301 | 		if not text:find(id, 1, true) then break end
 302 | 		counter = counter + 1
 303 | 	end
 304 | 	HASH.identifier = id
 305 | end
 306 | 
 307 | -- Returns the hashed value for s.
 308 | function hash(s)
 309 | 	assert(HASH.inited)
 310 | 	if not HASH.table[s] then
 311 | 		HASH.counter = HASH.counter + 1
 312 | 		local id = HASH.identifier .. HASH.counter .. "X"
 313 | 		HASH.table[s] = id
 314 | 	end
 315 | 	return HASH.table[s]
 316 | end
 317 | 
 318 | ----------------------------------------------------------------------
 319 | -- Protection
 320 | ----------------------------------------------------------------------
 321 | 
 322 | -- The protection module is used to "protect" parts of a document
 323 | -- so that they are not modified by subsequent processing steps. 
 324 | -- Protected parts are saved in a table for later unprotection
 325 | 
 326 | -- Protection data
 327 | local PD = {
 328 | 	-- Saved blocks that have been converted
 329 | 	blocks = {},
 330 | 
 331 | 	-- Block level tags that will be protected
 332 | 	tags = {"p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote",
 333 | 	"pre", "table", "dl", "ol", "ul", "script", "noscript", "form", "fieldset",
 334 | 	"iframe", "math", "ins", "del"}
 335 | }
 336 | 
 337 | -- Pattern for matching a block tag that begins and ends in the leftmost
 338 | -- column and may contain indented subtags, i.e.
 339 | -- 
340 | -- A nested block. 341 | --
342 | -- Nested data. 343 | --
344 | --
345 | function block_pattern(tag) 346 | return "\n<" .. tag .. ".-\n[ \t]*\n" 347 | end 348 | 349 | -- Pattern for matching a block tag that begins and ends with a newline 350 | function line_pattern(tag) 351 | return "\n<" .. tag .. ".-[ \t]*\n" 352 | end 353 | 354 | -- Protects the range of characters from start to stop in the text and 355 | -- returns the protected string. 356 | function protect_range(text, start, stop) 357 | local s = text:sub(start, stop) 358 | local h = hash(s) 359 | PD.blocks[h] = s 360 | text = text:sub(1,start) .. h .. text:sub(stop) 361 | return text 362 | end 363 | 364 | -- Protect every part of the text that matches any of the patterns. The first 365 | -- matching pattern is protected first, etc. 366 | function protect_matches(text, patterns) 367 | while true do 368 | local start, stop = find_first(text, patterns) 369 | if not start then break end 370 | text = protect_range(text, start, stop) 371 | end 372 | return text 373 | end 374 | 375 | -- Protects blocklevel tags in the specified text 376 | function protect(text) 377 | -- First protect potentially nested block tags 378 | text = protect_matches(text, map(PD.tags, block_pattern)) 379 | -- Then protect block tags at the line level. 380 | text = protect_matches(text, map(PD.tags, line_pattern)) 381 | -- Protect
and comment tags 382 | text = protect_matches(text, {"\n]->[ \t]*\n"}) 383 | text = protect_matches(text, {"\n[ \t]*\n"}) 384 | return text 385 | end 386 | 387 | -- Returns true if the string s is a hash resulting from protection 388 | function is_protected(s) 389 | return PD.blocks[s] 390 | end 391 | 392 | -- Unprotects the specified text by expanding all the nonces 393 | function unprotect(text) 394 | for k,v in pairs(PD.blocks) do 395 | v = v:gsub("%%", "%%%%") 396 | text = text:gsub(k, v) 397 | end 398 | return text 399 | end 400 | 401 | 402 | ---------------------------------------------------------------------- 403 | -- Block transform 404 | ---------------------------------------------------------------------- 405 | 406 | -- The block transform functions transform the text on the block level. 407 | -- They work with the text as an array of lines rather than as individual 408 | -- characters. 409 | 410 | -- Returns true if the line is a ruler of (char) characters. 411 | -- The line must contain at least three char characters and contain only spaces and 412 | -- char characters. 413 | function is_ruler_of(line, char) 414 | if not line:match("^[ %" .. char .. "]*$") then return false end 415 | if not line:match("%" .. char .. ".*%" .. char .. ".*%" .. char) then return false end 416 | return true 417 | end 418 | 419 | -- Identifies the block level formatting present in the line 420 | function classify(line) 421 | local info = {line = line, text = line} 422 | 423 | if line:match("^ ") then 424 | info.type = "indented" 425 | info.outdented = line:sub(5) 426 | return info 427 | end 428 | 429 | for _,c in ipairs({'*', '-', '_', '='}) do 430 | if is_ruler_of(line, c) then 431 | info.type = "ruler" 432 | info.ruler_char = c 433 | return info 434 | end 435 | end 436 | 437 | if line == "" then 438 | info.type = "blank" 439 | return info 440 | end 441 | 442 | if line:match("^(#+)[ \t]*(.-)[ \t]*#*[ \t]*$") then 443 | local m1, m2 = line:match("^(#+)[ \t]*(.-)[ \t]*#*[ \t]*$") 444 | info.type = "header" 445 | info.level = m1:len() 446 | info.text = m2 447 | return info 448 | end 449 | 450 | if line:match("^ ? ? ?(%d+)%.[ \t]+(.+)") then 451 | local number, text = line:match("^ ? ? ?(%d+)%.[ \t]+(.+)") 452 | info.type = "list_item" 453 | info.list_type = "numeric" 454 | info.number = 0 + number 455 | info.text = text 456 | return info 457 | end 458 | 459 | if line:match("^ ? ? ?([%*%+%-])[ \t]+(.+)") then 460 | local bullet, text = line:match("^ ? ? ?([%*%+%-])[ \t]+(.+)") 461 | info.type = "list_item" 462 | info.list_type = "bullet" 463 | info.bullet = bullet 464 | info.text= text 465 | return info 466 | end 467 | 468 | if line:match("^>[ \t]?(.*)") then 469 | info.type = "blockquote" 470 | info.text = line:match("^>[ \t]?(.*)") 471 | return info 472 | end 473 | 474 | if is_protected(line) then 475 | info.type = "raw" 476 | info.html = unprotect(line) 477 | return info 478 | end 479 | 480 | info.type = "normal" 481 | return info 482 | end 483 | 484 | -- Find headers constisting of a normal line followed by a ruler and converts them to 485 | -- header entries. 486 | function headers(array) 487 | local i = 1 488 | while i <= #array - 1 do 489 | if array[i].type == "normal" and array[i+1].type == "ruler" and 490 | (array[i+1].ruler_char == "-" or array[i+1].ruler_char == "=") then 491 | local info = {line = array[i].line} 492 | info.text = info.line 493 | info.type = "header" 494 | info.level = iff(array[i+1].ruler_char == "=", 1, 2) 495 | table.remove(array, i+1) 496 | array[i] = info 497 | end 498 | i = i + 1 499 | end 500 | return array 501 | end 502 | 503 | -- Find list blocks and convert them to protected data blocks 504 | function lists(array, sublist) 505 | local function process_list(arr) 506 | local function any_blanks(arr) 507 | for i = 1, #arr do 508 | if arr[i].type == "blank" then return true end 509 | end 510 | return false 511 | end 512 | 513 | local function split_list_items(arr) 514 | local acc = {arr[1]} 515 | local res = {} 516 | for i=2,#arr do 517 | if arr[i].type == "list_item" then 518 | table.insert(res, acc) 519 | acc = {arr[i]} 520 | else 521 | table.insert(acc, arr[i]) 522 | end 523 | end 524 | table.insert(res, acc) 525 | return res 526 | end 527 | 528 | local function process_list_item(lines, block) 529 | while lines[#lines].type == "blank" do 530 | table.remove(lines) 531 | end 532 | 533 | local itemtext = lines[1].text 534 | for i=2,#lines do 535 | itemtext = itemtext .. "\n" .. outdent(lines[i].line) 536 | end 537 | if block then 538 | itemtext = block_transform(itemtext, true) 539 | if not itemtext:find("
") then itemtext = indent(itemtext) end
 540 | 				return "    
  • " .. itemtext .. "
  • " 541 | else 542 | local lines = split(itemtext) 543 | lines = map(lines, classify) 544 | lines = lists(lines, true) 545 | lines = blocks_to_html(lines, true) 546 | itemtext = table.concat(lines, "\n") 547 | if not itemtext:find("
    ") then itemtext = indent(itemtext) end
     548 | 				return "    
  • " .. itemtext .. "
  • " 549 | end 550 | end 551 | 552 | local block_list = any_blanks(arr) 553 | local items = split_list_items(arr) 554 | local out = "" 555 | for _, item in ipairs(items) do 556 | out = out .. process_list_item(item, block_list) .. "\n" 557 | end 558 | if arr[1].list_type == "numeric" then 559 | return "
      \n" .. out .. "
    " 560 | else 561 | return "
      \n" .. out .. "
    " 562 | end 563 | end 564 | 565 | -- Finds the range of lines composing the first list in the array. A list 566 | -- starts with (^ list_item) or (blank list_item) and ends with 567 | -- (blank* $) or (blank normal). 568 | -- 569 | -- A sublist can start with just (list_item) does not need a blank... 570 | local function find_list(array, sublist) 571 | local function find_list_start(array, sublist) 572 | if array[1].type == "list_item" then return 1 end 573 | if sublist then 574 | for i = 1,#array do 575 | if array[i].type == "list_item" then return i end 576 | end 577 | else 578 | for i = 1, #array-1 do 579 | if array[i].type == "blank" and array[i+1].type == "list_item" then 580 | return i+1 581 | end 582 | end 583 | end 584 | return nil 585 | end 586 | local function find_list_end(array, start) 587 | local pos = #array 588 | for i = start, #array-1 do 589 | if array[i].type == "blank" and array[i+1].type ~= "list_item" 590 | and array[i+1].type ~= "indented" and array[i+1].type ~= "blank" then 591 | pos = i-1 592 | break 593 | end 594 | end 595 | while pos > start and array[pos].type == "blank" do 596 | pos = pos - 1 597 | end 598 | return pos 599 | end 600 | 601 | local start = find_list_start(array, sublist) 602 | if not start then return nil end 603 | return start, find_list_end(array, start) 604 | end 605 | 606 | while true do 607 | local start, stop = find_list(array, sublist) 608 | if not start then break end 609 | local text = process_list(splice(array, start, stop)) 610 | local info = { 611 | line = text, 612 | type = "raw", 613 | html = text 614 | } 615 | array = splice(array, start, stop, {info}) 616 | end 617 | 618 | -- Convert any remaining list items to normal 619 | for _,line in ipairs(array) do 620 | if line.type == "list_item" then line.type = "normal" end 621 | end 622 | 623 | return array 624 | end 625 | 626 | -- Find and convert blockquote markers. 627 | function blockquotes(lines) 628 | local function find_blockquote(lines) 629 | local start 630 | for i,line in ipairs(lines) do 631 | if line.type == "blockquote" then 632 | start = i 633 | break 634 | end 635 | end 636 | if not start then return nil end 637 | 638 | local stop = #lines 639 | for i = start+1, #lines do 640 | if lines[i].type == "blank" or lines[i].type == "blockquote" then 641 | elseif lines[i].type == "normal" then 642 | if lines[i-1].type == "blank" then stop = i-1 break end 643 | else 644 | stop = i-1 break 645 | end 646 | end 647 | while lines[stop].type == "blank" do stop = stop - 1 end 648 | return start, stop 649 | end 650 | 651 | local function process_blockquote(lines) 652 | local raw = lines[1].text 653 | for i = 2,#lines do 654 | raw = raw .. "\n" .. lines[i].text 655 | end 656 | local bt = block_transform(raw) 657 | if not bt:find("
    ") then bt = indent(bt) end
     658 | 		return "
    \n " .. bt .. 659 | "\n
    " 660 | end 661 | 662 | while true do 663 | local start, stop = find_blockquote(lines) 664 | if not start then break end 665 | local text = process_blockquote(splice(lines, start, stop)) 666 | local info = { 667 | line = text, 668 | type = "raw", 669 | html = text 670 | } 671 | lines = splice(lines, start, stop, {info}) 672 | end 673 | return lines 674 | end 675 | 676 | -- Find and convert codeblocks. 677 | function codeblocks(lines) 678 | local function find_codeblock(lines) 679 | local start 680 | for i,line in ipairs(lines) do 681 | if line.type == "indented" then start = i break end 682 | end 683 | if not start then return nil end 684 | 685 | local stop = #lines 686 | for i = start+1, #lines do 687 | if lines[i].type ~= "indented" and lines[i].type ~= "blank" then 688 | stop = i-1 689 | break 690 | end 691 | end 692 | while lines[stop].type == "blank" do stop = stop - 1 end 693 | return start, stop 694 | end 695 | 696 | local function process_codeblock(lines) 697 | local raw = detab(encode_code(outdent(lines[1].line))) 698 | for i = 2,#lines do 699 | raw = raw .. "\n" .. detab(encode_code(outdent(lines[i].line))) 700 | end 701 | return "
    " .. raw .. "\n
    " 702 | end 703 | 704 | while true do 705 | local start, stop = find_codeblock(lines) 706 | if not start then break end 707 | local text = process_codeblock(splice(lines, start, stop)) 708 | local info = { 709 | line = text, 710 | type = "raw", 711 | html = text 712 | } 713 | lines = splice(lines, start, stop, {info}) 714 | end 715 | return lines 716 | end 717 | 718 | local idcount = 1 719 | local list_of_headers = {} 720 | local first_header 721 | 722 | -- Convert lines to html code 723 | function blocks_to_html(lines, no_paragraphs) 724 | local out = {} 725 | local i = 1 726 | while i <= #lines do 727 | local line = lines[i] 728 | if line.type == "ruler" then 729 | table.insert(out, "
    ") 730 | elseif line.type == "raw" then 731 | table.insert(out, line.html) 732 | elseif line.type == "normal" then 733 | local s = line.line 734 | 735 | while i+1 <= #lines and lines[i+1].type == "normal" do 736 | i = i + 1 737 | s = s .. "\n" .. lines[i].line 738 | end 739 | 740 | if no_paragraphs then 741 | table.insert(out, span_transform(s)) 742 | else 743 | table.insert(out, "

    " .. span_transform(s) .. "

    ") 744 | end 745 | elseif line.type == "header" then 746 | local txt = span_transform(line.text) 747 | local id = "T" .. idcount 748 | local s = "' .. txt .. "" 749 | if not first_header then 750 | first_header = {line=s,text=txt} 751 | else 752 | table.insert(out, s) 753 | table.insert(list_of_headers, {level=line.level,text=txt,id=id}) 754 | end 755 | idcount = idcount + 1 756 | else 757 | table.insert(out, line.line) 758 | end 759 | i = i + 1 760 | end 761 | return out 762 | end 763 | 764 | -- Perform all the block level transforms 765 | function block_transform(text, sublist) 766 | local lines = split(text) 767 | lines = map(lines, classify) 768 | lines = headers(lines) 769 | lines = lists(lines, sublist) 770 | lines = codeblocks(lines) 771 | lines = blockquotes(lines) 772 | lines = blocks_to_html(lines) 773 | local text = table.concat(lines, "\n") 774 | return text 775 | end 776 | 777 | -- Debug function for printing a line array to see the result 778 | -- of partial transforms. 779 | function print_lines(lines) 780 | for i, line in ipairs(lines) do 781 | print(i, line.type, line.text or line.line) 782 | end 783 | end 784 | 785 | ---------------------------------------------------------------------- 786 | -- Span transform 787 | ---------------------------------------------------------------------- 788 | 789 | -- Functions for transforming the text at the span level. 790 | 791 | -- These characters may need to be escaped because they have a special 792 | -- meaning in markdown. 793 | escape_chars = "'\\`*_{}[]()>#+-.!'" 794 | escape_table = {} 795 | 796 | function init_escape_table() 797 | escape_table = {} 798 | for i = 1,#escape_chars do 799 | local c = escape_chars:sub(i,i) 800 | escape_table[c] = hash(c) 801 | end 802 | end 803 | 804 | -- Adds a new escape to the escape table. 805 | function add_escape(text) 806 | if not escape_table[text] then 807 | escape_table[text] = hash(text) 808 | end 809 | return escape_table[text] 810 | end 811 | 812 | -- Escape characters that should not be disturbed by markdown. 813 | function escape_special_chars(text) 814 | local tokens = tokenize_html(text) 815 | 816 | local out = "" 817 | for _, token in ipairs(tokens) do 818 | local t = token.text 819 | if token.type == "tag" then 820 | -- In tags, encode * and _ so they don't conflict with their use in markdown. 821 | t = t:gsub("%*", escape_table["*"]) 822 | t = t:gsub("%_", escape_table["_"]) 823 | else 824 | t = encode_backslash_escapes(t) 825 | end 826 | out = out .. t 827 | end 828 | return out 829 | end 830 | 831 | -- Encode backspace-escaped characters in the markdown source. 832 | function encode_backslash_escapes(t) 833 | for i=1,escape_chars:len() do 834 | local c = escape_chars:sub(i,i) 835 | t = t:gsub("\\%" .. c, escape_table[c]) 836 | end 837 | return t 838 | end 839 | 840 | -- Unescape characters that have been encoded. 841 | function unescape_special_chars(t) 842 | local tin = t 843 | for k,v in pairs(escape_table) do 844 | k = k:gsub("%%", "%%%%") 845 | t = t:gsub(v,k) 846 | end 847 | if t ~= tin then t = unescape_special_chars(t) end 848 | return t 849 | end 850 | 851 | -- Encode/escape certain characters inside Markdown code runs. 852 | -- The point is that in code, these characters are literals, 853 | -- and lose their special Markdown meanings. 854 | function encode_code(s) 855 | s = s:gsub("%&", "&") 856 | s = s:gsub("<", "<") 857 | s = s:gsub(">", ">") 858 | for k,v in pairs(escape_table) do 859 | s = s:gsub("%"..k, v) 860 | end 861 | return s 862 | end 863 | 864 | -- Handle backtick blocks. 865 | function code_spans(s) 866 | s = s:gsub("\\\\", escape_table["\\"]) 867 | s = s:gsub("\\`", escape_table["`"]) 868 | 869 | local pos = 1 870 | while true do 871 | local start, stop = s:find("`+", pos) 872 | if not start then return s end 873 | local count = stop - start + 1 874 | -- Find a matching numbert of backticks 875 | local estart, estop = s:find(string.rep("`", count), stop+1) 876 | local brstart = s:find("\n", stop+1) 877 | if estart and (not brstart or estart < brstart) then 878 | local code = s:sub(stop+1, estart-1) 879 | code = code:gsub("^[ \t]+", "") 880 | code = code:gsub("[ \t]+$", "") 881 | code = code:gsub(escape_table["\\"], escape_table["\\"] .. escape_table["\\"]) 882 | code = code:gsub(escape_table["`"], escape_table["\\"] .. escape_table["`"]) 883 | code = "" .. encode_code(code) .. "" 884 | code = add_escape(code) 885 | s = s:sub(1, start-1) .. code .. s:sub(estop+1) 886 | pos = start + code:len() 887 | else 888 | pos = stop + 1 889 | end 890 | end 891 | return s 892 | end 893 | 894 | -- Encode alt text... enodes &, and ". 895 | function encode_alt(s) 896 | if not s then return s end 897 | s = s:gsub('&', '&') 898 | s = s:gsub('"', '"') 899 | s = s:gsub('<', '<') 900 | return s 901 | end 902 | 903 | -- Handle image references 904 | function images(text) 905 | local function reference_link(alt, id) 906 | alt = encode_alt(alt:match("%b[]"):sub(2,-2)) 907 | id = id:match("%[(.*)%]"):lower() 908 | if id == "" then id = text:lower() end 909 | link_database[id] = link_database[id] or {} 910 | if not link_database[id].url then return nil end 911 | local url = link_database[id].url or id 912 | url = encode_alt(url) 913 | local title = encode_alt(link_database[id].title) 914 | if title then title = " title=\"" .. title .. "\"" else title = "" end 915 | return add_escape ('' .. alt .. '") 916 | end 917 | 918 | local function inline_link(alt, link) 919 | alt = encode_alt(alt:match("%b[]"):sub(2,-2)) 920 | local url, title = link:match("%(?[ \t]*['\"](.+)['\"]") 921 | url = url or link:match("%(?%)") 922 | url = encode_alt(url) 923 | title = encode_alt(title) 924 | if title then 925 | return add_escape('' .. alt .. '') 926 | else 927 | return add_escape('' .. alt .. '') 928 | end 929 | end 930 | 931 | text = text:gsub("!(%b[])[ \t]*\n?[ \t]*(%b[])", reference_link) 932 | text = text:gsub("!(%b[])(%b())", inline_link) 933 | return text 934 | end 935 | 936 | -- Handle anchor references 937 | function anchors(text) 938 | local function reference_link(text, id) 939 | text = text:match("%b[]"):sub(2,-2) 940 | id = id:match("%b[]"):sub(2,-2):lower() 941 | if id == "" then id = text:lower() end 942 | link_database[id] = link_database[id] or {} 943 | if not link_database[id].url then return nil end 944 | local url = link_database[id].url or id 945 | url = encode_alt(url) 946 | local title = encode_alt(link_database[id].title) 947 | if title then title = " title=\"" .. title .. "\"" else title = "" end 948 | return add_escape("") .. text .. add_escape("") 949 | end 950 | 951 | local function inline_link(text, link) 952 | text = text:match("%b[]"):sub(2,-2) 953 | local url, title = link:match("%(?[ \t]*['\"](.+)['\"]") 954 | title = encode_alt(title) 955 | url = url or link:match("%(?%)") or "" 956 | url = encode_alt(url) 957 | if title then 958 | return add_escape("") .. text .. "" 959 | else 960 | return add_escape("") .. text .. add_escape("") 961 | end 962 | end 963 | 964 | text = text:gsub("(%b[])[ \t]*\n?[ \t]*(%b[])", reference_link) 965 | text = text:gsub("(%b[])(%b())", inline_link) 966 | return text 967 | end 968 | 969 | -- Handle auto links, i.e. . 970 | function auto_links(text) 971 | local function link(s) 972 | return add_escape("") .. s .. "" 973 | end 974 | -- Encode chars as a mix of dec and hex entitites to (perhaps) fool 975 | -- spambots. 976 | local function encode_email_address(s) 977 | -- Use a deterministic encoding to make unit testing possible. 978 | -- Code 45% hex, 45% dec, 10% plain. 979 | local hex = {code = function(c) return "&#x" .. string.format("%x", c:byte()) .. ";" end, count = 1, rate = 0.45} 980 | local dec = {code = function(c) return "&#" .. c:byte() .. ";" end, count = 0, rate = 0.45} 981 | local plain = {code = function(c) return c end, count = 0, rate = 0.1} 982 | local codes = {hex, dec, plain} 983 | local function swap(t,k1,k2) local temp = t[k2] t[k2] = t[k1] t[k1] = temp end 984 | 985 | local out = "" 986 | for i = 1,s:len() do 987 | for _,code in ipairs(codes) do code.count = code.count + code.rate end 988 | if codes[1].count < codes[2].count then swap(codes,1,2) end 989 | if codes[2].count < codes[3].count then swap(codes,2,3) end 990 | if codes[1].count < codes[2].count then swap(codes,1,2) end 991 | 992 | local code = codes[1] 993 | local c = s:sub(i,i) 994 | -- Force encoding of "@" to make email address more invisible. 995 | if c == "@" and code == plain then code = codes[2] end 996 | out = out .. code.code(c) 997 | code.count = code.count - 1 998 | end 999 | return out 1000 | end 1001 | local function mail(s) 1002 | s = unescape_special_chars(s) 1003 | local address = encode_email_address("mailto:" .. s) 1004 | local text = encode_email_address(s) 1005 | return add_escape("") .. text .. "" 1006 | end 1007 | -- links 1008 | text = text:gsub("<(https?:[^'\">%s]+)>", link) 1009 | text = text:gsub("<(ftp:[^'\">%s]+)>", link) 1010 | 1011 | -- mail 1012 | text = text:gsub("%s]+)>", mail) 1013 | text = text:gsub("<([-.%w]+%@[-.%w]+)>", mail) 1014 | return text 1015 | end 1016 | 1017 | -- Encode free standing amps (&) and angles (<)... note that this does not 1018 | -- encode free >. 1019 | function amps_and_angles(s) 1020 | -- encode amps not part of &..; expression 1021 | local pos = 1 1022 | while true do 1023 | local amp = s:find("&", pos) 1024 | if not amp then break end 1025 | local semi = s:find(";", amp+1) 1026 | local stop = s:find("[ \t\n&]", amp+1) 1027 | if not semi or (stop and stop < semi) or (semi - amp) > 15 then 1028 | s = s:sub(1,amp-1) .. "&" .. s:sub(amp+1) 1029 | pos = amp+1 1030 | else 1031 | pos = amp+1 1032 | end 1033 | end 1034 | 1035 | -- encode naked <'s 1036 | s = s:gsub("<([^a-zA-Z/?$!])", "<%1") 1037 | s = s:gsub("<$", "<") 1038 | 1039 | -- what about >, nothing done in the original markdown source to handle them 1040 | return s 1041 | end 1042 | 1043 | -- Handles emphasis markers (* and _) in the text. 1044 | function emphasis(text) 1045 | for _, s in ipairs {"%*%*", "%_%_"} do 1046 | text = text:gsub(s .. "([^%s][%*%_]?)" .. s, "%1") 1047 | text = text:gsub(s .. "([^%s][^<>]-[^%s][%*%_]?)" .. s, "%1") 1048 | end 1049 | for _, s in ipairs {"%*", "%_"} do 1050 | text = text:gsub(s .. "([^%s_])" .. s, "%1") 1051 | text = text:gsub(s .. "([^%s_])" .. s, "%1") 1052 | text = text:gsub(s .. "([^%s_][^<>_]-[^%s_])" .. s, "%1") 1053 | text = text:gsub(s .. "([^<>_]-[^<>_]-[^<>_]-)" .. s, "%1") 1054 | end 1055 | return text 1056 | end 1057 | 1058 | -- Handles line break markers in the text. 1059 | function line_breaks(text) 1060 | return text:gsub(" +\n", "
    \n") 1061 | end 1062 | 1063 | -- Perform all span level transforms. 1064 | function span_transform(text) 1065 | text = code_spans(text) 1066 | text = escape_special_chars(text) 1067 | text = images(text) 1068 | text = anchors(text) 1069 | text = auto_links(text) 1070 | text = amps_and_angles(text) 1071 | text = emphasis(text) 1072 | text = line_breaks(text) 1073 | return text 1074 | end 1075 | 1076 | ---------------------------------------------------------------------- 1077 | -- Markdown 1078 | ---------------------------------------------------------------------- 1079 | 1080 | -- Cleanup the text by normalizing some possible variations to make further 1081 | -- processing easier. 1082 | function cleanup(text) 1083 | -- Standardize line endings 1084 | text = text:gsub("\r\n", "\n") -- DOS to UNIX 1085 | text = text:gsub("\r", "\n") -- Mac to UNIX 1086 | 1087 | -- Convert all tabs to spaces 1088 | text = detab(text) 1089 | 1090 | -- Strip lines with only spaces and tabs 1091 | while true do 1092 | local subs 1093 | text, subs = text:gsub("\n[ \t]+\n", "\n\n") 1094 | if subs == 0 then break end 1095 | end 1096 | 1097 | return "\n" .. text .. "\n" 1098 | end 1099 | 1100 | -- Strips link definitions from the text and stores the data in a lookup table. 1101 | function strip_link_definitions(text) 1102 | local linkdb = {} 1103 | 1104 | local function link_def(id, url, title) 1105 | id = id:match("%[(.+)%]"):lower() 1106 | linkdb[id] = linkdb[id] or {} 1107 | linkdb[id].url = url or linkdb[id].url 1108 | linkdb[id].title = title or linkdb[id].title 1109 | return "" 1110 | end 1111 | 1112 | local def_no_title = "\n ? ? ?(%b[]):[ \t]*\n?[ \t]*]+)>?[ \t]*" 1113 | local def_title1 = def_no_title .. "[ \t]+\n?[ \t]*[\"'(]([^\n]+)[\"')][ \t]*" 1114 | local def_title2 = def_no_title .. "[ \t]*\n[ \t]*[\"'(]([^\n]+)[\"')][ \t]*" 1115 | local def_title3 = def_no_title .. "[ \t]*\n?[ \t]+[\"'(]([^\n]+)[\"')][ \t]*" 1116 | 1117 | text = text:gsub(def_title1, link_def) 1118 | text = text:gsub(def_title2, link_def) 1119 | text = text:gsub(def_title3, link_def) 1120 | text = text:gsub(def_no_title, link_def) 1121 | return text, linkdb 1122 | end 1123 | 1124 | link_database = {} 1125 | 1126 | -- Main markdown processing function 1127 | function markdown(text) 1128 | init_hash(text) 1129 | init_escape_table() 1130 | 1131 | text = cleanup(text) 1132 | text = protect(text) 1133 | text, link_database = strip_link_definitions(text) 1134 | text = block_transform(text) 1135 | text = unescape_special_chars(text) 1136 | return text 1137 | end 1138 | 1139 | ---------------------------------------------------------------------- 1140 | -- End of module 1141 | ---------------------------------------------------------------------- 1142 | 1143 | setfenv(1, _G) 1144 | M.lock(M) 1145 | 1146 | -- Expose markdown function to the world 1147 | markdown = M.markdown 1148 | 1149 | -- Class for parsing command-line options 1150 | local OptionParser = {} 1151 | OptionParser.__index = OptionParser 1152 | 1153 | -- Creates a new option parser 1154 | function OptionParser:new() 1155 | local o = {short = {}, long = {}} 1156 | setmetatable(o, self) 1157 | return o 1158 | end 1159 | 1160 | -- Calls f() whenever a flag with specified short and long name is encountered 1161 | function OptionParser:flag(short, long, f) 1162 | local info = {type = "flag", f = f} 1163 | if short then self.short[short] = info end 1164 | if long then self.long[long] = info end 1165 | end 1166 | 1167 | -- Calls f(param) whenever a parameter flag with specified short and long name is encountered 1168 | function OptionParser:param(short, long, f) 1169 | local info = {type = "param", f = f} 1170 | if short then self.short[short] = info end 1171 | if long then self.long[long] = info end 1172 | end 1173 | 1174 | -- Calls f(v) for each non-flag argument 1175 | function OptionParser:arg(f) 1176 | self.arg = f 1177 | end 1178 | 1179 | -- Runs the option parser for the specified set of arguments. Returns true if all arguments 1180 | -- where successfully parsed and false otherwise. 1181 | function OptionParser:run(args) 1182 | local pos = 1 1183 | while pos <= #args do 1184 | local arg = args[pos] 1185 | if arg == "--" then 1186 | for i=pos+1,#args do 1187 | if self.arg then self.arg(args[i]) end 1188 | return true 1189 | end 1190 | end 1191 | if arg:match("^%-%-") then 1192 | local info = self.long[arg:sub(3)] 1193 | if not info then print("Unknown flag: " .. arg) return false end 1194 | if info.type == "flag" then 1195 | info.f() 1196 | pos = pos + 1 1197 | else 1198 | param = args[pos+1] 1199 | if not param then print("No parameter for flag: " .. arg) return false end 1200 | info.f(param) 1201 | pos = pos+2 1202 | end 1203 | elseif arg:match("^%-") then 1204 | for i=2,arg:len() do 1205 | local c = arg:sub(i,i) 1206 | local info = self.short[c] 1207 | if not info then print("Unknown flag: -" .. c) return false end 1208 | if info.type == "flag" then 1209 | info.f() 1210 | else 1211 | if i == arg:len() then 1212 | param = args[pos+1] 1213 | if not param then print("No parameter for flag: -" .. c) return false end 1214 | info.f(param) 1215 | pos = pos + 1 1216 | else 1217 | param = arg:sub(i+1) 1218 | info.f(param) 1219 | end 1220 | break 1221 | end 1222 | end 1223 | pos = pos + 1 1224 | else 1225 | if self.arg then self.arg(arg) end 1226 | pos = pos + 1 1227 | end 1228 | end 1229 | return true 1230 | end 1231 | 1232 | -- Handles the case when markdown is run from the command line 1233 | local function run_command_line(arg) 1234 | -- Generate output for input s given options 1235 | local function run(s, options) 1236 | s = markdown(s) 1237 | if not options.wrap_header then return s end 1238 | local header = "" 1239 | if options.header then 1240 | local f = io.open(options.header) or error("Could not open file: " .. options.header) 1241 | header = f:read("*a") 1242 | f:close() 1243 | else 1244 | header = [[ 1245 | 1246 | 1247 | 1248 | 1249 | TITLE 1250 | 1251 | 1252 | 1253 | ]] 1254 | local title = options.title or (first_header and first_header.text) or "Untitled" 1255 | header = header:gsub("TITLE", title) 1256 | if options.inline_style then 1257 | local style = "" 1258 | local f = io.open(options.stylesheet) 1259 | if f then 1260 | style = f:read("*a") f:close() 1261 | else 1262 | error("Could not include style sheet " .. options.stylesheet .. ": File not found") 1263 | end 1264 | header = header:gsub('', 1265 | "") 1266 | else 1267 | header = header:gsub("STYLESHEET", options.stylesheet) 1268 | end 1269 | header = header:gsub("CHARSET", options.charset) 1270 | end 1271 | local footer = "" 1272 | if options.footer then 1273 | local f = io.open(options.footer) or error("Could not open file: " .. options.footer) 1274 | footer = f:read("*a") 1275 | f:close() 1276 | end 1277 | if first_header then 1278 | header = header .. first_header.line ..'\n' 1279 | -- Build TOC if required! 1280 | local txt = '' 1281 | local indent = 0 1282 | local last_level 1283 | for i,h in ipairs(list_of_headers) do 1284 | if i > 1 then 1285 | local diff = h.level - last_level 1286 | if diff > 0 then indent = indent + 1 1287 | elseif diff < 0 then indent = indent - 1 1288 | end 1289 | end 1290 | txt = txt..string.rep('\t',indent)..'* ['..h.text..'](#'..h.id..')\n' 1291 | last_level = h.level 1292 | end 1293 | header = header .. markdown(txt) 1294 | end 1295 | return header .. s .. footer 1296 | end 1297 | 1298 | -- Generate output path name from input path name given options. 1299 | local function outpath(path, options) 1300 | if options.append then return path .. ".html" end 1301 | local m = path:match("^(.+%.html)[^/\\]+$") if m then return m end 1302 | m = path:match("^(.+%.)[^/\\]*$") if m and path ~= m .. "html" then return m .. "html" end 1303 | return path .. ".html" 1304 | end 1305 | 1306 | -- Default commandline options 1307 | local options = { 1308 | wrap_header = true, 1309 | header = nil, 1310 | footer = nil, 1311 | charset = "utf-8", 1312 | title = nil, 1313 | stylesheet = "default.css", 1314 | inline_style = false 1315 | } 1316 | local help = [[ 1317 | Usage: markdown.lua [OPTION] [FILE] 1318 | Runs the markdown text markup to HTML converter on each file specified on the 1319 | command line. If no files are specified, runs on standard input. 1320 | 1321 | No header: 1322 | -n, --no-wrap Don't wrap the output in ... tags. 1323 | Custom header: 1324 | -e, --header FILE Use content of FILE for header. 1325 | -f, --footer FILE Use content of FILE for footer. 1326 | Generated header: 1327 | -c, --charset SET Specifies charset (default utf-8). 1328 | -i, --title TITLE Specifies title (default from first

    tag). 1329 | -s, --style STYLE Specifies style sheet file (default default.css). 1330 | -l, --inline-style Include the style sheet file inline in the header. 1331 | Generated files: 1332 | -a, --append Append .html extension (instead of replacing). 1333 | Other options: 1334 | -h, --help Print this help text. 1335 | -t, --test Run the unit tests. 1336 | ]] 1337 | 1338 | local run_stdin = true 1339 | local op = OptionParser:new() 1340 | op:flag("n", "no-wrap", function () options.wrap_header = false end) 1341 | op:param("e", "header", function (x) options.header = x end) 1342 | op:param("f", "footer", function (x) options.footer = x end) 1343 | op:param("c", "charset", function (x) options.charset = x end) 1344 | op:param("i", "title", function(x) options.title = x end) 1345 | op:param("s", "style", function(x) options.stylesheet = x end) 1346 | op:flag("l", "inline-style", function(x) options.inline_style = true end) 1347 | op:flag("a", "append", function() options.append = true end) 1348 | op:flag("t", "test", function() 1349 | local n = arg[0]:gsub("markdown.lua", "markdown-tests.lua") 1350 | local f = io.open(n) 1351 | if f then 1352 | f:close() dofile(n) 1353 | else 1354 | error("Cannot find markdown-tests.lua") 1355 | end 1356 | run_stdin = false 1357 | end) 1358 | op:flag("h", "help", function() print(help) run_stdin = false end) 1359 | op:arg(function(path) 1360 | local file = io.open(path) or error("Could not open file: " .. path) 1361 | local s = file:read("*a") 1362 | file:close() 1363 | s = run(s, options) 1364 | file = io.open(outpath(path, options), "w") or error("Could not open output file: " .. outpath(path, options)) 1365 | file:write(s) 1366 | file:close() 1367 | run_stdin = false 1368 | end 1369 | ) 1370 | 1371 | if not op:run(arg) then 1372 | print(help) 1373 | run_stdin = false 1374 | end 1375 | 1376 | if run_stdin then 1377 | local s = io.read("*a") 1378 | s = run(s, options) 1379 | io.write(s) 1380 | end 1381 | end 1382 | 1383 | -- If we are being run from the command-line, act accordingly 1384 | if arg and arg[0]:find("markdown%.lua$") then 1385 | run_command_line(arg) 1386 | else 1387 | return markdown 1388 | end 1389 | -------------------------------------------------------------------------------- /processt_callback.lua: -------------------------------------------------------------------------------- 1 | require 'winapi' 2 | 3 | -- run the program: 'fire and forget' 4 | p,f = winapi.spawn_process 'lua52 process-wait.lua' 5 | 6 | -- echo process output to stdout 7 | f:read_async(print) 8 | 9 | -- call this function when we're finished 10 | p:wait_async(function(s) 11 | print ('finis',s,p:get_exit_code()) 12 | os.exit() 13 | end) 14 | 15 | a = 0 16 | 17 | --print 'sleeping' 18 | 19 | winapi.sleep(-1) 20 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # winapi A useful Windows API subset for Lua 2 | 3 | This module provides some basic tools for working with Windows systems, finding out system resources, and gives you more control over process creation. In this introduction any plain reference is in the `winapi` table, so that `find_window` means `winapi.find_window`. Normally `winapi` works with the current Windows code page, but can be told to use UTF-8 with @{set_encoding}; interally string operations are in Unicode. 4 | 5 | ## Creating and working with Processes 6 | 7 | An irritating fact is that Lua GUI applications (such as IUP or wxLua) cannot use @{os.execute} without the infamous 'flashing black box' of console creation. And @{io.popen} may in fact not work at all. 8 | 9 | @{execute} provides a _quiet_ method to call a shell command. It returns the result code (like @{os.execute}) but also any text generated from the command. So for many common applications it will do as a @{io.popen} replacement as well. 10 | 11 | This function is blocking, but `winapi` provides more general ways of launching processes in the background and even capturing their output asynchronously. This will be discussed later with @{spawn_process}. 12 | 13 | Apart from @{execute}, @{shell_exec} is the Swiss-Army-Knife of Windows process creation. The first parameter is the 'action' or 'verb' to apply to the path; common actions are 'open', 'edit' and 'print'. Notice that these are the actions defined in Explorer (hence the word 'shell'). So to open a document in Word (or whatever application is registered for this extension): 14 | 15 | winapi.shell_exec('open','myold.doc') 16 | 17 | Or an explorer window for a directory: 18 | 19 | winapi.shell_exec('open','\\users\\steve\\lua') 20 | 21 | Note that this function launches the process and does not block. The path may be an explicit program to use, and then we can also specify the command-line parameters: 22 | 23 | winapi.shell_exec(nil,'scite','wina.lua') 24 | 25 | The fourth parameter is the working directory for the process, and the fifth indicates how the program's window is to be opened. For instance, you can open a file in Notepad already minimized: 26 | 27 | winapi.shell_exec(nil,'notepad','wina.lua',nil,winapi.SW_MINIMIZE) 28 | 29 | For fine control over console programs, use @{spawn_process} - you pass it the command-line, and receive two values; a process object and a file object. You monitor the process with the first, and can read from or write to the second. 30 | 31 | > proc,file = winapi.spawn_process 'cmd /c dir /b' 32 | > = file:read() 33 | bonzo.lc 34 | cexport.lua 35 | class1.c 36 | ... 37 | > = proc:wait() 38 | userdata: 0000000000539608 OK 39 | > = proc:exit_code() 40 | 0 41 | 42 | If the command is invalid, then you will get an error message instead: 43 | 44 | > = winapi.spawn_process 'frodo' 45 | nil The system cannot find the file specified. 46 | 47 | This is what @{execute} does under the hood, but doing it explicitly gives you more control. For instance, the @{Process:wait} method of the process object can take an optional time-out parameter; if you wait too long for the process, it will return the process object and the string 'TIMEOUT'. 48 | 49 | local _,status = proc:wait(500) 50 | if status == 'TIMEOUT' then 51 | proc:kill() 52 | end 53 | 54 | The file object is unfortunately not a Lua file object, since it is not possible to _portably_ re-use the existing Lua implementation without copying large chunks of `liolib.c` into this library. So @{File:read} grabs what's available, unbuffered. But I feel that it's easy enough for Lua code to parse the result into separate lines, if needed. 55 | 56 | Having a @{File:write} method means that, yes, you can capture an interactive process, send it commands and read the result. The caveat is that this process must not buffer standard output. For instance, launch interactive Lua with a command-line like this: 57 | 58 | > proc,file = winapi.spawn_process [[lua -e "io.stdout:setvbuf('no')" -i]] 59 | > = file:read() -- always read the program banner first! 60 | Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio 61 | > 62 | > = file:write 'print "hello"\n' 63 | 14 64 | > = file:read() 65 | hello 66 | > 67 | > proc:kill() 68 | 69 | (We also found it necessary in the [Lua for Windows](http://code.google.com/p/luaforwindows/) project to switch off buffering for using Lua in SciTE) 70 | 71 | Note that reading the result also returns the prompt '>', which isn't so obvious if we're running Lua from within Lua itself. It's clearer when using Python: 72 | 73 | > proc,file = winapi.spawn_process [[python -i]] 74 | > = file:read() 75 | Python 2.6.2c1 (r262c1:71369, Apr 7 2009, 18:44:00) [MSC v.1500 32 bit (Intel)] 76 | on win32 77 | Type "help", "copyright", "credits" or "license" for more information. 78 | >>> 79 | > file:write '40+2\n' 80 | > = file:read() 81 | 42 82 | >>> 83 | 84 | This kind of interactive process capture is fine for a console application, but @{File:read} is blocking and will freeze any GUI program. For this, you use @{File:read_async} which returns the result through a callback. Continuing the Python example: 85 | 86 | > file:write '40+2\n' 87 | > file:read_async(function(s) print('++',s) end) 88 | > ++ 42 89 | >>> 90 | 91 | This can work nicely with Lua coroutines, allowing us to write pseudo-blocking code for interacting with processes. 92 | 93 | The process object can provide more useful information: 94 | 95 | > = proc:working_size() 96 | 200 1380 97 | > = proc:run_times() 98 | 0 31 99 | 100 | @{Process:get_working_size} gives you a lower and an upper bound on the process memory in kB; @{Process:get_run_times} gives you the time (in milliseconds) spent in the user process and in the kernel. So the time to calculate `40+2` twice is too fast to even register, and it has only spent 31 msec in the system. 101 | 102 | It is possible to wait on more than one process at a time. Consider this simple time-wasting script: 103 | 104 | for i = 1,1e8 do end 105 | 106 | It takes me 0.743 seconds to do this, with stock Lua 5.1. But running two such scripts in parallel is about the same speed (0.776): 107 | 108 | require 'winapi' 109 | local t = os.clock() 110 | local P = {} 111 | P[1] = winapi.spawn_process 'lua slow.lua' 112 | P[2] = winapi.spawn_process 'lua slow.lua' 113 | winapi.wait_for_processes(P,true) 114 | print(os.clock() - t) 115 | 116 | So my i3 is effectively a two-processor machine; four such processes take 1.325 seconds, just under twice as long. The second parameter means 'wait for all'; like the @{Process:wait} method, it has an optional timeout parameter. 117 | 118 | The `true` parameter forces it to wait until _all_ the proceses are finished. Jf successful, `wait_for_processes` will return the index of the exiting process in the array of processes, so by using `false` we can wait for any process to finish, deal with the results, and continue waiting for the others. This is how Lake does [multithreading](https://github.com/stevedonovan/Lake/blob/master/lake#L376) on Windows. 119 | 120 | ## Working with Windows 121 | 122 | The `Window` object provides methods for querying window properties. For instance, the desktop window fills the whole screen, so to find out the screen dimensions is straightforward: 123 | 124 | > = winapi.get_desktop_window():get_bounds() 125 | 1600 900 126 | 127 | Finding other windows is best done by iterating over all top-level windows and checking them for some desired property; (@{find_window} is provided for completeness, but you really have to provide the exact window caption for the second parameter.) 128 | 129 | @{find_all_windows} returns all windows matching some function. For convenience, two useful matchers are provided, @{make_name_matcher} and @{make_class_matcher}. Once you have a group of related windows, you can do fun things like tile them: 130 | 131 | > t = winapi.find_all_windows(winapi.make_name_matcher '- SciTE') 132 | > = #t 133 | 2 134 | > winapi.tile_windows(winapi.get_desktop_window(),false,t) 135 | 136 | This call needs the parent window (we just use the desktop), whether to tile horizontally, and a table of window objects. There is an optional fourth parameter, which is the bounds to use for the tiling, specified like so `{left=0,top=0,right=600,bottom=900}`. 137 | 138 | With tiling and the ability to hide windows with `w:show(winapi.SW_HIDE)` it is entirely possible to write a little 'virtual desktop' application. 139 | 140 | @{find_window_ex} also uses a matcher function; @{find_window_match} is a shortcut for the operation of finding a window by its caption. 141 | 142 | Every window has an associated text value. For top-level windows, this is the window caption: 143 | 144 | > = winapi.get_foreground_window() 145 | Command Prompt - lua -lwinapi 146 | 147 | So the equivalent of the old DOS command `title` would here be: 148 | 149 | winapi.get_foreground_window():set_text 'My new title' 150 | 151 | Any top-level window will contain child windows. For example, Notepad has a simple structure revealed by @{Window:enum_children}: 152 | 153 | > w = winapi.find_window_match 'Notepad' 154 | > = w 155 | Untitled - Notepad 156 | > t = {} 157 | > w:enum_children(function(w) table.insert(t,w) end) 158 | > = #t 159 | 2 160 | > = t[1]:get_class_name() 161 | Edit 162 | > = t[2]:get_class_name() 163 | msctls_statusbar32 164 | 165 | Windows controls like the 'Edit' control interact with the unverse via messages. 166 | `EM_GETLINECOUNT` will tell the control to return the number of lines. Looking up the numerical value of this message, it's easy to query Notepad's edit control: 167 | 168 | > = t[1]:send_message(186,0,0) 169 | 6 170 | 171 | An entertaining way to automate some programs is to send virtual keystrokes to them. The function @{send_to_window} sends characters to the current foreground window: 172 | 173 | > winapi.send_to_window '= 20 + 10\n' 174 | > = 20 + 10 175 | 30 176 | 177 | After launching a window, you can make it the foreground window and send it text: 178 | 179 | P = winapi.spawn_process 'notepad' 180 | P:wait_for_input_idle() 181 | w = winapi.find_window_match 'Untitled' 182 | w:show() 183 | w:set_foreground() 184 | winapi.send_to_window 'hello dammit' 185 | 186 | Waiting on the process is important: it gives the other process a chance to get going, and to create a new window which we can promote. 187 | 188 | ## Working with Text Encoding 189 | 190 | Lua has internally no concept of text encoding; strings are sequences of bytes. This means that the string functions cannot generally give you the correct length of a UTF-8 encoded string, for instance. Internally, Windows uses UTF-16 and winapi gives you several options for passing and getting lua strings from Windows. 191 | 192 | An important point is that you can choose to use UTF-8 encoding with winapi. This little program shows how: 193 | 194 | -- @{caption.lua} 195 | local W = require 'winapi' 196 | W.set_encoding(W.CP_UTF8) 197 | win = W.foreground_window() 198 | win:set_text 'ελληνική' 199 | 200 | When run in SciTE, it successfully puts a little bit of Greek in the title bar. 201 | 202 | @{encode} can translate text explicitly between encodings; `winapi.enode(ein,eout,text)` where the encodings can be one of the `winapi.CP_ACP`, `winapi_UTF8` and `winapi_UTF16` constants. 203 | 204 | @{utf8_expand} will expand '#' two-byte Unicode hex constants: 205 | 206 | local U = winapi.utf8_expand 207 | txt = U '#03BB + #03BC + C' 208 | print(txt) 209 | print(U '#03BD') 210 | ---> OUTPUT 211 | λ + μ + C 212 | ν 213 | 214 | You may work internally in UTF-8 and get a suitable _short file name_ for working with files in Lua. 215 | 216 | -- @{testshort.lua} 217 | name = winapi.short_path 'ελληνική.txt' 218 | print(name) 219 | local f,err = io.open(name,'w') 220 | f:write 'a new file\n' 221 | f:close() 222 | 223 | A filename with the correct Greek name appears in Explorer, and can be edited with any Unicode-aware application like Notepad. 224 | 225 | ## Working with Processes 226 | 227 | @{get_current_process} will give you a @{Process} object for the current program. It's also possible to get a process object from a program's window: 228 | 229 | > w = winapi.get_foreground_window() 230 | > = w 231 | Command Prompt - lua -lwinapi 232 | > p = w:get_process() 233 | > = p:get_process_name() 234 | cmd.exe 235 | > = p:get_process_name(true) 236 | C:\WINDOWS\system32\cmd.exe 237 | 238 | (Note that the @{Process:get_process_name} method can optionally give you the full path to the process.) 239 | 240 | To get all the current processes: 241 | 242 | pids = winapi.get_processes() 243 | 244 | for _,pid in ipairs(pids) do 245 | local P = winapi.process_from_id(pid) 246 | local name = P:get_process_name(true) 247 | if name then print(pid,name) end 248 | P:close() 249 | end 250 | 251 | 252 | ## Drive and Directory Operations 253 | 254 | There are functions for querying the filesystem: @{get_logical_drives} returns all available drives (in 'D:\\' format) and @{get_drive_type} will tell you whether these drives are fixed, remote, removable, etc. @{get_disk_free_space} will return the space used and the space available in kB as two results. 255 | 256 | -- @{drives.lua} 257 | require 'winapi' 258 | 259 | drives = winapi.get_logical_drives() 260 | for _,drive in ipairs(drives) do 261 | local free,avail = winapi.get_disk_free_space(drive) 262 | if not free then -- call failed, avail is error 263 | free = '('..avail..')' 264 | else 265 | free = math.ceil(free/1024) -- get Mb 266 | end 267 | local rname = '' 268 | local dtype = winapi.get_drive_type(drive) 269 | if dtype == 'remote' then -- note it wants the drive letter! 270 | rname = winapi.get_disk_network_name(drive:gsub('\\$','')) 271 | end 272 | print(drive,dtype,free,rname) 273 | end 274 | 275 | This script gives the following output on my home machine: 276 | 277 | C:\ fixed 218967 278 | F:\ fixed 1517 279 | G:\ cdrom (The device is not ready.) 280 | Q:\ fixed (Access is denied.) 281 | 282 | Or at work: 283 | 284 | C:\ fixed 1455 285 | D:\ fixed 49996 286 | E:\ cdrom (The device is not ready.) 287 | G:\ remote 33844 \\CARL-VFILE\SYS 288 | I:\ remote 452789 \\CARL-VFILE\GROUPS 289 | X:\ remote 12160 \\CARL-VFILE\APPS 290 | Y:\ remote 33844 \\CARL-VFILE\SYS\PUBLIC 291 | Z:\ remote 33844 \\CARL-VFILE\SYS\PUBLIC 292 | 293 | A useful operation is watching directories for changes. You specify the directory, the kind of change to monitor and whether subdirectories should be checked. You also provide a function that will be called when something changes. 294 | 295 | winapi.watch_for_file_changes(mydir,winapi.FILE_NOTIFY_CHANGE_LAST_WRITE,FALSE, 296 | function(what,who) 297 | -- 'what' will be winapi.FILE_ACTION_MODIFIED 298 | -- 'who' will be the name of the file that changed 299 | print(what,who) 300 | end 301 | ) 302 | 303 | Using a callback means that you can watch multiple directories and still respond to timers, etc. 304 | 305 | Finally, @{copy_file} and @{move_file} are indispensible operations which are surprisingly tricky to write correctly in pure Lua. For general filesystem operations like finding the contents of folders, I suggest a more portable library like [LuaFileSystem](?). However, you can get pretty far with a well-behaved way to call system commands: 306 | 307 | local status,output = winapi.execute('dir /B') 308 | local files = {} 309 | for f in output:gmatch '[^\r\n]+' do 310 | table.insert(files,f) 311 | end 312 | 313 | ## Output 314 | 315 | GUI applications do not have a console so @{print} does not work. @{show_message} will put up a message box to bother users. Here is the old favourite, system message boxes: 316 | 317 | print(winapi.show_message("Message","stuff\nand nonsense","yes-no","warning")) 318 | 319 | The first parameter is the caption of the message box, the second is the text (which may contain line feeds); the third controls which buttons are to be shown, and the fourth is the icon to use. The function returns a string indicating which button has been pressed: 'ok','yes','no','cancel', etc. 320 | 321 | Or you may prefer to irritate the user with a sound: 322 | 323 | winapi.beep 'warning' 324 | 325 | @{output_debug_string} will write text quietly to the debug stream. A utility such as [DebugView](http://technet.microsoft.com/en-us/sysinternals/bb896647) can be used to view this output, which shows it with a timestamp. 326 | 327 | ## Timers and Callbacks 328 | 329 | It is straightforward to create a timer. You could of course use @{sleep} but then your application will do nothing but sleep most of the time. This callback-driven timer can run in the background: 330 | 331 | winapi.make_timer(500,function() 332 | text:append 'gotcha' 333 | end) 334 | 335 | Such callbacks can be made GUI-safe by first calling @{use_gui} which ensures that any callback is called in the main GUI thread. You _must_ do this if integrating winapi with GUI toolkits such as [wxLua](?) or [IUP](?). 336 | 337 | The basic rule for callbacks enforced by `winapi` is that only one may be active at a time; otherwise we would risk re-entering Lua on another thread, using the same Lua state. So be quick when responding to callbacks, since they effectively block Lua. If possible, use asynchronous code - for instance `Process:wait_async` if you are launchhing a new process, or `File:read_async` for reading from a file. 338 | 339 | For a console application, callbacks only happen when the thread is sleeping. the best bet (after setting some timers and so forth) is just to sleep indefinitely: 340 | 341 | winapi.sleep(-1) 342 | 343 | To show what happens in an interactive prompt if you don't follow this rule: 344 | 345 | > winapi.timer(500,function() end) 346 | > = 23 347 | nil nil return 23 348 | 349 | In short: completely messed! 350 | 351 | It's possible to read from the console asynchronously, which allows you to write servers which are responsive to interactive commands. 352 | 353 | f = winapi.get_console() 354 | f:read_async(function(line) 355 | f:write(line) 356 | if line:match '^quit' then 357 | os.exit() 358 | end 359 | end) 360 | 361 | winapi.sleep(-1) 362 | 363 | Please note that you will get the end-of-line characters as well. 364 | 365 | As of version 1.4, this console file object can also be waited on using `wait_for_processes`, which gives another way of handling commands. That function also supports a timeout, hence this entertaining little program which reads from the console and runs another operation every 500 ms. 366 | 367 | local W = require 'winapi' 368 | local f = W.get_console() 369 | local title = W.get_foreground_window() 370 | local count = 1 371 | f:write '? ' 372 | while true do 373 | local res = W.wait_for_processes({f},false,500) 374 | if res == 1 then 375 | local line = f:read() 376 | if not line then break end 377 | -- strip line feed 378 | line = line:gsub('\r\n$','') 379 | if line == 'quit' then break end 380 | print(line:upper()) 381 | f:write '? ' 382 | else 383 | title:set_text('counting '..count) 384 | count = count + 1 385 | end 386 | end 387 | 388 | The console handle is signalled as soon as you type any character, but the read will block until a whole line is entered. This explains why the manic caption updating stops while you're entering a line. Please note that another alertable handles (like events or threads) can be waited on _as well_ in this way. 389 | 390 | 391 | ## Reading from the Registry 392 | 393 | The registry is an unavoidable part of living with Windows, since much useful information can be found in it, if you know the key. 394 | 395 | For instance, the environment for the _current user_ can be queried: 396 | 397 | local key = winapi.open_reg_key [[HKEY_CURRENT_USER\Environment]] 398 | print(key:get_value("PATH")) 399 | k:close() 400 | 401 | And `Regkey:set_value` will then allow you to update this path, which is useful for install programs. In that case, set the optional second argument to `true` to get write-access. 402 | 403 | This has an optional third parameter, which is the data type of the key: `winapi` has the constants `REG_BINARY`,`REG_DWORD`, `REG_SZ`, `REG_MULTI_SZ` and `REG_EXPAND_SZ`. `REG_DWORD` can be passed a _number_ value, and `REG_BINARY` is passed a plain un-encoded binary Lua string; all the other types use the current encoding. The `REG_MULTI_SZ` type is special, and requires strings that look like "alice\0bob\0". 404 | 405 | `Regkey:get_keys` will return a list of all subkeys of the current key. 406 | 407 | When finished, it's best to explicitly use the `close` method. 408 | 409 | ## Pipe Server 410 | 411 | Interprocess communication (IPC) is one of those tangled, operating-system-dependent things that can be terribly useful. On Unix, _named pipes_ are special files which can be used for two processes to easily exchange information. One process opens the pipe for reading, and the other process opens it for writing; the first process will start reading, and this will block until the other process writes to the pipe. Since pipes are a regular part of the filesystem, two Lua scripts can use regular I/O to complete this transaction. 412 | 413 | Life is more complicated on Windows (as usual) but with a little bit of help from the API you can get the equivalent mechanism from Windows named pipes. They do work differently; they are more like Unix domain sockets; a server waits for a client to connect ('accept') and then produces a handle for the new client to use; it then goes back to waiting for connections. 414 | 415 | require 'winapi' 416 | 417 | winapi.server(function(file) 418 | file:read_async(function(s) 419 | print('['..s..']') 420 | end) 421 | end) 422 | 423 | winapi.sleep(-1) 424 | 425 | Like timers and file notifications, this server runs in its own thread so we have to put the main thread to sleep. This function is passed a callback and a pipe name; pipe names must look like '\\\\.\\pipe\\NAME' and the default name is '\\\\.\\pipe\\luawinapi'. The callback receives a file object - in this case we use @{File:read_async} to play nice with other Lua threads. Multiple clients can have open connections in this way, up to the number of available pipes. 426 | 427 | The client can connect in a very straightforward way, but note that as with Unix pipes you have to flush the output to actually physically write to the pipe: 428 | 429 | > f = io.open('\\\\.\\pipe\\luawinapi','w') 430 | > f:write 'hello server!\n' 431 | > f:flush() 432 | > f:close() 433 | 434 | and our server will say: 435 | 436 | [hello server! 437 | ] 438 | [] 439 | 440 | (Note that @{File:read} receives an _empty string_ when the handle is closed.) 441 | 442 | However, we can't push 'standard' I/O very far here. So there is also a corresponding @{open_pipe} which returns a file object, both readable and writeable. It's probably best to think of it as a kind of socket; each call to @{File:read} and @{File:write} are regarded as receive/send events. 443 | 444 | The server can do something to the received string and pass it back: 445 | 446 | winapi.server(function(file) 447 | file:read_async(function(s) 448 | if s == '' then 449 | print 'client disconnected' 450 | else 451 | file:write (s:upper()) 452 | end 453 | end) 454 | end) 455 | 456 | On the client side: 457 | 458 | f = winapi.open_pipe() 459 | f:write 'hello\n' 460 | print(f:read()) -- HELLO 461 | f:write 'dog\n' 462 | print(f:read()) -- DOG 463 | f:close() 464 | 465 | Another similarity with sockets is that you can connect to _remote_ pipes (see [pipe names](http://msdn.microsoft.com/en-us/library/aa365783(v=vs.85).aspx)) 466 | 467 | ## Events 468 | 469 | Events are kernel-level synchronization objects in Windows. Initially they are 'unsignaled' and `Event:wait` will pause until they become signaled by calling `Event:signal`. 470 | 471 | local W = require 'winapi' 472 | local e = W.event() 473 | local count = 1 474 | 475 | W.make_timer(500,function() 476 | print 'finis' 477 | if count == 5 then 478 | os.exit() 479 | end 480 | e:signal() 481 | count = count + 1 482 | end) 483 | 484 | while true do 485 | e:wait() 486 | print 'gotcha' 487 | end 488 | 489 | There is also `Event:wait_async` to avoid blocking in a callback. 490 | 491 | They can optionally be given a name, and can then work across _different processes_. 492 | 493 | -------------------------------------------------------------------------------- /winapi.l.c: -------------------------------------------------------------------------------- 1 | /*** 2 | A useful set of Windows API functions. 3 | 4 | * Enumerating and accessing windows, including sending keys. 5 | * Enumerating processes and querying their program name, memory used, etc. 6 | * Reading and Writing to the Registry 7 | * Copying and moving files, and showing drive information. 8 | * Launching processes and opening documents. 9 | * Monitoring filesystem changes. 10 | 11 | @author Steve Donovan (steve.j.donovan@gmail.com) 12 | @copyright 2011 13 | @license MIT/X11 14 | @module winapi 15 | */ 16 | #define WINDOWS_LEAN_AND_MEAN 17 | #include 18 | #include 19 | #ifdef __GNUC__ 20 | #include /* GNU GCC specific */ 21 | #endif 22 | #include "Winnetwk.h" 23 | #include 24 | 25 | 26 | #define WBUFF 2048 27 | #define MAX_SHOW 100 28 | #define THREAD_STACK_SIZE (1024 * 1024) 29 | #define MAX_PROCESSES 1024 30 | #define MAX_KEYS 512 31 | #define FILE_BUFF_SIZE 2048 32 | #define MAX_WATCH 20 33 | #define MAX_WPATH 1024 34 | 35 | #define TIMEOUT(timeout) timeout == 0 ? INFINITE : timeout 36 | 37 | static wchar_t wbuff[WBUFF]; 38 | 39 | typedef LPCWSTR WStr; 40 | 41 | module "winapi" { 42 | 43 | #include "wutils.h" 44 | 45 | static WStr wstring(Str text) { 46 | return wstring_buff(text,wbuff,sizeof(wbuff)); 47 | } 48 | 49 | /// Text encoding. 50 | // @section encoding 51 | 52 | /// set the current text encoding. 53 | // @param e one of `CP_ACP` (Windows code page; default) and `CP_UTF8` 54 | // @function set_encoding 55 | def set_encoding (Int e) { 56 | set_encoding(e); 57 | return 0; 58 | } 59 | 60 | /// get the current text encoding. 61 | // @return either `CP_ACP` or `CP_UTF8` 62 | // @function get_encoding 63 | def get_encoding () { 64 | lua_pushinteger(L, get_encoding()); 65 | return 1; 66 | } 67 | 68 | /// encode a string in another encoding. 69 | // Note: currently there's a limit of about 2K on the string buffer. 70 | // @param e_in `CP_ACP`, `CP_UTF8` or `CP_UTF16` 71 | // @param e_out likewise 72 | // @param text the string 73 | // @function encode 74 | def encode(Int e_in, Int e_out, Str text) { 75 | int ce = get_encoding(); 76 | LPCWSTR ws; 77 | if (e_in != -1) { 78 | set_encoding(e_in); 79 | ws = wstring(text); 80 | } else { 81 | ws = (LPCWSTR)text; 82 | } 83 | if (e_out != -1) { 84 | set_encoding(e_out); 85 | push_wstring(L,ws); 86 | } else { 87 | lua_pushlstring(L,(LPCSTR)ws,wcslen(ws)*sizeof(WCHAR)); 88 | } 89 | set_encoding(ce); 90 | return 1; 91 | } 92 | 93 | /// expand # unicode escapes in a string. 94 | // @param text ASCII text with #XXXX, where XXXX is four hex digits. ## means # itself. 95 | // @return text as UTF-8 96 | // @see testu.lua 97 | // @function utf8_expand 98 | def utf8_expand(Str text) { 99 | int len = strlen(text), i = 0, enc = get_encoding(); 100 | WCHAR wch; 101 | LPWSTR P = wbuff; 102 | if (len > sizeof(wbuff)) { 103 | return push_error_msg(L,"string too big"); 104 | } 105 | while (i <= len) { 106 | if (text[i] == '#') { 107 | ++i; 108 | if (text[i] == '#') { 109 | wch = '#'; 110 | } else 111 | if (len-i >= 4) { 112 | char hexnum[5]; 113 | strncpy(hexnum,text+i,4); 114 | hexnum[4] = '\0'; 115 | wch = strtol(hexnum,NULL,16); 116 | i += 3; 117 | } else { 118 | return push_error_msg(L,"bad # escape"); 119 | } 120 | } else { 121 | wch = (WCHAR)text[i]; 122 | } 123 | *P++ = wch; 124 | ++i; 125 | } 126 | *P++ = 0; 127 | set_encoding(CP_UTF8); 128 | push_wstring(L,wbuff); 129 | set_encoding(enc); 130 | return 1; 131 | } 132 | 133 | // forward reference to Process constructor 134 | static int push_new_Process(lua_State *L,Int pid, HANDLE ph); 135 | 136 | const DWORD_PTR WIN_NOACTIVATE = (DWORD_PTR)SWP_NOACTIVATE, 137 | WIN_NOMOVE = (DWORD_PTR)SWP_NOMOVE, 138 | WIN_NOSIZE = (DWORD_PTR)SWP_NOSIZE, 139 | WIN_SHOWWINDOW = (DWORD_PTR)SWP_SHOWWINDOW, 140 | WIN_NOZORDER = (DWORD_PTR)SWP_NOZORDER, 141 | WIN_BOTTOM = (DWORD_PTR)HWND_BOTTOM, 142 | WIN_NOTOPMOST = (DWORD_PTR)HWND_NOTOPMOST, 143 | WIN_TOP = (DWORD_PTR)HWND_TOP, 144 | WIN_TOPMOST = (DWORD_PTR)HWND_TOPMOST; 145 | 146 | 147 | /// a class representing a Window. 148 | // @type Window 149 | class Window { 150 | HWND hwnd; 151 | 152 | constructor (HWND h) { 153 | this->hwnd = h; 154 | } 155 | 156 | static lua_State *sL; 157 | 158 | static BOOL CALLBACK enum_callback(HWND hwnd,LPARAM data) { 159 | push_ref(sL,(Ref)data); 160 | push_new_Window(sL,hwnd); 161 | lua_call(sL,1,0); 162 | return TRUE; 163 | } 164 | 165 | /// the handle of this window. 166 | // @function get_handle 167 | def get_handle() { 168 | lua_pushnumber(L,(DWORD_PTR)this->hwnd); 169 | return 1; 170 | } 171 | 172 | /// get the window text. 173 | // @function get_text 174 | def get_text() { 175 | GetWindowTextW(this->hwnd,wbuff,sizeof(wbuff)); 176 | return push_wstring(L,wbuff); 177 | } 178 | 179 | /// set the window text. 180 | // @function set_text 181 | def set_text(Str text) { 182 | SetWindowTextW(this->hwnd,wstring(text)); 183 | return 0; 184 | } 185 | 186 | /// change the visibility, state etc 187 | // @param flags one of `SW_SHOW`, `SW_MAXIMIZE`, etc 188 | // @function show 189 | def show(Int flags = SW_SHOW) { 190 | ShowWindow(this->hwnd,flags); 191 | return 0; 192 | } 193 | 194 | /// change the visibility without blocking. 195 | // @param flags one of `SW_SHOW`, `SW_MAXIMIZE`, etc 196 | // @function show_async 197 | def show_async(Int flags = SW_SHOW) { 198 | ShowWindowAsync(this->hwnd,flags); 199 | return 0; 200 | } 201 | 202 | /// get the position in pixels 203 | // @return left position 204 | // @return top position 205 | // @function get_position 206 | def get_position() { 207 | RECT rect; 208 | GetWindowRect(this->hwnd,&rect); 209 | lua_pushinteger(L,rect.left); 210 | lua_pushinteger(L,rect.top); 211 | return 2; 212 | } 213 | 214 | /// get the bounds in pixels 215 | // @return width 216 | // @return height 217 | // @function get_bounds 218 | def get_bounds() { 219 | RECT rect; 220 | GetWindowRect(this->hwnd,&rect); 221 | lua_pushinteger(L,rect.right - rect.left); 222 | lua_pushinteger(L,rect.bottom - rect.top); 223 | return 2; 224 | } 225 | 226 | /// is this window visible? 227 | // @function is_visible 228 | def is_visible() { 229 | lua_pushboolean(L,IsWindowVisible(this->hwnd)); 230 | return 1; 231 | } 232 | 233 | /// destroy this window. 234 | // @function destroy 235 | def destroy () { 236 | DestroyWindow(this->hwnd); 237 | return 0; 238 | } 239 | 240 | /// resize this window. 241 | // @param x0 left 242 | // @param y0 top 243 | // @param w width 244 | // @param h height 245 | // @function resize 246 | def resize(Int x0, Int y0, Int w, Int h) { 247 | MoveWindow(this->hwnd,x0,y0,w,h,TRUE); 248 | return 0; 249 | } 250 | 251 | /// resize or move a window. 252 | // see [API](http://msdn.microsoft.com/en-us/library/windows/desktop/ms633545%28v=vs.85%29.aspx) 253 | // @param w window _handle_ to insert after, or one of: 254 | // WINWIN_BOTTOM, WIN_NOTOPMOST, WIN_TOP (default), WIN_TOPMOST 255 | // @param x0 left (ignore if flags has WIN_NOMOVE) 256 | // @param y0 top 257 | // @param w width (ignore if flags has WIN_NOSIZE) 258 | // @param h height 259 | // @param flags one of 260 | // WIN_NOACTIVATE, WIN_NOMOVE, WIN_NOSIZE, WIN_SHOWWINDOW (default), WIN_NOZORDER 261 | def set_pos (Int wafter = WIN_TOP, Int x0, Int y0, Int w, Int h, Int flags = WIN_SHOWWINDOW) { 262 | SetWindowPos(this->hwnd,(HWND)(DWORD_PTR)wafter,x0,y0,w,h,flags); 263 | return 0; 264 | } 265 | 266 | /// send a message. 267 | // @param msg the message 268 | // @param wparam 269 | // @param lparam 270 | // @return the result 271 | // @function send_message 272 | def send_message(Int msg, Number wparam, Number lparam) { 273 | lua_pushinteger(L,SendMessage(this->hwnd,msg,(WPARAM)wparam,(LPARAM)lparam)); 274 | return 1; 275 | } 276 | 277 | /// send a message asynchronously. 278 | // @param msg the message 279 | // @param wparam 280 | // @param lparam 281 | // @return the result 282 | // @function post_message 283 | def post_message(Int msg, Number wparam, Number lparam) { 284 | return push_bool(L,PostMessage(this->hwnd,msg,(WPARAM)wparam,(LPARAM)lparam)); 285 | } 286 | 287 | 288 | /// enumerate all child windows. 289 | // @param a callback which to receive each window object 290 | // @function enum_children 291 | def enum_children(Value callback) { 292 | Ref ref; 293 | sL = L; 294 | ref = make_ref(L,callback); 295 | EnumChildWindows(this->hwnd,&enum_callback,ref); 296 | release_ref(L,ref); 297 | return 0; 298 | } 299 | 300 | /// get the parent window. 301 | // @function get_parent 302 | def get_parent() { 303 | return push_new_Window(L,GetParent(this->hwnd)); 304 | } 305 | 306 | /// get the name of the program owning this window. 307 | // @function get_module_filename 308 | def get_module_filename() { 309 | int sz = GetWindowModuleFileNameW(this->hwnd,wbuff,sizeof(wbuff)); 310 | wbuff[sz] = 0; 311 | return push_wstring(L,wbuff); 312 | } 313 | 314 | /// get the window class name. 315 | // Useful to find all instances of a running program, when you 316 | // know the class of the top level window. 317 | // @function get_class_name 318 | def get_class_name() { 319 | static char buff[1024]; 320 | int n = GetClassName(this->hwnd,buff,sizeof(buff)); 321 | if (n > 0) { 322 | lua_pushstring(L,buff); 323 | return 1; 324 | } else { 325 | return push_error(L); 326 | } 327 | } 328 | 329 | /// bring this window to the foreground. 330 | // @function set_foreground 331 | def set_foreground () { 332 | lua_pushboolean(L,SetForegroundWindow(this->hwnd)); 333 | return 1; 334 | } 335 | 336 | /// get the associated process of this window 337 | // @function get_process 338 | def get_process() { 339 | DWORD pid; 340 | GetWindowThreadProcessId(this->hwnd,&pid); 341 | return push_new_Process(L,pid,NULL); 342 | } 343 | 344 | /// this window as string (up to 100 chars). 345 | // @function __tostring 346 | def __tostring() { 347 | int ret; 348 | int sz = GetWindowTextW(this->hwnd,wbuff,sizeof(wbuff)); 349 | if (sz > MAX_SHOW) { 350 | wbuff[MAX_SHOW] = '\0'; 351 | } 352 | ret = push_wstring(L,wbuff); 353 | if (ret == 2) { // we had a conversion error 354 | lua_pushliteral(L,""); 355 | } 356 | return 1; 357 | } 358 | 359 | def __eq(Window other) { 360 | lua_pushboolean(L,this->hwnd == other->hwnd); 361 | return 1; 362 | } 363 | 364 | } 365 | 366 | /// Manipulating Windows. 367 | // @section Windows 368 | 369 | /// find a window based on classname and caption 370 | // @param cname class name (may be nil) 371 | // @param wname caption (may be nil) 372 | // @return @{Window} 373 | // @function find_window 374 | def find_window(StrNil cname, StrNil wname) { 375 | HWND hwnd = FindWindow(cname,wname); 376 | if (hwnd == NULL) { 377 | return push_error(L); 378 | } else { 379 | return push_new_Window(L,hwnd); 380 | } 381 | } 382 | 383 | /// makes a function that matches against window text 384 | // @param text 385 | // @function make_name_matcher 386 | 387 | /// makes a function that matches against window class name 388 | // @param text 389 | // @function make_class_matcher 390 | 391 | /// find a window using a condition function. 392 | // @param match will return true when its argument is the desired window 393 | // @return @{Window} 394 | // @function find_window_ex 395 | 396 | /// return all windows matching a condition. 397 | // @param match will return true when its argument is the desired window 398 | // @return a list of window objects 399 | // @function find_all_windows 400 | 401 | /// find a window matching the given text. 402 | // @param text the pattern to match against the caption 403 | // @return a window object. 404 | // @function find_window_match 405 | 406 | /// current foreground window. 407 | // An example of setting the caption is @{caption.lua} 408 | // @return @{Window} 409 | // @function get_foreground_window 410 | def get_foreground_window() { 411 | return push_new_Window(L, GetForegroundWindow()); 412 | } 413 | 414 | /// the desktop window. 415 | // @usage winapi.get_desktop_window():get_bounds() 416 | // @return @{Window} 417 | // @function get_desktop_window 418 | def get_desktop_window() { 419 | return push_new_Window(L, GetDesktopWindow()); 420 | } 421 | 422 | /// a Window object from a handle 423 | // @param a Windows nandle 424 | // @return @{Window} 425 | // @function window_from_handle 426 | def window_from_handle(Int hwnd) { 427 | return push_new_Window(L, (HWND)hwnd); 428 | } 429 | 430 | /// enumerate over all top-level windows. 431 | // @param callback a function to receive each window object 432 | // @function enum_windows 433 | def enum_windows(Value callback) { 434 | Ref ref; 435 | sL = L; 436 | ref = make_ref(L,callback); 437 | EnumWindows(&enum_callback,ref); 438 | release_ref(L,ref); 439 | return 0; 440 | } 441 | 442 | /// route callback dispatch through a message window. 443 | // You need to do this when using Winapi in a GUI application, 444 | // since it ensures that Lua callbacks happen in the GUI thread. 445 | // @function use_gui 446 | def use_gui() { 447 | make_message_window(); 448 | return 0; 449 | } 450 | 451 | static INPUT *add_input(INPUT *pi, WORD vkey, BOOL up) { 452 | pi->type = INPUT_KEYBOARD; 453 | pi->ki.dwFlags = up ? KEYEVENTF_KEYUP : 0; 454 | pi->ki.wVk = vkey; 455 | return pi+1; 456 | } 457 | 458 | // The Windows SendInput() is a low-level function, and you have to 459 | // simulate things like uppercase directly. Repeated characters need 460 | // an explicit 'key up' keystroke to work. 461 | // see http://stackoverflow.com/questions/2167156/sendinput-isnt-sending-the-correct-shifted-characters 462 | // this is a case where we have to convert the parameter directly, since 463 | // it may be an integer (virtual key code) or string of characters. 464 | 465 | /// send a string or virtual key to the active window. 466 | // @{input.lua} shows launching a process, waiting for it to be 467 | // ready, and sending it some keys 468 | // @param text either a key (like winapi.VK_SHIFT) or a string 469 | // @return number of keys sent, or nil if an error 470 | // @return any error string 471 | // @function send_to_window 472 | def send_to_window () { 473 | const char *text; 474 | int vkey, len = MAX_KEYS; 475 | UINT res; 476 | SHORT last_vk = 0; 477 | INPUT *input, *pi; 478 | if (lua_isnumber(L,1)) { 479 | INPUT inp; 480 | ZeroMemory(&inp,sizeof(INPUT)); 481 | vkey = lua_tointeger(L,1); 482 | add_input(&inp,vkey,lua_toboolean(L,2)); 483 | SendInput(1,&inp,sizeof(INPUT)); 484 | return 0; 485 | } else { 486 | text = lua_tostring(L,1); 487 | if (text == NULL) { 488 | return push_error_msg(L,"not a string or number"); 489 | } 490 | } 491 | input = (INPUT *)malloc(sizeof(INPUT)*len); 492 | pi = input; 493 | ZeroMemory(input, sizeof(INPUT)*len); 494 | for(; *text; ++text) { 495 | SHORT vk = VkKeyScan(*text); 496 | if (last_vk == vk) { 497 | pi = add_input(pi,last_vk & 0xFF,TRUE); 498 | } 499 | if (vk & 0x100) pi = add_input(pi,VK_SHIFT,FALSE); 500 | pi = add_input(pi,vk & 0xFF,FALSE); 501 | if (vk & 0x100) pi = add_input(pi,VK_SHIFT,TRUE); 502 | last_vk = vk; 503 | } 504 | res = SendInput(((DWORD_PTR)pi-(DWORD_PTR)input)/sizeof(INPUT), input, sizeof(INPUT)); 505 | free(input); 506 | if (res > 0) { 507 | lua_pushinteger(L,res); 508 | return 1; 509 | } else { 510 | return push_error(L); 511 | } 512 | return 0; 513 | } 514 | 515 | /// tile a group of windows. 516 | // @param parent @{Window} (can use the desktop) 517 | // @param horiz tile vertically by default 518 | // @param kids a table of window objects 519 | // @param bounds a bounds table (left,top,right,bottom) - can be nil 520 | // @function tile_windows 521 | def tile_windows(Window parent, Boolean horiz, Value kids, Value bounds) { 522 | RECT rt; 523 | HWND *kids_arr; 524 | int i,n_kids; 525 | LPRECT lpRect = NULL; 526 | if (! lua_isnoneornil(L,bounds)) { 527 | lua_pushvalue(L,bounds); 528 | Int_get(rt.left,"left"); 529 | Int_get(rt.top,"top"); 530 | Int_get(rt.right,"right"); 531 | Int_get(rt.bottom,"bottom"); 532 | lua_pop(L,1); 533 | lpRect = &rt; 534 | } 535 | n_kids = lua_objlen(L,kids); 536 | kids_arr = (HWND *)malloc(sizeof(HWND)*n_kids); 537 | for (i = 0; i < n_kids; ++i) { 538 | Window *w; 539 | lua_rawgeti(L,kids,i+1); 540 | w = Window_arg(L,-1); 541 | kids_arr[i] = w->hwnd; 542 | } 543 | TileWindows(parent->hwnd,horiz ? MDITILE_HORIZONTAL : MDITILE_VERTICAL, lpRect, n_kids, kids_arr); 544 | free(kids_arr); 545 | return 0; 546 | } 547 | 548 | /// Miscellaneous functions. 549 | // @section miscellaneous 550 | 551 | static int push_new_File(lua_State *L,HANDLE hread, HANDLE hwrite); 552 | 553 | /// sleep and use no processing time. 554 | // @param millisec sleep period 555 | // @function sleep 556 | def sleep(Int millisec) { 557 | release_mutex(); 558 | Sleep(millisec); 559 | lock_mutex(); 560 | return 0; 561 | } 562 | 563 | /// show a message box. 564 | // @param caption for dialog 565 | // @param msg the message 566 | // @param btns (default 'ok') one of 'ok','ok-cancel','yes','yes-no', 567 | // "abort-retry-ignore", "retry-cancel", "yes-no-cancel" 568 | // @param icon (default 'information') one of 'information','question','warning','error' 569 | // @return a string giving the pressed button: one of 'ok','yes','no','cancel', 570 | // 'try','abort' and 'retry' 571 | // @see message.lua 572 | // @function show_message 573 | def show_message(Str caption, Str msg, Str btns = "ok", Str icon = "information") { 574 | int res, type; 575 | WCHAR capb [512]; 576 | type = mb_const(btns) | mb_const(icon); 577 | wstring_buff(caption,capb,sizeof(capb)); 578 | res = MessageBoxW( NULL, wstring(msg), capb, type); 579 | lua_pushstring(L,mb_result(res)); 580 | return 1; 581 | } 582 | 583 | /// make a beep sound. 584 | // @param type (default 'ok'); one of 'information','question','warning','error' 585 | // @function beep 586 | def beep (Str icon = "ok") { 587 | return push_bool(L, MessageBeep(mb_const(icon))); 588 | } 589 | 590 | /// copy a file. 591 | // @param src source file 592 | // @param dest destination file 593 | // @param fail_if_exists if true, then cannot copy onto existing file 594 | // @function copy_file 595 | def copy_file(Str src, Str dest, Int fail_if_exists = 0) { 596 | return push_bool(L, CopyFile(src,dest,fail_if_exists)); 597 | } 598 | 599 | /// output text to the system debugger. 600 | // A uility such as [DebugView](http://technet.microsoft.com/en-us/sysinternals/bb896647) 601 | // can show the output 602 | // @param str text 603 | // @function output_debug_string 604 | def output_debug_string(Str str) { 605 | OutputDebugString(str); 606 | return 0; 607 | } 608 | 609 | /// move a file. 610 | // @param src source file 611 | // @param dest destination file 612 | // @function move_file 613 | def move_file(Str src, Str dest) { 614 | return push_bool(L, MoveFile(src,dest)); 615 | } 616 | 617 | #define wconv(name) (name ? wstring_buff(name,w##name,sizeof(w##name)) : NULL) 618 | 619 | /// execute a shell command. 620 | // @param verb the action (e.g. 'open' or 'edit') can be nil. 621 | // @param file the command 622 | // @param parms any parameters (optional) 623 | // @param dir the working directory (optional) 624 | // @param show the window show flags (default is SW_SHOWNORMAL) 625 | // @function shell_exec 626 | def shell_exec(StrNil verb, Str file, StrNil parms, StrNil dir, Int show=SW_SHOWNORMAL) { 627 | WCHAR wverb[128], wfile[MAX_WPATH], wdir[MAX_WPATH], wparms[MAX_WPATH]; 628 | int res = (DWORD_PTR)ShellExecuteW(NULL,wconv(verb),wconv(file),wconv(parms),wconv(dir),show) > 32; 629 | return push_bool(L, res); 630 | } 631 | 632 | /// copy text onto the clipboard. 633 | // @param text the text 634 | // @function set_clipboard 635 | def set_clipboard(Str text) { 636 | HGLOBAL glob; 637 | LPWSTR p; 638 | int bufsize = 3*strlen(text); 639 | if (! OpenClipboard(NULL)) { 640 | return push_perror(L,"openclipboard"); 641 | } 642 | EmptyClipboard(); 643 | glob = GlobalAlloc(GMEM_MOVEABLE, bufsize); 644 | p = (LPWSTR)GlobalLock(glob); 645 | wstring_buff(text,p,bufsize); 646 | GlobalUnlock(glob); 647 | if (SetClipboardData(CF_UNICODETEXT,glob) == NULL) { 648 | CloseClipboard(); 649 | return push_error(L); 650 | } 651 | CloseClipboard(); 652 | return 0; 653 | } 654 | 655 | /// get the text on the clipboard. 656 | // @return the text 657 | // @function get_clipboard 658 | def get_clipboard() { 659 | HGLOBAL glob; 660 | LPCWSTR p; 661 | if (! OpenClipboard(NULL)) { 662 | return push_perror(L,"openclipboard"); 663 | } 664 | glob = GetClipboardData(CF_UNICODETEXT); 665 | if (glob == NULL) { 666 | CloseClipboard(); 667 | return push_error(L); 668 | } 669 | p = GlobalLock(glob); 670 | push_wstring(L,p); 671 | GlobalUnlock(glob); 672 | CloseClipboard(); 673 | return 1; 674 | } 675 | 676 | /// open console i/o. 677 | // @return @{File} 678 | // @function get_console 679 | def get_console() { 680 | HANDLE w = GetStdHandle(STD_OUTPUT_HANDLE); 681 | HANDLE r = GetStdHandle(STD_INPUT_HANDLE); 682 | return push_new_File(L,r,w); 683 | } 684 | 685 | def pipe() { 686 | HANDLE hRead, hWrite; 687 | if (CreatePipe(&hRead,&hWrite,NULL,0) != 0) { 688 | push_new_File(L,hRead,NULL); 689 | push_new_File(L,NULL,hWrite); 690 | return 2; 691 | } else { 692 | return push_error(L); 693 | } 694 | } 695 | 696 | /// open a serial port for reading and writing. 697 | // @param defn a string as used by the [mode command](http://technet.microsoft.com/en-us/library/cc732236%28WS.10%29.aspx) 698 | // @return @{File} 699 | // @function open_serial 700 | def open_serial(Str defn) { 701 | DCB dcb = {0}; 702 | char port[20]; 703 | HANDLE hSerial; 704 | const char *p = defn; 705 | char *q = port; 706 | for (; *p != ' '; p++) { 707 | *q++ = *p; 708 | } 709 | *q = '\0'; 710 | dcb.DCBlength = sizeof(dcb); 711 | hSerial = CreateFile(port,GENERIC_READ | GENERIC_WRITE, 0, 0, 712 | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); 713 | if (hSerial == INVALID_HANDLE_VALUE) { 714 | return push_perror(L,"createfile"); 715 | } 716 | GetCommState(hSerial,&dcb); 717 | if (! BuildCommDCB(defn,&dcb)) { 718 | CloseHandle(hSerial); 719 | return push_perror(L,"buildcom"); 720 | } 721 | if (! SetCommState(hSerial,&dcb)) { 722 | CloseHandle(hSerial); 723 | return push_perror(L,"setcomm"); 724 | } 725 | return push_new_File(L,hSerial,hSerial); 726 | } 727 | 728 | static int push_wait_result(lua_State *L, DWORD res) { 729 | if (res == WAIT_OBJECT_0) { 730 | lua_pushvalue(L,1); 731 | lua_pushliteral(L,"OK"); 732 | return 2; 733 | } else if (res == WAIT_TIMEOUT) { 734 | lua_pushvalue(L,1); 735 | lua_pushliteral(L,"TIMEOUT"); 736 | return 2; 737 | } else { 738 | return push_error(L); 739 | } 740 | } 741 | 742 | static int wait_single(HANDLE h, int timeout) { 743 | DWORD res; 744 | release_mutex(); 745 | res = WaitForSingleObject (h, timeout); 746 | lock_mutex(); 747 | return res; 748 | } 749 | 750 | static int push_wait(lua_State *L, HANDLE h, int timeout) { 751 | return push_wait_result(L,wait_single(h,timeout)); 752 | } 753 | 754 | static int push_wait_async(lua_State *L, HANDLE h, int timeout, int callback); 755 | 756 | /// The Event class. 757 | // @type Event 758 | class Event { 759 | HANDLE hEvent; 760 | 761 | constructor(HANDLE h) { 762 | this->hEvent = h; 763 | } 764 | 765 | /// wait for this event to be signalled. 766 | // @param timeout optional timeout in millisec; defaults to waiting indefinitely. 767 | // @return this event object 768 | // @return either "OK" or "TIMEOUT" 769 | // @function wait 770 | def wait(Int timeout=0) { 771 | return push_wait(L,this->hEvent, TIMEOUT(timeout)); 772 | } 773 | 774 | /// run callback when this process is finished. 775 | // @param callback the callback 776 | // @param timeout optional timeout in millisec; defaults to waiting indefinitely. 777 | // @return this process object 778 | // @return either "OK" or "TIMEOUT" 779 | // @function wait_async 780 | def wait_async(Value callback, Int timeout = 0) { 781 | return push_wait_async(L,this->hEvent, TIMEOUT(timeout), callback); 782 | } 783 | 784 | def signal() { 785 | SetEvent(this->hEvent); 786 | return 0; 787 | } 788 | 789 | def __gc() { 790 | CloseHandle(this->hEvent); 791 | return 0; 792 | } 793 | } 794 | 795 | /// The Mutex class. 796 | // @type Mutex 797 | class Mutex { 798 | HANDLE hMutex; 799 | 800 | constructor (HANDLE h) { 801 | this->hMutex = h; 802 | } 803 | 804 | def lock() { 805 | WaitForSingleObject(this->hMutex,INFINITE); 806 | return 0; 807 | } 808 | 809 | def release() { 810 | ReleaseMutex(this->hMutex); 811 | return 0; 812 | } 813 | 814 | def __gc() { 815 | CloseHandle(this->hMutex); 816 | return 0; 817 | } 818 | } 819 | 820 | static int _event_count = 1; 821 | 822 | /// create a new @{Event} object. 823 | // @param name string (optional) 824 | // @return @{Event}, or nil, error. 825 | def event (Str name="?") { 826 | HANDLE hEvent; 827 | char buff[MAX_PATH]; 828 | if (strcmp(name,"?")==0) { 829 | sprintf(buff,"_event_%d",_event_count++); 830 | name = buff; 831 | } 832 | hEvent = CreateEvent (NULL,0,0,name); 833 | if (hEvent == NULL) { 834 | return push_error(L); 835 | } else { 836 | return push_new_Event(L,hEvent); 837 | } 838 | } 839 | 840 | /// create a new @{Mutex} object. 841 | // @param name string (optional) 842 | // @return @{Mutex}, or nil, error. 843 | def mutex(Str name="") { 844 | return push_new_Mutex(L,CreateMutex(NULL,FALSE,*name==0 ? NULL : name)); 845 | } 846 | 847 | /// A class representing a Windows process. 848 | // this example was [helpful](http://msdn.microsoft.com/en-us/library/ms682623%28VS.85%29.aspx) 849 | // @type Process 850 | class Process { 851 | HANDLE hProcess; 852 | int pid; 853 | 854 | constructor(Int pid, HANDLE ph) { 855 | if (ph) { 856 | this->pid = pid; 857 | this->hProcess = ph; 858 | } else { 859 | this->pid = pid; 860 | this->hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | 861 | PROCESS_VM_READ | PROCESS_TERMINATE, 862 | FALSE, pid ); 863 | if (!this->hProcess) { 864 | this->hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | 865 | PROCESS_VM_READ, 866 | FALSE, pid ); 867 | } 868 | } 869 | } 870 | 871 | /// get the name of the process. 872 | // @param full true if you want the full path; otherwise returns the base name. 873 | // @function get_process_name 874 | def get_process_name(Boolean full) { 875 | HMODULE hMod; 876 | DWORD cbNeeded; 877 | wchar_t modname[MAX_PATH]; 878 | 879 | if (EnumProcessModules(this->hProcess, &hMod, sizeof(hMod), &cbNeeded)) { 880 | if (full) { 881 | GetModuleFileNameExW(this->hProcess, hMod, modname, sizeof(modname)); 882 | } else { 883 | GetModuleBaseNameW(this->hProcess, hMod, modname, sizeof(modname)); 884 | } 885 | return push_wstring(L,modname); 886 | } else { 887 | return push_error(L); 888 | } 889 | } 890 | 891 | /// get the the pid of the process. 892 | // @function get_pid 893 | def get_pid() { 894 | lua_pushnumber(L, this->pid); 895 | return 1; 896 | } 897 | 898 | /// kill the process. 899 | // @{test-spawn.lua} kills a launched process after a certain amount of output. 900 | // @function kill 901 | def kill() { 902 | TerminateProcess(this->hProcess,0); 903 | return 0; 904 | } 905 | 906 | /// get the working size of the process. 907 | // @return minimum working set size 908 | // @return maximum working set size. 909 | // @function get_working_size 910 | def get_working_size() { 911 | SIZE_T minsize, maxsize; 912 | GetProcessWorkingSetSize(this->hProcess,&minsize,&maxsize); 913 | lua_pushnumber(L,minsize/1024); 914 | lua_pushnumber(L,maxsize/1024); 915 | return 2; 916 | } 917 | 918 | /// get the start time of this process. 919 | // @return a table in the same format as os.time() and os.date() expects. 920 | // @function get_start_time 921 | def get_start_time() { 922 | FILETIME create,exit,kernel,user,local; 923 | SYSTEMTIME time; 924 | GetProcessTimes(this->hProcess,&create,&exit,&kernel,&user); 925 | FileTimeToLocalFileTime(&create,&local); 926 | FileTimeToSystemTime(&local,&time); 927 | #define set(name,val) lua_pushinteger(L,val); lua_setfield(L,-2,#name); 928 | lua_newtable(L); 929 | set(year,time.wYear); 930 | set(month,time.wMonth); 931 | set(day,time.wDay); 932 | set(hour,time.wHour); 933 | set(min,time.wMinute); 934 | set(sec,time.wSecond); 935 | #undef set 936 | return 1; 937 | } 938 | 939 | // MS likes to be different: the 64-bit value encoded in FILETIME 940 | // is defined as the number of 100-nsec intervals since Jan 1, 1601 UTC 941 | static double fileTimeToMillisec(FILETIME *ft) { 942 | ULARGE_INTEGER ui; 943 | ui.LowPart = ft->dwLowDateTime; 944 | ui.HighPart = ft->dwHighDateTime; 945 | return (double) (ui.QuadPart/10000); 946 | } 947 | 948 | /// elapsed run time of this process. 949 | // @return user time in msec 950 | // @return system time in msec 951 | // @function get_run_times 952 | def get_run_times() { 953 | FILETIME create,exit,kernel,user; 954 | GetProcessTimes(this->hProcess,&create,&exit,&kernel,&user); 955 | lua_pushnumber(L,fileTimeToMillisec(&user)); 956 | lua_pushnumber(L,fileTimeToMillisec(&kernel)); 957 | return 2; 958 | } 959 | 960 | /// wait for this process to finish. 961 | // @param timeout optional timeout in millisec; defaults to waiting indefinitely. 962 | // @return this process object 963 | // @return either "OK" or "TIMEOUT" 964 | // @function wait 965 | def wait(Int timeout = 0) { 966 | return push_wait(L,this->hProcess, TIMEOUT(timeout)); 967 | } 968 | 969 | /// run callback when this process is finished. 970 | // @param callback the callback 971 | // @param timeout optional timeout in millisec; defaults to waiting indefinitely. 972 | // @return this process object 973 | // @return either "OK" or "TIMEOUT" 974 | // @function wait_async 975 | def wait_async(Value callback, Int timeout = 0) { 976 | return push_wait_async(L,this->hProcess, TIMEOUT(timeout), callback); 977 | } 978 | 979 | 980 | /// wait for this process to become idle and ready for input. 981 | // Only makes sense for processes with windows (will return immediately if not) 982 | // @param timeout optional timeout in millisec 983 | // @return this process object 984 | // @return either "OK" or "TIMEOUT" 985 | // @function wait_for_input_idle 986 | def wait_for_input_idle (Int timeout = 0) { 987 | return push_wait_result(L, WaitForInputIdle(this->hProcess, TIMEOUT(timeout))); 988 | } 989 | 990 | /// exit code of this process. 991 | // (Only makes sense if the process has in fact finished.) 992 | // @return exit code 993 | // @function get_exit_code 994 | def get_exit_code() { 995 | DWORD code; 996 | GetExitCodeProcess(this->hProcess, &code); 997 | lua_pushinteger(L,code); 998 | return 1; 999 | } 1000 | 1001 | /// close this process handle. 1002 | // @function close 1003 | def close() { 1004 | CloseHandle(this->hProcess); 1005 | this->hProcess = NULL; 1006 | return 0; 1007 | } 1008 | 1009 | def __gc () { 1010 | if (this->hProcess != NULL) 1011 | CloseHandle(this->hProcess); 1012 | return 0; 1013 | } 1014 | } 1015 | 1016 | /// Working with processes. 1017 | // @{readme.md.Creating_and_working_with_Processes} 1018 | // @section Processes 1019 | 1020 | /// create a process object from the id. 1021 | // @param pid the process id 1022 | // @return @{Process} 1023 | // @function process_from_id 1024 | def process_from_id(Int pid) { 1025 | return push_new_Process(L,pid,NULL); 1026 | } 1027 | 1028 | /// process id of current process. 1029 | // @return integer id 1030 | // @function get_current_pid 1031 | def get_current_pid() { 1032 | lua_pushinteger(L,GetCurrentProcessId()); 1033 | return 1; 1034 | } 1035 | 1036 | /// process object of the current process. 1037 | // @return @{Process} 1038 | // @function get_current_process 1039 | def get_current_process() { 1040 | return push_new_Process(L,0,GetCurrentProcess()); 1041 | } 1042 | 1043 | /// get all process ids in the system. 1044 | // @{test-processes.lua} is a simple `ps` equivalent. 1045 | // @return an array of process ids. 1046 | // @function get_processes 1047 | def get_processes() { 1048 | DWORD processes[MAX_PROCESSES], cbNeeded, nProcess; 1049 | int i, k = 1; 1050 | 1051 | if (! EnumProcesses (processes,sizeof(processes),&cbNeeded)) { 1052 | return push_error(L); 1053 | } 1054 | 1055 | nProcess = cbNeeded/sizeof (DWORD); 1056 | lua_newtable(L); 1057 | for (i = 0; i < nProcess; i++) { 1058 | if (processes[i] != 0) { 1059 | lua_pushinteger(L,processes[i]); 1060 | lua_rawseti(L,-2,k++); 1061 | } 1062 | } 1063 | return 1; 1064 | } 1065 | 1066 | /// wait for a group of processes. 1067 | // Note that this will work with @{Event} and @{Thread} objects as well. 1068 | // @{process-wait.lua} shows a number of processes launched 1069 | // in parallel 1070 | // @param processes an array of @{Process} objects 1071 | // @param all wait for all processes to finish (default false) 1072 | // @param timeout wait upto this time in msec (default infinite) 1073 | // @function wait_for_processes 1074 | def wait_for_processes(Value processes, Boolean all, Int timeout = 0) { 1075 | int status, i; 1076 | void *p; 1077 | int n = lua_objlen(L,processes); 1078 | HANDLE handles[MAXIMUM_WAIT_OBJECTS]; 1079 | if (n > MAXIMUM_WAIT_OBJECTS) { 1080 | return push_error_msg(L,"cannot wait on so many processes"); 1081 | } 1082 | 1083 | for (i = 0; i < n; i++) { 1084 | lua_rawgeti(L,processes,i+1); 1085 | // any user data with a handle as the first field will work here 1086 | p = lua_touserdata(L,-1); 1087 | if (p == NULL) { 1088 | return push_error_msg(L,"non-object in list!"); 1089 | } 1090 | handles[i] = *(HANDLE*)p; 1091 | } 1092 | release_mutex(); 1093 | status = WaitForMultipleObjects(n, handles, all, TIMEOUT(timeout)); 1094 | lock_mutex(); 1095 | status = status - WAIT_OBJECT_0 + 1; 1096 | if (status < 1 || status > n) { 1097 | return push_error(L); 1098 | } else { 1099 | lua_pushinteger(L,status); 1100 | return 1; 1101 | } 1102 | } 1103 | 1104 | // These functions are all run in background threads, and a little bit of poor man's 1105 | // OOP helps here. This is the base struct for describing threads with callbacks, 1106 | // which may have an associated buffer and handle. 1107 | 1108 | #define callback_data_ \ 1109 | HANDLE handle; \ 1110 | lua_State *L; \ 1111 | Ref callback; \ 1112 | char *buf; \ 1113 | int bufsz; 1114 | 1115 | typedef struct { 1116 | callback_data_ 1117 | } LuaCallback, *PLuaCallback; 1118 | 1119 | LuaCallback *lcb_callback(void *lcb, lua_State *L, int idx) { 1120 | LuaCallback *data; 1121 | if (lcb == NULL) { 1122 | lcb = malloc(sizeof(LuaCallback)); 1123 | } 1124 | data = (LuaCallback*) lcb; 1125 | data->L = L; 1126 | data->callback = make_ref(L,idx); 1127 | data->buf = NULL; 1128 | data->handle = NULL; 1129 | return data; 1130 | } 1131 | 1132 | BOOL lcb_call(void *data, int idx, Str text, int flags) { 1133 | LuaCallback *lcb = (LuaCallback*)data; 1134 | return call_lua(lcb->L,lcb->callback,idx,text,flags); 1135 | } 1136 | 1137 | void lcb_allocate_buffer(void *data, int size) { 1138 | LuaCallback *lcb = (LuaCallback*)data; 1139 | lcb->buf = malloc(size); 1140 | lcb->bufsz = size; 1141 | } 1142 | 1143 | void lcb_free(void *data) { 1144 | LuaCallback *lcb = (LuaCallback*)data; 1145 | if (! lcb) return; 1146 | if (lcb->buf) { 1147 | free(lcb->buf); 1148 | lcb->buf = NULL; 1149 | } 1150 | if (lcb->handle) { 1151 | CloseHandle(lcb->handle); 1152 | lcb->handle = NULL; 1153 | } 1154 | release_ref(lcb->L,lcb->callback); 1155 | } 1156 | 1157 | #define lcb_buf(data) ((LuaCallback *)data)->buf 1158 | #define lcb_bufsz(data) ((LuaCallback *)data)->bufsz 1159 | #define lcb_handle(data) ((LuaCallback *)data)->handle 1160 | 1161 | /// Thread object. This is returned by the @{File:read_async} method and the @{make_timer}, 1162 | // @{make_pipe_server} and @{watch_for_file_changes} functions. Useful to kill a thread 1163 | // and free associated resources. 1164 | // @type Thread 1165 | class Thread { 1166 | HANDLE thread; 1167 | LuaCallback *lcb; 1168 | 1169 | constructor (PLuaCallback lcb, HANDLE thread) { 1170 | this->lcb = lcb; 1171 | this->thread = thread; 1172 | } 1173 | 1174 | /// suspend this thread. 1175 | // @function suspend 1176 | def suspend() { 1177 | return push_bool(L, SuspendThread(this->thread) >= 0); 1178 | } 1179 | 1180 | /// resume a suspended thread. 1181 | // @function resume 1182 | def resume() { 1183 | return push_bool(L, ResumeThread(this->thread) >= 0); 1184 | } 1185 | 1186 | /// kill this thread. Generally considered a 'nuclear' option, but 1187 | // this implementation will free any associated callback references, buffers 1188 | // and handles. @{test-timer.lua} shows how a timer can be terminated. 1189 | // @function kill 1190 | def kill() { 1191 | BOOL ret = TerminateThread(this->thread,1); 1192 | lcb_free(this->lcb); 1193 | return push_bool(L,ret); 1194 | } 1195 | 1196 | /// set a thread's priority 1197 | // @param p positive integer to increase thread priority 1198 | // @function set_priority 1199 | def set_priority(Int p) { 1200 | return push_bool(L, SetThreadPriority(this->thread,p)); 1201 | } 1202 | 1203 | /// get a thread's priority 1204 | // @function get_priority 1205 | def get_priority() { 1206 | int res = GetThreadPriority(this->thread); 1207 | if (res != THREAD_PRIORITY_ERROR_RETURN) { 1208 | lua_pushinteger(L,res); 1209 | return 1; 1210 | } else { 1211 | return push_error(L); 1212 | } 1213 | } 1214 | /// wait for this thread to finish. 1215 | // @param timeout optional timeout in millisec; defaults to waiting indefinitely. 1216 | // @return this thread object 1217 | // @return either "OK" or "TIMEOUT" 1218 | // @function wait 1219 | def wait(Int timeout = 0) { 1220 | return push_wait(L,this->thread, TIMEOUT(timeout)); 1221 | } 1222 | 1223 | /// run callback when this thread is finished. 1224 | // @param callback the callback 1225 | // @param timeout optional timeout in millisec; defaults to waiting indefinitely. 1226 | // @return this thread object 1227 | // @return either "OK" or "TIMEOUT" 1228 | // @function wait_async 1229 | def wait_async(Value callback, Int timeout = 0) { 1230 | return push_wait_async(L,this->thread, TIMEOUT(timeout), callback); 1231 | } 1232 | 1233 | 1234 | def __gc() { 1235 | // lcb_free(this->lcb); concerned that this cd kick in prematurely! 1236 | CloseHandle(this->thread); 1237 | return 0; 1238 | } 1239 | } 1240 | 1241 | typedef LPTHREAD_START_ROUTINE TCB; 1242 | 1243 | int lcb_new_thread(TCB fun, void *data) { 1244 | LuaCallback *lcb = (LuaCallback*)data; 1245 | HANDLE thread = CreateThread(NULL,THREAD_STACK_SIZE,fun,data,0,NULL); 1246 | return push_new_Thread(lcb->L,lcb,thread); 1247 | } 1248 | 1249 | static void handle_waiter (LuaCallback *lcb) { 1250 | DWORD res = WaitForSingleObject(lcb->handle,lcb->bufsz); 1251 | lcb_call(lcb,0,res == WAIT_TIMEOUT ? "TIMEOUT" : "OK",0); 1252 | } 1253 | 1254 | static int push_wait_async(lua_State *L, HANDLE h, int timeout, int callback) { 1255 | LuaCallback *lcb = lcb_callback(NULL,L,callback); 1256 | lcb->handle = h; 1257 | lcb->bufsz = timeout; 1258 | return lcb_new_thread((TCB)handle_waiter,lcb); 1259 | } 1260 | 1261 | /// this represents a raw Windows file handle. 1262 | // The write handle may be distinct from the read handle. 1263 | // @type File 1264 | class File { 1265 | callback_data_ 1266 | HANDLE hWrite; 1267 | 1268 | constructor (HANDLE hread, HANDLE hwrite) { 1269 | lcb_handle(this) = hread; 1270 | this->hWrite = hwrite; 1271 | this->L = L; 1272 | lcb_allocate_buffer(this,FILE_BUFF_SIZE); 1273 | } 1274 | 1275 | /// write to a file. 1276 | // @param s text 1277 | // @return number of bytes written. 1278 | // @function write 1279 | def write(Str s) { 1280 | DWORD bytesWrote; 1281 | WriteFile(this->hWrite, s, lua_objlen(L,2), &bytesWrote, NULL); 1282 | lua_pushinteger(L,bytesWrote); 1283 | return 1; 1284 | } 1285 | 1286 | static BOOL raw_read (File *this) { 1287 | DWORD bytesRead = 0; 1288 | BOOL res = ReadFile(lcb_handle(this), lcb_buf(this), lcb_bufsz(this), &bytesRead, NULL); 1289 | lcb_buf(this)[bytesRead] = '\0'; 1290 | return res && bytesRead; 1291 | } 1292 | 1293 | /// read from a file. 1294 | // Please note that this is not buffered, and you will have to 1295 | // split into lines, etc yourself. 1296 | // @return text if successful, nil plus error otherwise. 1297 | // @function read 1298 | def read() { 1299 | if (raw_read(this)) { 1300 | lua_pushstring(L,lcb_buf(this)); 1301 | return 1; 1302 | } else { 1303 | return push_error(L); 1304 | } 1305 | } 1306 | 1307 | static void file_reader (File *this) { // background reader thread 1308 | int n; 1309 | do { 1310 | n = raw_read(this); 1311 | // empty buffer is passed at end - we can discard the callback then. 1312 | lcb_call (this,0,lcb_buf(this),n == 0 ? DISCARD : 0); 1313 | } while (n); 1314 | 1315 | } 1316 | 1317 | /// asynchronous read. 1318 | // @param callback function that will receive each chunk of text 1319 | // as it comes in. 1320 | // @return @{Thread} 1321 | // @function read_async 1322 | def read_async (Value callback) { 1323 | this->callback = make_ref(L,callback); 1324 | return lcb_new_thread((TCB)&file_reader,this); 1325 | } 1326 | 1327 | def close() { 1328 | if (this->hWrite != lcb_handle(this)) 1329 | CloseHandle(this->hWrite); 1330 | lcb_free(this); 1331 | return 0; 1332 | } 1333 | 1334 | def __gc () { 1335 | free(this->buf); 1336 | return 0; 1337 | } 1338 | } 1339 | 1340 | 1341 | /// Launching processes. 1342 | // @section Launch 1343 | 1344 | /// set an environment variable for any child processes. 1345 | // @{setenv.lua} shows how this also affects processes 1346 | // launched with @{os.execute} 1347 | // Note that this can't affect any system environment variables, see 1348 | // [here](http://msdn.microsoft.com/en-us/library/ms682653%28VS.85%29.aspx) 1349 | // for how to set these. 1350 | // @param name name of variable 1351 | // @param value value to set 1352 | // @function setenv 1353 | def setenv(Str name, Str value) { 1354 | WCHAR wname[256],wvalue[MAX_WPATH]; 1355 | return push_bool(L, SetEnvironmentVariableW(wconv(name),wconv(value))); 1356 | } 1357 | 1358 | /// Spawn a process. 1359 | // @param program the command-line (program + parameters) 1360 | // @param dir the working directory for the process (optional) 1361 | // @return @{Process} 1362 | // @return @{File} 1363 | // @function spawn_process 1364 | def spawn_process(Str program, StrNil dir) { 1365 | WCHAR wdir [MAX_WPATH]; 1366 | SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), 0, 0}; 1367 | SECURITY_DESCRIPTOR sd; 1368 | STARTUPINFOW si = { 1369 | sizeof(STARTUPINFOW), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 1370 | }; 1371 | HANDLE hPipeRead,hWriteSubProcess; 1372 | HANDLE hRead2,hPipeWrite; 1373 | BOOL running; 1374 | PROCESS_INFORMATION pi; 1375 | sa.bInheritHandle = TRUE; 1376 | sa.lpSecurityDescriptor = NULL; 1377 | InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); 1378 | SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE); 1379 | sa.nLength = sizeof(SECURITY_ATTRIBUTES); 1380 | sa.lpSecurityDescriptor = &sd; 1381 | 1382 | // Create pipe for output redirection 1383 | CreatePipe(&hPipeRead, &hPipeWrite, &sa, 0); 1384 | 1385 | // Create pipe for input redirection. In this code, you do not 1386 | // redirect the output of the child process, but you need a handle 1387 | // to set the hStdInput field in the STARTUP_INFO struct. For safety, 1388 | // you should not set the handles to an invalid handle. 1389 | 1390 | hRead2 = NULL; 1391 | CreatePipe(&hRead2, &hWriteSubProcess, &sa, 0); 1392 | 1393 | SetHandleInformation(hPipeRead, HANDLE_FLAG_INHERIT, 0); 1394 | SetHandleInformation(hWriteSubProcess, HANDLE_FLAG_INHERIT, 0); 1395 | 1396 | si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; 1397 | si.wShowWindow = SW_HIDE; 1398 | si.hStdInput = hRead2; 1399 | si.hStdOutput = hPipeWrite; 1400 | si.hStdError = hPipeWrite; 1401 | 1402 | running = CreateProcessW( 1403 | NULL, 1404 | (LPWSTR)wstring(program), 1405 | NULL, NULL, 1406 | TRUE, CREATE_NEW_PROCESS_GROUP, 1407 | NULL, 1408 | wconv(dir), 1409 | &si, &pi); 1410 | 1411 | if (running) { 1412 | CloseHandle(pi.hThread); 1413 | CloseHandle(hRead2); 1414 | CloseHandle(hPipeWrite); 1415 | push_new_Process(L,pi.dwProcessId,pi.hProcess); 1416 | push_new_File(L,hPipeRead,hWriteSubProcess); 1417 | return 2; 1418 | } else { 1419 | return push_error(L); 1420 | } 1421 | } 1422 | 1423 | /// execute a system command. 1424 | // This is like `os.execute()`, except that it works without ugly 1425 | // console flashing in Windows GUI applications. It additionally 1426 | // returns all text read from stdout and stderr. 1427 | // @param cmd a shell command (may include redirection, etc) 1428 | // @param unicode if 'unicode' force built-in commands to output in unicode; 1429 | // in this case the result is always UTF-8 1430 | // @return return code 1431 | // @return program output 1432 | // @function execute 1433 | 1434 | static void launcher(LuaCallback *lcb) { 1435 | lua_State *L = lcb->L; 1436 | lua_State *Lnew = lua_newthread(L); 1437 | push_ref(L,lcb->callback); 1438 | lua_xmove(L,Lnew,1); 1439 | push_ref(L, (int)lcb->bufsz); 1440 | lua_xmove(L, Lnew,1); 1441 | if (lua_pcall(Lnew,1,0,0) != 0) { 1442 | fprintf(stderr,"error %s\n",lua_tostring(Lnew,-1)); 1443 | } 1444 | lcb_free(lcb); 1445 | } 1446 | 1447 | /// launch a function in a new thread. 1448 | // @param fun a Lua function 1449 | // @param data any Lua value to be passed to function 1450 | // @return @{Thread} object 1451 | // @function thread 1452 | def thread(Value fun, Value data) { 1453 | LuaCallback *lcb = lcb_callback(NULL, L, fun); 1454 | lcb->bufsz = make_ref(L,data); 1455 | return lcb_new_thread((TCB)launcher,lcb); 1456 | } 1457 | 1458 | // Timer support ////////// 1459 | typedef struct { 1460 | callback_data_ 1461 | int msec; 1462 | } TimerData; 1463 | 1464 | static void timer_thread(TimerData *data) { // background timer thread 1465 | while (1) { 1466 | Sleep(data->msec); 1467 | // no parameters passed, but if we return true then we exit! 1468 | if (lcb_call(data,0,0,0)) 1469 | break; 1470 | } 1471 | } 1472 | 1473 | /// Asynchronous Timers. 1474 | // @section Timers 1475 | 1476 | /// Create an asynchronous timer. 1477 | // The callback can return true if it wishes to cancel the timer. 1478 | // @{test-sleep.lua} shows how you need to call @{sleep} at the end of 1479 | // a console application for these timers to work in the background. 1480 | // @param msec interval in millisec 1481 | // @param callback a function to be called at each interval. 1482 | // @return @{Thread} 1483 | // @function make_timer 1484 | def make_timer(Int msec, Value callback) { 1485 | TimerData *data = (TimerData *)malloc(sizeof(TimerData)); 1486 | data->msec = msec; 1487 | lcb_callback(data,L,callback); 1488 | return lcb_new_thread((TCB)&timer_thread,data); 1489 | } 1490 | 1491 | #define PSIZE 512 1492 | 1493 | typedef struct { 1494 | callback_data_ 1495 | const char *pipename; 1496 | } PipeServerParms; 1497 | 1498 | static void pipe_server_thread(PipeServerParms *parms) { 1499 | while (1) { 1500 | BOOL connected; 1501 | HANDLE hPipe = CreateNamedPipe( 1502 | parms->pipename, // pipe named 1503 | PIPE_ACCESS_DUPLEX, // read/write access 1504 | PIPE_WAIT, // blocking mode 1505 | 255, 1506 | PSIZE, // output buffer size 1507 | PSIZE, // input buffer size 1508 | 0, // client time-out 1509 | NULL); // default security attribute 1510 | 1511 | if (hPipe == INVALID_HANDLE_VALUE) { 1512 | // could not create named pipe - callback is passed nil, err msg. 1513 | lua_pushnil(parms->L); 1514 | lcb_call(parms,-1,last_error(0),REF_IDX | DISCARD); 1515 | return; 1516 | } 1517 | // Wait for the client to connect; if it succeeds, 1518 | // the function returns a nonzero value. If the function 1519 | // returns zero, GetLastError returns ERROR_PIPE_CONNECTED. 1520 | 1521 | connected = ConnectNamedPipe(hPipe, NULL) ? 1522 | TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); 1523 | 1524 | if (connected) { 1525 | push_new_File(parms->L,hPipe,hPipe); 1526 | lcb_call(parms,-1,0,REF_IDX); // pass it a new File reference 1527 | } else { 1528 | CloseHandle(hPipe); 1529 | } 1530 | } 1531 | } 1532 | 1533 | /// Dealing with named pipes. 1534 | // @section Pipes 1535 | 1536 | /// open a pipe for reading and writing. 1537 | // @param pipename the pipename (default is "\\\\.\\pipe\\luawinapi") 1538 | // @function open_pipe 1539 | def open_pipe(Str pipename = "\\\\.\\pipe\\luawinapi") { 1540 | HANDLE hPipe = CreateFile( 1541 | pipename, 1542 | GENERIC_READ | // read and write access 1543 | GENERIC_WRITE, 1544 | 0, // no sharing 1545 | NULL, // default security attributes 1546 | OPEN_EXISTING, // opens existing pipe 1547 | 0, // default attributes 1548 | NULL); // no template file 1549 | if (hPipe == INVALID_HANDLE_VALUE) { 1550 | return push_error(L); 1551 | } else { 1552 | return push_new_File(L,hPipe,hPipe); 1553 | } 1554 | } 1555 | 1556 | /// create a named pipe server. 1557 | // This goes into a background loop, and accepts client connections. 1558 | // For each new connection, the callback will be called with a File 1559 | // object for reading and writing to the client. 1560 | // @param callback a function that will be passed a File object 1561 | // @param pipename Must be of the form \\.\pipe\name, defaults to 1562 | // \\.\pipe\luawinapi. 1563 | // @return @{Thread}. 1564 | // @function make_pipe_server 1565 | def make_pipe_server(Value callback, Str pipename = "\\\\.\\pipe\\luawinapi") { 1566 | PipeServerParms *psp = (PipeServerParms*)malloc(sizeof(PipeServerParms)); 1567 | lcb_callback(psp,L,callback); 1568 | psp->pipename = pipename; 1569 | return lcb_new_thread((TCB)&pipe_server_thread,psp); 1570 | } 1571 | 1572 | 1573 | /// Drive information and directories. 1574 | // @section Directories 1575 | 1576 | /// the short path name of a directory or file. 1577 | // This is always in ASCII, 8.3 format. This function will create the 1578 | // file first if it does not exist; the result can be used to open 1579 | // files with unicode names (see @{testshort.lua}) 1580 | // @param path multibyte encoded file path 1581 | // @return ASCII 8.3 format file path 1582 | // @function short_path 1583 | def short_path(Str path) { 1584 | WCHAR wpath[MAX_WPATH]; 1585 | HANDLE hFile; 1586 | int res; 1587 | wconv(path); 1588 | // if the file doesn't exist, then force its creation 1589 | hFile = CreateFileW(wpath, 1590 | GENERIC_WRITE, 1591 | FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, 1592 | CREATE_NEW, 1593 | FILE_ATTRIBUTE_NORMAL, 1594 | NULL); 1595 | if (hFile == INVALID_HANDLE_VALUE) { 1596 | if (GetLastError() != ERROR_FILE_EXISTS) // that error is fine! 1597 | return push_perror(L,"createfile"); 1598 | } else { // if we created it successfully, then close. 1599 | CloseHandle(hFile); 1600 | } 1601 | res = GetShortPathNameW(wpath,wbuff,sizeof(wbuff)); 1602 | if (res > 0) { 1603 | return push_wstring(L,wbuff); 1604 | } else { 1605 | return push_error(L); 1606 | } 1607 | } 1608 | 1609 | /// get a temporary filename. 1610 | // (Don't use os.tmpname) 1611 | // @return full path within temporary files directory. 1612 | // @function temp_name 1613 | 1614 | /// delete a file or directory. 1615 | // @param file may be a wildcard 1616 | // @function delete_file_or_dir 1617 | 1618 | /// make a directory. 1619 | // Will make necessary subpaths if command extensions are enabled. 1620 | // @function make_dir 1621 | 1622 | /// remove a directory. 1623 | // @param dir the directory 1624 | // @param tree if true, clean out the directory tree 1625 | // @function remove_dir 1626 | 1627 | /// iterator over directory contents. 1628 | // @usage for f in winapi.files 'dir\\*.txt' do print(f) end 1629 | // @param mask a file mask like "*.txt" 1630 | // @param subdirs iterate over subdirectories (default no) 1631 | // @param attrib iterate over items with given attribute (as in dir /A:) 1632 | // @see files.lua 1633 | // @function files 1634 | 1635 | /// iterate over subdirectories 1636 | // @param file mask like "mydirs\\t*" 1637 | // @param subdirs iterate over subdirectories (default no) 1638 | // @see files 1639 | // @function dirs 1640 | 1641 | /// get all the drives on this computer. 1642 | // An example is @{drives.lua} 1643 | // @return a table of drive names 1644 | // @function get_logical_drives 1645 | def get_logical_drives() { 1646 | int i, lasti = 0, k = 1; 1647 | WCHAR dbuff[MAX_WPATH]; 1648 | LPWSTR p = dbuff; 1649 | DWORD size = GetLogicalDriveStringsW(sizeof(dbuff),dbuff); 1650 | lua_newtable(L); 1651 | for (i = 0; i < size; i++) { 1652 | if (dbuff[i] == '\0') { 1653 | push_wstring_l(L,p, i - lasti); 1654 | lua_rawseti(L,-2,k++); 1655 | p = dbuff + i+1; 1656 | lasti = i+1; 1657 | } 1658 | } 1659 | return 1; 1660 | } 1661 | 1662 | /// get the type of the given drive. 1663 | // @param root root of drive (e.g. 'c:\\') 1664 | // @return one of the following: unknown, none, removable, fixed, remote, 1665 | // cdrom, ramdisk. 1666 | // @function get_drive_type 1667 | def get_drive_type(Str root) { 1668 | UINT res = GetDriveType(root); 1669 | const char *type = "?"; 1670 | switch(res) { 1671 | case DRIVE_UNKNOWN: type = "unknown"; break; 1672 | case DRIVE_NO_ROOT_DIR: type = "none"; break; 1673 | case DRIVE_REMOVABLE: type = "removable"; break; 1674 | case DRIVE_FIXED: type = "fixed"; break; 1675 | case DRIVE_REMOTE: type = "remote"; break; 1676 | case DRIVE_CDROM: type = "cdrom"; break; 1677 | case DRIVE_RAMDISK: type = "ramdisk"; break; 1678 | } 1679 | lua_pushstring(L,type); 1680 | return 1; 1681 | } 1682 | 1683 | /// get the free disk space. 1684 | // @param root the root of the drive (e.g. 'd:\\') 1685 | // @return free space in kB 1686 | // @return total space in kB 1687 | // @function get_disk_free_space 1688 | def get_disk_free_space(Str root) { 1689 | ULARGE_INTEGER freebytes, totalbytes; 1690 | if (! GetDiskFreeSpaceEx(root,&freebytes,&totalbytes,NULL)) { 1691 | return push_error(L); 1692 | } 1693 | lua_pushnumber(L,freebytes.QuadPart/1024); 1694 | lua_pushnumber(L,totalbytes.QuadPart/1024); 1695 | return 2; 1696 | } 1697 | 1698 | /// get the network resource associated with this drive. 1699 | // @param root drive name in the form 'X:' 1700 | // @return UNC name 1701 | // @function get_disk_network_name 1702 | def get_disk_network_name(Str root) { 1703 | DWORD size = sizeof(wbuff); 1704 | DWORD res = WNetGetConnectionW(wstring(root),wbuff,&size); 1705 | if (res == NO_ERROR) { 1706 | return push_wstring(L,wbuff); 1707 | } else { 1708 | return push_error(L); 1709 | } 1710 | } 1711 | 1712 | // Directory change notification /////// 1713 | 1714 | typedef struct { 1715 | callback_data_ 1716 | DWORD how; 1717 | DWORD subdirs; 1718 | } FileChangeParms; 1719 | 1720 | static void file_change_thread(FileChangeParms *fc) { // background file monitor thread 1721 | while (1) { 1722 | int next, offset; 1723 | DWORD bytes; 1724 | // This fills in some gaps: 1725 | // http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw_19.html 1726 | if (! ReadDirectoryChangesW(lcb_handle(fc),lcb_buf(fc),lcb_bufsz(fc), 1727 | fc->subdirs, fc->how, &bytes,NULL,NULL)) { 1728 | lcb_call(fc,-1,last_error(0),INTEGER | DISCARD); 1729 | break; 1730 | } 1731 | next = 0; 1732 | offset = 0; 1733 | do { 1734 | int outchars; 1735 | char outbuff[MAX_PATH]; 1736 | PFILE_NOTIFY_INFORMATION pni = (PFILE_NOTIFY_INFORMATION)(lcb_buf(fc)+offset); 1737 | outchars = WideCharToMultiByte( 1738 | get_encoding(), 0, 1739 | pni->FileName, 1740 | pni->FileNameLength/2, // it's bytes, not number of characters! 1741 | outbuff,sizeof(outbuff), 1742 | NULL,NULL); 1743 | if (outchars == 0) { 1744 | lcb_call(fc,-1,"wide char conversion borked",INTEGER | DISCARD); 1745 | break; 1746 | } 1747 | outbuff[outchars] = '\0'; // not null-terminated! 1748 | // pass the action that occurred and the file name 1749 | lcb_call(fc,pni->Action,outbuff,INTEGER); 1750 | next = pni->NextEntryOffset; 1751 | offset += next; 1752 | } while (next != 0); 1753 | } 1754 | } 1755 | 1756 | //// start watching a directory. 1757 | // @param dir the directory 1758 | // @param how what events to monitor. Can be a sum of these flags: 1759 | // 1760 | // * `FILE_NOTIFY_CHANGE_FILE_NAME` 1761 | // * `FILE_NOTIFY_CHANGE_DIR_NAME` 1762 | // * `FILE_NOTIFY_CHANGE_LAST_WRITE` 1763 | // 1764 | // @param subdirs whether subdirectories should be monitored 1765 | // @param callback a function which will receive the kind of change 1766 | // plus the filename that changed. The change will be one of these: 1767 | // 1768 | // * `FILE_ACTION_ADDED` 1769 | // * `FILE_ACTION_REMOVED` 1770 | // * `FILE_ACTION_MODIFIED` 1771 | // * `FILE_ACTION_RENAMED_OLD_NAME` 1772 | // * `FILE_ACTION_RENAMED_NEW_NAME` 1773 | // 1774 | // @return a thread object. 1775 | // @see test-watcher.lua 1776 | // @function watch_for_file_changes 1777 | def watch_for_file_changes (Str dir, Int how, Boolean subdirs, Value callback) { 1778 | FileChangeParms *fc = (FileChangeParms*)malloc(sizeof(FileChangeParms)); 1779 | lcb_callback(fc,L,callback); 1780 | fc->how = how; 1781 | fc->subdirs = subdirs; 1782 | lcb_handle(fc) = CreateFileW(wstring(dir), 1783 | FILE_LIST_DIRECTORY, 1784 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 1785 | NULL, 1786 | OPEN_ALWAYS, 1787 | FILE_FLAG_BACKUP_SEMANTICS, 1788 | NULL 1789 | ); 1790 | if (lcb_handle(fc) == INVALID_HANDLE_VALUE) { 1791 | return push_error(L); 1792 | } 1793 | lcb_allocate_buffer(fc,2048); 1794 | return lcb_new_thread((TCB)&file_change_thread,fc); 1795 | } 1796 | 1797 | /// Class representing Windows registry keys. 1798 | // @type Regkey 1799 | class Regkey { 1800 | HKEY key; 1801 | 1802 | constructor (HKEY k) { 1803 | this->key = k; 1804 | } 1805 | 1806 | /// set the string value of a name. 1807 | // @param name the name 1808 | // @param val the string value 1809 | // @param type one of `REG_BINARY`,`REG_DWORD`,`REG_SZ`,`REG_MULTI_SZ`,`REG_EXPAND_SZ` 1810 | // @function set_value 1811 | def set_value(Str name, Value val, Int type=REG_SZ) { 1812 | int sz; 1813 | DWORD ival; 1814 | LONG res; 1815 | const char *str; 1816 | const BYTE *data; 1817 | WCHAR wname[MAX_KEYS]; 1818 | wstring_buff(name,wname,sizeof(wname)); 1819 | if (lua_isstring(L,val)) { 1820 | if (type == REG_DWORD) { 1821 | return push_error_msg(L, "parameter must be a number for REG_DWORD"); 1822 | } 1823 | str = lua_tostring(L,val); 1824 | if (type != REG_BINARY) { 1825 | WStr res = wstring(str); 1826 | sz = (lstrlenW(res)+1)*sizeof(WCHAR); 1827 | data = (const BYTE *)res; 1828 | } else { 1829 | sz = lua_objlen(L,val); 1830 | data = (const BYTE *)str; 1831 | } 1832 | } else { 1833 | ival = (DWORD)lua_tonumber(L,val); 1834 | data = (const BYTE *)&ival; 1835 | sz = sizeof(DWORD); 1836 | } 1837 | res = RegSetValueExW(this->key,wname,0,type,data,sz); 1838 | if (res == ERROR_SUCCESS) { 1839 | return push_ok(L); 1840 | } else { 1841 | return push_error_code(L, res); 1842 | } 1843 | } 1844 | 1845 | /// get the value and type of a name. 1846 | // @param name the name (can be empty for the default value) 1847 | // @return the value (either a string or a number) 1848 | // @return the type 1849 | // @function get_value 1850 | def get_value(Str name = "") { 1851 | DWORD type,size = sizeof(wbuff); 1852 | void *data = wbuff; 1853 | if (RegQueryValueExW(this->key,wstring(name),0,&type,data,&size) != ERROR_SUCCESS) { 1854 | return push_error(L); 1855 | } 1856 | if (type == REG_BINARY) { 1857 | lua_pushlstring(L,(const char *)data,size); 1858 | } else if (type == REG_EXPAND_SZ || type == REG_SZ) { 1859 | push_wstring(L,wbuff); //,size); 1860 | } else { 1861 | lua_pushnumber(L,*(unsigned long *)data); 1862 | } 1863 | lua_pushinteger(L,type); 1864 | return 2; 1865 | 1866 | } 1867 | 1868 | def delete_key(Str name) { 1869 | if (RegDeleteKeyW(this->key,wstring(name)) == ERROR_SUCCESS) { 1870 | lua_pushboolean(L,1); 1871 | } else { 1872 | return push_error(L); 1873 | } 1874 | return 1; 1875 | } 1876 | 1877 | /// enumerate the subkeys of a key. 1878 | // @return a table of key names 1879 | // @function get_keys 1880 | def get_keys() { 1881 | int i = 0; 1882 | LONG res; 1883 | DWORD size; 1884 | lua_newtable(L); 1885 | while (1) { 1886 | size = sizeof(wbuff); 1887 | res = RegEnumKeyExW(this->key,i,wbuff,&size,NULL,NULL,NULL,NULL); 1888 | if (res != ERROR_SUCCESS) break; 1889 | push_wstring(L,wbuff); 1890 | lua_rawseti(L,-2,i+1); 1891 | ++i; 1892 | } 1893 | if (res != ERROR_NO_MORE_ITEMS) { 1894 | lua_pop(L,1); 1895 | return push_error(L); 1896 | } 1897 | return 1; 1898 | } 1899 | 1900 | /// close this key. 1901 | // Although this will happen when garbage collection happens, it 1902 | // is good practice to call this explicitly. 1903 | // @function close 1904 | def close() { 1905 | RegCloseKey(this->key); 1906 | this->key = NULL; 1907 | return 0; 1908 | } 1909 | 1910 | /// flush the key. 1911 | // Considered an expensive function; use it only when you have 1912 | // to guarantee modification. 1913 | // @function flush 1914 | def flush() { 1915 | return push_bool(L,RegFlushKey(this->key)); 1916 | } 1917 | 1918 | def __gc() { 1919 | if (this->key != NULL) 1920 | RegCloseKey(this->key); 1921 | return 0; 1922 | } 1923 | 1924 | } 1925 | 1926 | /// Registry Functions. 1927 | // @section Registry 1928 | 1929 | /// Open a registry key. 1930 | // @{test-reg.lua} shows reading a registry value and enumerating subkeys. 1931 | // @param path the full registry key 1932 | // e.g `[[HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion]]` 1933 | // @param writeable true if you want to set values 1934 | // @return @{Regkey} 1935 | // @function open_reg_key 1936 | def open_reg_key(Str path, Boolean writeable) { 1937 | HKEY hKey; 1938 | DWORD access; 1939 | char kbuff[1024]; 1940 | hKey = split_registry_key(path,kbuff); 1941 | if (hKey == NULL) { 1942 | return push_error_msg(L,"unrecognized registry key"); 1943 | } 1944 | access = writeable ? KEY_ALL_ACCESS : (KEY_READ | KEY_ENUMERATE_SUB_KEYS); 1945 | if (RegOpenKeyExW(hKey,wstring(kbuff),0,access,&hKey) == ERROR_SUCCESS) { 1946 | return push_new_Regkey(L,hKey); 1947 | } else { 1948 | return push_error(L); 1949 | } 1950 | } 1951 | 1952 | /// Create a registry key. 1953 | // @param path the full registry key 1954 | // @return @{Regkey} 1955 | // @function create_reg_key 1956 | def create_reg_key (Str path) { 1957 | char kbuff[1024]; 1958 | HKEY hKey = split_registry_key(path,kbuff); 1959 | if (hKey == NULL) { 1960 | return push_error_msg(L,"unrecognized registry key"); 1961 | } 1962 | if (RegCreateKeyExW(hKey,wstring(kbuff),0,NULL,0,KEY_ALL_ACCESS,NULL,&hKey,NULL)) { 1963 | return push_new_Regkey(L,hKey); 1964 | } else { 1965 | return push_error(L); 1966 | } 1967 | } 1968 | 1969 | lua { 1970 | function winapi.execute(cmd,unicode) 1971 | local comspec = os.getenv('COMSPEC') 1972 | if unicode ~= 'unicode' then 1973 | cmd = comspec ..' /c '..cmd 1974 | local P,f = winapi.spawn_process(cmd) 1975 | if not P then return nil,f end 1976 | local txt = f:read() 1977 | local out = {} 1978 | while txt do 1979 | table.insert(out,txt) 1980 | txt = f:read() 1981 | end 1982 | return P:wait():get_exit_code(),table.concat(out,'') 1983 | else 1984 | local tmpfile,res,f,out = winapi.temp_name() 1985 | cmd = comspec..' /u /c '..cmd..' > "'..tmpfile..'"' 1986 | local P,err = winapi.spawn_process(cmd) 1987 | if not P then return nil,err end 1988 | res = P:wait():get_exit_code() 1989 | f = io.open(tmpfile) 1990 | out = f:read '*a' 1991 | f:close() 1992 | os.remove(tmpfile) 1993 | out, err = winapi.encode(winapi.CP_UTF16,winapi.CP_UTF8,out) 1994 | if err then return nil,err end 1995 | return res,out 1996 | end 1997 | end 1998 | function winapi.make_name_matcher(text) 1999 | return function(w) return tostring(w):match(text) end 2000 | end 2001 | function winapi.make_class_matcher(classname) 2002 | return function(w) return w:get_class_name():match(classname) end 2003 | end 2004 | function winapi.find_window_ex(match) 2005 | local res 2006 | winapi.enum_windows(function(w) 2007 | if match(w) then res = w end 2008 | end) 2009 | return res 2010 | end 2011 | function winapi.find_all_windows(match) 2012 | local res = {} 2013 | winapi.enum_windows(function(w) 2014 | if match(w) then res[#res+1] = w end 2015 | end) 2016 | return res 2017 | end 2018 | function winapi.find_window_match(text) 2019 | return winapi.find_window_ex(winapi.make_name_matcher(text)) 2020 | end 2021 | function winapi.temp_name () return os.getenv('TEMP')..os.tmpname() end 2022 | local function exec_cmd (cmd,arg) 2023 | local res,err = winapi.execute(cmd..' "'..arg..'"') 2024 | if res == 0 then return true 2025 | else return nil,err 2026 | end 2027 | end 2028 | function winapi.make_dir(dir) return exec_cmd('mkdir',dir) end 2029 | function winapi.remove_dir(dir,tree) return exec_cmd('rmdir '.. ((tree and '/S /Q') or ''),dir) end 2030 | function winapi.delete_file_or_dir(file) return exec_cmd('del',file) end 2031 | function winapi.files(mask,subdirs,attrib) 2032 | local flags = '/B ' 2033 | if subdirs then flags = flags..' /S' end 2034 | if attrib then flags = flags..' /A:'..attrib end 2035 | local ret, text = winapi.execute('dir '..flags..' "'..mask..'"','unicode') 2036 | if ret ~= 0 then return nil,text end 2037 | return text:gmatch('[^\r\n]+') 2038 | end 2039 | function winapi.dirs(mask,subdirs) return winapi.files(mask,subdirs,'D') end 2040 | } 2041 | 2042 | initial init_mutex { 2043 | setup_mutex(); 2044 | return 0; 2045 | } 2046 | 2047 | /*** Constants. 2048 | The following constants are available: 2049 | 2050 | * CP_ACP, (valid values for encoding) 2051 | * CP_UTF8, 2052 | * CP_UTF16, 2053 | * SW_HIDE, (Window operations for Window.show) 2054 | * SW_MAXIMIZE, 2055 | * SW_MINIMIZE, 2056 | * SW_SHOWNORMAL, 2057 | * VK_BACK, 2058 | * VK_TAB, 2059 | * VK_RETURN, 2060 | * VK_SPACE, 2061 | * VK_PRIOR, 2062 | * VK_NEXT, 2063 | * VK_END, 2064 | * VK_HOME, 2065 | * VK_LEFT, 2066 | * VK_UP, 2067 | * VK_RIGHT, 2068 | * VK_DOWN, 2069 | * VK_INSERT, 2070 | * VK_DELETE, 2071 | * VK_ESCAPE, 2072 | * VK_F1, 2073 | * VK_F2, 2074 | * VK_F3, 2075 | * VK_F4, 2076 | * VK_F5, 2077 | * VK_F6, 2078 | * VK_F7, 2079 | * VK_F8, 2080 | * VK_F9, 2081 | * VK_F10, 2082 | * VK_F11, 2083 | * VK_F12, 2084 | * FILE\_NOTIFY\_CHANGE\_FILE\_NAME (these are input flags for watch\_for\_file\_changes) 2085 | * FILE\_NOTIFY\_CHANGE\_DIR\_NAME 2086 | * FILE\_NOTIFY\_CHANGE\_LAST\_WRITE 2087 | * FILE\_ACTION\_ADDED (these describe the change: first argument of callback) 2088 | * FILE\_ACTION\_REMOVED 2089 | * FILE\_ACTION\_MODIFIED 2090 | * FILE\_ACTION\_RENAMED\_OLD\_NAME 2091 | * FILE\_ACTION\_RENAMED\_NEW\_NAME 2092 | 2093 | @section constants 2094 | */ 2095 | 2096 | /// useful Windows API constants 2097 | // @table constants 2098 | 2099 | #define CP_UTF16 -1 2100 | 2101 | 2102 | constants { 2103 | CP_ACP, 2104 | CP_UTF8, 2105 | CP_UTF16, 2106 | SW_HIDE, 2107 | SW_MAXIMIZE, 2108 | SW_MINIMIZE, 2109 | SW_SHOWNORMAL, 2110 | SW_SHOWNOACTIVATE, 2111 | SW_SHOW, 2112 | SW_RESTORE, 2113 | VK_BACK, 2114 | VK_TAB, 2115 | VK_RETURN, 2116 | VK_SPACE, 2117 | VK_PRIOR, 2118 | VK_NEXT, 2119 | VK_END, 2120 | VK_HOME, 2121 | VK_LEFT, 2122 | VK_UP, 2123 | VK_RIGHT, 2124 | VK_DOWN, 2125 | VK_INSERT, 2126 | VK_DELETE, 2127 | VK_ESCAPE, 2128 | VK_F1, 2129 | VK_F2, 2130 | VK_F3, 2131 | VK_F4, 2132 | VK_F5, 2133 | VK_F6, 2134 | VK_F7, 2135 | VK_F8, 2136 | VK_F9, 2137 | VK_F10, 2138 | VK_F11, 2139 | VK_F12, 2140 | FILE_NOTIFY_CHANGE_FILE_NAME, 2141 | FILE_NOTIFY_CHANGE_DIR_NAME, 2142 | FILE_NOTIFY_CHANGE_LAST_WRITE, 2143 | FILE_ACTION_ADDED, 2144 | FILE_ACTION_REMOVED, 2145 | FILE_ACTION_MODIFIED, 2146 | FILE_ACTION_RENAMED_OLD_NAME, 2147 | FILE_ACTION_RENAMED_NEW_NAME, 2148 | WIN_NOACTIVATE, 2149 | WIN_NOMOVE, 2150 | WIN_NOSIZE, 2151 | WIN_SHOWWINDOW, 2152 | WIN_NOZORDER, 2153 | WIN_BOTTOM, 2154 | WIN_NOTOPMOST, 2155 | WIN_TOP, 2156 | WIN_TOPMOST, 2157 | REG_BINARY, 2158 | REG_DWORD, 2159 | REG_SZ, 2160 | REG_MULTI_SZ, 2161 | REG_EXPAND_SZ 2162 | } 2163 | 2164 | } 2165 | -------------------------------------------------------------------------------- /wutils.c: -------------------------------------------------------------------------------- 1 | #define WINDOWS_LEAN_AND_MEAN 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define MAX_KEY MAX_PATH 10 | 11 | #include "wutils.h" 12 | 13 | #define eq(s1,s2) (strcmp(s1,s2)==0) 14 | 15 | /// make a reference to a Lua object. 16 | // @param L the state 17 | // @param idx the index of the value on the stack. 18 | // @return a reference (an integer value) 19 | // @function make_ref 20 | Ref make_ref(lua_State *L, int idx) { 21 | lua_pushvalue(L,idx); 22 | return luaL_ref(L,LUA_REGISTRYINDEX); 23 | } 24 | 25 | /// release a reference to a Lua value. 26 | // @param L the state 27 | // @param ref the reference 28 | // @function release_ref 29 | void release_ref(lua_State *L, Ref ref) { 30 | luaL_unref(L,LUA_REGISTRYINDEX,ref); 31 | } 32 | 33 | /// push a referenced value on the stack. 34 | // @param L the state 35 | // @param ref the reference 36 | // @return 1; the value is on the stack 37 | // @function push_ref 38 | int push_ref(lua_State *L, Ref ref) { 39 | lua_rawgeti(L,LUA_REGISTRYINDEX,ref); 40 | return 1; 41 | } 42 | 43 | const char *last_error(int err) { 44 | static char errbuff[256]; 45 | int sz; 46 | if (err == 0) { 47 | err = GetLastError(); 48 | } 49 | sz = FormatMessage( 50 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 51 | NULL,err, 52 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language 53 | errbuff, 256, NULL 54 | ); 55 | errbuff[sz-2] = '\0'; // strip the \r\n 56 | return errbuff; 57 | } 58 | 59 | /// push a error message. 60 | // @param L the state 61 | // @param msg a message string 62 | // @return 2; `nil` and the message on the stack 63 | // @function push_error_msg 64 | int push_error_msg(lua_State *L, const char *msg) { 65 | lua_pushnil(L); 66 | lua_pushstring(L,msg); 67 | return 2; 68 | } 69 | 70 | /// push the last Windows error. 71 | // @param L the state 72 | // @return 2; 'nil' and the message 73 | // @function push_error 74 | int push_error(lua_State *L) { 75 | return push_error_msg(L,last_error(0)); 76 | } 77 | 78 | /// push the last Windows error with a prefix. 79 | // @param L the state 80 | // @return 2; 'nil' and the message 81 | // @function push_error 82 | int push_perror(lua_State *L, const char *prefix) { 83 | char buff[512]; 84 | sprintf(buff,"%s: %s",prefix,last_error(0)); 85 | return push_error_msg(L,buff); 86 | } 87 | 88 | /// push a particular Windows error. 89 | // @param L the state 90 | // @param err the error code 91 | // @return 2; 'nil' and the message 92 | // @function push_error 93 | int push_error_code(lua_State *L, int err) { 94 | return push_error_msg(L,last_error(err)); 95 | } 96 | 97 | /// push a true value. 98 | // @param L the state 99 | // @return 1; a boolean true value 100 | // @function push_ok 101 | int push_ok(lua_State *L) { 102 | lua_pushboolean(L,1); 103 | return 1; 104 | } 105 | 106 | /// push error if needed. 107 | // @param L the state 108 | // @param bval 0 or 1 109 | // @return 1; a boolean true value, or 2, `nil`, last Windows error 110 | // @function push_bool 111 | int push_bool(lua_State *L, int bval) { 112 | if (bval) { 113 | return push_ok(L); 114 | } else { 115 | return push_error(L); 116 | } 117 | } 118 | 119 | BOOL call_lua_direct(lua_State *L, Ref ref, int idx, const char *text, int flags) { 120 | BOOL res,ipush = 1; 121 | // push the function 122 | push_ref(L,ref); 123 | 124 | // first argument is optional, it may be a stack reference or an integer 125 | if (flags & REF_IDX) 126 | lua_pushvalue(L,idx); 127 | else if (flags & INTEGER) 128 | lua_pushinteger(L,idx); 129 | else 130 | ipush = 0; 131 | 132 | // there may be text - if so, we are responsible for cleaning it up! 133 | if (text != NULL) { 134 | lua_pushstring(L,text); 135 | ++ipush; 136 | free((char*)text); 137 | } 138 | 139 | lua_call(L, ipush, 1); 140 | res = lua_toboolean(L,-1); 141 | 142 | // optionally dispose of the function 143 | if (flags & DISCARD) { 144 | release_ref(L,ref); 145 | } 146 | return res; 147 | } 148 | 149 | // Calling back to Lua ///// 150 | // For console applications, we just use a mutex to ensure that Lua will not 151 | // be re-entered, but if use_gui() is called, we use a message window to 152 | // make sure that the callback happens on the main GUI thread. 153 | 154 | typedef struct { 155 | lua_State *L; 156 | Ref ref; 157 | int idx; 158 | const char *text; 159 | int flags; 160 | } LuaCallParms; 161 | 162 | #define MY_INTERNAL_LUA_MESSAGE WM_USER+42 163 | 164 | static LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 165 | { 166 | WNDPROC lpPrevWndProc; 167 | if (uMsg == MY_INTERNAL_LUA_MESSAGE) { 168 | BOOL res; 169 | LuaCallParms *P = (LuaCallParms*)lParam; 170 | res = call_lua_direct(P->L,P->ref,P->idx,P->text,P->flags); 171 | free(P); 172 | return res; 173 | } 174 | 175 | lpPrevWndProc = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA); 176 | if (lpPrevWndProc) 177 | return CallWindowProc(lpPrevWndProc, hwnd, uMsg, wParam, lParam); 178 | 179 | return DefWindowProc(hwnd, uMsg, wParam, lParam); 180 | } 181 | 182 | static BOOL s_use_mutex = TRUE; 183 | static HWND hMessageWin = NULL; 184 | 185 | /// start a message window for GUI Lua dispatching. 186 | // @function make_message_windowS 187 | void make_message_window() { 188 | if (hMessageWin == NULL) { 189 | LONG_PTR subclassedProc; 190 | s_use_mutex = FALSE; 191 | hMessageWin = CreateWindow( 192 | "STATIC", "winapi_Spawner_Dispatcher", 193 | 0, 0, 0, 0, 0, 0, 0, GetModuleHandle(NULL), 0 194 | ); 195 | subclassedProc = SetWindowLongPtr(hMessageWin, GWLP_WNDPROC, (LONG_PTR)WndProc); 196 | SetWindowLongPtr(hMessageWin, GWLP_USERDATA, subclassedProc); 197 | } 198 | } 199 | 200 | static HANDLE hMutex = NULL; 201 | 202 | void lock_mutex() { 203 | WaitForSingleObject(hMutex,INFINITE); 204 | } 205 | 206 | void setup_mutex() { 207 | hMutex = CreateMutex(NULL,TRUE,NULL); 208 | } 209 | 210 | void release_mutex() { 211 | ReleaseMutex(hMutex); 212 | } 213 | 214 | // this is a useful function to call a Lua function within an exclusive 215 | // mutex lock. There are two parameters: 216 | // 217 | // - the first can be zero, negative or postive. If zero, nothing happens. If 218 | // negative, it's assumed to be an index to a value on the stack; if positive, 219 | // assumed to be an integer value. 220 | // - the second can be NULL or some text. If NULL, nothing is pushed. 221 | // 222 | 223 | /// call a Lua function. 224 | // This ensures that only one Lua function can be entered at any time, controlled 225 | // by a mutex. If in 'GUI mode' then the Lua function is furthermore called 226 | // from the GUI state. 227 | // @param L the state 228 | // @param ref a reference to the function 229 | // @param idx a stack index: if greater than zero, pass value to function 230 | // @param text a string: if not NULL, pass this string to the function 231 | // @param flags if DISCARD remove the reference after calling. If INTEGER, treat 232 | // idx as an integer. If REF_IDX treat idx as a stack reference. 233 | // @function call_lua 234 | BOOL call_lua(lua_State *L, Ref ref, int idx, const char *text, int flags) { 235 | BOOL res; 236 | if (text) { 237 | size_t len = strlen(text); 238 | char *mtext = (char *)malloc(len+1); 239 | memcpy(mtext,text,len+1); 240 | text = mtext; 241 | } 242 | if (s_use_mutex) { 243 | lock_mutex(); 244 | res = call_lua_direct(L,ref,idx,text,flags); 245 | release_mutex(); 246 | } else { 247 | LuaCallParms *parms = (LuaCallParms*)malloc(sizeof(LuaCallParms)); 248 | parms->L = L; 249 | parms->ref = ref; 250 | parms->idx = idx; 251 | parms->text = text; 252 | parms->flags = flags; 253 | PostMessage(hMessageWin,MY_INTERNAL_LUA_MESSAGE,0,(LPARAM)parms); 254 | res = FALSE; // for now 255 | } 256 | 257 | return res; 258 | } 259 | 260 | static int current_encoding = CP_ACP; 261 | 262 | /// set the encoding. 263 | // Will be one of `CP_ACP` or `CP_UTF8` 264 | // @param e 265 | // @function set_encoding 266 | void set_encoding(int e) { 267 | current_encoding = e; 268 | } 269 | 270 | /// get the encoding. 271 | // @return current encoding 272 | // @function set_encoding 273 | int get_encoding() { 274 | return current_encoding; 275 | } 276 | 277 | /// convert text to UTF-16 depending on encoding. 278 | // @param text the input multi-byte text 279 | // @param wbuf the output wide char text 280 | // @param bufsz the size of the output buffer. 281 | // @return a pointer to `wbuf` 282 | // @function wstring_buff 283 | LPWSTR wstring_buff(LPCSTR text, LPWSTR wbuf, int bufsz) { 284 | int res = MultiByteToWideChar( 285 | current_encoding, 0, 286 | text,-1, 287 | wbuf,bufsz); 288 | if (res != 0) { 289 | return wbuf; 290 | } else { 291 | return NULL; // how to indicate error, hm?? 292 | } 293 | } 294 | 295 | /// push a wide string on the Lua stack with given size. 296 | // This converts to the current encoding first. 297 | // @param L the State 298 | // @param us the wide string 299 | // @param len size of wide string 300 | // @return 1; the encoded string or 2, `nil` and the error message 301 | // @function push_wstring_l 302 | int push_wstring_l(lua_State *L, LPCWSTR us, int len) { 303 | int osz = 3*len; 304 | char *obuff = malloc(osz); 305 | int res = WideCharToMultiByte( 306 | current_encoding, 0, 307 | us,len, 308 | obuff,osz, 309 | NULL,NULL); 310 | if (res == 0) { 311 | free(obuff); 312 | return push_error(L); 313 | } else { 314 | lua_pushlstring(L,obuff,res); 315 | free(obuff); 316 | return 1; 317 | } 318 | } 319 | 320 | /// push a wide string on the Lua stack. 321 | // @param L the state 322 | // @param us the wide string 323 | // @return 1; the encoded string or 2, `nil` and the error message 324 | // @function push_wstring 325 | int push_wstring(lua_State *L,LPCWSTR us) { 326 | int len = wcslen(us); 327 | return push_wstring_l(L,us,len); 328 | } 329 | 330 | static HKEY predefined_keys(LPCSTR key) { 331 | #define check(predef) if (eq(key,#predef)) return predef; 332 | check(HKEY_CLASSES_ROOT); 333 | check(HKEY_CURRENT_CONFIG); 334 | check(HKEY_CURRENT_USER); 335 | check(HKEY_LOCAL_MACHINE); 336 | check(HKEY_USERS); 337 | #undef check 338 | return NULL; 339 | } 340 | 341 | #define SLASH '\\' 342 | 343 | /// split a full registry path into predefined key and subkey path. 344 | // @param path the input full path 345 | // @param keypath the output path 346 | // @return the predefined HKEY 347 | // @function split_registry_key 348 | HKEY split_registry_key(LPCSTR path, char *keypath) { 349 | char key[MAX_KEY]; 350 | LPCSTR slash = strchr(path,SLASH); 351 | int i = (int)((DWORD_PTR)slash - (DWORD_PTR)path); 352 | strncpy(key,path,i); 353 | key[i] = '\0'; 354 | strcpy(keypath,path+i+1); 355 | return predefined_keys(key); 356 | } 357 | 358 | 359 | /// map message box names onto corresponding constants. 360 | // @param name string like "ok" or "ok-cancel" 361 | // @return MB_OK, MB_OKCANCEL, etc 362 | // @function mb_const 363 | int mb_const (LPCSTR name) { 364 | if (eq(name,"ok")) return MB_OK; 365 | if (eq(name,"abort-retry-ignore")) return MB_ABORTRETRYIGNORE; 366 | if (eq(name,"ok-cancel")) return MB_OKCANCEL; 367 | if (eq(name,"retry-cancel")) return MB_RETRYCANCEL; 368 | if (eq(name,"yes-no")) return MB_YESNO; 369 | if (eq(name,"yes-no-cancel")) return MB_YESNOCANCEL; 370 | // icons 371 | if (eq(name,"warning")) return MB_ICONWARNING; 372 | if (eq(name,"information")) return MB_ICONINFORMATION; 373 | if (eq(name,"question")) return MB_ICONQUESTION; 374 | if (eq(name,"error")) return MB_ICONERROR; 375 | return 0; 376 | } 377 | 378 | /// map message box returns to strings. 379 | // @param res the return code (like IDOK) 380 | // @return a string (like "ok") 381 | // @function mb_result 382 | LPCSTR mb_result (int res) { 383 | switch(res) { 384 | case IDABORT: return "abort"; 385 | case IDCANCEL: return "cancel"; 386 | case IDIGNORE: return "ignore"; 387 | case IDNO: return "no"; 388 | case IDOK: return "ok"; 389 | case IDRETRY: return "retry"; 390 | case IDYES: return "yes"; 391 | default: return "?"; 392 | } 393 | } 394 | 395 | -------------------------------------------------------------------------------- /wutils.h: -------------------------------------------------------------------------------- 1 | #ifndef WUTILS_H 2 | #define WUTILS_H 3 | typedef int Ref; 4 | 5 | extern int mutex_locked; 6 | 7 | enum { 8 | INTEGER = 1, 9 | REF_IDX = 2, 10 | DISCARD = 4 11 | }; 12 | 13 | Ref make_ref(lua_State *L, int idx); 14 | void release_ref(lua_State *L, Ref ref); 15 | int push_ref(lua_State *L, Ref ref); 16 | const char *last_error(int err); 17 | int push_error_msg(lua_State *L, LPCSTR msg) ; 18 | int push_error(lua_State *L); 19 | int push_perror(lua_State *L, const char *prefix); 20 | int push_error_code(lua_State *L, int err); 21 | int push_ok(lua_State *L); 22 | int push_bool(lua_State *L, int bval); 23 | void throw_error(lua_State *L, LPCSTR msg); 24 | BOOL call_lua_direct(lua_State *L, Ref ref, int idx, LPCSTR text, int discard); 25 | void make_message_window(); 26 | BOOL call_lua(lua_State *L, Ref ref, int idx, LPCSTR text, int discard); 27 | void lock_mutex(); 28 | void release_mutex(); 29 | void setup_mutex(); 30 | 31 | // encoding and converting text 32 | void set_encoding(int e); 33 | int get_encoding(); 34 | 35 | LPWSTR wstring_buff(LPCSTR text, LPWSTR wbuf, int bufsz); 36 | int push_wstring_l(lua_State *L, LPCWSTR us, int len); 37 | int push_wstring(lua_State *L, LPCWSTR us); 38 | 39 | HKEY split_registry_key(LPCSTR path, char *keypath); 40 | int mb_const (LPCSTR name); 41 | LPCSTR mb_result (int res); 42 | 43 | #endif 44 | --------------------------------------------------------------------------------