xpra icon
Bug tracker and wiki

This bug tracker and wiki are being discontinued
please use https://github.com/Xpra-org/xpra instead.


Ticket #1354: window_hooks.py

File window_hooks.py, 6.1 KB (added by Antoine Martin, 5 years ago)

try to always clamp to the monitor size when maximized

Line 
1# This file is part of Xpra.
2# Copyright (C) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>)
3# Copyright (C) 2010-2016 Antoine Martin <antoine@devloop.org.uk>
4# Copyright (C) 2008, 2010 Nathaniel Smith <njs@pobox.com>
5# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
6# later version. See the file COPYING for details.
7
8from xpra.util import envbool
9from xpra.log import Logger
10log = Logger("win32", "window", "util")
11vlog = Logger("verbose")
12
13import win32con         #@UnresolvedImport
14import win32api         #@UnresolvedImport
15import ctypes
16from ctypes import c_int, c_long
17from ctypes.wintypes import POINT
18from xpra.platform.win32.wndproc_events import WNDPROC_EVENT_NAMES
19
20#use ctypes to ensure we call the "W" version:
21SetWindowLong = ctypes.windll.user32.SetWindowLongW
22CallWindowProc = ctypes.windll.user32.CallWindowProcW
23WndProcType = ctypes.WINFUNCTYPE(c_int, c_long, c_int, c_int, c_int)
24
25
26class MINMAXINFO(ctypes.Structure):
27    _fields_ = [
28                ("ptReserved",      POINT),
29                ("ptMaxSize",       POINT),
30                ("ptMaxPosition",   POINT),
31                ("ptMinTrackSize",  POINT),
32                ("ptMaxTrackSize",  POINT),
33               ]
34
35
36#loosely based on this recipe:
37#http://code.activestate.com/recipes/334779-pygtk-win32-extension-empower-gtk-with-win32-windo/
38#and this WM_GETMINMAXINFO ctypes code:
39#https://github.com/Mozillion/SublimeSpeech/blob/master/lib/dragonfly/windows/dialog_base.py
40#only hardcoded for handling WM_GETMINMAXINFO,
41#but should be pretty easy to tweak if needed.
42
43HOOK_MINMAXINFO = envbool("XPRA_WIN32_MINMAXINFO", True)
44
45
46class Win32Hooks(object):
47
48    def __init__(self, hwnd):
49        self._hwnd = hwnd
50        self._message_map = {}
51        self.max_size = None
52        if HOOK_MINMAXINFO:
53            self.add_window_event_handler(win32con.WM_GETMINMAXINFO, self.on_getminmaxinfo)
54        try:
55            #we only use this code for resizable windows, so use SM_C?SIZEFRAME:
56            self.frame_width = win32api.GetSystemMetrics(win32con.SM_CXSIZEFRAME)
57            self.frame_height = win32api.GetSystemMetrics(win32con.SM_CYSIZEFRAME)
58            self.caption_height = win32api.GetSystemMetrics(win32con.SM_CYCAPTION);
59        except:
60            self.frame_width = 4
61            self.frame_height = 4
62            self.caption_height = 26
63        log("Win32Hooks: window frame size is %sx%s", self.frame_width, self.frame_height)
64        log("Win32Hooks: message_map=%s", self._message_map)
65        self._oldwndproc = None
66
67    def add_window_event_handler(self, event, handler):
68        self._message_map[event] = handler
69
70    def setup(self):
71        assert self._oldwndproc is None
72        self._newwndproc = WndProcType(self._wndproc)
73        self._oldwndproc = SetWindowLong(self._hwnd, win32con.GWL_WNDPROC, self._newwndproc)
74
75    def on_getminmaxinfo(self, hwnd, msg, wparam, lparam):
76        if not lparam:
77            return None
78        info = ctypes.cast(lparam, ctypes.POINTER(MINMAXINFO)).contents
79        style = win32api.GetWindowLong(hwnd, win32con.GWL_STYLE)
80        is_maximized = style & win32con.WS_MAXIMIZE
81        if self.max_size or is_maximized:
82            has_border = style & win32con.WS_BORDER
83            if has_border:
84                fw, fh = self.frame_width, self.frame_height
85            else:
86                fw, fh = 0, 0
87            #use the monitor dimensions as limit if they're smaller
88            #(for maximized state only - users can enlarge more if they want)
89            mmax_x, mmax_y = 32768, 32768
90            if is_maximized:
91                try:
92                    monitor = win32api.MonitorFromWindow(hwnd, win32con.MONITOR_DEFAULTTONEAREST)
93                    minfo = win32api.GetMonitorInfo(monitor)
94                    log("on_getminmaxinfo GetMonitorInfo(%s)=%s", monitor, minfo)
95                    geom = minfo["Monitor"]
96                    if geom:
97                        mmax_x, mmax_y = geom[2], geom[3]
98                except:
99                    log.error("Error: cannot query monitor information for this window", exc_info=True)
100            wmax_x, wmax_y = 32768, 32768
101            if self.max_size:
102                wmax_x, wmax_y = self.max_size
103            w = min(mmax_x, wmax_x + fw*2)
104            h = min(mmax_y, wmax_y + self.caption_height + fh*2)
105            log("on_getminmaxinfo window=%#x max_size=%s, has-border=%s, is-maximized=%s, frame=%sx%s, minmaxinfo size=%sx%s",
106                hwnd, self.max_size, has_border, is_maximized, fw, fh, w, h)
107            if w<32768 or h<32768:
108                point  = POINT(w, h)
109                info.ptMaxSize       = point
110                info.ptMaxTrackSize  = point
111            return 0
112        log("on_getminmaxinfo window=%#x, max_size=%s, is-maximized=%s", hwnd, self.max_size, is_maximized)
113
114    def cleanup(self, *args):
115        log("cleanup%s", args)
116        self._message_map = {}
117        #since we assume the window is closed, restoring the wnd proc may be redundant here:
118        if not self._oldwndproc or not self._hwnd:
119            return
120        try:
121            SetWindowLong(self._hwnd, win32con.GWL_WNDPROC, self._oldwndproc)
122            self._oldwndproc = None
123            self._hwnd = None
124        except:
125            log.error("cleanup", exc_info=True)
126
127    def _wndproc(self, hwnd, msg, wparam, lparam):
128        event_name = WNDPROC_EVENT_NAMES.get(msg, msg)
129        callback = self._message_map.get(msg)
130        vlog("_wndproc%s event name=%s, callback=%s", (hwnd, msg, wparam, lparam), event_name, callback)
131        v = None
132        if callback:
133            #run our callback
134            try:
135                v = callback(hwnd, msg, wparam, lparam)
136                vlog("%s%s=%s", callback, (hwnd, msg, wparam, lparam), v)
137            except Exception as e:
138                log.error("Error: callback %s failed:", callback)
139                log.error(" %s", e)
140        #if our callback doesn't define the return value, use the default handler:
141        if v is None:
142            v = CallWindowProc(self._oldwndproc, hwnd, msg, wparam, lparam)
143            vlog("_wndproc%s return value=%s", (hwnd, msg, wparam, lparam), v)
144        return v