Re: MoveWindow with Windows API

eryk sun <[email protected]> Sat, 22 Apr 2017 03:35:17 +0000
Newsgroups gmane.comp.python.ctypes
Message-ID <CACL+1asU-QR3nO_pCjJmw+1uTcuAKtO+KcqYyeWtME+hWV2=YA@mail.gmail.com>
On Fri, Apr 21, 2017 at 11:19 PM, Michael C
<[email protected]> wrote:
> Hi all,
>
> I am trying to move and resize a window of a video game, but the following
> code doesn't work for some reason. How it works is that it acquire the
> handle to the foreground window, and then move it to the specified location
> and size. Since it works on notepad, but not my game window, i used
> Kernel32.GetLastError() to find out what went wrong.

Since you've loaded the library with use_last_error=True, you should
use the more reliable function, ctypes.get_last_error() to get the
thread's last WinAPI error. But probably in this case GetLastError()
has returned the right error value.

In a reply you mentioned you're getting the error code
ERROR_ACCESS_DENIED (5). This is to be expected when the target window
is at a higher integrity level. With UAC enabled, standard users and
administrators are logged on with a medium integrity-level access
token. You'll have to elevate your script or run it from an elevated
command prompt if you need to work with high integrity windows.

> import ctypes
> import time
>
> time.sleep(2)
>
> User32 = ctypes.WinDLL('User32', use_last_error=True)
> Kernel32 = ctypes.WinDLL('Kernel32', use_last_error=True)
>
> handle = User32.GetForegroundWindow()
>
> print(User32.MoveWindow(handle, 50, 50, 500, 500, False))

If this is running in 64-bit Python 2, avoid passing integer handle
arguments without defining the prototype. Handles are 64-bit pointers
in 64-bit Windows (though the actual values of real handles are
limited to 32-bit), but for ctypes the default integer argument type
is a 32-bit C int. In Python 2, ctypes doesn't zero the call stack
when copying the 32-bit integer value, so it's possible for garbage to
be passed in the high DWORD, which will be read as an invalid handle
value. Python 3 overwrites the full 64-bit QWORD in this case by
casting the value to intptr_t, so it doesn't have this problem.

Plus it's better in general to define a function's prototype and set
an errcheck handler. You'll get a safer, more pythonic interface. It's
not free though. ctypes is pretty clunky. You can write functions to
paper over some of the awkwardness. Here's an example DEF function.

    import ctypes
    from ctypes import wintypes

    user32 = ctypes.WinDLL('user32', use_last_error=True)

    def DEF(name, lib, restype=Ellipsis, errcheck=None, argtypes=(),
            use_last_error=None):
        base = lib._FuncPtr
        if restype is Ellipsis:
            restype = base._restype_
        if use_last_error is None:
            use_last_error = bool(base._flags_ & ctypes._FUNCFLAG_USE_LASTERROR)
        if (argtypes and isinstance(argtypes[0], (tuple, list)) and
            len(argtypes[0]) > 1):
            paramflags = tuple(tuple(p[1:]) if isinstance(p[1], int) else
                               (0,) + tuple(p[1:]) for p in argtypes)
            argtypes = tuple(a[0] for a in argtypes)
        else:
            paramflags = None
        proto = ctypes.WINFUNCTYPE(restype, *argtypes,
                    use_last_error=use_last_error)
        func = proto((name, lib), paramflags)
        if errcheck:
            func.errcheck = errcheck
        setattr(lib, name, func)

    def _check_bool(result, func, args):
        if not result:
            err = ctypes.get_last_error()
            if err:
                raise ctypes.WinError(err)
        return args

    DEF('GetForegroundWindow', user32, wintypes.HWND)
    DEF('MoveWindow', user32, wintypes.BOOL, _check_bool,
        [(wintypes.HWND, 'hWnd'),
         (ctypes.c_int,  'X'),
         (ctypes.c_int,  'Y'),
         (ctypes.c_int,  'nWidth'),
         (ctypes.c_int,  'nHeight'),
         (wintypes.BOOL, 'bRepaint')])

    def get_foreground_window():
        while True:
            hwnd = user32.GetForegroundWindow()
            if hwnd is not None:
                return hwnd

    if __name__ == '__main__':
        import time
        time.sleep(2)
        hwnd = get_foreground_window()
        user32.MoveWindow(hwnd, X=50, Y=50, nWidth=500, nHeight=500,
            bRepaint=False)

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot