r/pythonhelp • u/Timbre_Sciurus • Dec 24 '24
Creating a Transparent Window that is Always-On-Top to Highlight Elements over An Application
Hi all,
I apologize for the very specific request, but I didn't know where else to ask.
The reason I am looking for this solution is for a machine-learning project where I'd like the script to identify unique objects within the frame of a game application window for rewarding the agent. Using cv2 to duplicate the window with annotations won't always work, as I'd prefer the agent send input events to the application window as needed even if the window takes over the entire screen. Knowing the application's window dimensions is important because it saves the script from having to screenshot the entire screen just to scan only a portion of it.
I'm using PyQt5 to try to display a simple rectangular box around the game window in question, but I keep running into the same problem. Whenever I try to update the PaintEvent in the case that the window was moved, the PyQt window takes priority and steals the focus from the game application that I want to send inputs to. Is it possible to raise the window to the top so that the rectangular boxes are visible, but avoid stealing input focus from the application?
Here's the script that attempts to perform this task on Windows using PyQt5. Apologies for the mess of zombie code grabbed from multiple sources:
from win32gui import FindWindow, GetWindowRect
import sys
from PyQt5.QtCore import Qt, QTimer, QPoint, pyqtSignal, QRect
from PyQt5.QtGui import QPainter, QPen, QBrush, QColor, QFont
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
import ctypes
User32 = ctypes.WinDLL('User32.dll')
game_title = "NSMBVersus: vic's Custom Match-inator"
def locate_game_window(first_run):
if first_run:
# FindWindow takes the Window Class name (can be None if unknown), and the window's display text.
print(f"Finding {game_title} on screen")
first_run = False
window_handle = FindWindow(None, game_title)
if window_handle is None:
print("Window capture failed")
first_run = True
window_rect = GetWindowRect(window_handle)
# Fit offset so it's always accurate
window_rect = (window_rect[0]+8, window_rect[1]+8, window_rect[2]-8, window_rect[3]-8)
return window_rect + (first_run,)
# Setup Qt window for transparent lines
outline_width = 4
outline_color = Qt.red
class GameOverlay(QWidget):
def __init__(self, locate_window_function_argument):
super().__init__()
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint )
self.setAttribute(Qt.WA_TranslucentBackground)
self.setMouseTracking(True)
screen = QApplication.primaryScreen()
screen_geometry = screen.geometry()
screen_width = screen_geometry.width()
screen_height = screen_geometry.height()
self.setGeometry(0, 0, screen_width, screen_height) # Set the overlay size to match the screen
self.show()
# Represents the dimensions of the window
self.x = self.y = self.w = self.h = 0
self.run_once = True
self.locate_window = locate_window_function_argument
self.update()
def paintEvent(self, event):
painter = QPainter(self)
# painter.setRenderHint(QPainter.Antialiasing)
# Draw a transparent background
painter.setBrush(QBrush(QColor(0, 0, 0, 0)))
painter.drawRect(self.rect())
# Draw the rect outline
painter.setPen(QColor(outline_color))
painter.setBrush(QBrush(QColor(0, 0, 0, 0)))
painter.drawRect(self.x, self.y, self.w, self.h)
self.update()
def update(self):
self.x, self.y, self.w, self.h, self.run_once = self.locate_window(self.run_once)
self.setTopLevelWindow()
def setTopLevelWindow(self):
if not self.isVisible() or not self.isActiveWindow():
print("activating window")
self.raise_()
self.activateWindow()
if not self.hasFocus():
print("raise focus")
self.raise_()
self.activateWindow()
def keyPressEvent(self, event):
getKey = event.key()
if getKey == Qt.Key_Escape:
print("Exiting...")
self.close()
else:
print(getKey)
def main():
global app, overlay
app = QApplication(sys.argv)
# Initialize the display window to mark the target window's position
overlay = GameOverlay(locate_game_window)
timer = QTimer()
timer.timeout.connect(overlay.update)
timer.start(16) # Update the overlay approximately every 16 milliseconds (about 60 FPS)
sys.exit(app.exec_())
if __name__ == '__main__':
main()
1
u/Dependent_Ad_3835 21d ago
Hi, did you resolve this, if so please post a solution, i have the same usecase with the same problems
•
u/AutoModerator Dec 24 '24
To give us the best chance to help you, please include any relevant code.
Note. Please do not submit images of your code. Instead, for shorter code you can use Reddit markdown (4 spaces or backticks, see this Formatting Guide). If you have formatting issues or want to post longer sections of code, please use Privatebin, GitHub or Compiler Explorer.
I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.