Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
winapi/winapi.l.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
2164 lines (1944 sloc)
57.3 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /*** | |
| A useful set of Windows API functions. | |
| * Enumerating and accessing windows, including sending keys. | |
| * Enumerating processes and querying their program name, memory used, etc. | |
| * Reading and Writing to the Registry | |
| * Copying and moving files, and showing drive information. | |
| * Launching processes and opening documents. | |
| * Monitoring filesystem changes. | |
| @author Steve Donovan (steve.j.donovan@gmail.com) | |
| @copyright 2011 | |
| @license MIT/X11 | |
| @module winapi | |
| */ | |
| #define WINDOWS_LEAN_AND_MEAN | |
| #include <windows.h> | |
| #include <string.h> | |
| #ifdef __GNUC__ | |
| #include <winable.h> /* GNU GCC specific */ | |
| #endif | |
| #include "Winnetwk.h" | |
| #include <psapi.h> | |
| #define WBUFF 2048 | |
| #define MAX_SHOW 100 | |
| #define THREAD_STACK_SIZE (1024 * 1024) | |
| #define MAX_PROCESSES 1024 | |
| #define MAX_KEYS 512 | |
| #define FILE_BUFF_SIZE 2048 | |
| #define MAX_WATCH 20 | |
| #define MAX_WPATH 1024 | |
| #define TIMEOUT(timeout) timeout == 0 ? INFINITE : timeout | |
| static wchar_t wbuff[WBUFF]; | |
| typedef LPCWSTR WStr; | |
| module "winapi" { | |
| #include "wutils.h" | |
| static WStr wstring(Str text) { | |
| return wstring_buff(text,wbuff,sizeof(wbuff)); | |
| } | |
| /// Text encoding. | |
| // @section encoding | |
| /// set the current text encoding. | |
| // @param e one of `CP_ACP` (Windows code page; default) and `CP_UTF8` | |
| // @function set_encoding | |
| def set_encoding (Int e) { | |
| set_encoding(e); | |
| return 0; | |
| } | |
| /// get the current text encoding. | |
| // @return either `CP_ACP` or `CP_UTF8` | |
| // @function get_encoding | |
| def get_encoding () { | |
| lua_pushinteger(L, get_encoding()); | |
| return 1; | |
| } | |
| /// encode a string in another encoding. | |
| // Note: currently there's a limit of about 2K on the string buffer. | |
| // @param e_in `CP_ACP`, `CP_UTF8` or `CP_UTF16` | |
| // @param e_out likewise | |
| // @param text the string | |
| // @function encode | |
| def encode(Int e_in, Int e_out, Str text) { | |
| int ce = get_encoding(); | |
| LPCWSTR ws; | |
| if (e_in != -1) { | |
| set_encoding(e_in); | |
| ws = wstring(text); | |
| } else { | |
| ws = (LPCWSTR)text; | |
| } | |
| if (e_out != -1) { | |
| set_encoding(e_out); | |
| push_wstring(L,ws); | |
| } else { | |
| lua_pushlstring(L,(LPCSTR)ws,wcslen(ws)*sizeof(WCHAR)); | |
| } | |
| set_encoding(ce); | |
| return 1; | |
| } | |
| /// expand # unicode escapes in a string. | |
| // @param text ASCII text with #XXXX, where XXXX is four hex digits. ## means # itself. | |
| // @return text as UTF-8 | |
| // @see testu.lua | |
| // @function utf8_expand | |
| def utf8_expand(Str text) { | |
| int len = strlen(text), i = 0, enc = get_encoding(); | |
| WCHAR wch; | |
| LPWSTR P = wbuff; | |
| if (len > sizeof(wbuff)) { | |
| return push_error_msg(L,"string too big"); | |
| } | |
| while (i <= len) { | |
| if (text[i] == '#') { | |
| ++i; | |
| if (text[i] == '#') { | |
| wch = '#'; | |
| } else | |
| if (len-i >= 4) { | |
| char hexnum[5]; | |
| strncpy(hexnum,text+i,4); | |
| hexnum[4] = '\0'; | |
| wch = strtol(hexnum,NULL,16); | |
| i += 3; | |
| } else { | |
| return push_error_msg(L,"bad # escape"); | |
| } | |
| } else { | |
| wch = (WCHAR)text[i]; | |
| } | |
| *P++ = wch; | |
| ++i; | |
| } | |
| *P++ = 0; | |
| set_encoding(CP_UTF8); | |
| push_wstring(L,wbuff); | |
| set_encoding(enc); | |
| return 1; | |
| } | |
| // forward reference to Process constructor | |
| static int push_new_Process(lua_State *L,Int pid, HANDLE ph); | |
| const DWORD_PTR WIN_NOACTIVATE = (DWORD_PTR)SWP_NOACTIVATE, | |
| WIN_NOMOVE = (DWORD_PTR)SWP_NOMOVE, | |
| WIN_NOSIZE = (DWORD_PTR)SWP_NOSIZE, | |
| WIN_SHOWWINDOW = (DWORD_PTR)SWP_SHOWWINDOW, | |
| WIN_NOZORDER = (DWORD_PTR)SWP_NOZORDER, | |
| WIN_BOTTOM = (DWORD_PTR)HWND_BOTTOM, | |
| WIN_NOTOPMOST = (DWORD_PTR)HWND_NOTOPMOST, | |
| WIN_TOP = (DWORD_PTR)HWND_TOP, | |
| WIN_TOPMOST = (DWORD_PTR)HWND_TOPMOST; | |
| /// a class representing a Window. | |
| // @type Window | |
| class Window { | |
| HWND hwnd; | |
| constructor (HWND h) { | |
| this->hwnd = h; | |
| } | |
| static lua_State *sL; | |
| static BOOL CALLBACK enum_callback(HWND hwnd,LPARAM data) { | |
| push_ref(sL,(Ref)data); | |
| push_new_Window(sL,hwnd); | |
| lua_call(sL,1,0); | |
| return TRUE; | |
| } | |
| /// the handle of this window. | |
| // @function get_handle | |
| def get_handle() { | |
| lua_pushnumber(L,(DWORD_PTR)this->hwnd); | |
| return 1; | |
| } | |
| /// get the window text. | |
| // @function get_text | |
| def get_text() { | |
| GetWindowTextW(this->hwnd,wbuff,sizeof(wbuff)); | |
| return push_wstring(L,wbuff); | |
| } | |
| /// set the window text. | |
| // @function set_text | |
| def set_text(Str text) { | |
| SetWindowTextW(this->hwnd,wstring(text)); | |
| return 0; | |
| } | |
| /// change the visibility, state etc | |
| // @param flags one of `SW_SHOW`, `SW_MAXIMIZE`, etc | |
| // @function show | |
| def show(Int flags = SW_SHOW) { | |
| ShowWindow(this->hwnd,flags); | |
| return 0; | |
| } | |
| /// change the visibility without blocking. | |
| // @param flags one of `SW_SHOW`, `SW_MAXIMIZE`, etc | |
| // @function show_async | |
| def show_async(Int flags = SW_SHOW) { | |
| ShowWindowAsync(this->hwnd,flags); | |
| return 0; | |
| } | |
| /// get the position in pixels | |
| // @return left position | |
| // @return top position | |
| // @function get_position | |
| def get_position() { | |
| RECT rect; | |
| GetWindowRect(this->hwnd,&rect); | |
| lua_pushinteger(L,rect.left); | |
| lua_pushinteger(L,rect.top); | |
| return 2; | |
| } | |
| /// get the bounds in pixels | |
| // @return width | |
| // @return height | |
| // @function get_bounds | |
| def get_bounds() { | |
| RECT rect; | |
| GetWindowRect(this->hwnd,&rect); | |
| lua_pushinteger(L,rect.right - rect.left); | |
| lua_pushinteger(L,rect.bottom - rect.top); | |
| return 2; | |
| } | |
| /// is this window visible? | |
| // @function is_visible | |
| def is_visible() { | |
| lua_pushboolean(L,IsWindowVisible(this->hwnd)); | |
| return 1; | |
| } | |
| /// destroy this window. | |
| // @function destroy | |
| def destroy () { | |
| DestroyWindow(this->hwnd); | |
| return 0; | |
| } | |
| /// resize this window. | |
| // @param x0 left | |
| // @param y0 top | |
| // @param w width | |
| // @param h height | |
| // @function resize | |
| def resize(Int x0, Int y0, Int w, Int h) { | |
| MoveWindow(this->hwnd,x0,y0,w,h,TRUE); | |
| return 0; | |
| } | |
| /// resize or move a window. | |
| // see [API](http://msdn.microsoft.com/en-us/library/windows/desktop/ms633545%28v=vs.85%29.aspx) | |
| // @param w window _handle_ to insert after, or one of: | |
| // WINWIN_BOTTOM, WIN_NOTOPMOST, WIN_TOP (default), WIN_TOPMOST | |
| // @param x0 left (ignore if flags has WIN_NOMOVE) | |
| // @param y0 top | |
| // @param w width (ignore if flags has WIN_NOSIZE) | |
| // @param h height | |
| // @param flags one of | |
| // WIN_NOACTIVATE, WIN_NOMOVE, WIN_NOSIZE, WIN_SHOWWINDOW (default), WIN_NOZORDER | |
| def set_pos (Int wafter = WIN_TOP, Int x0, Int y0, Int w, Int h, Int flags = WIN_SHOWWINDOW) { | |
| SetWindowPos(this->hwnd,(HWND)(DWORD_PTR)wafter,x0,y0,w,h,flags); | |
| return 0; | |
| } | |
| /// send a message. | |
| // @param msg the message | |
| // @param wparam | |
| // @param lparam | |
| // @return the result | |
| // @function send_message | |
| def send_message(Int msg, Number wparam, Number lparam) { | |
| lua_pushinteger(L,SendMessage(this->hwnd,msg,(WPARAM)wparam,(LPARAM)lparam)); | |
| return 1; | |
| } | |
| /// send a message asynchronously. | |
| // @param msg the message | |
| // @param wparam | |
| // @param lparam | |
| // @return the result | |
| // @function post_message | |
| def post_message(Int msg, Number wparam, Number lparam) { | |
| return push_bool(L,PostMessage(this->hwnd,msg,(WPARAM)wparam,(LPARAM)lparam)); | |
| } | |
| /// enumerate all child windows. | |
| // @param a callback which to receive each window object | |
| // @function enum_children | |
| def enum_children(Value callback) { | |
| Ref ref; | |
| sL = L; | |
| ref = make_ref(L,callback); | |
| EnumChildWindows(this->hwnd,&enum_callback,ref); | |
| release_ref(L,ref); | |
| return 0; | |
| } | |
| /// get the parent window. | |
| // @function get_parent | |
| def get_parent() { | |
| return push_new_Window(L,GetParent(this->hwnd)); | |
| } | |
| /// get the name of the program owning this window. | |
| // @function get_module_filename | |
| def get_module_filename() { | |
| int sz = GetWindowModuleFileNameW(this->hwnd,wbuff,sizeof(wbuff)); | |
| wbuff[sz] = 0; | |
| return push_wstring(L,wbuff); | |
| } | |
| /// get the window class name. | |
| // Useful to find all instances of a running program, when you | |
| // know the class of the top level window. | |
| // @function get_class_name | |
| def get_class_name() { | |
| static char buff[1024]; | |
| int n = GetClassName(this->hwnd,buff,sizeof(buff)); | |
| if (n > 0) { | |
| lua_pushstring(L,buff); | |
| return 1; | |
| } else { | |
| return push_error(L); | |
| } | |
| } | |
| /// bring this window to the foreground. | |
| // @function set_foreground | |
| def set_foreground () { | |
| lua_pushboolean(L,SetForegroundWindow(this->hwnd)); | |
| return 1; | |
| } | |
| /// get the associated process of this window | |
| // @function get_process | |
| def get_process() { | |
| DWORD pid; | |
| GetWindowThreadProcessId(this->hwnd,&pid); | |
| return push_new_Process(L,pid,NULL); | |
| } | |
| /// this window as string (up to 100 chars). | |
| // @function __tostring | |
| def __tostring() { | |
| int ret; | |
| int sz = GetWindowTextW(this->hwnd,wbuff,sizeof(wbuff)); | |
| if (sz > MAX_SHOW) { | |
| wbuff[MAX_SHOW] = '\0'; | |
| } | |
| ret = push_wstring(L,wbuff); | |
| if (ret == 2) { // we had a conversion error | |
| lua_pushliteral(L,""); | |
| } | |
| return 1; | |
| } | |
| def __eq(Window other) { | |
| lua_pushboolean(L,this->hwnd == other->hwnd); | |
| return 1; | |
| } | |
| } | |
| /// Manipulating Windows. | |
| // @section Windows | |
| /// find a window based on classname and caption | |
| // @param cname class name (may be nil) | |
| // @param wname caption (may be nil) | |
| // @return @{Window} | |
| // @function find_window | |
| def find_window(StrNil cname, StrNil wname) { | |
| HWND hwnd = FindWindow(cname,wname); | |
| if (hwnd == NULL) { | |
| return push_error(L); | |
| } else { | |
| return push_new_Window(L,hwnd); | |
| } | |
| } | |
| /// makes a function that matches against window text | |
| // @param text | |
| // @function make_name_matcher | |
| /// makes a function that matches against window class name | |
| // @param text | |
| // @function make_class_matcher | |
| /// find a window using a condition function. | |
| // @param match will return true when its argument is the desired window | |
| // @return @{Window} | |
| // @function find_window_ex | |
| /// return all windows matching a condition. | |
| // @param match will return true when its argument is the desired window | |
| // @return a list of window objects | |
| // @function find_all_windows | |
| /// find a window matching the given text. | |
| // @param text the pattern to match against the caption | |
| // @return a window object. | |
| // @function find_window_match | |
| /// current foreground window. | |
| // An example of setting the caption is @{caption.lua} | |
| // @return @{Window} | |
| // @function get_foreground_window | |
| def get_foreground_window() { | |
| return push_new_Window(L, GetForegroundWindow()); | |
| } | |
| /// the desktop window. | |
| // @usage winapi.get_desktop_window():get_bounds() | |
| // @return @{Window} | |
| // @function get_desktop_window | |
| def get_desktop_window() { | |
| return push_new_Window(L, GetDesktopWindow()); | |
| } | |
| /// a Window object from a handle | |
| // @param a Windows nandle | |
| // @return @{Window} | |
| // @function window_from_handle | |
| def window_from_handle(Int hwnd) { | |
| return push_new_Window(L, (HWND)hwnd); | |
| } | |
| /// enumerate over all top-level windows. | |
| // @param callback a function to receive each window object | |
| // @function enum_windows | |
| def enum_windows(Value callback) { | |
| Ref ref; | |
| sL = L; | |
| ref = make_ref(L,callback); | |
| EnumWindows(&enum_callback,ref); | |
| release_ref(L,ref); | |
| return 0; | |
| } | |
| /// route callback dispatch through a message window. | |
| // You need to do this when using Winapi in a GUI application, | |
| // since it ensures that Lua callbacks happen in the GUI thread. | |
| // @function use_gui | |
| def use_gui() { | |
| make_message_window(); | |
| return 0; | |
| } | |
| static INPUT *add_input(INPUT *pi, WORD vkey, BOOL up) { | |
| pi->type = INPUT_KEYBOARD; | |
| pi->ki.dwFlags = up ? KEYEVENTF_KEYUP : 0; | |
| pi->ki.wVk = vkey; | |
| return pi+1; | |
| } | |
| // The Windows SendInput() is a low-level function, and you have to | |
| // simulate things like uppercase directly. Repeated characters need | |
| // an explicit 'key up' keystroke to work. | |
| // see http://stackoverflow.com/questions/2167156/sendinput-isnt-sending-the-correct-shifted-characters | |
| // this is a case where we have to convert the parameter directly, since | |
| // it may be an integer (virtual key code) or string of characters. | |
| /// send a string or virtual key to the active window. | |
| // @{input.lua} shows launching a process, waiting for it to be | |
| // ready, and sending it some keys | |
| // @param text either a key (like winapi.VK_SHIFT) or a string | |
| // @return number of keys sent, or nil if an error | |
| // @return any error string | |
| // @function send_to_window | |
| def send_to_window () { | |
| const char *text; | |
| int vkey, len = MAX_KEYS; | |
| UINT res; | |
| SHORT last_vk = 0; | |
| INPUT *input, *pi; | |
| if (lua_isnumber(L,1)) { | |
| INPUT inp; | |
| ZeroMemory(&inp,sizeof(INPUT)); | |
| vkey = lua_tointeger(L,1); | |
| add_input(&inp,vkey,lua_toboolean(L,2)); | |
| SendInput(1,&inp,sizeof(INPUT)); | |
| return 0; | |
| } else { | |
| text = lua_tostring(L,1); | |
| if (text == NULL) { | |
| return push_error_msg(L,"not a string or number"); | |
| } | |
| } | |
| input = (INPUT *)malloc(sizeof(INPUT)*len); | |
| pi = input; | |
| ZeroMemory(input, sizeof(INPUT)*len); | |
| for(; *text; ++text) { | |
| SHORT vk = VkKeyScan(*text); | |
| if (last_vk == vk) { | |
| pi = add_input(pi,last_vk & 0xFF,TRUE); | |
| } | |
| if (vk & 0x100) pi = add_input(pi,VK_SHIFT,FALSE); | |
| pi = add_input(pi,vk & 0xFF,FALSE); | |
| if (vk & 0x100) pi = add_input(pi,VK_SHIFT,TRUE); | |
| last_vk = vk; | |
| } | |
| res = SendInput(((DWORD_PTR)pi-(DWORD_PTR)input)/sizeof(INPUT), input, sizeof(INPUT)); | |
| free(input); | |
| if (res > 0) { | |
| lua_pushinteger(L,res); | |
| return 1; | |
| } else { | |
| return push_error(L); | |
| } | |
| return 0; | |
| } | |
| /// tile a group of windows. | |
| // @param parent @{Window} (can use the desktop) | |
| // @param horiz tile vertically by default | |
| // @param kids a table of window objects | |
| // @param bounds a bounds table (left,top,right,bottom) - can be nil | |
| // @function tile_windows | |
| def tile_windows(Window parent, Boolean horiz, Value kids, Value bounds) { | |
| RECT rt; | |
| HWND *kids_arr; | |
| int i,n_kids; | |
| LPRECT lpRect = NULL; | |
| if (! lua_isnoneornil(L,bounds)) { | |
| lua_pushvalue(L,bounds); | |
| Int_get(rt.left,"left"); | |
| Int_get(rt.top,"top"); | |
| Int_get(rt.right,"right"); | |
| Int_get(rt.bottom,"bottom"); | |
| lua_pop(L,1); | |
| lpRect = &rt; | |
| } | |
| n_kids = lua_objlen(L,kids); | |
| kids_arr = (HWND *)malloc(sizeof(HWND)*n_kids); | |
| for (i = 0; i < n_kids; ++i) { | |
| Window *w; | |
| lua_rawgeti(L,kids,i+1); | |
| w = Window_arg(L,-1); | |
| kids_arr[i] = w->hwnd; | |
| } | |
| TileWindows(parent->hwnd,horiz ? MDITILE_HORIZONTAL : MDITILE_VERTICAL, lpRect, n_kids, kids_arr); | |
| free(kids_arr); | |
| return 0; | |
| } | |
| /// Miscellaneous functions. | |
| // @section miscellaneous | |
| static int push_new_File(lua_State *L,HANDLE hread, HANDLE hwrite); | |
| /// sleep and use no processing time. | |
| // @param millisec sleep period | |
| // @function sleep | |
| def sleep(Int millisec) { | |
| release_mutex(); | |
| Sleep(millisec); | |
| lock_mutex(); | |
| return 0; | |
| } | |
| /// show a message box. | |
| // @param caption for dialog | |
| // @param msg the message | |
| // @param btns (default 'ok') one of 'ok','ok-cancel','yes','yes-no', | |
| // "abort-retry-ignore", "retry-cancel", "yes-no-cancel" | |
| // @param icon (default 'information') one of 'information','question','warning','error' | |
| // @return a string giving the pressed button: one of 'ok','yes','no','cancel', | |
| // 'try','abort' and 'retry' | |
| // @see message.lua | |
| // @function show_message | |
| def show_message(Str caption, Str msg, Str btns = "ok", Str icon = "information") { | |
| int res, type; | |
| WCHAR capb [512]; | |
| type = mb_const(btns) | mb_const(icon); | |
| wstring_buff(caption,capb,sizeof(capb)); | |
| res = MessageBoxW( NULL, wstring(msg), capb, type); | |
| lua_pushstring(L,mb_result(res)); | |
| return 1; | |
| } | |
| /// make a beep sound. | |
| // @param type (default 'ok'); one of 'information','question','warning','error' | |
| // @function beep | |
| def beep (Str icon = "ok") { | |
| return push_bool(L, MessageBeep(mb_const(icon))); | |
| } | |
| /// copy a file. | |
| // @param src source file | |
| // @param dest destination file | |
| // @param fail_if_exists if true, then cannot copy onto existing file | |
| // @function copy_file | |
| def copy_file(Str src, Str dest, Int fail_if_exists = 0) { | |
| return push_bool(L, CopyFile(src,dest,fail_if_exists)); | |
| } | |
| /// output text to the system debugger. | |
| // A uility such as [DebugView](http://technet.microsoft.com/en-us/sysinternals/bb896647) | |
| // can show the output | |
| // @param str text | |
| // @function output_debug_string | |
| def output_debug_string(Str str) { | |
| OutputDebugString(str); | |
| return 0; | |
| } | |
| /// move a file. | |
| // @param src source file | |
| // @param dest destination file | |
| // @function move_file | |
| def move_file(Str src, Str dest) { | |
| return push_bool(L, MoveFile(src,dest)); | |
| } | |
| #define wconv(name) (name ? wstring_buff(name,w##name,sizeof(w##name)) : NULL) | |
| /// execute a shell command. | |
| // @param verb the action (e.g. 'open' or 'edit') can be nil. | |
| // @param file the command | |
| // @param parms any parameters (optional) | |
| // @param dir the working directory (optional) | |
| // @param show the window show flags (default is SW_SHOWNORMAL) | |
| // @function shell_exec | |
| def shell_exec(StrNil verb, Str file, StrNil parms, StrNil dir, Int show=SW_SHOWNORMAL) { | |
| WCHAR wverb[128], wfile[MAX_WPATH], wdir[MAX_WPATH], wparms[MAX_WPATH]; | |
| int res = (DWORD_PTR)ShellExecuteW(NULL,wconv(verb),wconv(file),wconv(parms),wconv(dir),show) > 32; | |
| return push_bool(L, res); | |
| } | |
| /// copy text onto the clipboard. | |
| // @param text the text | |
| // @function set_clipboard | |
| def set_clipboard(Str text) { | |
| HGLOBAL glob; | |
| LPWSTR p; | |
| int bufsize = 3*strlen(text); | |
| if (! OpenClipboard(NULL)) { | |
| return push_perror(L,"openclipboard"); | |
| } | |
| EmptyClipboard(); | |
| glob = GlobalAlloc(GMEM_MOVEABLE, bufsize); | |
| p = (LPWSTR)GlobalLock(glob); | |
| wstring_buff(text,p,bufsize); | |
| GlobalUnlock(glob); | |
| if (SetClipboardData(CF_UNICODETEXT,glob) == NULL) { | |
| CloseClipboard(); | |
| return push_error(L); | |
| } | |
| CloseClipboard(); | |
| return 0; | |
| } | |
| /// get the text on the clipboard. | |
| // @return the text | |
| // @function get_clipboard | |
| def get_clipboard() { | |
| HGLOBAL glob; | |
| LPCWSTR p; | |
| if (! OpenClipboard(NULL)) { | |
| return push_perror(L,"openclipboard"); | |
| } | |
| glob = GetClipboardData(CF_UNICODETEXT); | |
| if (glob == NULL) { | |
| CloseClipboard(); | |
| return push_error(L); | |
| } | |
| p = GlobalLock(glob); | |
| push_wstring(L,p); | |
| GlobalUnlock(glob); | |
| CloseClipboard(); | |
| return 1; | |
| } | |
| /// open console i/o. | |
| // @return @{File} | |
| // @function get_console | |
| def get_console() { | |
| HANDLE w = GetStdHandle(STD_OUTPUT_HANDLE); | |
| HANDLE r = GetStdHandle(STD_INPUT_HANDLE); | |
| return push_new_File(L,r,w); | |
| } | |
| def pipe() { | |
| HANDLE hRead, hWrite; | |
| if (CreatePipe(&hRead,&hWrite,NULL,0) != 0) { | |
| push_new_File(L,hRead,NULL); | |
| push_new_File(L,NULL,hWrite); | |
| return 2; | |
| } else { | |
| return push_error(L); | |
| } | |
| } | |
| /// open a serial port for reading and writing. | |
| // @param defn a string as used by the [mode command](http://technet.microsoft.com/en-us/library/cc732236%28WS.10%29.aspx) | |
| // @return @{File} | |
| // @function open_serial | |
| def open_serial(Str defn) { | |
| DCB dcb = {0}; | |
| char port[20]; | |
| HANDLE hSerial; | |
| const char *p = defn; | |
| char *q = port; | |
| for (; *p != ' '; p++) { | |
| *q++ = *p; | |
| } | |
| *q = '\0'; | |
| dcb.DCBlength = sizeof(dcb); | |
| hSerial = CreateFile(port,GENERIC_READ | GENERIC_WRITE, 0, 0, | |
| OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); | |
| if (hSerial == INVALID_HANDLE_VALUE) { | |
| return push_perror(L,"createfile"); | |
| } | |
| GetCommState(hSerial,&dcb); | |
| if (! BuildCommDCB(defn,&dcb)) { | |
| CloseHandle(hSerial); | |
| return push_perror(L,"buildcom"); | |
| } | |
| if (! SetCommState(hSerial,&dcb)) { | |
| CloseHandle(hSerial); | |
| return push_perror(L,"setcomm"); | |
| } | |
| return push_new_File(L,hSerial,hSerial); | |
| } | |
| static int push_wait_result(lua_State *L, DWORD res) { | |
| if (res == WAIT_OBJECT_0) { | |
| lua_pushvalue(L,1); | |
| lua_pushliteral(L,"OK"); | |
| return 2; | |
| } else if (res == WAIT_TIMEOUT) { | |
| lua_pushvalue(L,1); | |
| lua_pushliteral(L,"TIMEOUT"); | |
| return 2; | |
| } else { | |
| return push_error(L); | |
| } | |
| } | |
| static int wait_single(HANDLE h, int timeout) { | |
| DWORD res; | |
| release_mutex(); | |
| res = WaitForSingleObject (h, timeout); | |
| lock_mutex(); | |
| return res; | |
| } | |
| static int push_wait(lua_State *L, HANDLE h, int timeout) { | |
| return push_wait_result(L,wait_single(h,timeout)); | |
| } | |
| static int push_wait_async(lua_State *L, HANDLE h, int timeout, int callback); | |
| /// The Event class. | |
| // @type Event | |
| class Event { | |
| HANDLE hEvent; | |
| constructor(HANDLE h) { | |
| this->hEvent = h; | |
| } | |
| /// wait for this event to be signalled. | |
| // @param timeout optional timeout in millisec; defaults to waiting indefinitely. | |
| // @return this event object | |
| // @return either "OK" or "TIMEOUT" | |
| // @function wait | |
| def wait(Int timeout=0) { | |
| return push_wait(L,this->hEvent, TIMEOUT(timeout)); | |
| } | |
| /// run callback when this process is finished. | |
| // @param callback the callback | |
| // @param timeout optional timeout in millisec; defaults to waiting indefinitely. | |
| // @return this process object | |
| // @return either "OK" or "TIMEOUT" | |
| // @function wait_async | |
| def wait_async(Value callback, Int timeout = 0) { | |
| return push_wait_async(L,this->hEvent, TIMEOUT(timeout), callback); | |
| } | |
| def signal() { | |
| SetEvent(this->hEvent); | |
| return 0; | |
| } | |
| def __gc() { | |
| CloseHandle(this->hEvent); | |
| return 0; | |
| } | |
| } | |
| /// The Mutex class. | |
| // @type Mutex | |
| class Mutex { | |
| HANDLE hMutex; | |
| constructor (HANDLE h) { | |
| this->hMutex = h; | |
| } | |
| def lock() { | |
| WaitForSingleObject(this->hMutex,INFINITE); | |
| return 0; | |
| } | |
| def release() { | |
| ReleaseMutex(this->hMutex); | |
| return 0; | |
| } | |
| def __gc() { | |
| CloseHandle(this->hMutex); | |
| return 0; | |
| } | |
| } | |
| static int _event_count = 1; | |
| /// create a new @{Event} object. | |
| // @param name string (optional) | |
| // @return @{Event}, or nil, error. | |
| def event (Str name="?") { | |
| HANDLE hEvent; | |
| char buff[MAX_PATH]; | |
| if (strcmp(name,"?")==0) { | |
| sprintf(buff,"_event_%d",_event_count++); | |
| name = buff; | |
| } | |
| hEvent = CreateEvent (NULL,0,0,name); | |
| if (hEvent == NULL) { | |
| return push_error(L); | |
| } else { | |
| return push_new_Event(L,hEvent); | |
| } | |
| } | |
| /// create a new @{Mutex} object. | |
| // @param name string (optional) | |
| // @return @{Mutex}, or nil, error. | |
| def mutex(Str name="") { | |
| return push_new_Mutex(L,CreateMutex(NULL,FALSE,*name==0 ? NULL : name)); | |
| } | |
| /// A class representing a Windows process. | |
| // this example was [helpful](http://msdn.microsoft.com/en-us/library/ms682623%28VS.85%29.aspx) | |
| // @type Process | |
| class Process { | |
| HANDLE hProcess; | |
| int pid; | |
| constructor(Int pid, HANDLE ph) { | |
| if (ph) { | |
| this->pid = pid; | |
| this->hProcess = ph; | |
| } else { | |
| this->pid = pid; | |
| this->hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | | |
| PROCESS_VM_READ | PROCESS_TERMINATE, | |
| FALSE, pid ); | |
| if (!this->hProcess) { | |
| this->hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | | |
| PROCESS_VM_READ, | |
| FALSE, pid ); | |
| } | |
| } | |
| } | |
| /// get the name of the process. | |
| // @param full true if you want the full path; otherwise returns the base name. | |
| // @function get_process_name | |
| def get_process_name(Boolean full) { | |
| HMODULE hMod; | |
| DWORD cbNeeded; | |
| wchar_t modname[MAX_PATH]; | |
| if (EnumProcessModules(this->hProcess, &hMod, sizeof(hMod), &cbNeeded)) { | |
| if (full) { | |
| GetModuleFileNameExW(this->hProcess, hMod, modname, sizeof(modname)); | |
| } else { | |
| GetModuleBaseNameW(this->hProcess, hMod, modname, sizeof(modname)); | |
| } | |
| return push_wstring(L,modname); | |
| } else { | |
| return push_error(L); | |
| } | |
| } | |
| /// get the the pid of the process. | |
| // @function get_pid | |
| def get_pid() { | |
| lua_pushnumber(L, this->pid); | |
| return 1; | |
| } | |
| /// kill the process. | |
| // @{test-spawn.lua} kills a launched process after a certain amount of output. | |
| // @function kill | |
| def kill() { | |
| TerminateProcess(this->hProcess,0); | |
| return 0; | |
| } | |
| /// get the working size of the process. | |
| // @return minimum working set size | |
| // @return maximum working set size. | |
| // @function get_working_size | |
| def get_working_size() { | |
| SIZE_T minsize, maxsize; | |
| GetProcessWorkingSetSize(this->hProcess,&minsize,&maxsize); | |
| lua_pushnumber(L,minsize/1024); | |
| lua_pushnumber(L,maxsize/1024); | |
| return 2; | |
| } | |
| /// get the start time of this process. | |
| // @return a table in the same format as os.time() and os.date() expects. | |
| // @function get_start_time | |
| def get_start_time() { | |
| FILETIME create,exit,kernel,user,local; | |
| SYSTEMTIME time; | |
| GetProcessTimes(this->hProcess,&create,&exit,&kernel,&user); | |
| FileTimeToLocalFileTime(&create,&local); | |
| FileTimeToSystemTime(&local,&time); | |
| #define set(name,val) lua_pushinteger(L,val); lua_setfield(L,-2,#name); | |
| lua_newtable(L); | |
| set(year,time.wYear); | |
| set(month,time.wMonth); | |
| set(day,time.wDay); | |
| set(hour,time.wHour); | |
| set(min,time.wMinute); | |
| set(sec,time.wSecond); | |
| #undef set | |
| return 1; | |
| } | |
| // MS likes to be different: the 64-bit value encoded in FILETIME | |
| // is defined as the number of 100-nsec intervals since Jan 1, 1601 UTC | |
| static double fileTimeToMillisec(FILETIME *ft) { | |
| ULARGE_INTEGER ui; | |
| ui.LowPart = ft->dwLowDateTime; | |
| ui.HighPart = ft->dwHighDateTime; | |
| return (double) (ui.QuadPart/10000); | |
| } | |
| /// elapsed run time of this process. | |
| // @return user time in msec | |
| // @return system time in msec | |
| // @function get_run_times | |
| def get_run_times() { | |
| FILETIME create,exit,kernel,user; | |
| GetProcessTimes(this->hProcess,&create,&exit,&kernel,&user); | |
| lua_pushnumber(L,fileTimeToMillisec(&user)); | |
| lua_pushnumber(L,fileTimeToMillisec(&kernel)); | |
| return 2; | |
| } | |
| /// wait for this process to finish. | |
| // @param timeout optional timeout in millisec; defaults to waiting indefinitely. | |
| // @return this process object | |
| // @return either "OK" or "TIMEOUT" | |
| // @function wait | |
| def wait(Int timeout = 0) { | |
| return push_wait(L,this->hProcess, TIMEOUT(timeout)); | |
| } | |
| /// run callback when this process is finished. | |
| // @param callback the callback | |
| // @param timeout optional timeout in millisec; defaults to waiting indefinitely. | |
| // @return this process object | |
| // @return either "OK" or "TIMEOUT" | |
| // @function wait_async | |
| def wait_async(Value callback, Int timeout = 0) { | |
| return push_wait_async(L,this->hProcess, TIMEOUT(timeout), callback); | |
| } | |
| /// wait for this process to become idle and ready for input. | |
| // Only makes sense for processes with windows (will return immediately if not) | |
| // @param timeout optional timeout in millisec | |
| // @return this process object | |
| // @return either "OK" or "TIMEOUT" | |
| // @function wait_for_input_idle | |
| def wait_for_input_idle (Int timeout = 0) { | |
| return push_wait_result(L, WaitForInputIdle(this->hProcess, TIMEOUT(timeout))); | |
| } | |
| /// exit code of this process. | |
| // (Only makes sense if the process has in fact finished.) | |
| // @return exit code | |
| // @function get_exit_code | |
| def get_exit_code() { | |
| DWORD code; | |
| GetExitCodeProcess(this->hProcess, &code); | |
| lua_pushinteger(L,code); | |
| return 1; | |
| } | |
| /// close this process handle. | |
| // @function close | |
| def close() { | |
| CloseHandle(this->hProcess); | |
| this->hProcess = NULL; | |
| return 0; | |
| } | |
| def __gc () { | |
| if (this->hProcess != NULL) | |
| CloseHandle(this->hProcess); | |
| return 0; | |
| } | |
| } | |
| /// Working with processes. | |
| // @{readme.md.Creating_and_working_with_Processes} | |
| // @section Processes | |
| /// create a process object from the id. | |
| // @param pid the process id | |
| // @return @{Process} | |
| // @function process_from_id | |
| def process_from_id(Int pid) { | |
| return push_new_Process(L,pid,NULL); | |
| } | |
| /// process id of current process. | |
| // @return integer id | |
| // @function get_current_pid | |
| def get_current_pid() { | |
| lua_pushinteger(L,GetCurrentProcessId()); | |
| return 1; | |
| } | |
| /// process object of the current process. | |
| // @return @{Process} | |
| // @function get_current_process | |
| def get_current_process() { | |
| return push_new_Process(L,0,GetCurrentProcess()); | |
| } | |
| /// get all process ids in the system. | |
| // @{test-processes.lua} is a simple `ps` equivalent. | |
| // @return an array of process ids. | |
| // @function get_processes | |
| def get_processes() { | |
| DWORD processes[MAX_PROCESSES], cbNeeded, nProcess; | |
| int i, k = 1; | |
| if (! EnumProcesses (processes,sizeof(processes),&cbNeeded)) { | |
| return push_error(L); | |
| } | |
| nProcess = cbNeeded/sizeof (DWORD); | |
| lua_newtable(L); | |
| for (i = 0; i < nProcess; i++) { | |
| if (processes[i] != 0) { | |
| lua_pushinteger(L,processes[i]); | |
| lua_rawseti(L,-2,k++); | |
| } | |
| } | |
| return 1; | |
| } | |
| /// wait for a group of processes. | |
| // Note that this will work with @{Event} and @{Thread} objects as well. | |
| // @{process-wait.lua} shows a number of processes launched | |
| // in parallel | |
| // @param processes an array of @{Process} objects | |
| // @param all wait for all processes to finish (default false) | |
| // @param timeout wait upto this time in msec (default infinite) | |
| // @function wait_for_processes | |
| def wait_for_processes(Value processes, Boolean all, Int timeout = 0) { | |
| int status, i; | |
| void *p; | |
| int n = lua_objlen(L,processes); | |
| HANDLE handles[MAXIMUM_WAIT_OBJECTS]; | |
| if (n > MAXIMUM_WAIT_OBJECTS) { | |
| return push_error_msg(L,"cannot wait on so many processes"); | |
| } | |
| for (i = 0; i < n; i++) { | |
| lua_rawgeti(L,processes,i+1); | |
| // any user data with a handle as the first field will work here | |
| p = lua_touserdata(L,-1); | |
| if (p == NULL) { | |
| return push_error_msg(L,"non-object in list!"); | |
| } | |
| handles[i] = *(HANDLE*)p; | |
| } | |
| release_mutex(); | |
| status = WaitForMultipleObjects(n, handles, all, TIMEOUT(timeout)); | |
| lock_mutex(); | |
| status = status - WAIT_OBJECT_0 + 1; | |
| if (status < 1 || status > n) { | |
| return push_error(L); | |
| } else { | |
| lua_pushinteger(L,status); | |
| return 1; | |
| } | |
| } | |
| // These functions are all run in background threads, and a little bit of poor man's | |
| // OOP helps here. This is the base struct for describing threads with callbacks, | |
| // which may have an associated buffer and handle. | |
| #define callback_data_ \ | |
| HANDLE handle; \ | |
| lua_State *L; \ | |
| Ref callback; \ | |
| char *buf; \ | |
| int bufsz; | |
| typedef struct { | |
| callback_data_ | |
| } LuaCallback, *PLuaCallback; | |
| LuaCallback *lcb_callback(void *lcb, lua_State *L, int idx) { | |
| LuaCallback *data; | |
| if (lcb == NULL) { | |
| lcb = malloc(sizeof(LuaCallback)); | |
| } | |
| data = (LuaCallback*) lcb; | |
| data->L = L; | |
| data->callback = make_ref(L,idx); | |
| data->buf = NULL; | |
| data->handle = NULL; | |
| return data; | |
| } | |
| BOOL lcb_call(void *data, int idx, Str text, int flags) { | |
| LuaCallback *lcb = (LuaCallback*)data; | |
| return call_lua(lcb->L,lcb->callback,idx,text,flags); | |
| } | |
| void lcb_allocate_buffer(void *data, int size) { | |
| LuaCallback *lcb = (LuaCallback*)data; | |
| lcb->buf = malloc(size); | |
| lcb->bufsz = size; | |
| } | |
| void lcb_free(void *data) { | |
| LuaCallback *lcb = (LuaCallback*)data; | |
| if (! lcb) return; | |
| if (lcb->buf) { | |
| free(lcb->buf); | |
| lcb->buf = NULL; | |
| } | |
| if (lcb->handle) { | |
| CloseHandle(lcb->handle); | |
| lcb->handle = NULL; | |
| } | |
| release_ref(lcb->L,lcb->callback); | |
| } | |
| #define lcb_buf(data) ((LuaCallback *)data)->buf | |
| #define lcb_bufsz(data) ((LuaCallback *)data)->bufsz | |
| #define lcb_handle(data) ((LuaCallback *)data)->handle | |
| /// Thread object. This is returned by the @{File:read_async} method and the @{make_timer}, | |
| // @{make_pipe_server} and @{watch_for_file_changes} functions. Useful to kill a thread | |
| // and free associated resources. | |
| // @type Thread | |
| class Thread { | |
| HANDLE thread; | |
| LuaCallback *lcb; | |
| constructor (PLuaCallback lcb, HANDLE thread) { | |
| this->lcb = lcb; | |
| this->thread = thread; | |
| } | |
| /// suspend this thread. | |
| // @function suspend | |
| def suspend() { | |
| return push_bool(L, SuspendThread(this->thread) >= 0); | |
| } | |
| /// resume a suspended thread. | |
| // @function resume | |
| def resume() { | |
| return push_bool(L, ResumeThread(this->thread) >= 0); | |
| } | |
| /// kill this thread. Generally considered a 'nuclear' option, but | |
| // this implementation will free any associated callback references, buffers | |
| // and handles. @{test-timer.lua} shows how a timer can be terminated. | |
| // @function kill | |
| def kill() { | |
| BOOL ret = TerminateThread(this->thread,1); | |
| lcb_free(this->lcb); | |
| return push_bool(L,ret); | |
| } | |
| /// set a thread's priority | |
| // @param p positive integer to increase thread priority | |
| // @function set_priority | |
| def set_priority(Int p) { | |
| return push_bool(L, SetThreadPriority(this->thread,p)); | |
| } | |
| /// get a thread's priority | |
| // @function get_priority | |
| def get_priority() { | |
| int res = GetThreadPriority(this->thread); | |
| if (res != THREAD_PRIORITY_ERROR_RETURN) { | |
| lua_pushinteger(L,res); | |
| return 1; | |
| } else { | |
| return push_error(L); | |
| } | |
| } | |
| /// wait for this thread to finish. | |
| // @param timeout optional timeout in millisec; defaults to waiting indefinitely. | |
| // @return this thread object | |
| // @return either "OK" or "TIMEOUT" | |
| // @function wait | |
| def wait(Int timeout = 0) { | |
| return push_wait(L,this->thread, TIMEOUT(timeout)); | |
| } | |
| /// run callback when this thread is finished. | |
| // @param callback the callback | |
| // @param timeout optional timeout in millisec; defaults to waiting indefinitely. | |
| // @return this thread object | |
| // @return either "OK" or "TIMEOUT" | |
| // @function wait_async | |
| def wait_async(Value callback, Int timeout = 0) { | |
| return push_wait_async(L,this->thread, TIMEOUT(timeout), callback); | |
| } | |
| def __gc() { | |
| // lcb_free(this->lcb); concerned that this cd kick in prematurely! | |
| CloseHandle(this->thread); | |
| return 0; | |
| } | |
| } | |
| typedef LPTHREAD_START_ROUTINE TCB; | |
| int lcb_new_thread(TCB fun, void *data) { | |
| LuaCallback *lcb = (LuaCallback*)data; | |
| HANDLE thread = CreateThread(NULL,THREAD_STACK_SIZE,fun,data,0,NULL); | |
| return push_new_Thread(lcb->L,lcb,thread); | |
| } | |
| static void handle_waiter (LuaCallback *lcb) { | |
| DWORD res = WaitForSingleObject(lcb->handle,lcb->bufsz); | |
| lcb_call(lcb,0,res == WAIT_TIMEOUT ? "TIMEOUT" : "OK",0); | |
| } | |
| static int push_wait_async(lua_State *L, HANDLE h, int timeout, int callback) { | |
| LuaCallback *lcb = lcb_callback(NULL,L,callback); | |
| lcb->handle = h; | |
| lcb->bufsz = timeout; | |
| return lcb_new_thread((TCB)handle_waiter,lcb); | |
| } | |
| /// this represents a raw Windows file handle. | |
| // The write handle may be distinct from the read handle. | |
| // @type File | |
| class File { | |
| callback_data_ | |
| HANDLE hWrite; | |
| constructor (HANDLE hread, HANDLE hwrite) { | |
| lcb_handle(this) = hread; | |
| this->hWrite = hwrite; | |
| this->L = L; | |
| lcb_allocate_buffer(this,FILE_BUFF_SIZE); | |
| } | |
| /// write to a file. | |
| // @param s text | |
| // @return number of bytes written. | |
| // @function write | |
| def write(Str s) { | |
| DWORD bytesWrote; | |
| WriteFile(this->hWrite, s, lua_objlen(L,2), &bytesWrote, NULL); | |
| lua_pushinteger(L,bytesWrote); | |
| return 1; | |
| } | |
| static BOOL raw_read (File *this) { | |
| DWORD bytesRead = 0; | |
| BOOL res = ReadFile(lcb_handle(this), lcb_buf(this), lcb_bufsz(this), &bytesRead, NULL); | |
| lcb_buf(this)[bytesRead] = '\0'; | |
| return res && bytesRead; | |
| } | |
| /// read from a file. | |
| // Please note that this is not buffered, and you will have to | |
| // split into lines, etc yourself. | |
| // @return text if successful, nil plus error otherwise. | |
| // @function read | |
| def read() { | |
| if (raw_read(this)) { | |
| lua_pushstring(L,lcb_buf(this)); | |
| return 1; | |
| } else { | |
| return push_error(L); | |
| } | |
| } | |
| static void file_reader (File *this) { // background reader thread | |
| int n; | |
| do { | |
| n = raw_read(this); | |
| // empty buffer is passed at end - we can discard the callback then. | |
| lcb_call (this,0,lcb_buf(this),n == 0 ? DISCARD : 0); | |
| } while (n); | |
| } | |
| /// asynchronous read. | |
| // @param callback function that will receive each chunk of text | |
| // as it comes in. | |
| // @return @{Thread} | |
| // @function read_async | |
| def read_async (Value callback) { | |
| this->callback = make_ref(L,callback); | |
| return lcb_new_thread((TCB)&file_reader,this); | |
| } | |
| def close() { | |
| if (this->hWrite != lcb_handle(this)) | |
| CloseHandle(this->hWrite); | |
| lcb_free(this); | |
| return 0; | |
| } | |
| def __gc () { | |
| free(this->buf); | |
| return 0; | |
| } | |
| } | |
| /// Launching processes. | |
| // @section Launch | |
| /// set an environment variable for any child processes. | |
| // @{setenv.lua} shows how this also affects processes | |
| // launched with @{os.execute} | |
| // Note that this can't affect any system environment variables, see | |
| // [here](http://msdn.microsoft.com/en-us/library/ms682653%28VS.85%29.aspx) | |
| // for how to set these. | |
| // @param name name of variable | |
| // @param value value to set | |
| // @function setenv | |
| def setenv(Str name, Str value) { | |
| WCHAR wname[256],wvalue[MAX_WPATH]; | |
| return push_bool(L, SetEnvironmentVariableW(wconv(name),wconv(value))); | |
| } | |
| /// Spawn a process. | |
| // @param program the command-line (program + parameters) | |
| // @param dir the working directory for the process (optional) | |
| // @return @{Process} | |
| // @return @{File} | |
| // @function spawn_process | |
| def spawn_process(Str program, StrNil dir) { | |
| WCHAR wdir [MAX_WPATH]; | |
| SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), 0, 0}; | |
| SECURITY_DESCRIPTOR sd; | |
| STARTUPINFOW si = { | |
| sizeof(STARTUPINFOW), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 | |
| }; | |
| HANDLE hPipeRead,hWriteSubProcess; | |
| HANDLE hRead2,hPipeWrite; | |
| BOOL running; | |
| PROCESS_INFORMATION pi; | |
| sa.bInheritHandle = TRUE; | |
| sa.lpSecurityDescriptor = NULL; | |
| InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); | |
| SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE); | |
| sa.nLength = sizeof(SECURITY_ATTRIBUTES); | |
| sa.lpSecurityDescriptor = &sd; | |
| // Create pipe for output redirection | |
| CreatePipe(&hPipeRead, &hPipeWrite, &sa, 0); | |
| // Create pipe for input redirection. In this code, you do not | |
| // redirect the output of the child process, but you need a handle | |
| // to set the hStdInput field in the STARTUP_INFO struct. For safety, | |
| // you should not set the handles to an invalid handle. | |
| hRead2 = NULL; | |
| CreatePipe(&hRead2, &hWriteSubProcess, &sa, 0); | |
| SetHandleInformation(hPipeRead, HANDLE_FLAG_INHERIT, 0); | |
| SetHandleInformation(hWriteSubProcess, HANDLE_FLAG_INHERIT, 0); | |
| si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; | |
| si.wShowWindow = SW_HIDE; | |
| si.hStdInput = hRead2; | |
| si.hStdOutput = hPipeWrite; | |
| si.hStdError = hPipeWrite; | |
| running = CreateProcessW( | |
| NULL, | |
| (LPWSTR)wstring(program), | |
| NULL, NULL, | |
| TRUE, CREATE_NEW_PROCESS_GROUP, | |
| NULL, | |
| wconv(dir), | |
| &si, &pi); | |
| if (running) { | |
| CloseHandle(pi.hThread); | |
| CloseHandle(hRead2); | |
| CloseHandle(hPipeWrite); | |
| push_new_Process(L,pi.dwProcessId,pi.hProcess); | |
| push_new_File(L,hPipeRead,hWriteSubProcess); | |
| return 2; | |
| } else { | |
| return push_error(L); | |
| } | |
| } | |
| /// execute a system command. | |
| // This is like `os.execute()`, except that it works without ugly | |
| // console flashing in Windows GUI applications. It additionally | |
| // returns all text read from stdout and stderr. | |
| // @param cmd a shell command (may include redirection, etc) | |
| // @param unicode if 'unicode' force built-in commands to output in unicode; | |
| // in this case the result is always UTF-8 | |
| // @return return code | |
| // @return program output | |
| // @function execute | |
| static void launcher(LuaCallback *lcb) { | |
| lua_State *L = lcb->L; | |
| lua_State *Lnew = lua_newthread(L); | |
| push_ref(L,lcb->callback); | |
| lua_xmove(L,Lnew,1); | |
| push_ref(L, (int)lcb->bufsz); | |
| lua_xmove(L, Lnew,1); | |
| if (lua_pcall(Lnew,1,0,0) != 0) { | |
| fprintf(stderr,"error %s\n",lua_tostring(Lnew,-1)); | |
| } | |
| lcb_free(lcb); | |
| } | |
| /// launch a function in a new thread. | |
| // @param fun a Lua function | |
| // @param data any Lua value to be passed to function | |
| // @return @{Thread} object | |
| // @function thread | |
| def thread(Value fun, Value data) { | |
| LuaCallback *lcb = lcb_callback(NULL, L, fun); | |
| lcb->bufsz = make_ref(L,data); | |
| return lcb_new_thread((TCB)launcher,lcb); | |
| } | |
| // Timer support ////////// | |
| typedef struct { | |
| callback_data_ | |
| int msec; | |
| } TimerData; | |
| static void timer_thread(TimerData *data) { // background timer thread | |
| while (1) { | |
| Sleep(data->msec); | |
| // no parameters passed, but if we return true then we exit! | |
| if (lcb_call(data,0,0,0)) | |
| break; | |
| } | |
| } | |
| /// Asynchronous Timers. | |
| // @section Timers | |
| /// Create an asynchronous timer. | |
| // The callback can return true if it wishes to cancel the timer. | |
| // @{test-sleep.lua} shows how you need to call @{sleep} at the end of | |
| // a console application for these timers to work in the background. | |
| // @param msec interval in millisec | |
| // @param callback a function to be called at each interval. | |
| // @return @{Thread} | |
| // @function make_timer | |
| def make_timer(Int msec, Value callback) { | |
| TimerData *data = (TimerData *)malloc(sizeof(TimerData)); | |
| data->msec = msec; | |
| lcb_callback(data,L,callback); | |
| return lcb_new_thread((TCB)&timer_thread,data); | |
| } | |
| #define PSIZE 512 | |
| typedef struct { | |
| callback_data_ | |
| const char *pipename; | |
| } PipeServerParms; | |
| static void pipe_server_thread(PipeServerParms *parms) { | |
| while (1) { | |
| BOOL connected; | |
| HANDLE hPipe = CreateNamedPipe( | |
| parms->pipename, // pipe named | |
| PIPE_ACCESS_DUPLEX, // read/write access | |
| PIPE_WAIT, // blocking mode | |
| 255, | |
| PSIZE, // output buffer size | |
| PSIZE, // input buffer size | |
| 0, // client time-out | |
| NULL); // default security attribute | |
| if (hPipe == INVALID_HANDLE_VALUE) { | |
| // could not create named pipe - callback is passed nil, err msg. | |
| lua_pushnil(parms->L); | |
| lcb_call(parms,-1,last_error(0),REF_IDX | DISCARD); | |
| return; | |
| } | |
| // Wait for the client to connect; if it succeeds, | |
| // the function returns a nonzero value. If the function | |
| // returns zero, GetLastError returns ERROR_PIPE_CONNECTED. | |
| connected = ConnectNamedPipe(hPipe, NULL) ? | |
| TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); | |
| if (connected) { | |
| push_new_File(parms->L,hPipe,hPipe); | |
| lcb_call(parms,-1,0,REF_IDX); // pass it a new File reference | |
| } else { | |
| CloseHandle(hPipe); | |
| } | |
| } | |
| } | |
| /// Dealing with named pipes. | |
| // @section Pipes | |
| /// open a pipe for reading and writing. | |
| // @param pipename the pipename (default is "\\\\.\\pipe\\luawinapi") | |
| // @function open_pipe | |
| def open_pipe(Str pipename = "\\\\.\\pipe\\luawinapi") { | |
| HANDLE hPipe = CreateFile( | |
| pipename, | |
| GENERIC_READ | // read and write access | |
| GENERIC_WRITE, | |
| 0, // no sharing | |
| NULL, // default security attributes | |
| OPEN_EXISTING, // opens existing pipe | |
| 0, // default attributes | |
| NULL); // no template file | |
| if (hPipe == INVALID_HANDLE_VALUE) { | |
| return push_error(L); | |
| } else { | |
| return push_new_File(L,hPipe,hPipe); | |
| } | |
| } | |
| /// create a named pipe server. | |
| // This goes into a background loop, and accepts client connections. | |
| // For each new connection, the callback will be called with a File | |
| // object for reading and writing to the client. | |
| // @param callback a function that will be passed a File object | |
| // @param pipename Must be of the form \\.\pipe\name, defaults to | |
| // \\.\pipe\luawinapi. | |
| // @return @{Thread}. | |
| // @function make_pipe_server | |
| def make_pipe_server(Value callback, Str pipename = "\\\\.\\pipe\\luawinapi") { | |
| PipeServerParms *psp = (PipeServerParms*)malloc(sizeof(PipeServerParms)); | |
| lcb_callback(psp,L,callback); | |
| psp->pipename = pipename; | |
| return lcb_new_thread((TCB)&pipe_server_thread,psp); | |
| } | |
| /// Drive information and directories. | |
| // @section Directories | |
| /// the short path name of a directory or file. | |
| // This is always in ASCII, 8.3 format. This function will create the | |
| // file first if it does not exist; the result can be used to open | |
| // files with unicode names (see @{testshort.lua}) | |
| // @param path multibyte encoded file path | |
| // @return ASCII 8.3 format file path | |
| // @function short_path | |
| def short_path(Str path) { | |
| WCHAR wpath[MAX_WPATH]; | |
| HANDLE hFile; | |
| int res; | |
| wconv(path); | |
| // if the file doesn't exist, then force its creation | |
| hFile = CreateFileW(wpath, | |
| GENERIC_WRITE, | |
| FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, | |
| CREATE_NEW, | |
| FILE_ATTRIBUTE_NORMAL, | |
| NULL); | |
| if (hFile == INVALID_HANDLE_VALUE) { | |
| if (GetLastError() != ERROR_FILE_EXISTS) // that error is fine! | |
| return push_perror(L,"createfile"); | |
| } else { // if we created it successfully, then close. | |
| CloseHandle(hFile); | |
| } | |
| res = GetShortPathNameW(wpath,wbuff,sizeof(wbuff)); | |
| if (res > 0) { | |
| return push_wstring(L,wbuff); | |
| } else { | |
| return push_error(L); | |
| } | |
| } | |
| /// get a temporary filename. | |
| // (Don't use os.tmpname) | |
| // @return full path within temporary files directory. | |
| // @function temp_name | |
| /// delete a file or directory. | |
| // @param file may be a wildcard | |
| // @function delete_file_or_dir | |
| /// make a directory. | |
| // Will make necessary subpaths if command extensions are enabled. | |
| // @function make_dir | |
| /// remove a directory. | |
| // @param dir the directory | |
| // @param tree if true, clean out the directory tree | |
| // @function remove_dir | |
| /// iterator over directory contents. | |
| // @usage for f in winapi.files 'dir\\*.txt' do print(f) end | |
| // @param mask a file mask like "*.txt" | |
| // @param subdirs iterate over subdirectories (default no) | |
| // @param attrib iterate over items with given attribute (as in dir /A:) | |
| // @see files.lua | |
| // @function files | |
| /// iterate over subdirectories | |
| // @param file mask like "mydirs\\t*" | |
| // @param subdirs iterate over subdirectories (default no) | |
| // @see files | |
| // @function dirs | |
| /// get all the drives on this computer. | |
| // An example is @{drives.lua} | |
| // @return a table of drive names | |
| // @function get_logical_drives | |
| def get_logical_drives() { | |
| int i, lasti = 0, k = 1; | |
| WCHAR dbuff[MAX_WPATH]; | |
| LPWSTR p = dbuff; | |
| DWORD size = GetLogicalDriveStringsW(sizeof(dbuff),dbuff); | |
| lua_newtable(L); | |
| for (i = 0; i < size; i++) { | |
| if (dbuff[i] == '\0') { | |
| push_wstring_l(L,p, i - lasti); | |
| lua_rawseti(L,-2,k++); | |
| p = dbuff + i+1; | |
| lasti = i+1; | |
| } | |
| } | |
| return 1; | |
| } | |
| /// get the type of the given drive. | |
| // @param root root of drive (e.g. 'c:\\') | |
| // @return one of the following: unknown, none, removable, fixed, remote, | |
| // cdrom, ramdisk. | |
| // @function get_drive_type | |
| def get_drive_type(Str root) { | |
| UINT res = GetDriveType(root); | |
| const char *type = "?"; | |
| switch(res) { | |
| case DRIVE_UNKNOWN: type = "unknown"; break; | |
| case DRIVE_NO_ROOT_DIR: type = "none"; break; | |
| case DRIVE_REMOVABLE: type = "removable"; break; | |
| case DRIVE_FIXED: type = "fixed"; break; | |
| case DRIVE_REMOTE: type = "remote"; break; | |
| case DRIVE_CDROM: type = "cdrom"; break; | |
| case DRIVE_RAMDISK: type = "ramdisk"; break; | |
| } | |
| lua_pushstring(L,type); | |
| return 1; | |
| } | |
| /// get the free disk space. | |
| // @param root the root of the drive (e.g. 'd:\\') | |
| // @return free space in kB | |
| // @return total space in kB | |
| // @function get_disk_free_space | |
| def get_disk_free_space(Str root) { | |
| ULARGE_INTEGER freebytes, totalbytes; | |
| if (! GetDiskFreeSpaceEx(root,&freebytes,&totalbytes,NULL)) { | |
| return push_error(L); | |
| } | |
| lua_pushnumber(L,freebytes.QuadPart/1024); | |
| lua_pushnumber(L,totalbytes.QuadPart/1024); | |
| return 2; | |
| } | |
| /// get the network resource associated with this drive. | |
| // @param root drive name in the form 'X:' | |
| // @return UNC name | |
| // @function get_disk_network_name | |
| def get_disk_network_name(Str root) { | |
| DWORD size = sizeof(wbuff); | |
| DWORD res = WNetGetConnectionW(wstring(root),wbuff,&size); | |
| if (res == NO_ERROR) { | |
| return push_wstring(L,wbuff); | |
| } else { | |
| return push_error(L); | |
| } | |
| } | |
| // Directory change notification /////// | |
| typedef struct { | |
| callback_data_ | |
| DWORD how; | |
| DWORD subdirs; | |
| } FileChangeParms; | |
| static void file_change_thread(FileChangeParms *fc) { // background file monitor thread | |
| while (1) { | |
| int next, offset; | |
| DWORD bytes; | |
| // This fills in some gaps: | |
| // http://qualapps.blogspot.com/2010/05/understanding-readdirectorychangesw_19.html | |
| if (! ReadDirectoryChangesW(lcb_handle(fc),lcb_buf(fc),lcb_bufsz(fc), | |
| fc->subdirs, fc->how, &bytes,NULL,NULL)) { | |
| lcb_call(fc,-1,last_error(0),INTEGER | DISCARD); | |
| break; | |
| } | |
| next = 0; | |
| offset = 0; | |
| do { | |
| int outchars; | |
| char outbuff[MAX_PATH]; | |
| PFILE_NOTIFY_INFORMATION pni = (PFILE_NOTIFY_INFORMATION)(lcb_buf(fc)+offset); | |
| outchars = WideCharToMultiByte( | |
| get_encoding(), 0, | |
| pni->FileName, | |
| pni->FileNameLength/2, // it's bytes, not number of characters! | |
| outbuff,sizeof(outbuff), | |
| NULL,NULL); | |
| if (outchars == 0) { | |
| lcb_call(fc,-1,"wide char conversion borked",INTEGER | DISCARD); | |
| break; | |
| } | |
| outbuff[outchars] = '\0'; // not null-terminated! | |
| // pass the action that occurred and the file name | |
| lcb_call(fc,pni->Action,outbuff,INTEGER); | |
| next = pni->NextEntryOffset; | |
| offset += next; | |
| } while (next != 0); | |
| } | |
| } | |
| //// start watching a directory. | |
| // @param dir the directory | |
| // @param how what events to monitor. Can be a sum of these flags: | |
| // | |
| // * `FILE_NOTIFY_CHANGE_FILE_NAME` | |
| // * `FILE_NOTIFY_CHANGE_DIR_NAME` | |
| // * `FILE_NOTIFY_CHANGE_LAST_WRITE` | |
| // | |
| // @param subdirs whether subdirectories should be monitored | |
| // @param callback a function which will receive the kind of change | |
| // plus the filename that changed. The change will be one of these: | |
| // | |
| // * `FILE_ACTION_ADDED` | |
| // * `FILE_ACTION_REMOVED` | |
| // * `FILE_ACTION_MODIFIED` | |
| // * `FILE_ACTION_RENAMED_OLD_NAME` | |
| // * `FILE_ACTION_RENAMED_NEW_NAME` | |
| // | |
| // @return a thread object. | |
| // @see test-watcher.lua | |
| // @function watch_for_file_changes | |
| def watch_for_file_changes (Str dir, Int how, Boolean subdirs, Value callback) { | |
| FileChangeParms *fc = (FileChangeParms*)malloc(sizeof(FileChangeParms)); | |
| lcb_callback(fc,L,callback); | |
| fc->how = how; | |
| fc->subdirs = subdirs; | |
| lcb_handle(fc) = CreateFileW(wstring(dir), | |
| FILE_LIST_DIRECTORY, | |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | |
| NULL, | |
| OPEN_ALWAYS, | |
| FILE_FLAG_BACKUP_SEMANTICS, | |
| NULL | |
| ); | |
| if (lcb_handle(fc) == INVALID_HANDLE_VALUE) { | |
| return push_error(L); | |
| } | |
| lcb_allocate_buffer(fc,2048); | |
| return lcb_new_thread((TCB)&file_change_thread,fc); | |
| } | |
| /// Class representing Windows registry keys. | |
| // @type Regkey | |
| class Regkey { | |
| HKEY key; | |
| constructor (HKEY k) { | |
| this->key = k; | |
| } | |
| /// set the string value of a name. | |
| // @param name the name | |
| // @param val the string value | |
| // @param type one of `REG_BINARY`,`REG_DWORD`,`REG_SZ`,`REG_MULTI_SZ`,`REG_EXPAND_SZ` | |
| // @function set_value | |
| def set_value(Str name, Value val, Int type=REG_SZ) { | |
| int sz; | |
| DWORD ival; | |
| LONG res; | |
| const char *str; | |
| const BYTE *data; | |
| WCHAR wname[MAX_KEYS]; | |
| wstring_buff(name,wname,sizeof(wname)); | |
| if (lua_isstring(L,val)) { | |
| if (type == REG_DWORD) { | |
| return push_error_msg(L, "parameter must be a number for REG_DWORD"); | |
| } | |
| str = lua_tostring(L,val); | |
| if (type != REG_BINARY) { | |
| WStr res = wstring(str); | |
| sz = (lstrlenW(res)+1)*sizeof(WCHAR); | |
| data = (const BYTE *)res; | |
| } else { | |
| sz = lua_objlen(L,val); | |
| data = (const BYTE *)str; | |
| } | |
| } else { | |
| ival = (DWORD)lua_tonumber(L,val); | |
| data = (const BYTE *)&ival; | |
| sz = sizeof(DWORD); | |
| } | |
| res = RegSetValueExW(this->key,wname,0,type,data,sz); | |
| if (res == ERROR_SUCCESS) { | |
| return push_ok(L); | |
| } else { | |
| return push_error_code(L, res); | |
| } | |
| } | |
| /// get the value and type of a name. | |
| // @param name the name (can be empty for the default value) | |
| // @return the value (either a string or a number) | |
| // @return the type | |
| // @function get_value | |
| def get_value(Str name = "") { | |
| DWORD type,size = sizeof(wbuff); | |
| void *data = wbuff; | |
| if (RegQueryValueExW(this->key,wstring(name),0,&type,data,&size) != ERROR_SUCCESS) { | |
| return push_error(L); | |
| } | |
| if (type == REG_BINARY) { | |
| lua_pushlstring(L,(const char *)data,size); | |
| } else if (type == REG_EXPAND_SZ || type == REG_SZ) { | |
| push_wstring(L,wbuff); //,size); | |
| } else { | |
| lua_pushnumber(L,*(unsigned long *)data); | |
| } | |
| lua_pushinteger(L,type); | |
| return 2; | |
| } | |
| def delete_key(Str name) { | |
| if (RegDeleteKeyW(this->key,wstring(name)) == ERROR_SUCCESS) { | |
| lua_pushboolean(L,1); | |
| } else { | |
| return push_error(L); | |
| } | |
| return 1; | |
| } | |
| /// enumerate the subkeys of a key. | |
| // @return a table of key names | |
| // @function get_keys | |
| def get_keys() { | |
| int i = 0; | |
| LONG res; | |
| DWORD size; | |
| lua_newtable(L); | |
| while (1) { | |
| size = sizeof(wbuff); | |
| res = RegEnumKeyExW(this->key,i,wbuff,&size,NULL,NULL,NULL,NULL); | |
| if (res != ERROR_SUCCESS) break; | |
| push_wstring(L,wbuff); | |
| lua_rawseti(L,-2,i+1); | |
| ++i; | |
| } | |
| if (res != ERROR_NO_MORE_ITEMS) { | |
| lua_pop(L,1); | |
| return push_error(L); | |
| } | |
| return 1; | |
| } | |
| /// close this key. | |
| // Although this will happen when garbage collection happens, it | |
| // is good practice to call this explicitly. | |
| // @function close | |
| def close() { | |
| RegCloseKey(this->key); | |
| this->key = NULL; | |
| return 0; | |
| } | |
| /// flush the key. | |
| // Considered an expensive function; use it only when you have | |
| // to guarantee modification. | |
| // @function flush | |
| def flush() { | |
| return push_bool(L,RegFlushKey(this->key)); | |
| } | |
| def __gc() { | |
| if (this->key != NULL) | |
| RegCloseKey(this->key); | |
| return 0; | |
| } | |
| } | |
| /// Registry Functions. | |
| // @section Registry | |
| /// Open a registry key. | |
| // @{test-reg.lua} shows reading a registry value and enumerating subkeys. | |
| // @param path the full registry key | |
| // e.g `[[HKEY\_LOCAL\_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion]]` | |
| // @param writeable true if you want to set values | |
| // @return @{Regkey} | |
| // @function open_reg_key | |
| def open_reg_key(Str path, Boolean writeable) { | |
| HKEY hKey; | |
| DWORD access; | |
| char kbuff[1024]; | |
| hKey = split_registry_key(path,kbuff); | |
| if (hKey == NULL) { | |
| return push_error_msg(L,"unrecognized registry key"); | |
| } | |
| access = writeable ? KEY_ALL_ACCESS : (KEY_READ | KEY_ENUMERATE_SUB_KEYS); | |
| if (RegOpenKeyExW(hKey,wstring(kbuff),0,access,&hKey) == ERROR_SUCCESS) { | |
| return push_new_Regkey(L,hKey); | |
| } else { | |
| return push_error(L); | |
| } | |
| } | |
| /// Create a registry key. | |
| // @param path the full registry key | |
| // @return @{Regkey} | |
| // @function create_reg_key | |
| def create_reg_key (Str path) { | |
| char kbuff[1024]; | |
| HKEY hKey = split_registry_key(path,kbuff); | |
| if (hKey == NULL) { | |
| return push_error_msg(L,"unrecognized registry key"); | |
| } | |
| if (RegCreateKeyExW(hKey,wstring(kbuff),0,NULL,0,KEY_ALL_ACCESS,NULL,&hKey,NULL)) { | |
| return push_new_Regkey(L,hKey); | |
| } else { | |
| return push_error(L); | |
| } | |
| } | |
| lua { | |
| function winapi.execute(cmd,unicode) | |
| local comspec = os.getenv('COMSPEC') | |
| if unicode ~= 'unicode' then | |
| cmd = comspec ..' /c '..cmd | |
| local P,f = winapi.spawn_process(cmd) | |
| if not P then return nil,f end | |
| local txt = f:read() | |
| local out = {} | |
| while txt do | |
| table.insert(out,txt) | |
| txt = f:read() | |
| end | |
| return P:wait():get_exit_code(),table.concat(out,'') | |
| else | |
| local tmpfile,res,f,out = winapi.temp_name() | |
| cmd = comspec..' /u /c '..cmd..' > "'..tmpfile..'"' | |
| local P,err = winapi.spawn_process(cmd) | |
| if not P then return nil,err end | |
| res = P:wait():get_exit_code() | |
| f = io.open(tmpfile) | |
| out = f:read '*a' | |
| f:close() | |
| os.remove(tmpfile) | |
| out, err = winapi.encode(winapi.CP_UTF16,winapi.CP_UTF8,out) | |
| if err then return nil,err end | |
| return res,out | |
| end | |
| end | |
| function winapi.make_name_matcher(text) | |
| return function(w) return tostring(w):match(text) end | |
| end | |
| function winapi.make_class_matcher(classname) | |
| return function(w) return w:get_class_name():match(classname) end | |
| end | |
| function winapi.find_window_ex(match) | |
| local res | |
| winapi.enum_windows(function(w) | |
| if match(w) then res = w end | |
| end) | |
| return res | |
| end | |
| function winapi.find_all_windows(match) | |
| local res = {} | |
| winapi.enum_windows(function(w) | |
| if match(w) then res[#res+1] = w end | |
| end) | |
| return res | |
| end | |
| function winapi.find_window_match(text) | |
| return winapi.find_window_ex(winapi.make_name_matcher(text)) | |
| end | |
| function winapi.temp_name () return os.getenv('TEMP')..os.tmpname() end | |
| local function exec_cmd (cmd,arg) | |
| local res,err = winapi.execute(cmd..' "'..arg..'"') | |
| if res == 0 then return true | |
| else return nil,err | |
| end | |
| end | |
| function winapi.make_dir(dir) return exec_cmd('mkdir',dir) end | |
| function winapi.remove_dir(dir,tree) return exec_cmd('rmdir '.. ((tree and '/S /Q') or ''),dir) end | |
| function winapi.delete_file_or_dir(file) return exec_cmd('del',file) end | |
| function winapi.files(mask,subdirs,attrib) | |
| local flags = '/B ' | |
| if subdirs then flags = flags..' /S' end | |
| if attrib then flags = flags..' /A:'..attrib end | |
| local ret, text = winapi.execute('dir '..flags..' "'..mask..'"','unicode') | |
| if ret ~= 0 then return nil,text end | |
| return text:gmatch('[^\r\n]+') | |
| end | |
| function winapi.dirs(mask,subdirs) return winapi.files(mask,subdirs,'D') end | |
| } | |
| initial init_mutex { | |
| setup_mutex(); | |
| return 0; | |
| } | |
| /*** Constants. | |
| The following constants are available: | |
| * CP_ACP, (valid values for encoding) | |
| * CP_UTF8, | |
| * CP_UTF16, | |
| * SW_HIDE, (Window operations for Window.show) | |
| * SW_MAXIMIZE, | |
| * SW_MINIMIZE, | |
| * SW_SHOWNORMAL, | |
| * VK_BACK, | |
| * VK_TAB, | |
| * VK_RETURN, | |
| * VK_SPACE, | |
| * VK_PRIOR, | |
| * VK_NEXT, | |
| * VK_END, | |
| * VK_HOME, | |
| * VK_LEFT, | |
| * VK_UP, | |
| * VK_RIGHT, | |
| * VK_DOWN, | |
| * VK_INSERT, | |
| * VK_DELETE, | |
| * VK_ESCAPE, | |
| * VK_F1, | |
| * VK_F2, | |
| * VK_F3, | |
| * VK_F4, | |
| * VK_F5, | |
| * VK_F6, | |
| * VK_F7, | |
| * VK_F8, | |
| * VK_F9, | |
| * VK_F10, | |
| * VK_F11, | |
| * VK_F12, | |
| * FILE\_NOTIFY\_CHANGE\_FILE\_NAME (these are input flags for watch\_for\_file\_changes) | |
| * FILE\_NOTIFY\_CHANGE\_DIR\_NAME | |
| * FILE\_NOTIFY\_CHANGE\_LAST\_WRITE | |
| * FILE\_ACTION\_ADDED (these describe the change: first argument of callback) | |
| * FILE\_ACTION\_REMOVED | |
| * FILE\_ACTION\_MODIFIED | |
| * FILE\_ACTION\_RENAMED\_OLD\_NAME | |
| * FILE\_ACTION\_RENAMED\_NEW\_NAME | |
| @section constants | |
| */ | |
| /// useful Windows API constants | |
| // @table constants | |
| #define CP_UTF16 -1 | |
| constants { | |
| CP_ACP, | |
| CP_UTF8, | |
| CP_UTF16, | |
| SW_HIDE, | |
| SW_MAXIMIZE, | |
| SW_MINIMIZE, | |
| SW_SHOWNORMAL, | |
| SW_SHOWNOACTIVATE, | |
| SW_SHOW, | |
| SW_RESTORE, | |
| VK_BACK, | |
| VK_TAB, | |
| VK_RETURN, | |
| VK_SPACE, | |
| VK_PRIOR, | |
| VK_NEXT, | |
| VK_END, | |
| VK_HOME, | |
| VK_LEFT, | |
| VK_UP, | |
| VK_RIGHT, | |
| VK_DOWN, | |
| VK_INSERT, | |
| VK_DELETE, | |
| VK_ESCAPE, | |
| VK_F1, | |
| VK_F2, | |
| VK_F3, | |
| VK_F4, | |
| VK_F5, | |
| VK_F6, | |
| VK_F7, | |
| VK_F8, | |
| VK_F9, | |
| VK_F10, | |
| VK_F11, | |
| VK_F12, | |
| FILE_NOTIFY_CHANGE_FILE_NAME, | |
| FILE_NOTIFY_CHANGE_DIR_NAME, | |
| FILE_NOTIFY_CHANGE_LAST_WRITE, | |
| FILE_ACTION_ADDED, | |
| FILE_ACTION_REMOVED, | |
| FILE_ACTION_MODIFIED, | |
| FILE_ACTION_RENAMED_OLD_NAME, | |
| FILE_ACTION_RENAMED_NEW_NAME, | |
| WIN_NOACTIVATE, | |
| WIN_NOMOVE, | |
| WIN_NOSIZE, | |
| WIN_SHOWWINDOW, | |
| WIN_NOZORDER, | |
| WIN_BOTTOM, | |
| WIN_NOTOPMOST, | |
| WIN_TOP, | |
| WIN_TOPMOST, | |
| REG_BINARY, | |
| REG_DWORD, | |
| REG_SZ, | |
| REG_MULTI_SZ, | |
| REG_EXPAND_SZ | |
| } | |
| } |