Launch Python Script with Pythonw from Batch and give it Focus - python

I'm on Windows 10 and I have a PyQt5 Application I launch using a .bat file to use the venv interpreter.
When I call the script using python my_script.py it opens the main window in focus, but also shows the Python console in background. To get rid of the console, I tried launching it with pythonw my_script.py, but then it silently opens in background.
I tried things like window.setWindowState(Qt.WindowState.WindowActive) or window.setFocus(), but this only makes the icon blink in the task bar. Other Google results said that Windows does not allow programs to easily grab focus anymore, but then again, python can do it on start-up, so I would like to replicate that behavior with pythonw.
Below you can find testing code and the batch file, context was launching it from a custom URI protocol.
# https://stackoverflow.com/a/38205984 to register any protocol for testing
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
from PyQt5.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self, title):
super().__init__()
self.setWindowTitle("Test App")
label = QLabel(title)
self.setCentralWidget(label)
if __name__ == '__main__':
if len(sys.argv) == 1:
the_title = "I got no arguments"
else:
the_title = f"I was run with argument {sys.argv[1]}"
app = QApplication(sys.argv)
window = MainWindow(the_title)
window.show()
window.setFocus()
app.exec()
and
cd %~dp0
call ..\venv\Scripts\activate
start "" "pythonw" "test_url_scheme_one.py" "%1"
deactivate

Replacing the batch file with the following fixed it:
start "" "C:\path\to\venv\Scripts\pythonw.exe" "C:\path\to\my_script.py" "%1"
Now the window gets started in foreground and in focus.

Related

QSystemTrayIcon size not respected when run from system terminal but not vscode

I have the following simple Qt app that creates a system tray icon from a given text. When I run the app via vscode terminal, everything seems to be fine (see the screenshot below):
Strangely, when I run the app via system terminal (bash), icon size is not respected, and the text is shrunk (see below):
I'd appreciate it if anyone could shed some light what might be causing this strange behaviour. Here's the code:
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtWidgets, QtGui, QtSvg
def create_tray_icon(label):
r"""Creates QIcon with the given label."""
w, h = 22*4, 22
pixmap = QtGui.QPixmap(w, h)
pixmap.fill(QtCore.Qt.transparent) # alternative: QtGui.QColor("white")
painter = QtGui.QPainter(pixmap)
painter.setPen(QtGui.QColor("white"))
align = int(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
painter.drawText(pixmap.rect(), align, str(label))
painter.end()
icon = QtGui.QIcon()
icon.addPixmap(pixmap)
return icon
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
def __init__(self, icon, parent=None):
QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
self.menu = QtWidgets.QMenu(parent)
exitAction = self.menu.addAction("Exit")
exitAction.triggered.connect(lambda: sys.exit())
self.setContextMenu(self.menu)
def memprofilerApp():
r"""Runs the Qt Application."""
QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps)
app = QApplication(sys.argv)
icon = create_tray_icon(label="Hello world!")
trayIcon = SystemTrayIcon(icon)
trayIcon.show()
sys.exit(app.exec_())
if __name__ == '__main__':
memprofilerApp()
After inspecting environment variables (by running env) on vscode terminal and comparing it with that on system terminal, it turned out that the culprit was XDG_CURRENT_DESKTOP! In vscode terminal (which QIcon size is respected), it is set to UNITY, whereas in system terminal it is set to ubuntu:GNOME.
I don't know the root cause, but a quick fix is to use the following script to run the app:
#!/bin/bash
XDG_CURRENT_DESKTOP=Unity
/path/to/python main.py
Just out of curiosity, if anyone knows why ubuntu:GNOME cause the QIcon sizing to break, please let us know!

How can I make my PyQt5 app oney one instance?

What I want to achieve:
when I run application from start menu, app starts (if app not running).
If an app is already running, then don't create another instance, just show the previous running app window.
What I've tried:
created a .txt file in a directory, write 'running' & 'not running' into the file while opening & exiting the window. And checking the file contents at very start. For ex:
When starting app: 1.) Check the file content, 2.) If file content is 'running', show warning & exit the app 3.) If file content is 'not running', write 'running' into the file & start app 4.) Write 'not running' into the file on exit.
Issues I'm facing:
This method doesn't feel like the right way to achieve this.
If file item says 'not running', app shows warning and exits. While I want it to show already running instance.
File items not updated to 'not running' when app exits because of some error in code (because that part is never reached due to error)
Can anybody please help me out regarding this?
You can achieve something similar with the win32gui module. To install it type into CMD pip install pywin32. Now this is the code:
from win32 import win32gui
import sys
def windowEnumerationHandler(hwnd, top_windows):
top_windows.append((hwnd, win32gui.GetWindowText(hwnd)))
top_windows = []
win32gui.EnumWindows(windowEnumerationHandler, top_windows)
for i in top_windows:
if "Program" in i[1]: #CHANGE PROGRAM TO THE NAME OF YOUR WINDOW
win32gui.ShowWindow(i[0],5)
win32gui.SetForegroundWindow(i[0])
sys.exit()
from PyQt5.QtWidgets import QApplication, QWidget
def main():
#YOUR PROGRAM GOES HERE
app = QApplication(sys.argv)
w = QWidget()
w.setGeometry(500, 500, 500, 500)
w.setWindowTitle('Simple')
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Basically, at the beginning, the program gets the name of every open window. If the window name is equal to the name of the program, then it brings the program to the front and closes the program. If not, then it opens a new program.
PyWin32 link
Stack Overflow get a list of every open window
This solution doesn't require you to install win32 or any other module, works with PyQt5 package itself.
import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import QSystemSemaphore, QSharedMemory
from ui import window_ui # .py file compiled from .ui file
class LoginWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.ui = window_ui.Ui_MainWindow() # call to init ui
self.ui.setupUi(self)
def launch():
app = QtWidgets.QApplication(sys.argv) # create app instance at top, to able to show QMessageBox is required
window_id = 'pingidapplication'
shared_mem_id = 'pingidsharedmem'
semaphore = QSystemSemaphore(window_id, 1)
semaphore.acquire() # Raise the semaphore, barring other instances to work with shared memory
if sys.platform != 'win32':
# in linux / unix shared memory is not freed when the application terminates abnormally,
# so you need to get rid of the garbage
nix_fix_shared_mem = QSharedMemory(shared_mem_id)
if nix_fix_shared_mem.attach():
nix_fix_shared_mem.detach()
shared_memory = QSharedMemory(shared_mem_id)
if shared_memory.attach(): # attach a copy of the shared memory, if successful, the application is already running
is_running = True
else:
shared_memory.create(1) # allocate a shared memory block of 1 byte
is_running = False
semaphore.release()
if is_running: # if the application is already running, show the warning message
QtWidgets.QMessageBox.warning(None, 'Application already running',
'One instance of the application is already running.')
return
# normal process of creating & launching MainWindow
window = LoginWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
launch()
This solution I converted from a C++ code posted on this website originally.

GUI interaction with terminal

Is it possible to set up a Python script with a PyQt5 GUI such that an active terminal can interact with it?
An example project:
from PyQt5 import QtWidgets from PyQt5.QtCore import pyqtSlot
class Machine:
"""Simpel state machine"""
STATE_IDLE = 'idle'
STATE_OPERATION = 'operation'
def __init__(self):
self.state = self.STATE_IDLE
def get_state(self):
return self.state
def set_state(self, new_state):
self.state = new_state
#pyqtSlot() def on_click():
"""Callback for the PyQt button"""
print('State:', machine.get_state())
if __name__ == '__main__':
# Create machine instance
machine = Machine()
# Set up GUI
app = QtWidgets.QApplication([])
window = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
label = QtWidgets.QLabel('Hello World!')
button = QtWidgets.QPushButton('Go')
button.clicked.connect(on_click)
layout.addWidget(label)
layout.addWidget(button)
window.setLayout(layout)
window.show()
code = app.exec()
exit(code)
I would like to run this script and to then be able to access the objects from the same terminal. Something like:
$ python main.py
State: idle [< I just pressed the GUI button]
>> machine.set_state(Machine.STATE_OPERATION)
State: operation [< I just pressed the GUI button again]
If I run it like:
python -i main.py
The command line hangs until the application was closed (that's what .exec() does after all), but then I can access machine. Can I make .exec() not hang like this? Maybe run app from a separate thread?
EDIT: I found that thy Pyzo IDE allows you to create a shell with PyQt5 support, which handles the PyQt event loop. So when using the script above with such a Pyzo shell, I can remove code = app.exec() exit(code) because Pyzo will run the event loop and keep the window alive. Now I'd like to do the same across all IDEs.

Can't Kill PyQT Window after closing it. Which requires me to restart the kernal

I guess that I am not closing my PyQT5 Window correctly. I am using spyder (3.3.5) which I have installed with anaconda, to program a pyqt5 program. I am using qt creator to design my ui file, which I load using the loadUi function in pyqt package. Everything works fine with the code, until I need to close it. I close the window via the close button (the x button in the top right). The window itself is closed, but it seems like the console (or shell) is stuck, and I can't give it further commands or to re run the program, without having to restart the kernel (to completely close my IDE and re opening it).
I have tried looking for solutions to the problem in the internet, but none seems to work for me. Including changing the IPython Console > Graphics to automatic.
Edit: Also Created an issure:
https://github.com/spyder-ide/spyder/issues/9991
import sys
from PyQt5 import QtWidgets,uic
from PyQt5.QtWidgets import QMainWindow
class Mic_Analysis(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.ui=uic.loadUi("qt_test.ui",self)
...
if __name__ == "__main__":
def run_app():
if not QtWidgets.QApplication.instance():
app = QtWidgets.QApplication(sys.argv)
else:
app=QtWidgets.QApplication.instance()
mainWin=Mic_Analysis()
mainWin.show()
app.exec_()
run_app()
If someone have any suggestion I would be very happy to hear them.
For me, it helped to remove the 'app.exec_()' command.
But then it closes immediatly when running the code. To keep the window open, I needed to return the MainWindow instance to the global scope (or make it a global object). My code looks like this:
from PyQt5 import QtWidgets
import sys
def main():
if not QtWidgets.QApplication.instance():
app = QtWidgets.QApplication(sys.argv)
else:
app = QtWidgets.QApplication.instance()
main = MainWindow()
main.show()
return main
if __name__ == '__main__':
m = main()
Try adding :
app.setQuitOnLastWindowClosed(True)
to your main() function
def main():
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(True)
win = MainWindow()
win.show()
sys.exit(app.exec_())
Surprizinglly, for me this works well. No QApplication is needed.
It seems to work in another thread.
from PyQt5 import QtWidgets,uic
class Ui(QtWidgets.QDialog):
def __init__(self):
super().__init__()
uic.loadUi('./test.ui',self)
self.show()
w=Ui()

How to Restart PyQt4 Application

Is there a way to restart PyQt application QApplication
I have an app created with pyqt4 and python 2.6 using below code
app = QtGui.QApplication(sys.argv)
i have settings options where i set some settings. Now when i save settings i need to reload the application so that new settings are effected. Without the need of end user to exit and launch the app again.
I had a similar problem and simply used this at the appropriate place:
subprocess.Popen([__file__])
sys.exit(0)
It was a simple application, and didn't need any further arguments.
I explain how I did it :
I create a extra one file main.py which calls my actual main program file dash.py.
And I emits a signal for restarting (my programs auto updates at the closeEvent) so I required to emit a signal for it. This is the snippets hope this will help you.
This one is in my main program file in dash.py
def restart(self):
# create a signal equivalent to "void someSignal(int, QWidget)"
self.emit(QtCore.SIGNAL("RESTARTREQUIRED"), True)
This one in main.py which calls actual program only and restarts the app
import sys
from PyQt4 import QtGui,QtCore
from bin import dash
if __name__ == "__main__":
application = QtGui.QApplication(sys.argv)
uDesk = dash.app()
uDesk.show()
uDesk.actionRestart.triggered.disconnect()
# define restart slot
#QtCore.pyqtSlot()
def restartSlot():
print 'Restarting app'
global uDesk
uDesk.deleteLater()
uDesk = dash.app()
uDesk.show()
uDesk.actionRestart.triggered.disconnect()
uDesk.actionRestart.triggered.connect(restartSlot)
print 'New app started !'
QtCore.QObject.connect(uDesk,
QtCore.SIGNAL("RESTARTREQUIRED"),
restartSlot)
uDesk.actionRestart.triggered.connect(restartSlot)
sys.exit(application.exec_())
Hope this was helpful !!
EDIT: Changing the way to get the application path
You could just start a new process and exit yours, something like this: (CODE NOT TESTED, but based on this answer)
// Restart Application
def restart(self, abort):
// Spawn a new instance of myApplication:
proc = QProcess()
//proc.start(self.applicationFilePath());
import os
proc.start(os.path.abspath(__file__))
self.exit(0);
Code it as a method of your Qapplication or even a function if you don't feel like subclassing
This is how I restart TicTacToe game in PySide (it should be the same in PyQt):
I have a single class - a QWidget class - in which is coded the Tic Tac Toe game. To restart the application I use:
import subprocess
a QPushButton() like so:
self.button = QPushButton("Restart", self)
the connection of the button to Slot:
self.buton.clicked.connect(self.restartGame)
the Slot for this button, like so:
def restartGame(self):
self.close()
subprocess.call("python" + " TicTAcToe.py", shell=True)
All these are in the same - single - class. And what these do: close the active window of the game and create a new one.
How this code looks in the TicTacToe class:
import subprocess
class TicTacToe(QWidget):
def __init__(self):
QWidget.__init__(self)
self.button = QPushButton("Restart", self)
self.buton.clicked.connect(self.restartGame)
def restartGame(self):
self.close()
subprocess.call("python" + " TicTacToe.py", shell=True)
def main():
app = QApplication(sys.argv)
widget = TicTacToe()
widget.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
EDIT
I know this doesn't answer the question (it doesn't restart a QApplication), but I hope this helps those who want to restart their QWidget single class.

Categories

Resources