System tray application would detect hotkey with PyQt5 - QSystemTrayIcon - python

I want to create an application with a system tray icon. This app should detect the hotkey and react to it, even if the app is not focused (icon tray).
Example: I want to show the window, then I press Alt + X. The window is showing.
I tried using some global hotkey libraries but whenever the window is showing by hotkey, it freezes. The Show option in my QMenu still working.
I'm new to programming, thanks!
Here is my code:
from PyQt5.QtCore import QSize, Qt
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLineEdit, QVBoxLayout, QHBoxLayout, QWidget, QSystemTrayIcon, QMenu
from PyQt5.QtGui import QIcon, QKeySequence
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('GTyperP5')
self.setWindowIcon(QIcon('GTyperP5.ico'))
self.setFixedSize(QSize(192, 64))
self.setWindowFlag(Qt.WindowMinimizeButtonHint, False)
self.textInput = QLineEdit()
self.typeButton = QPushButton('Type')
self.typeButton.setFixedSize(QSize(50, 25))
self.typeButton.clicked.connect(self.cut_all)
self.textInput.returnPressed.connect(self.typeButton.click)
self.cancelButton = QPushButton('Cancel')
self.cancelButton.setFixedSize(QSize(50, 25))
self.cancelButton.clicked.connect(self.hide_window)
layout = QVBoxLayout()
layout.addWidget(self.textInput, alignment = Qt.AlignHCenter | Qt.AlignTop)
layout_ex = QHBoxLayout()
layout_ex.addWidget(self.cancelButton, alignment = Qt.AlignRight)
layout_ex.addWidget(self.typeButton, alignment = Qt.AlignLeft)
layout.addLayout(layout_ex)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
self.trayIcon = QSystemTrayIcon(QIcon('GTyperP5.ico'), parent = app)
self.trayIcon.setToolTip('GTyperP5')
menu = QMenu()
showAction = menu.addAction('Show')
showAction.triggered.connect(self.show_window)
exitAction = menu.addAction('Exit')
exitAction.triggered.connect(app.quit)
self.trayIcon.setContextMenu(menu)
self.trayIcon.show()
self.trayIcon.showMessage('GTyperP5 has started!', 'Thank you for using! :D', QIcon('GTyperP5.ico'), msecs = 10000)
self.trayIcon.activated.connect(self.doubleClick)
def closeEvent(self, event):
self.hide_window()
event.ignore()
def doubleClick(self, reason):
if reason == QSystemTrayIcon.DoubleClick:
self.show_window()
def cut_all(self):
clip = QApplication.clipboard()
clip.clear(mode = clip.Clipboard)
clip.setText(self.textInput.text(), mode = clip.Clipboard)
self.textInput.setText('')
self.hide_window()
def hide_window(self):
self.trayIcon.show()
self.hide()
def show_window(self):
self.trayIcon.hide()
self.show()
self.raise_()
self.textInput.setFocus()
app = QApplication([])
window = MainWindow()
window.show()
app.exec()

Related

PyQt5 tray program closes after QDialog executing [duplicate]

This question already has an answer here:
PyQt, After MessageBox application quits, why?
(1 answer)
Closed 11 months ago.
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QGridLayout, QWidget, QCheckBox, QSystemTrayIcon, \
QSpacerItem, QSizePolicy, QMenu, QAction, QStyle, qApp, QPushButton
from PyQt5.QtCore import QSize
from PyQt5 import QtWidgets
class MainWindow(QMainWindow):
check_box = None
tray_icon = None
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(480, 80))
self.setWindowTitle("System Tray Application")
central_widget = QWidget(self)
self.setCentralWidget(central_widget)
grid_layout = QGridLayout(self)
central_widget.setLayout(grid_layout)
grid_layout.addWidget(QLabel("Application, which can minimize to Tray", self), 0, 0)
self.check_box = QCheckBox('Minimize to Tray')
grid_layout.addWidget(self.check_box, 1, 0)
grid_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding), 2, 0)
self.btn = QPushButton('Dialog', self)
grid_layout.addWidget(self.btn, 1, 1)
self.btn.clicked.connect(self.dial_func)
self.tray_icon = QSystemTrayIcon(self)
self.tray_icon.setIcon(self.style().standardIcon(QStyle.SP_ComputerIcon))
show_action = QAction("Show", self)
quit_action = QAction("Exit", self)
hide_action = QAction("Hide", self)
dial = QAction('Dial', self)
dial.triggered.connect(self.dial_func)
show_action.triggered.connect(self.show)
hide_action.triggered.connect(self.hide)
quit_action.triggered.connect(qApp.quit)
tray_menu = QMenu()
tray_menu.addAction(show_action)
tray_menu.addAction(hide_action)
tray_menu.addAction(dial)
tray_menu.addAction(quit_action)
self.tray_icon.setContextMenu(tray_menu)
self.tray_icon.show()
def closeEvent(self, event):
if self.check_box.isChecked():
event.ignore()
self.hide()
self.tray_icon.showMessage(
"Tray Program",
"Application was minimized to Tray",
QSystemTrayIcon.Information,
2000
)
def dial_func(self):
text, ok = QtWidgets.QInputDialog.getText(self, 'Input dialog', 'Enter your name')
if ok:
print('simple text', text)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec())
I am trying to write little application for diagnostic. Application should work in tray and have options to change settings from QDialog.
With this code, I can run dialog from btn (in main window) and from Dial (from QMenu). And if program not in tray, everything works fine, but if it's in tray, program finishes. What's way to solve this problem?
I think you need to call the show function when calling the dialog window.
def dial_func(self):
self.show()
text, ok = QtWidgets.QInputDialog.getText(
self, 'Input dialog', 'Enter your name')
if ok:
print('simple text', text)
This solution will solve your issue.

PYQT5 GIF freezes on GUI initialization

Keep gif running while GUI starts. Is that possible? I have read many reporitys but none with the true and understandable answer.
I have prepared a code example that shows the problem.
import sys
from PyQt5.QtWidgets import QMainWindow, QLabel, QGridLayout, QWidget
from PyQt5 import QtWidgets
from PyQt5.QtGui import QMovie
from PyQt5.QtCore import QSize, QThread
class Main_Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(500, 500))
self.setWindowTitle("Main Window")
centralWidget = QWidget(self)
self.setCentralWidget(centralWidget)
gridLayout = QGridLayout(self)
centralWidget.setLayout(gridLayout)
gif = QLabel(self)
gif.setGeometry(0,0,500,500)
self.movie = QMovie(r"C:\Users\...\Pictures\Icon_LOAD.gif")
gif.setMovie(self.movie)
self.movie.start()
# #Call event handler to process the queue, but it is shocking, dirty and unsuitable
#app.processEvents()
self.show()
for i in range(0,1000000):
print(i)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWin = Main_Window()
sys.exit(app.exec_())
Now it works smooth
import sys
from PyQt5.QtWidgets import QMainWindow, QLabel, QGridLayout, QWidget
from PyQt5 import QtWidgets
from PyQt5.QtGui import QMovie
from PyQt5.QtCore import QSize
from threading import Thread
class Main_Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(500, 500))
self.setWindowTitle("Main Window")
centralWidget = QWidget(self)
self.setCentralWidget(centralWidget)
gridLayout = QGridLayout(self)
centralWidget.setLayout(gridLayout)
gif = QLabel(self)
gif.setGeometry(0,0,500,500)
self.movie = QMovie(r"gif.gif")
gif.setMovie(self.movie)
self.movie.start()
self.show()
Thread(target=self.function_on_thread).start()
def function_on_thread(self):
for i in range(0,1000000):
print(i)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWin = Main_Window()
sys.exit(app.exec_())

Remember size of Dock widget on toggle view

I have two dock widgets in my QApplication widget.
On each widget toggle view action is called.So that it hides and shows when I check and uncheck the dock widget in menu options.`
On toggle, dock widget are not taking the size which they had before being toggled.
How can I go ahead and implement this?
following the minimal code which produces the same problem.
import sys
from PySide2.QtWidgets import QMainWindow, QAction, qApp,QApplication,QDockWidget,QWidget
from PySide2.QtGui import QIcon
from PySide2.QtCore import Qt
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
menubar = self.menuBar()
fileMenu = menubar.addMenu('&Funcionalities')
dockwindow1 = QDockWidget("dock1",self)
dockwindow1.setWidget(QWidget())
fileMenu.addAction(dockwindow1.toggleViewAction())
self.setCentralWidget(dockwindow1)
dockwindow2 = QDockWidget("dock2",self)
fileMenu.addAction(dockwindow2.toggleViewAction())
self.addDockWidget(Qt.RightDockWidgetArea, dockwindow2)
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Simple menu')
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())`

How to show new QMainWindow in every loop in PyQT5?

I'm trying to write a Python program using PyQt5 that will display a window in each iteration of the for loop. I would like to close after incrementing and displaying the next window. However, I do not know how to stop the loop every iteration and at the moment I am getting 6 windows at once.
main.py
import sys
from PyQt5.QtWidgets import (QLineEdit, QVBoxLayout, QMainWindow,
QWidget, QDesktopWidget, QApplication, QPushButton, QLabel,
QComboBox, QFileDialog, QRadioButton)
from PyQt5.QtCore import pyqtSlot, QByteArray
from alert import Window2
from test import test
class SG(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.resize(300, 150)
self.setWindowTitle('TEST')
self.resultsGen = QPushButton('TEST', self)
self.resultsGen.clicked.connect(lambda: self.on_click())
self.show()
#pyqtSlot()
def on_click(self):
test(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
sg = SG()
sys.exit(app.exec_())
alert.py
from PyQt5.QtWidgets import (QLineEdit, QVBoxLayout, QMainWindow,
QWidget, QDesktopWidget, QApplication, QPushButton, QLabel,
QComboBox, QFileDialog, QRadioButton)
from PyQt5.QtCore import pyqtSlot, QByteArray
from PyQt5.QtGui import QPixmap
from PyQt5 import QtGui, QtCore
class Window2(QMainWindow):
def __init__(self):
super().__init__()
self.initPopup()
def initPopup(self):
self.resize(500, 500)
self.setWindowTitle("Window22222")
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
lay = QVBoxLayout(self.central_widget)
label = QLabel(self)
pixmap = QPixmap('cropped/8.png')
label.setPixmap(pixmap)
self.resize(pixmap.width(), pixmap.height())
lay.addWidget(label)
self.textbox = QLineEdit(self)
self.textbox.move(20, 20)
self.textbox.resize(280, 40)
# Create a button in the window
self.button = QPushButton('Show text', self)
self.button.move(20, 80)
# connect button to function on_click
self.button.clicked.connect(lambda: self.on_clickX())
self.show()
#pyqtSlot()
def on_clickX(self):
textboxValue = self.textbox.text()
print(textboxValue)
self.textbox.setText("")
self.hide()
test.py
from alert import Window2
def test(self):
for x in range(6):
w = Window2()
As soon as you run the for cycle, all the code of the initialization will be executed, which includes the show() call you used at the end of initPopup().
A simple solution is to create a new signal that is emitted whenever you hide a window, and connect that signal to a function that creates a new one until it reaches the maximum number.
main.py:
import sys
from PyQt5.QtWidgets import QWidget, QApplication, QPushButton
from alert import Window2
class SG(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.alerts = []
def initUI(self):
self.resize(300, 150)
self.setWindowTitle('TEST')
self.resultsGen = QPushButton('TEST', self)
self.resultsGen.clicked.connect(self.nextAlert)
self.show()
def nextAlert(self):
if len(self.alerts) >= 6:
return
alert = Window2()
self.alerts.append(alert)
alert.setWindowTitle('Window {}'.format(len(self.alerts)))
alert.closed.connect(self.nextAlert)
alert.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
sg = SG()
sys.exit(app.exec_())
alert.py:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class Window2(QMainWindow):
closed = pyqtSignal()
def __init__(self):
super().__init__()
self.initPopup()
def initPopup(self):
self.resize(500, 500)
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
lay = QVBoxLayout(self.central_widget)
label = QLabel(self)
pixmap = QPixmap('cropped/8.png')
label.setPixmap(pixmap)
self.resize(pixmap.width(), pixmap.height())
lay.addWidget(label)
self.textbox = QLineEdit(self)
lay.addWidget(self.textbox)
# Create a button in the window
self.button = QPushButton('Show text', self)
lay.addWidget(self.button)
# connect button to function on_click
self.button.clicked.connect(lambda: self.on_clickX())
self.show()
#pyqtSlot()
def on_clickX(self):
textboxValue = self.textbox.text()
print(textboxValue)
self.textbox.setText("")
self.hide()
self.closed.emit()
Just note that with this very simplified example the user might click on the button of the "SG" widget even if an "alert" window is visibile. You might prefer to use a QDialog instead of a QMainWindow and make the main widget a parent of that dialog.
main.py:
class SG(QWidget):
# ...
def nextAlert(self):
if len(self.alerts) >= 6:
return
alert = Window2(self)
# ...
alert.py:
class Window2(QDialog):
closed = pyqtSignal()
def __init__(self, parent):
super().__init__()
self.initPopup()
def initPopup(self):
self.resize(500, 500)
# a QDialog doesn't need a central widget
lay = QVBoxLayout(self)
# ...
Also, if an alert window is closed using the "X" button the new one will not be shown automatically. To avoid that, you can implement the "closeEvent" and ignore the event, so that the user will not be able to close the window until the button is clicked. As QDialogs can close themself when pressing the escape key, I'm also ignoring that situation.
alert.py:
class Window2(QMainWindow):
# ...
def closeEvent(self, event):
event.ignore()
def keyPressEvent(self, event):
if event.key() != Qt.Key_Escape:
super().keyPressEvent(event)

PyQt5: Want to start a specific subprocess with a button click

One day old in terms of experience with PyQT, I followed sample code HERE to do this, but I am clueless as to how I could separate the start download part from the start GUI part, so that I can instead start that when I press the OK (startBtn)button. Also, know the command I do doesn't do anything but give you an error, but I know that works.
Any help appreciated!
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QAction, qApp, QDesktopWidget, QPushButton, QHBoxLayout, QVBoxLayout, QTextEdit
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QThread, QProcess
import sys
class GUI(QProcess):
def __init__(self):
super().__init__()
# Create an instance variable here (of type QTextEdit)
startBtn = QPushButton('OK')
stopBtn = QPushButton('Cancel')
#startBtn.clicked.connect()
stopBtn.clicked.connect(qApp.exit)
self.hbox = QHBoxLayout()
self.hbox.addStretch(1)
self.hbox.addWidget(startBtn)
self.hbox.addWidget(stopBtn)
self.edit = QTextEdit()
self.edit.setWindowTitle("QTextEdit Standard Output Redirection")
self.vbox = QVBoxLayout()
self.vbox.addStretch(1)
self.vbox.addWidget(self.edit)
self.vbox.addLayout(self.hbox)
#setLayout(self.vbox)
self.central=QWidget()
#self.vbox.addWidget(self.edit)
self.central.setLayout(self.vbox)
self.central.show()
def readStdOutput(self):
self.edit.append(str(self.readAllStandardOutput()))
def main():
app = QApplication(sys.argv)
qProcess = GUI()
qProcess.setProcessChannelMode(QProcess.MergedChannels);
qProcess.start("youtube-dl")
qProcess.readyReadStandardOutput.connect(qProcess.readStdOutput);
return app.exec_()
if __name__ == '__main__':
main()
2 notes:
If you also know how to disable the OK button when you press it, until the process is finished, then I'd love to know.
Not all imports are used, but I can clean that later. PyCharm show which is used and not. Cleanup is for later.
To do what you ask you have to have some considerations:
youtube-dl requires parameters, like the url, for this I have placed a QLineEdit.
To know when the process starts and ends, we use the signal: stateChanged(newState)
Complete code:
import sys
from PyQt5.QtCore import QProcess
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QVBoxLayout, QTextEdit, QLabel, QLineEdit
class GUI(QProcess):
def __init__(self, parent=None):
super(GUI, self).__init__(parent=parent)
# Create an instance variable here (of type QTextEdit)
self.startBtn = QPushButton('OK')
self.stopBtn = QPushButton('Cancel')
self.hbox = QHBoxLayout()
self.hbox.addStretch(1)
self.hbox.addWidget(self.startBtn)
self.hbox.addWidget(self.stopBtn)
self.label = QLabel("Url: ")
self.lineEdit = QLineEdit()
self.lineEdit.textChanged.connect(self.EnableStart)
self.hbox2 = QHBoxLayout()
self.hbox2.addWidget(self.label)
self.hbox2.addWidget(self.lineEdit)
self.edit = QTextEdit()
self.edit.setWindowTitle("QTextEdit Standard Output Redirection")
self.vbox = QVBoxLayout()
self.vbox.addStretch(1)
self.vbox.addLayout(self.hbox2)
self.vbox.addWidget(self.edit)
self.vbox.addLayout(self.hbox)
self.central = QWidget()
self.central.setLayout(self.vbox)
self.central.show()
self.startBtn.clicked.connect(self.startDownload)
self.stopBtn.clicked.connect(self.kill)
self.stateChanged.connect(self.slotChanged)
self.EnableStart()
def slotChanged(self, newState):
if newState == QProcess.NotRunning:
self.startBtn.setDisabled(False)
elif newState == QProcess.Running:
self.startBtn.setDisabled(True)
def startDownload(self):
self.start("youtube-dl", [self.lineEdit.text()])
def readStdOutput(self):
self.edit.append(str(self.readAllStandardOutput()))
def EnableStart(self):
self.startBtn.setDisabled(self.lineEdit.text() == "")
def main():
app = QApplication(sys.argv)
qProcess = GUI()
qProcess.setProcessChannelMode(QProcess.MergedChannels)
qProcess.readyReadStandardOutput.connect(qProcess.readStdOutput)
return app.exec_()
if __name__ == '__main__':
main()
Screenshot:

Categories