I would like to show notifications using my Gtk app, but when I run my code below, I everything works but the notification doesn't show, even when I click on the button. I tried running it using a desktop file like the one suggested in this answer, but it still didn't work. Here's my code:
import gi
import sys
gi.require_version("Gtk", "3.0")
from gi.repository import Gio, Gtk
class App(Gtk.Application):
def __init__(self, *args, **kwargs):
Gtk.Application.__init__(self, *args, application_id="org.example.myapp", **kwargs)
self.window = None
def do_startup(self):
Gtk.Application.do_startup(self)
def do_activate(self):
if not self.window:
self.button = Gtk.Button(label="send notification")
self.button.connect("clicked", self.notnotnot)
self.window = Gtk.ApplicationWindow(application=self)
self.window.add(self.button)
self.window.show_all()
self.window.present()
def notnotnot(self, *args):
notification = Gio.Notification()
notification.set_body("Hello!")
self.send_notification(None, notification)
if __name__ == "__main__":
app = App()
app.run(sys.argv)
and here's the desktop file org.example.myapp.desktop:
[Desktop Entry]
Type=Application
Name=My Application
Exec=python3 /home/user/programs/python/testing/SO/problem_why_is_gtk....py
Terminal=true
X-GNOME-UsesNotifications=true
I figured out that just setting the priority to high made the notification appear. Note that Gio.Notification.set_urgent() is deprecated. You need to use Gio.Notification.set_priority(). Here is the code with the added line marked accordingly:
import gi
import sys
gi.require_version("Gtk", "3.0")
from gi.repository import Gio, Gtk
class App(Gtk.Application):
def __init__(self, *args, **kwargs):
Gtk.Application.__init__(self, *args, application_id="org.example.myapp", **kwargs)
self.window = None
def do_startup(self):
Gtk.Application.do_startup(self)
def do_activate(self):
if not self.window:
self.button = Gtk.Button(label="send notification")
self.button.connect("clicked", self.notnotnot)
self.window = Gtk.ApplicationWindow(application=self)
self.window.add(self.button)
self.window.show_all()
self.window.present()
def notnotnot(self, *args):
notification = Gio.Notification()
notification.set_body("Hello!")
notification.set_priority(Gio.NotificationPriority.HIGH) ### ADDED LINE
self.send_notification(None, notification)
if __name__ == "__main__":
app = App()
app.run(sys.argv)
If you specify a string id instead of None when sending the notification (self.send_notification("my_notif_id", notification)), you can later withdraw the notification using self.withdraw_notification("my_notif_id").
Related
The following code freezes when a user is moving the main window, and a dialog box will pop up.
https://imgur.com/a/aAh6Xee
Has anyone noticed this problem before?
I'm using idle_add to display the message dialog, but that doesn't solve the problem.
from time import sleep
import gtk
import pygtk
pygtk.require("2.0")
from threading import Thread
import gobject
class PyApp(gtk.Window):
def __init__(self):
super(PyApp, self).__init__()
self.connect("destroy", gtk.main_quit)
self.set_size_request(250, 100)
self.set_position(gtk.WIN_POS_CENTER)
self.set_title("Test")
btn = gtk.Button("Click Here")
btn.connect("clicked", self.on_click)
self.add(btn)
self.show_all()
def decorator_threaded(func):
def wrapper(*args, **kwargs):
gtk.gdk.threads_enter()
thread = Thread(target=func, args=args, kwargs=kwargs)
thread.start()
return thread
return wrapper
#decorator_threaded
def running_on_another_thread(self):
sleep(2) # Heavy task
gobject.idle_add(self.error_message)
def on_click(self, widget):
self.running_on_another_thread()
def error_message(self):
md = gtk.MessageDialog(self,
gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR,
gtk.BUTTONS_CLOSE, "Error")
md.run()
md.destroy()
PyApp()
gtk.gdk.threads_init()
gtk.main()
I also tried using Gtk 3.0 and noticed the same error.
import threading
import time
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gtk
def app_main():
win = Gtk.Window(default_height=100, default_width=250)
win.connect("destroy", Gtk.main_quit)
win.set_position(Gtk.WindowPosition.CENTER)
def error_message():
md = Gtk.MessageDialog(
transient_for=win,
flags=0,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.CLOSE,
text="Error",
)
md.run()
md.destroy()
def example_target():
time.sleep(2) # Heavy task
GLib.idle_add(error_message)
win.show_all()
thread = threading.Thread(target=example_target)
thread.daemon = True
thread.start()
if __name__ == "__main__":
app_main()
Gtk.main()
The solution is to call timeout_add on the thread to schedule the MessageDialog to run on the main GUI thread.
The class MessageBox also worked in Gtk 2 using pygtk.
import threading
import time
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gtk
class MessageBox(Gtk.MessageDialog):
def __init__(self, parent, message):
Gtk.MessageDialog.__init__(self, transient_for=parent,
flags=0,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.CLOSE,
text=message)
self.set_default_response(Gtk.ResponseType.OK)
self.connect('response', self._handle_clicked)
def _handle_clicked(self, *args):
self.destroy()
def show_dialog(self):
GLib.timeout_add(0, self._do_show_dialog)
def _do_show_dialog(self):
self.show_all()
return False
def app_main():
win = Gtk.Window(default_height=100, default_width=250)
win.connect("destroy", Gtk.main_quit)
win.set_position(Gtk.WindowPosition.CENTER)
def error_message():
dialog = MessageBox(win, "Error")
dialog.show_dialog()
def example_target():
time.sleep(2) # Heavy task
error_message()
win.show_all()
thread = threading.Thread(target=example_target)
thread.daemon = True
thread.start()
if __name__ == "__main__":
app_main()
Gtk.main()
Note: The issue cannot be reproduced on Linux.
References:
https://bytes.com/topic/python/answers/717463-showing-message-dialog-thread-using-gtk#post2858146
https://mail.gnome.org/archives/gtk-app-devel-list/2007-October/msg00034.html.
I would like to create a "primary menu" programmatically, which I believe is also called a "hamburger menu". I have done several of these while working on web development side, but I have never done these using Python and GTK. This topic seems to be controversial and there are a lot of different solutions out there. I would like to create a menu like this using the non-deprecated way.
In the documentation is mentioned that the old style menus are deprecated (the whole section is archived under "deprecated"): https://python-gtk-3-tutorial.readthedocs.io/en/latest/menus.html
In this example, the whole HeaderBar is made programmatically and a (popover) menu is added into it:
GtkMenuButton popups
While that seems to do the trick, it's not a "hamburger" menu and the documentation seems to suggest "Your menus should be defined in XML using Gio.Menu":
https://python-gtk-3-tutorial.readthedocs.io/en/latest/application.html#menus
So I am quite lost here. Can someone give me an example how to achieve this? Preferably done programmatically, but if the XML is the only way then so be it.
Thanks in advance!
I don't have enough reputation to comment so I'll just put this here: you could use a GMenu: https://wiki.gnome.org/HowDoI/GMenu
I post my solution that I was able to do with the kind help from irc.gnome.org #python channel. It's not perfect, menu actions are still not working, but at least I got the menu done which was the point of this post.
#!/usr/bin/env python3
# Python imports
import sys
# GTK imports
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gio
from gi.repository import Gtk
class AppWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_border_width(10)
self.set_default_size(640, 480)
open_selection = Gtk.ModelButton(action_name="open_file", label="Open")
about_selection = Gtk.ModelButton(action_name="about_application", label="About")
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin=10, spacing=10)
vbox.add(open_selection)
vbox.add(about_selection)
vbox.show_all()
self.popover = Gtk.Popover()
self.popover.add(vbox)
self.popover.set_position(Gtk.PositionType.BOTTOM)
menu_button = Gtk.MenuButton(popover=self.popover)
menu_icon = Gtk.Image.new_from_icon_name("open-menu-symbolic", Gtk.IconSize.MENU)
menu_icon.show()
menu_button.add(menu_icon)
menu_button.show()
headerbar = Gtk.HeaderBar()
headerbar.props.show_close_button = True
headerbar.props.title = "Hamburger Menu Demo"
headerbar.add(menu_button)
headerbar.show()
self.set_titlebar(headerbar)
def open_file(self, widget):
print("Open file")
class Application(Gtk.Application):
def __init__(self, *args, **kwargs):
super().__init__(*args, application_id="org.example.myapp")
self.window = None
def do_startup(self):
Gtk.Application.do_startup(self)
action = Gio.SimpleAction.new("open_file", None)
action.connect("activate", self.open_file)
action.set_enabled(True)
self.add_action(action)
action = Gio.SimpleAction.new("about_application", None)
action.connect("activate", self.on_about)
self.add_action(action)
action = Gio.SimpleAction.new("quit", None)
action.connect("activate", self.on_quit)
self.add_action(action)
def do_activate(self):
# We only allow a single window and raise any existing ones
if not self.window:
# Windows are associated with the application
# when the last one is closed the application shuts down
self.window = AppWindow(application=self, title="Main Window")
self.window.present()
def open_file(self, action, param):
print("Open file")
def on_about(self, action, param):
print("About application")
def on_quit(self, action, param):
self.quit()
if __name__ == "__main__":
app = Application()
app.run(sys.argv)
The application is a toy application for me to present the basics of MVC at a meetup. Basically, the application is (for now, composed of an app.py, controller.py, view.py, user.py) and flows as such:
(a) The app app.py initialises the controller
(b) The controller controller.py initialises the login view.
(c) The controller checks if the login view is in an Accepted State. If yes,
then it initialises the main view view.py.
(d) The main view has a button which upon clicked initialises the user view user.py where the user can type in some input, m. When the OK button is clicked and the user input, m is not empty, both the user view and the main view is closed.
(e) The user input, m is then printed out to the console.
My questions are these:
(a) How do I revert back to the login screen on exit?
(b) Is this implementation, "pure" MVC?
(c) The controller needs to be able to save all the modified variables(state) and commit them to the database before the application exits. For now, that is done in cleanup(). A scenario that I would want to implement would be , that there would be a child widget attached to the main view where a user can change their login details. Upon such a change, the child widget window and the main view window should close and the changed login details be committed. Should I do that in cleanup or is there a better implementation?
(d) Is this the proper way to allow any other child widget of the main view to close the application? Also, I tried to using sender() to detect the button that closes the application but it returns 'Nonetype'. Is there something that I am doing wrong?
app.py
#!/usr/bin/env python
# File: app.py
import sys, os
from controller import Controller
from PySide.QtGui import *
from PySide.QtCore import *
if __name__=='__main__':
try:
app = Controller()
ret = app.start()
app.cleanup()
sys.exit(ret)
except NameError:
print("Name Error:", sys.exc_info()[1])
except SystemExit:
print("Closing Window..")
controller.py
#!/usr/bin/env python
# File: controller.py
import sys
from PySide.QtGui import *
from PySide.QtCore import *
from view import MainApplication
class Controller():
def __init__(self):
self.app = QApplication(sys.argv)
self.model = None
"""
if self.login != None:
#self.__logger.debug("Login is cancelled")
sys.exit(-1)
"""
self.view = MainApplication()
def cleanup(self):
print(self.view.getSavedUserEdit())
def start(self):
return self.app.exec_()
view.py
#!/usr/bin/env python
# File: view.py
from PySide.QtGui import *
from PySide.QtCore import *
from user import UserPage
class MainApplication(QMainWindow):
def __init__(self):
super(MainApplication, self).__init__()
self.__savedUserEdit = None
self.__sender = None
self.layout = QWidget()
self.__initGUI()
def __initGUI(self):
self.setWindowTitle('SSCE for StackOverflow')
self.setGeometry(300, 300, 130, 140)
self.__editBtn = QPushButton('Click me')
self.__logoutBtn = QPushButton('Log Out')
self.__editBtn.clicked.connect(self.__showEditPage)
self.__logoutBtn.clicked.connect(self.close)
self.__layout = QVBoxLayout()
self.__layout.addWidget(self.__editBtn)
self.__layout.addWidget(self.__logoutBtn)
self.layout.setLayout(self.__layout)
self.setCentralWidget(self.layout)
self.show()
def __showEditPage(self):
dialog = UserPage()
if dialog.exec_() == QDialog.Accepted:
self.__savedUserEdit = dialog.getUserInput()
self.close()
def getSavedUserEdit(self):
return self.__savedUserEdit
def closeEvent(self, event):
"""
self.__sender = self.sender()
if self.__sender:
answer = QMessageBox.question(self, 'Are you sure you want to quit ?',
QMessageBox.Yes, QMessageBox.No)
if answer == QMessageBox.Yes:
event.accept()
else:
event.ignore()
else:
"""
event.accept()
user.py
#!/usr/bin/env python
# File: user.py
from PySide.QtGui import *
from PySide.QtCore import *
class UserPage(QDialog):
def __init__(self):
super(UserPage, self).__init__()
self.__userInput = ""
self.__initGUI()
def __initGUI(self):
self.setWindowTitle('User Page')
self.setGeometry(300, 300, 96, 86)
self.__layout = QVBoxLayout()
self.__sayHello = QLineEdit()
self.__sayHelloLabel = QLabel('Type what you want: ')
self.__okButton = QPushButton('OK')
self.__okButton.clicked.connect(self.__logout)
self.__layout.addWidget(self.__sayHelloLabel)
self.__layout.addWidget(self.__sayHello)
self.__layout.addWidget(self.__okButton)
self.setLayout(self.__layout)
def getUserInput(self):
return self.__userInput
def __logout(self):
if str(self.__sayHello.text()):
self.__userInput = str(self.__sayHello.text())
self.accept()
else:
QMessageBox.warning(self, 'Warning', "String is empty",
QMessageBox.StandardButton.Ok)
Thanks.
I'm using Python 2.7 and PyQT4.
I want to hide a modal QDialog instance and later on show it again. However, when dialog.setVisible(false) is called (e.g., using QTimer), the dialog.exec_() call returns (with QDialog.Rejected return value).
However, according to http://pyqt.sourceforge.net/Docs/PyQt4/qdialog.html#exec, the _exec() call should block until the user closes the dialog.
Is there a way to hide the dialog without _exec() returning?
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import os
from PyQt4 import QtGui, QtCore
class MyDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QDialog.__init__(self, parent)
def closeEvent(self, QCloseEvent):
print "Close Event"
def hideEvent(self, QHideEvent):
print "Hide Event"
class MyWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setWindowTitle("Main Window")
button = QtGui.QPushButton("Press me", self)
button.clicked.connect(self.run_dialog)
def run_dialog(self):
self.dialog = MyDialog(self)
self.dialog.setModal(True)
self.dialog.show()
QtCore.QTimer.singleShot(1000, self.hide_dialog)
status = self.dialog.exec_()
print "Dialog exited with status {}".format(status), "::", QtGui.QDialog.Accepted, QtGui.QDialog.Rejected
def hide_dialog(self):
self.dialog.setVisible(False)
# self.dialog.setHidden(True)
if __name__ == '__main__':
app = QtGui.QApplication([])
w = MyWindow()
w.show()
sys.exit(app.exec_())
PS1: This code prints the following output:
Hide Event
Dialog exited with status 0 :: 1 0
(the close event is not called).
PS2: For context, I'm trying to implement a SystemTrayIcon that allows to hide and restore a QMainWindow (this part is fine) and possibly its modal QDialog without closing the dialog.
Thanks!
You can bypass the normal behaviour of QDialog.setVisible (which implicitly closes the dialog), by calling the base-class method instead:
def hide_dialog(self):
# self.dialog.setVisible(False)
QtGui.QWidget.setVisible(self.dialog, False)
However, it might be preferrable to connect to the dialog's finished() signal, rather than using exec(), and explicitly reject() the dialog in its closeEvent.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import os
from PyQt4 import QtGui, QtCore
class MyDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QDialog.__init__(self, parent)
layout = QtGui.QHBoxLayout(self)
for title, slot in ('Ok', self.accept), ('Cancel', self.reject):
button = QtGui.QPushButton(title)
button.clicked.connect(slot)
layout.addWidget(button)
def closeEvent(self, QCloseEvent):
print "Close Event"
self.reject()
def hideEvent(self, QHideEvent):
print "Hide Event"
class MyWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setWindowTitle("Main Window")
button = QtGui.QPushButton("Press me", self)
button.clicked.connect(self.run_dialog)
def run_dialog(self):
self.dialog = MyDialog(self)
self.dialog.finished.connect(self.dialog_finished)
self.dialog.setModal(True)
self.dialog.show()
QtCore.QTimer.singleShot(3000, self.dialog.hide)
def dialog_finished(self, status):
print "Dialog exited with status:", status
if __name__ == '__main__':
app = QtGui.QApplication([])
w = MyWindow()
w.show()
sys.exit(app.exec_())
In case anyone is interested, the following code provides a quick-and-dirty way to circunvent the problem for me, although it does not really answer the question.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Extension of the QDialog class so that exec_() does not exit when hidden.
Dialogs inheriting will only exit exec_() via close(), reject() or accept()
"""
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class HideableQDialog(QDialog):
def __init__(self, *args, **kwargs):
super(HideableQDialog, self).__init__(*args, **kwargs)
self._mutex = QMutex()
self._is_finished = False
self._finished_condition = QWaitCondition()
self.finished.connect(self._finish_dialog)
def _finish_dialog(self):
self._is_finished = True
self._finished_condition.wakeOne()
def close(self):
super(HideableQDialog, self).close()
self._finish_dialog()
def reject(self):
super(HideableQDialog, self).reject()
self._finish_dialog()
def accept(self):
super(HideableQDialog, self).accept()
self._finish_dialog()
def exec_(self):
status = super(HideableQDialog, self).exec_()
self._mutex.lock()
condition_succedeed = False
while not condition_succedeed and not self._is_finished:
condition_succedeed = self._finished_condition.wait(self._mutex, 10)
QApplication.processEvents()
self._mutex.unlock()
I want to check if the desktop of Windows 7 is shown (e.g. after clicking control+D or the showDesktop-Button).
It is meant for an application where I've set the QtCore.Qt.FramelessWindowHint to make the decorations disappear. After the desktop is shown and everything is minimized, I want to reshow the app (This should be optional and the user can set or unset this behaviour).
I tested this in IDLE. I want to detect a change if I show the desktop, but still there is the "1" printed. Only if I close(destroy) the window, it changes to "0", but thats not what I am searching for...
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
import win32gui
import time
class Main(QWidget):
def __init__(self, app):
QtGui.QWidget.__init__(self)
self.app=app
self.window=QWidget()
self.window.resize(200,100)
self.window.setWindowTitle("Dummy")
#self.window.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.window.show()
self.check_Minimized_Thread=Worker(self)
self.check_Minimized_Thread.start()
self.app.exec_()
class Worker(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def __del__(self):
self.exiting = True
self.wait()
def run(self):
while True:
time.sleep(0.5)
hwnd=win32gui.FindWindow(None,"Dummy")
A=win32gui.IsWindowVisible(hwnd)
print(A)
self.exit()
if __name__ == '__main__':
app=QApplication(sys.argv)
Main(app)