require 'ffi' module Win32 class Screenshot # internal methods class BitmapMaker #:nodoc:all class << self extend FFI::Library ffi_lib 'user32', 'gdi32' ffi_convention :stdcall callback :enum_callback, [:long, :pointer], :bool # user32.dll attach_function :enum_windows, :EnumWindows, [:enum_callback, :pointer], :long attach_function :window_text, :GetWindowTextA, [:long, :pointer, :int], :int attach_function :window_text_length, :GetWindowTextLengthA, [:long], :int attach_function :window_visible, :IsWindowVisible, [:long], :bool attach_function :dc, :GetDC, [:long], :long attach_function :client_rect, :GetClientRect, [:long, :pointer], :bool attach_function :minimized, :IsIconic, [:long], :bool attach_function :show_window, :ShowWindow, [:long, :int], :bool attach_function :foreground_window, :GetForegroundWindow, [], :long attach_function :desktop_window, :GetDesktopWindow, [], :long attach_function :window_thread_process_id, :GetWindowThreadProcessId, [:long, :pointer], :long attach_function :attach_thread_input, :AttachThreadInput, [:long, :long, :bool], :bool attach_function :set_foreground_window, :SetForegroundWindow, [:long], :bool attach_function :bring_window_to_top, :BringWindowToTop, [:long], :bool attach_function :set_active_window, :SetActiveWindow, [:long], :long # gdi32.dll attach_function :create_compatible_dc, :CreateCompatibleDC, [:long], :long attach_function :create_compatible_bitmap, :CreateCompatibleBitmap, [:long, :int, :int], :long attach_function :select_object, :SelectObject, [:long, :long], :long attach_function :bit_blt, :BitBlt, [:long, :int, :int, :int, :int, :long, :int, :int, :long], :bool attach_function :di_bits, :GetDIBits, [:long, :long, :int, :int, :pointer, :pointer, :int], :int attach_function :delete_object, :DeleteObject, [:long], :bool attach_function :delete_dc, :DeleteDC, [:long], :bool attach_function :release_dc, :ReleaseDC, [:long, :long], :int EnumWindowCallback = FFI::Function.new(:bool, [ :long, :pointer ], { :convention => :stdcall }) do |hwnd, param| searched_window = WindowStruct.new param title = Util.window_title(hwnd) if title =~ Regexp.new(searched_window[:title].read_string) && window_visible(hwnd) searched_window[:hwnd] = hwnd false else true end end class WindowStruct < FFI::Struct layout :title, :pointer, :hwnd, :long end def hwnd(window_title) window = WindowStruct.new unless window_title.is_a?(Regexp) window_title = Regexp.escape(window_title.to_s) else window_title = window_title.to_s end window_title = FFI::MemoryPointer.from_string(window_title) window[:title] = window_title enum_windows(EnumWindowCallback, window.to_ptr) window[:hwnd] == 0 ? nil : window[:hwnd] end def prepare_window(hwnd, pause) restore(hwnd) if minimized(hwnd) set_foreground(hwnd) sleep pause end SW_RESTORE = 9 def restore(hwnd) show_window(hwnd, SW_RESTORE) end def set_foreground(hwnd) if foreground_window != hwnd set_foreground_window(hwnd) set_active_window(hwnd) bring_window_to_top(hwnd) # and just in case... foreground_thread = window_thread_process_id(foreground_window, nil) other_thread = window_thread_process_id(hwnd, nil) attach_thread_input(foreground_thread, other_thread, true) unless other_thread == foreground_thread set_foreground_window(hwnd) set_active_window(hwnd) bring_window_to_top(hwnd) attach_thread_input(foreground_thread, other_thread, false) unless other_thread == foreground_thread end end def capture_all(hwnd, &proc) width, height = Util.dimensions_for(hwnd) capture_area(hwnd, 0, 0, width, height, &proc) end SRCCOPY = 0x00CC0020 DIB_RGB_COLORS = 0 def capture_area(hwnd, x1, y1, x2, y2) # block hScreenDC = dc(hwnd) w = x2-x1 h = y2-y1 hmemDC = create_compatible_dc(hScreenDC) hmemBM = create_compatible_bitmap(hScreenDC, w, h) select_object(hmemDC, hmemBM) bit_blt(hmemDC, 0, 0, w, h, hScreenDC, x1, y1, SRCCOPY) bitmap_size = w * h * 3 + w % 4 * h lpvpxldata = FFI::MemoryPointer.new(bitmap_size) # Bitmap header # http://www.fortunecity.com/skyscraper/windows/364/bmpffrmt.html bmInfo = [40, w, h, 1, 24, 0, 0, 0, 0, 0, 0, 0].pack('L3S2L6') di_bits(hmemDC, hmemBM, 0, h, lpvpxldata, bmInfo, DIB_RGB_COLORS) bmFileHeader = [ 19778, bitmap_size + 40 + 14, 0, 0, 54 ].pack('SLSSL') bmp_data = bmFileHeader + bmInfo + lpvpxldata.read_string(bitmap_size) yield(w, h, bmp_data) ensure lpvpxldata.free delete_object(hmemBM) delete_dc(hmemDC) release_dc(0, hScreenDC) end end end end end