Skip to content

Commit 5b19dad

Browse files
committed
Windows: Improved AlwaysOnBottom() (still struggling to find better solutions)
1 parent 5ab178c commit 5b19dad

File tree

1 file changed

+32
-23
lines changed

1 file changed

+32
-23
lines changed

src/pygetwindowmp/_pygetwindow_win.py

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/python
22
# -*- coding: utf-8 -*-
3-
3+
import atexit
44
import ctypes
55
import sys
66
import time
@@ -314,19 +314,18 @@ def alwaysOnBottom(self, aob=True):
314314
"""
315315

316316
if aob:
317-
result = win32gui.SetWindowPos(self._hWnd, HWND_BOTTOM, 0, 0, 0, 0,
318-
win32con.SWP_NOSIZE | win32con.SWP_NOMOVE | win32con.SWP_NOACTIVATE)
319-
# there is no HWND_TOPBOTTOM (similar to TOPMOST), so it won't keep window below all others as desired
320-
if self._t is None:
321-
self._t = _sendBottom(self._hWnd)
322-
self._t.daemon = True
323-
if not self._t.is_alive():
324-
self._t.start()
325-
# TODO: Catch win32con.WM_WINDOWPOSCHANGING and resend window to bottom (is it possible with pywin32?)
326-
# https://stackoverflow.com/questions/527950/how-to-make-always-on-bottom-window
317+
result = win32gui.SetWindowPos(self._hWnd, win32con.HWND_BOTTOM, 0, 0, 0, 0,
318+
win32con.SWP_NOSENDCHANGING | win32con.SWP_NOOWNERZORDER | win32con.SWP_ASYNCWINDOWPOS | win32con.SWP_NOSIZE | win32con.SWP_NOMOVE | win32con.SWP_NOACTIVATE | win32con.SWP_NOREDRAW | win32con.SWP_NOCOPYBITS)
319+
if result != 0:
320+
# There is no HWND_TOPBOTTOM (similar to TOPMOST), so it won't keep window below all others as desired
321+
# Is there an easy way to catch WM_WINDOWPOSCHANGING event, unlike this: https://stackoverflow.com/questions/64529896/attach-keyboard-hook-to-specific-window?
322+
if self._t is None:
323+
self._t = _sendBottom(self._hWnd)
324+
if not self._t.is_alive():
325+
self._t.start()
327326
else:
328327
if self._t.is_alive():
329-
self._t.stop()
328+
self._t.kill()
330329
result = self.sendBehind(sb=False)
331330
if result == 0:
332331
_raiseWithLastError()
@@ -428,19 +427,29 @@ def __init__(self, hWnd, interval=0.5):
428427
threading.Thread.__init__(self)
429428
self._hWnd = hWnd
430429
self._interval = interval
431-
self._stop = threading.Event()
430+
self._kill = threading.Event()
431+
432+
def _isLast(self):
433+
# This avoids flickering and CPU consumption. Not very smart, but no other option found... by the moment
434+
h = win32gui.GetWindow(self._hWnd, win32con.GW_HWNDLAST)
435+
last = True
436+
while h != 0 and h != self._hWnd:
437+
h = win32gui.GetWindow(h, win32con.GW_HWNDPREV)
438+
# not sure if this always guarantees these other windows are "system" windows (not user windows)
439+
if h != self._hWnd and win32gui.IsWindowVisible(h) and win32gui.GetClassName(h) not in ("WorkerW", "Progman"):
440+
last = False
441+
break
442+
return last
432443

433444
def run(self):
434-
435-
while not self._stop.is_set() and win32gui.IsWindow(self._hWnd):
436-
# TODO: Find a smart way (not a for) to get if this is necessary (window is not already at the bottom)
437-
# Window flickers a bit. All these parameters are intended to minimize it... with limited success
438-
win32gui.SetWindowPos(self._hWnd, win32con.HWND_BOTTOM, 0, 0, 0, 0,
439-
win32con.SWP_NOSENDCHANGING | win32con.SWP_NOOWNERZORDER | win32con.SWP_ASYNCWINDOWPOS | win32con.SWP_NOSIZE | win32con.SWP_NOMOVE | win32con.SWP_NOACTIVATE | win32con.SWP_NOREDRAW | win32con.SWP_NOCOPYBITS)
440-
self._stop.wait(self._interval)
441-
442-
def stop(self):
443-
self._stop.set()
445+
while not self._kill.is_set() and win32gui.IsWindow(self._hWnd):
446+
if not self._isLast():
447+
win32gui.SetWindowPos(self._hWnd, win32con.HWND_BOTTOM, 0, 0, 0, 0,
448+
win32con.SWP_NOSENDCHANGING | win32con.SWP_NOOWNERZORDER | win32con.SWP_ASYNCWINDOWPOS | win32con.SWP_NOSIZE | win32con.SWP_NOMOVE | win32con.SWP_NOACTIVATE | win32con.SWP_NOREDRAW | win32con.SWP_NOCOPYBITS)
449+
self._kill.wait(self._interval)
450+
451+
def kill(self):
452+
self._kill.set()
444453

445454

446455
def cursor():

0 commit comments

Comments
 (0)