Topic readme.md
winapi A useful Windows API subset for Lua
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.
Creating and working with Processes
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.
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.
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.
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):
winapi.shell_exec('open','myold.doc')
Or an explorer window for a directory:
winapi.shell_exec('open','\\users\\steve\\lua')
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:
winapi.shell_exec(nil,'scite','wina.lua')
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:
winapi.shell_exec(nil,'notepad','wina.lua',nil,winapi.SW_MINIMIZE)
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.
> proc,file = winapi.spawn_process 'cmd /c dir /b' > = file:read() bonzo.lc cexport.lua class1.c ... > = proc:wait() userdata: 0000000000539608 OK > = proc:exit_code() 0
If the command is invalid, then you will get an error message instead:
> = winapi.spawn_process 'frodo' nil The system cannot find the file specified.
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’.
local _,status = proc:wait(500) if status == 'TIMEOUT' then proc:kill() end
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.
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:
> proc,file = winapi.spawn_process [[lua -e "io.stdout:setvbuf('no')" -i]] > = file:read() -- always read the program banner first! Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio > > = file:write 'print "hello"\n' 14 > = file:read() hello > > proc:kill()
(We also found it necessary in the Lua for Windows project to switch off buffering for using Lua in SciTE)
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:
> proc,file = winapi.spawn_process [[python -i]] > = file:read() Python 2.6.2c1 (r262c1:71369, Apr 7 2009, 18:44:00) [MSC v.1500 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> > file:write '40+2\n' > = file:read() 42 >>>
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:
> file:write '40+2\n' > file:read_async(function(s) print('++',s) end) > ++ 42 >>>
This can work nicely with Lua coroutines, allowing us to write pseudo-blocking code for interacting with processes.
The process object can provide more useful information:
> = proc:working_size() 200 1380 > = proc:run_times() 0 31
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.
It is possible to wait on more than one process at a time. Consider this simple time-wasting script:
for i = 1,1e8 do end
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):
require 'winapi' local t = os.clock() local P = {} P[1] = winapi.spawn_process 'lua slow.lua' P[2] = winapi.spawn_process 'lua slow.lua' winapi.wait_for_processes(P,true) print(os.clock() - t)
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.
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 on Windows.
Working with Windows
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:
> = winapi.get_desktop_window():get_bounds() 1600 900
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.)
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:
> t = winapi.find_all_windows(winapi.make_name_matcher '- SciTE') > = #t 2 > winapi.tile_windows(winapi.get_desktop_window(),false,t)
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}
.
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.
find_window_ex also uses a matcher function; find_window_match is a shortcut for the operation of finding a window by its caption.
Every window has an associated text value. For top-level windows, this is the window caption:
> = winapi.get_foreground_window() Command Prompt - lua -lwinapi
So the equivalent of the old DOS command title
would here be:
winapi.get_foreground_window():set_text 'My new title'
Any top-level window will contain child windows. For example, Notepad has a simple structure revealed by Window:enum_children:
> w = winapi.find_window_match 'Notepad' > = w Untitled - Notepad > t = {} > w:enum_children(function(w) table.insert(t,w) end) > = #t 2 > = t[1]:get_class_name() Edit > = t[2]:get_class_name() msctls_statusbar32
Windows controls like the ‘Edit’ control interact with the unverse via messages.
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:
> = t[1]:send_message(186,0,0) 6
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:
> winapi.send_to_window '= 20 + 10\n' > = 20 + 10 30
After launching a window, you can make it the foreground window and send it text:
P = winapi.spawn_process 'notepad' P:wait_for_input_idle() w = winapi.find_window_match 'Untitled' w:show() w:set_foreground() winapi.send_to_window 'hello dammit'
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.
Working with Text Encoding
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.
An important point is that you can choose to use UTF-8 encoding with winapi. This little program shows how:
-- caption.lua local W = require 'winapi' W.set_encoding(W.CP_UTF8) win = W.foreground_window() win:set_text 'ελληνική'
When run in SciTE, it successfully puts a little bit of Greek in the title bar.
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.
utf8_expand will expand ‘#’ two-byte Unicode hex constants:
local U = winapi.utf8_expand txt = U '#03BB + #03BC + C' print(txt) print(U '#03BD') ---> OUTPUT λ + μ + C ν
You may work internally in UTF-8 and get a suitable short file name for working with files in Lua.
-- testshort.lua name = winapi.short_path 'ελληνική.txt' print(name) local f,err = io.open(name,'w') f:write 'a new file\n' f:close()
A filename with the correct Greek name appears in Explorer, and can be edited with any Unicode-aware application like Notepad.
Working with Processes
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:
> w = winapi.get_foreground_window()
> = w
Command Prompt - lua -lwinapi
> p = w:get_process()
> = p:get_process_name()
cmd.exe
> = p:get_process_name(true)
C:\WINDOWS\system32\cmd.exe
(Note that the Process:get_process_name method can optionally give you the full path to the process.)
To get all the current processes:
pids = winapi.get_processes() for _,pid in ipairs(pids) do local P = winapi.process_from_id(pid) local name = P:get_process_name(true) if name then print(pid,name) end P:close() end
Drive and Directory Operations
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.
-- drives.lua require 'winapi' drives = winapi.get_logical_drives() for _,drive in ipairs(drives) do local free,avail = winapi.get_disk_free_space(drive) if not free then -- call failed, avail is error free = '('..avail..')' else free = math.ceil(free/1024) -- get Mb end local rname = '' local dtype = winapi.get_drive_type(drive) if dtype == 'remote' then -- note it wants the drive letter! rname = winapi.get_disk_network_name(drive:gsub('\\$','')) end print(drive,dtype,free,rname) end
This script gives the following output on my home machine:
C:\ fixed 218967 F:\ fixed 1517 G:\ cdrom (The device is not ready.) Q:\ fixed (Access is denied.)
Or at work:
C:\ fixed 1455 D:\ fixed 49996 E:\ cdrom (The device is not ready.) G:\ remote 33844 \\CARL-VFILE\SYS I:\ remote 452789 \\CARL-VFILE\GROUPS X:\ remote 12160 \\CARL-VFILE\APPS Y:\ remote 33844 \\CARL-VFILE\SYS\PUBLIC Z:\ remote 33844 \\CARL-VFILE\SYS\PUBLIC
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.
winapi.watch_for_file_changes(mydir,winapi.FILE_NOTIFY_CHANGE_LAST_WRITE,FALSE, function(what,who) -- 'what' will be winapi.FILE_ACTION_MODIFIED -- 'who' will be the name of the file that changed print(what,who) end )
Using a callback means that you can watch multiple directories and still respond to timers, etc.
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:
local status,output = winapi.execute('dir /B') local files = {} for f in output:gmatch '[^\r\n]+' do table.insert(files,f) end
Output
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:
print(winapi.show_message("Message","stuff\nand nonsense","yes-no","warning"))
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.
Or you may prefer to irritate the user with a sound:
winapi.beep 'warning'
output_debug_string will write text quietly to the debug stream. A utility such as DebugView can be used to view this output, which shows it with a timestamp.
Timers and Callbacks
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:
winapi.make_timer(500,function() text:append 'gotcha' end)
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.
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.
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:
winapi.sleep(-1)
To show what happens in an interactive prompt if you don’t follow this rule:
> winapi.timer(500,function() end) > = 23 nil nil return 23
In short: completely messed!
It’s possible to read from the console asynchronously, which allows you to write servers which are responsive to interactive commands.
f = winapi.get_console() f:read_async(function(line) f:write(line) if line:match '^quit' then os.exit() end end) winapi.sleep(-1)
Please note that you will get the end-of-line characters as well.
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.
local W = require 'winapi' local f = W.get_console() local title = W.get_foreground_window() local count = 1 f:write '? ' while true do local res = W.wait_for_processes({f},false,500) if res == 1 then local line = f:read() if not line then break end -- strip line feed line = line:gsub('\r\n$','') if line == 'quit' then break end print(line:upper()) f:write '? ' else title:set_text('counting '..count) count = count + 1 end end
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.
Reading from the Registry
The registry is an unavoidable part of living with Windows, since much useful information can be found in it, if you know the key.
For instance, the environment for the current user can be queried:
local key = winapi.open_reg_key [[HKEY_CURRENT_USER\Environment]] print(key:get_value("PATH")) k:close()
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.
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”.
Regkey:get_keys will return a list of all subkeys of the current key.
When finished, it’s best to explicitly use the close
method.
Pipe Server
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.
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.
require 'winapi' winapi.server(function(file) file:read_async(function(s) print('['..s..']') end) end) winapi.sleep(-1)
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.
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:
> f = io.open('\\\\.\\pipe\\luawinapi','w') > f:write 'hello server!\n' > f:flush() > f:close()
and our server will say:
[hello server! ] []
(Note that File:read receives an empty string when the handle is closed.)
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.
The server can do something to the received string and pass it back:
winapi.server(function(file) file:read_async(function(s) if s == '' then print 'client disconnected' else file:write (s:upper()) end end) end)
On the client side:
f = winapi.open_pipe() f:write 'hello\n' print(f:read()) -- HELLO f:write 'dog\n' print(f:read()) -- DOG f:close()
Another similarity with sockets is that you can connect to remote pipes (see pipe names.aspx))
Events
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
.
local W = require 'winapi' local e = W.event() local count = 1 W.make_timer(500,function() print 'finis' if count == 5 then os.exit() end e:signal() count = count + 1 end) while true do e:wait() print 'gotcha' end
There is also Event:wait_async to avoid blocking in a callback.
They can optionally be given a name, and can then work across different processes.