Issue
I'm trying to develop an overlay PopUp for Windows that's triggered by a global keybind, which should capture focus into a QLineEdit once the keybind is pressed. The issue is that if it was focused once, but I click outside effectively removing focus, the focus can't be reacquired afterward.
This is a simplified version of the code I'm trying to use to force keyboard focus on the QLineEdit:
from PySide6 import QtCore, QtWidgets, QtGui
from pynput import keyboard
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.input = QtWidgets.QLineEdit()
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.addWidget(self.input)
self.input.setWindowModality(QtCore.Qt.ApplicationModal)
self.setWindowState(QtCore.Qt.WindowActive)
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
@QtCore.Slot()
def toggle_visible(self):
if self.isVisible():
print("Hiding popup")
self.hide()
else:
print("Showing popup")
self.show()
self.activateWindow()
self.input.grabKeyboard()
self.input.setFocus()
class KeybindPressed(QtCore.QObject):
keybind_pressed = QtCore.Signal()
def __call__(self):
self.keybind_pressed.emit()
if __name__ == "__main__":
app = QtWidgets.QApplication([])
pressed = KeybindPressed()
with keyboard.GlobalHotKeys({"<alt>+<space>": pressed}):
widget = MyWidget()
pressed.keybind_pressed.connect(widget.toggle_visible)
widget.resize(800, 600)
widget.show()
app.exec()
This is a recording showing the undesired behaviour of the focus staying in the other app instead of returning to the window when showing.
Solution
Thanks to ekhumoro i was able to figure out how to force focus on windows. Below is the code needed to force focus:
imports and setup
windows = False
if os.name == "nt":
import win32gui, win32con, win32process, win32api
win32gui.SystemParametersInfo(win32con.SPI_SETFOREGROUNDLOCKTIMEOUT, 0, win32con.SPIF_SENDWININICHANGE | win32con.SPIF_UPDATEINIFILE)
windows = True
code to actually force focus, this would be called when your window should get focus:
def force_focus(qt_widget_instance: QtWidgets.QWidget):
if windows:
fgwin = win32gui.GetForegroundWindow()
fg = win32process.GetWindowThreadProcessId(fgwin)[0]
current = win32api.GetCurrentThreadId()
if current != fg:
win32process.AttachThreadInput(fg, current, True)
win32gui.SetForegroundWindow(qt_widget_instance.winId())
win32process.AttachThreadInput(fg, win32api.GetCurrentThreadId(), False)
And here is the full example with this implemented:
from PySide6 import QtCore, QtWidgets, QtGui
from pynput import keyboard
import os
windows = False
if os.name == "nt":
import win32gui, win32con, win32process, win32api
win32gui.SystemParametersInfo(win32con.SPI_SETFOREGROUNDLOCKTIMEOUT, 0, win32con.SPIF_SENDWININICHANGE | win32con.SPIF_UPDATEINIFILE)
windows = True
def force_focus(qt_widget_instance: QtWidgets.QWidget):
if windows:
fgwin = win32gui.GetForegroundWindow()
fg = win32process.GetWindowThreadProcessId(fgwin)[0]
current = win32api.GetCurrentThreadId()
if current != fg:
win32process.AttachThreadInput(fg, current, True)
win32gui.SetForegroundWindow(qt_widget_instance.winId())
win32process.AttachThreadInput(fg, win32api.GetCurrentThreadId(), False)
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.input = QtWidgets.QLineEdit()
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.addWidget(self.input)
self.input.setWindowModality(QtCore.Qt.ApplicationModal)
self.setWindowState(QtCore.Qt.WindowActive)
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
@QtCore.Slot()
def toggle_visible(self):
if self.isVisible():
print("Hiding popup")
self.hide()
else:
print("Showing popup")
self.show()
force_focus(self)
self.activateWindow()
self.input.grabKeyboard()
self.input.setFocus()
class KeybindPressed(QtCore.QObject):
keybind_pressed = QtCore.Signal()
def __call__(self):
self.keybind_pressed.emit()
if __name__ == "__main__":
app = QtWidgets.QApplication([])
pressed = KeybindPressed()
with keyboard.GlobalHotKeys({"<cmd>+<space>": pressed}):
widget = MyWidget()
pressed.keybind_pressed.connect(widget.toggle_visible)
widget.resize(800, 600)
widget.show()
app.exec()
Answered By - laundmo
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.