lib/win/window.rb in win-0.0.4 vs lib/win/window.rb in win-0.0.6

- old
+ new

@@ -3,10 +3,16 @@ module Win # Contains constants, functions and wrappers related to Windows manipulation # module Window + # Internal constants: + + # Windows keyboard-related Constants: + # ? move to keyboard.rb? + KEY_DELAY = 0.00001 + include Win::Library #General constants: # Windows Message Get Text @@ -14,10 +20,74 @@ # Windows Message Sys Command WM_SYSCOMMAND = 0x0112 # Sys Command Close SC_CLOSE = 0xF060 + # Key down keyboard event (the key is being depressed) + KEYEVENTF_KEYDOWN = 0 + # Key up keyboard event (the key is being released) + KEYEVENTF_KEYUP = 2 + # Extended kb event. If specified, the scan code was preceded by a prefix byte having the value 0xE0 (224). + KEYEVENTF_EXTENDEDKEY = 1 + + # Virtual key codes: + + # Control-break processing + VK_CANCEL = 0x03 + # Backspace? key + VK_BACK = 0x08 + # Tab key + VK_TAB = 0x09 + # Shift key + VK_SHIFT = 0x10 + # Ctrl key + VK_CONTROL = 0x11 + # ENTER key + VK_RETURN = 0x0D + # ALT key + VK_ALT = 0x12 + # ALT key alias + VK_MENU = 0x12 + # PAUSE key + VK_PAUSE = 0x13 + # CAPS LOCK key + VK_CAPITAL = 0x14 + # ESC key + VK_ESCAPE = 0x1B + # SPACEBAR + VK_SPACE = 0x20 + # PAGE UP key + VK_PRIOR = 0x21 + # PAGE DOWN key + VK_NEXT = 0x22 + # END key + VK_END = 0x23 + # HOME key + VK_HOME = 0x24 + # LEFT ARROW key + VK_LEFT = 0x25 + # UP ARROW key + VK_UP = 0x26 + # RIGHT ARROW key + VK_RIGHT = 0x27 + # DOWN ARROW key + VK_DOWN = 0x28 + # SELECT key + VK_SELECT = 0x29 + # PRINT key + VK_PRINT = 0x2A + # EXECUTE key + VK_EXECUTE = 0x2B + # PRINT SCREEN key + VK_SNAPSHOT = 0x2C + # INS key + VK_INSERT = 0x2D + # DEL key + VK_DELETE = 0x2E + # HELP key + VK_HELP = 0x2F + # ShowWindow constants: # Hides the window and activates another window. SW_HIDE = 0 # Same as SW_SHOWNORMAL @@ -49,11 +119,55 @@ SW_SHOWDEFAULT = 10 # Windows 2000/XP: Minimizes a window, even if the thread that owns the window is not responding. Only use this # flag when minimizing windows from a different thread. SW_FORCEMINIMIZE = 11 + class << self + # Def_block that calls API function expecting EnumWindowsProc callback (EnumWindows, EnumChildWindows, ...). + # Default callback just pushes all passed handles into Array that is returned if Enum function call was successful. + # If runtime block is given it is added to the end of default callback (handles Array is still collected/returned). + # If Enum function call failed, method returns nil, otherwise an Array of window handles. + # + def return_enum #:nodoc: + lambda do |api, *args, &block| + args.push 0 if args.size == api.prototype.size - 2 # If value is missing, it defaults to 0 + handles = [] + # Insert callback proc into appropriate place of args Array + args[api.prototype.find_index(:enum_callback), 0] = + proc do |handle, message| + handles << handle + block ? block[handle, message] : true + end + handles if api.call *args + end + end + + # Helper method that creates def_block returning (possibly encoded) string as a result of + # api function call or nil if zero characters was returned by api call + # + def return_string( encode = nil ) #:nodoc: + lambda do |api, *args| + namespace.enforce_count( args, api.prototype, -2) + buffer = FFI::MemoryPointer.new :char, 1024 + buffer.put_string(0, "\x00" * 1023) + args += [buffer, 1024] + num_chars = api.call(*args) + return nil if num_chars == 0 + if encode + string = buffer.get_bytes(0, num_chars*2) + string = string.force_encoding('utf-16LE').encode(encode) + else + string = buffer.get_bytes(0, num_chars) + end + string.rstrip + end + end + + private :return_enum, :return_string + end + # Windows GUI API definitions: ## # Tests whether the specified window handle identifies an existing window. # A thread should not use IsWindow for a window that it did not create because the window @@ -69,11 +183,11 @@ # Tests if the specified window, its parent window, its parent's parent window, and so forth, # have the WS_VISIBLE style. Because the return value specifies whether the window has the # WS_VISIBLE style, it may be true even if the window is totally obscured by other windows. # # :call-seq: - # visible?( win_handle ), window_visible?( win_handle ) + # [window_]visible?( win_handle ) # function 'IsWindowVisible', 'L', 'L', aliases: :visible? ## # Tests whether the specified window is maximized. @@ -146,24 +260,10 @@ # :call-seq: # win_handle = find_window_ex( win_handle, after_child, class_name, win_name ) # function 'FindWindowEx', 'LLPP', 'L', zeronil: true - # Helper method that creates def_block returning (possibly encoded) string as a result of - # api function call or nil if zero characters was returned by api call - # TODO: should be private - def self.return_string( encode = nil ) #:nodoc: - lambda do |api, *args| - namespace.enforce_count( args, api.prototype, -2) - args += [string = buffer, string.length] - num_chars = api.call(*args) - return nil if num_chars == 0 - string = string.force_encoding('utf-16LE').encode(encode) if encode - string.rstrip - end - end - ## # Returns the text of the specified window's title bar (if it has one). # If the specified window is a control, the text of the control is copied. However, GetWindowText # cannot retrieve the text of a control in another application. # @@ -193,20 +293,20 @@ # belongs to the calling app, GetWindowText will cause the calling app to become unresponsive. # To retrieve the text of a control in another process, send a WM_GETTEXT message directly instead # of calling GetWindowText. # #:call-seq: - # text = get_window_text( win_handle ) + # text = [get_]window_text( win_handle ) # function 'GetWindowText', 'LPI', 'L', &return_string ## # Unicode version of get_window_text (returns rstripped utf-8 string) # API improved to require only win_handle and return rstripped string # #:call-seq: - # text = get_window_text_w( win_handle ) + # text = [get_]window_text_w( win_handle ) # function 'GetWindowTextW', 'LPI', 'L', &return_string('utf-8') ## # Retrieves the name of the class to which the specified window belongs. @@ -225,20 +325,20 @@ # Enhanced Parameters: # win_handle (L) - Handle to the window and, indirectly, the class to which the window belongs. # Returns: Name of the class or NIL if function fails. For extended error information, call GetLastError. # #:call-seq: - # text = get_class_name( win_handle ) + # text = [get_]class_name( win_handle ) # function 'GetClassName', 'LPI', 'I', &return_string ## # Unicode version of get_class_name (returns rstripped utf-8 string) # API improved to require only win_handle and return rstripped string # #:call-seq: - # text = get_class_name_w( win_handle ) + # text = [get_]class_name_w( win_handle ) # function 'GetClassNameW', 'LPI', 'I', &return_string('utf-8') ## # Shows and hides windows. @@ -263,16 +363,10 @@ # Hides the window and activates another window def hide_window(win_handle) show_window(win_handle, SW_HIDE) end - return_thread_process = lambda do |api, *args| - namespace.enforce_count( args, api.prototype, -1) - thread = api.call(args.first, process = [1].pack('L')) - nonzero_array(thread, *process.unpack('L')) - end - ## # Retrieves the identifier of the thread that created the specified window # and, optionally, the identifier of the process that created the window. # # Original Parameters: @@ -285,21 +379,20 @@ # New Parameters: # handle (L) - Handle to the window. # Returns: Pair of identifiers of the thread and process_id that created the window. # #:call-seq: - # thread, process_id = get_window_tread_process_id( win_handle ) + # thread, process_id = [get_]window_tread_process_id( win_handle ) # - function 'GetWindowThreadProcessId', 'LP', 'L', &return_thread_process + function 'GetWindowThreadProcessId', [:long, :pointer], :long, &->(api, *args) { + namespace.enforce_count( args, api.prototype, -1) + process = FFI::MemoryPointer.new(:long) + process.write_long(1) + thread = api.call(args.first, process) + thread == 0 ? [nil, nil] : [thread, process.read_long()] } + # weird lambda literal instead of block is needed because RDoc goes crazy if block is attached to meta-definition - return_rect = lambda do |api, *args| - namespace.enforce_count( args, api.prototype, -1) - rectangle = [0, 0, 0, 0].pack('L*') - res = api.call args.first, rectangle - res == 0 ? [nil, nil, nil, nil] : rectangle.unpack('L*') - end - ## # Retrieves the dimensions of the specified window bounding rectangle. # Dimensions are given relative to the upper-left corner of the screen. # # Original Parameters: @@ -316,111 +409,151 @@ # # Remarks: As a convention for the RECT structure, the bottom-right coordinates of the returned rectangle # are exclusive. In other words, the pixel at (right, bottom) lies immediately outside the rectangle. # #:call-seq: - # rect = get_window_rect( win_handle ) + # rect = [get_]window_rect( win_handle ) # - function 'GetWindowRect', 'LP', 'I', &return_rect + function 'GetWindowRect', 'LP', 'I', &->(api, *args) { + namespace.enforce_count( args, api.prototype, -1) + rect = FFI::MemoryPointer.new(:long, 4) + rect.write_array_of_long([0, 0, 0, 0]) + res = api.call args.first, rect + res == 0 ? [nil, nil, nil, nil] : rect.read_array_of_long(4) } + # weird lambda literal instead of block is needed because RDoc goes crazy if block is attached to meta-definition -# # Procedure that calls api function expecting a callback. If runtime block is given -# # it is converted into callback, otherwise procedure returns an array of all handles -# # pushed into callback by api enumeration -# # TODO: should be private -# # -# def self.return_enum #:nodoc: -# lambda do |api, *args, &block| -# namespace.enforce_count( args, api.prototype, -1) -# handles = [] -# cb = if block -# callback('LP', 'I', &block) -# else -# callback('LP', 'I') do |handle, message| -# handles << handle -# true -# end -# end -# args[api.prototype.find_index('K'), 0] = cb # Insert callback into appropriate place of args Array -# api.call *args -# handles -# end -# end -# -# ## -# # The EnumWindows function enumerates all top-level windows on the screen by passing the handle to -# # each window, in turn, to an application-defined callback function. EnumWindows continues until -# # the last top-level window is enumerated or the callback function returns FALSE. -# # -# # Original Parameters: -# # callback [K] - Pointer to an application-defined callback function (see EnumWindowsProc). -# # message [P] - Specifies an application-defined value(message) to be passed to the callback function. -# # Original Return: Nonzero if the function succeeds, zero if the function fails. GetLastError for error info. -# # If callback returns zero, the return value is also zero. In this case, the callback function should -# # call SetLastError to obtain a meaningful error code to be returned to the caller of EnumWindows. -# # -# # API improved to accept blocks (instead of callback objects) and message as a single arg -# # -# # New Parameters: -# # message [P] - Specifies an application-defined value(message) to be passed to the callback function. -# # block given to method invocation serves as an application-defined callback function (see EnumWindowsProc). -# # Returns: True if the function succeeds, false if the function fails. GetLastError for error info. -# # If callback returns zero, the return value is also zero. In this case, the callback function should -# # call SetLastError to obtain a meaningful error code to be returned to the caller of EnumWindows. -# # -# # Remarks: The EnumWindows function does not enumerate child windows, with the exception of a few top-level -# # windows owned by the system that have the WS_CHILD style. This function is more reliable than calling -# # the GetWindow function in a loop. An application that calls GetWindow to perform this task risks being -# # caught in an infinite loop or referencing a handle to a window that has been destroyed. -# # -# #:call-seq: -# # status = enum_windows( message ) {|win_handle, message| callback procedure } -# # -# function'EnumWindows', 'KP', 'L', boolean: true, &return_enum -# -# ## -# # Enumerates child windows to a given window. -# # -# # Original Parameters: -# # parent (L) - Handle to the parent window whose child windows are to be enumerated. -# # callback [K] - Pointer to an application-defined callback function (see EnumWindowsProc). -# # message [P] - Specifies an application-defined value(message) to be passed to the callback function. -# # Original Return: Not used (?!) -# # If callback returns zero, the return value is also zero. In this case, the callback function should -# # call SetLastError to obtain a meaningful error code to be returned to the caller of EnumWindows. -# # If it is nil, this function is equivalent to EnumWindows. Windows 95/98/Me: parent cannot be NULL. -# # -# # API improved to accept blocks (instead of callback objects) and two args: parent handle and message. -# # New Parameters: -# # parent (L) - Handle to the parent window whose child windows are to be enumerated. -# # message (P) - Specifies an application-defined value(message) to be passed to the callback function. -# # block given to method invocation serves as an application-defined callback function (see EnumChildProc). -# # -# # Remarks: -# # If a child window has created child windows of its own, EnumChildWindows enumerates those windows as well. -# # A child window that is moved or repositioned in the Z order during the enumeration process will be properly enumerated. -# # The function does not enumerate a child window that is destroyed before being enumerated or that is created during the enumeration process. -# # -# #:call-seq: -# # enum_windows( parent_handle, message ) {|win_handle, message| callback procedure } -# # -# function 'EnumChildWindows', 'LKP', 'L', &return_enum + # This is an application-defined callback function that receives top-level window handles as a result of a call + # to the EnumWindows, EnumChildWindows or EnumDesktopWindows function. + # + # Syntax: BOOL CALLBACK EnumWindowsProc( HWND hwnd, LPARAM lParam ); + # + # Parameters: + # hwnd (L) - [out] Handle to a top-level window. + # lParam (L) - [in] Specifies the application-defined value given in EnumWindows or EnumDesktopWindows. + # Return Values: + # TRUE continues enumeration. FALSE stops enumeration. + # + # Remarks: An application must register this callback function by passing its address to EnumWindows or EnumDesktopWindows. + callback :enum_callback, 'LL', :bool ## + # The EnumWindows function enumerates all top-level windows on the screen by passing the handle to + # each window, in turn, to an application-defined callback function. EnumWindows continues until + # the last top-level window is enumerated or the callback function returns FALSE. + # + # Original Parameters: + # callback [K] - Pointer to an application-defined callback function (see EnumWindowsProc). + # value [P] - Specifies an application-defined value(message) to be passed to the callback function. + # Original Return: Nonzero if the function succeeds, zero if the function fails. GetLastError for error info. + # If callback returns zero, the return value is also zero. In this case, the callback function should + # call SetLastError to obtain a meaningful error code to be returned to the caller of EnumWindows. + # + # API improved to accept blocks (instead of callback objects) and message as a single arg + # + # New Parameters: + # message [P] - Specifies an application-defined value(message) to be passed to the callback function. + # block given to method invocation serves as an application-defined callback function (see EnumWindowsProc). + # Returns: True if the function succeeds, false if the function fails. GetLastError for error info. + # If callback returns zero, the return value is also zero. In this case, the callback function should + # call SetLastError to obtain a meaningful error code to be returned to the caller of EnumWindows. + # + # Remarks: The EnumWindows function does not enumerate child windows, with the exception of a few top-level + # windows owned by the system that have the WS_CHILD style. This function is more reliable than calling + # the GetWindow function in a loop. An application that calls GetWindow to perform this task risks being + # caught in an infinite loop or referencing a handle to a window that has been destroyed. + # + #:call-seq: + # handles = enum_windows( [value] ) {|handle, message| your callback procedure } + # + function'EnumWindows', [:enum_callback, :long], :bool, &return_enum + + ## + #EnumDesktopWindows Function + # + #Enumerates all top-level windows associated with the specified desktop. It passes the handle to each window, in turn, to an application-defined callback function. + # + # + #Syntax + #BOOL WINAPI EnumDesktopWindows( + # __in_opt HDESK hDesktop, + # __in WNDENUMPROC lpfn, + # __in LPARAM lParam + #); + # + #Parameters + #hDesktop + #A handle to the desktop whose top-level windows are to be enumerated. This handle is returned by the CreateDesktop, GetThreadDesktop, OpenDesktop, or OpenInputDesktop function, and must have the DESKTOP_ENUMERATE access right. For more information, see Desktop Security and Access Rights. + # + #If this parameter is NULL, the current desktop is used. + # + #lpfn + #A pointer to an application-defined EnumWindowsProc callback function. + # + #lParam + #An application-defined value to be passed to the callback function. + # + #Return Value + #If the function fails or is unable to perform the enumeration, the return value is zero. + # + #To get extended error information, call GetLastError. + # + #You must ensure that the callback function sets SetLastError if it fails. + # + #Windows Server 2003 and Windows XP/2000: If there are no windows on the desktop, GetLastError returns ERROR_INVALID_HANDLE. + #Remarks + #The EnumDesktopWindows function repeatedly invokes the lpfn callback function until the last top-level window is enumerated or the callback function returns FALSE. + # + #Requirements + #Client Requires Windows Vista, Windows XP, or Windows 2000 Professional. + function'EnumDesktopWindows', [:ulong, :enum_callback, :long], :bool, &return_enum + + ## + # Enumerates child windows to a given window. + # + # Original Parameters: + # parent (L) - Handle to the parent window whose child windows are to be enumerated. + # callback [K] - Pointer to an application-defined callback function (see EnumWindowsProc). + # message [P] - Specifies an application-defined value(message) to be passed to the callback function. + # Original Return: Not used (?!) + # If callback returns zero, the return value is also zero. In this case, the callback function should + # call SetLastError to obtain a meaningful error code to be returned to the caller of EnumWindows. + # If it is nil, this function is equivalent to EnumWindows. Windows 95/98/Me: parent cannot be NULL. + # + # API improved to accept blocks (instead of callback objects) and parent handle (value is optional, default 0) + # New Parameters: + # parent (L) - Handle to the parent window whose child windows are to be enumerated. + # value (P) - Specifies an application-defined value(message) to be passed to the callback function. + # block given to method invocation serves as an application-defined callback function (see EnumChildProc). + # + # Remarks: + # If a child window has created child windows of its own, EnumChildWindows enumerates those windows as well. + # A child window that is moved or repositioned in the Z order during the enumeration process will be properly enumerated. + # The function does not enumerate a child window that is destroyed before being enumerated or that is created during the enumeration process. + # + #:call-seq: + # handles = enum_child_windows( parent_handle, [value = 0] ) {|handle, message| your callback procedure } + # + function 'EnumChildWindows', [:ulong, :enum_callback, :long], :bool, &return_enum + + ## # GetForegroundWindow function returns a handle to the foreground window (the window with which the user # is currently working). The system assigns a slightly higher priority to the thread that creates the # foreground window than it does to other threads. # # Syntax: HWND GetForegroundWindow(VOID); # # Return Value: The return value is a handle to the foreground window. The foreground window can be NULL in # certain circumstances, such as when a window is losing activation. # #:call-seq: - # win_handle = (get_)foreground_window() + # win_handle = [get_]foreground_window() # function 'GetForegroundWindow', [], 'L' + ## + # Tests if given window handle points to foreground (topmost) window + # def foreground?(win_handle) win_handle == foreground_window end ## @@ -434,117 +567,42 @@ # # Remarks: To get the handle to the foreground window, you can use GetForegroundWindow. # To get the window handle to the active window in the message queue for another thread, use GetGUIThreadInfo. # #:call-seq: - # win_handle = (get_)active_window() + # win_handle = [get_]active_window() # function 'GetActiveWindow', [], 'L' + ## + # The keybd_event function synthesizes a keystroke. The system can use such a synthesized keystroke to generate + # a WM_KEYUP or WM_KEYDOWN message. The keyboard driver's interrupt handler calls the keybd_event function. + # + # !!!! Windows NT/2000/XP/Vista:This function has been superseded. Use SendInput instead. + # + # Syntax: VOID keybd_event( BYTE bVk, BYTE bScan, DWORD dwFlags, PTR dwExtraInfo); + # + # Parameters: + # bVk [C] - [in] Specifies a virtual-key code. The code must be a value in the range 1 to 254. + # For a complete list, see Virtual-Key Codes. + # bScan [C] - [in] Specifies a hardware scan code for the key. + # dwFlags [L] - [in] Specifies various aspects of function operation. This parameter can be + # one or more of the following values: + # KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, KEYEVENTF_KEYDOWN + # dwExtraInfo [L] -[in] Specifies an additional value associated with the key stroke. + # + # Return Value: none + # + # Remarks: An application can simulate a press of the PRINTSCRN key in order to obtain a screen snapshot and save + # it to the clipboard. To do this, call keybd_event with the bVk parameter set to VK_SNAPSHOT. + # + # Windows NT/2000/XP: The keybd_event function can toggle the NUM LOCK, CAPS LOCK, and SCROLL LOCK keys. + # Windows 95/98/Me: The keybd_event function can toggle only the CAPS LOCK and SCROLL LOCK keys. + # function 'keybd_event', 'IILL', 'V' + function 'PostMessage', 'LLLL', 'L' function 'SendMessage', 'LLLP', 'L' function 'GetDlgItem', 'LL', 'L' - - # Convenience wrapper methods: - - # emulates combinations of keys pressed (Ctrl+Alt+P+M, etc) - def keystroke(*keys) - return if keys.empty? - keybd_event keys.first, 0, KEYEVENTF_KEYDOWN, 0 - sleep WG_KEY_DELAY - keystroke *keys[1..-1] - sleep WG_KEY_DELAY - keybd_event keys.first, 0, KEYEVENTF_KEYUP, 0 - end - - # types text message into window holding the focus - def type_in(message) - message.scan(/./m) do |char| - keystroke(*char.to_vkeys) - end - end - - # finds top-level dialog window by title and yields it to given block - def dialog(title, seconds=3) - d = begin - win = Window.top_level(title, seconds) - yield(win) ? win : nil - rescue TimeoutError - end - d.wait_for_close if d - return d - end - - # Thin wrapper around window handle - class Window - include Win::Window - extend Win::Window - - attr_reader :handle - - # find top level window by title, return wrapped Window object - def self.top_level(title, seconds=3) - @handle = timeout(seconds) do - sleep WG_SLEEP_DELAY while (h = find_window nil, title) == nil; h - end - Window.new @handle - end - - def initialize(handle) - @handle = handle - end - - # find child window (control) by title, window class, or control ID: - def child(id) - result = case id - when String - by_title = find_window_ex @handle, 0, nil, id.gsub('_' , '&' ) - by_class = find_window_ex @handle, 0, id, nil - by_title ? by_title : by_class - when Fixnum - get_dlg_item @handle, id - when nil - find_window_ex @handle, 0, nil, nil - else - nil - end - raise "Control '#{id}' not found" unless result - Window.new result - end - - def children - enum_child_windows(@handle,'Msg').map{|child_handle| Window.new child_handle} - end - - # emulate click of the control identified by id - def click(id) - h = child(id).handle - rectangle = [0, 0, 0, 0].pack 'LLLL' - get_window_rect h, rectangle - left, top, right, bottom = rectangle.unpack 'LLLL' - center = [(left + right) / 2, (top + bottom) / 2] - set_cursor_pos *center - mouse_event MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0 - mouse_event MOUSEEVENTF_LEFTUP, 0, 0, 0, 0 - end - - def close - post_message @handle, WM_SYSCOMMAND, SC_CLOSE, 0 - end - - def wait_for_close - timeout(WG_CLOSE_TIMEOUT) do - sleep WG_SLEEP_DELAY while window_visible?(@handle) - end - end - - def text - buffer = "\x0" * 2048 - length = send_message @handle, WM_GETTEXT, buffer.length, buffer - length == 0 ? '' : buffer[0..length - 1] - end - end - end end