-
Notifications
You must be signed in to change notification settings - Fork 220
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix master issue 1306 (system-tray icon) and releated "missing icons" issues #1480
Changes from 9 commits
0ef157b
9db82c2
a476da9
36d8d59
8cfcea2
6606180
8f3b46d
53dc84d
a107ad9
7cd14af
426794a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import sys | ||
import resource | ||
import logger | ||
|
||
# This mini python script is used to determine if a Qt5 GUI application | ||
# can be created without an error. | ||
# | ||
# It is used e.g. for diagnostics output of backintime | ||
# or to check if a system tray icon could be shown... | ||
# | ||
# It is called by "tools.is_Qt5_working()" normally | ||
# but you can also execute it manually via | ||
# python3 qt5_probing.py | ||
|
||
# It works by trying to create a QApplication instance | ||
# Any error indicates that Qt5 is not available or not correctly configured. | ||
|
||
# WORK AROUND: | ||
# | ||
# The C++ code of Qt5 ends abruptly with a SIGABRT signal (qFatal macro) | ||
# if a QApplication cannot be instantiated. | ||
# This causes a coredump creation by the python default signal handler | ||
# and the signal handler cannot be disabled since it reacts to a | ||
# non-python low-level signal sent via C/C++. | ||
# | ||
# Even though the coredump message cannot be prevent there is | ||
# workaround to prevent the cordump **file** creation which | ||
# would take too much time just to probe Qt5's availability: | ||
# | ||
# Use resource.setrlimit() to set resource.RLIMIT_CORE’s soft limit to 0 | ||
# | ||
# Note: This does NOT prevent the console output "Aborted (core dumped)" | ||
# even though no coredump file will be created! | ||
# You can check that no coredump file was created with the command | ||
# sudo coredumpctl list -r | ||
# | ||
# More details: | ||
# | ||
# To suppress the creation of coredump file on Linux | ||
# use resource.setrlimit() to set resource.RLIMIT_CORE’s soft limit to 0 | ||
# to prevent coredump file creation. | ||
# https://docs.python.org/3.10/library/resource.html#resource.RLIMIT_CORE | ||
# https://docs.python.org/3.10/library/resource.html#resource.setrlimit | ||
# See also the source code of the test.support.SuppressCrashReport() context manager: | ||
# if self.resource is not None: | ||
# try: | ||
# self.old_value = self.resource.getrlimit(self.resource.RLIMIT_CORE) | ||
# self.resource.setrlimit(self.resource.RLIMIT_CORE, | ||
# (0, self.old_value[1])) | ||
# except (ValueError, OSError): | ||
# pass | ||
# https://github.com/python/cpython/blob/32718f908cc92c474fd968912368b8a4500bd055/Lib/test/support/__init__.py#L1712-L1718 | ||
# and cpython "faulthandler_suppress_crash_report()" | ||
# https://github.com/python/cpython/blob/32718f908cc92c474fd968912368b8a4500bd055/Modules/faulthandler.c#L954 | ||
# See "man 2 getrlimit" for more details: | ||
# > RLIMIT_CORE | ||
# > This is the maximum size of a core file (see core(5)) in bytes | ||
# > that the process may dump. When 0 no core dump files are created | ||
# > When nonzero, larger dumps are truncated to this size. | ||
# > ... | ||
# > The soft limit is the value that the kernel enforces for the corresponding resource. | ||
# > The hard limit acts as a ceiling for the soft limit: | ||
# > an unprivileged process may set only its soft limit to a value | ||
# > in the range from 0 up to the hard limit, and (irreversibly) lower its | ||
# > hard limit. A privileged process (under Linux: one with the | ||
# > CAP_SYS_RESOURCE capability in the initial user namespace) may make | ||
# > arbitrary changes to either limit value. | ||
# | ||
# Note: The context manager test.support.SuppressCrashReport() is NOT used | ||
# here since the "test.support" module not public and its API is subject | ||
# to change without backwards compatibility concerns between releases. | ||
|
||
# Work-around to prevent the time-consuming creation of a core dump | ||
old_limits = resource.getrlimit(resource.RLIMIT_CORE) | ||
resource.setrlimit(resource.RLIMIT_CORE, (0, old_limits[1])) | ||
|
||
exit_code = 0 | ||
|
||
try: | ||
|
||
logger.debug(f"{__file__} started... Call args: {str(sys.argv)}") | ||
|
||
from PyQt5 import QtCore | ||
from PyQt5.QtWidgets import QApplication | ||
|
||
app = QApplication(['']) | ||
|
||
exit_code = 1 | ||
|
||
# https://doc.qt.io/qt-5/qsystemtrayicon.html#details: | ||
# > To check whether a system tray is present on the user's desktop, | ||
# > call the QSystemTrayIcon::isSystemTrayAvailable() static function. | ||
# | ||
# This requires a QApplication instance (otherwise Qt5 causes a segfault) | ||
# which we don't have here so we create it to check if a window manager | ||
# ("GUI") is active at all (e.g. in headless installations it isn't). | ||
# See: https://forum.qt.io/topic/3852/issystemtrayavailable-always-crashes-segfault-on-ubuntu-10-10-desktop/6 | ||
|
||
from PyQt5.QtWidgets import QSystemTrayIcon | ||
is_sys_tray_available = QSystemTrayIcon.isSystemTrayAvailable() | ||
|
||
if is_sys_tray_available: | ||
exit_code = 2 | ||
|
||
logger.debug(f"isSystemTrayAvailable for Qt5: {is_sys_tray_available}") | ||
|
||
except Exception as e: | ||
logger.debug(f"Error: {repr(e)}") | ||
|
||
logger.debug(f"{__file__} is terminating normally (exit code: {exit_code})") | ||
|
||
# Exit codes: | ||
# 0 = no Qt5 GUI available | ||
# 1 = only Qt5 GUI available (no sys tray support) | ||
# 2 = Qt5 GUI and sys tray available | ||
# 134 (-6 as signed byte exit code type!) = SIGABRT caught by python | ||
# ("interrupted by signal 6: SIGABRT"). | ||
# This is most probably caused by a misconfigured Qt5... | ||
# So the interpretation is the same as exit code 0. | ||
sys.exit(exit_code) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -508,9 +508,17 @@ def checkXServer(): | |
""" | ||
Check if there is a X11 server running on this system. | ||
|
||
Use ``is_Qt5_working`` instead if you want to be sure that Qt5 is working. | ||
|
||
Returns: | ||
bool: ``True`` if X11 server is running | ||
""" | ||
# Note: Return values of xdpyinfo <> 0 are not clearly documented. | ||
# xdpyinfo does indeed return 1 if it prints | ||
# xdypinfo: unable to open display "..." | ||
# This seems to be undocumented (at least not in the man pages) | ||
# and the source is not obvious here: | ||
# https://cgit.freedesktop.org/xorg/app/xdpyinfo/tree/xdpyinfo.c | ||
if checkCommand('xdpyinfo'): | ||
proc = subprocess.Popen(['xdpyinfo'], | ||
stdout = subprocess.DEVNULL, | ||
|
@@ -520,6 +528,56 @@ def checkXServer(): | |
else: | ||
return False | ||
|
||
|
||
def is_Qt5_working(systray_required=False): | ||
""" | ||
Check if the Qt5 GUI library is working (installed and configured) | ||
|
||
This function is contained in BiT CLI (not BiT Qt) to allow Qt5 | ||
diagnostics output even if the BiT Qt GUI is not installed. | ||
This function does NOT add a hard Qt5 dependency (just "probing") | ||
so it is OK to be in BiT CLI. | ||
|
||
Args: | ||
systray_required: Set to ``True`` if the systray of the desktop | ||
environment must be available too to consider Qt5 as "working" | ||
|
||
Returns: | ||
bool: ``True`` Qt5 can create a GUI | ||
``False`` Qt5 fails (or the systray is not available | ||
if ``systray_required`` is ``True``) | ||
""" | ||
|
||
# Spawns a new process since it may crash with a SIGABRT and we | ||
# don't want to crash BiT if this happens... | ||
|
||
try: | ||
path = os.path.join(backintimePath("common"), "qt5_probing.py") | ||
cmd = [sys.executable, path] | ||
with subprocess.Popen(cmd, | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
universal_newlines=True) as proc: | ||
|
||
std_output, error_output = proc.communicate() # to get the exit code | ||
|
||
logger.debug(f"Qt5 probing result: exit code {proc.returncode}") | ||
|
||
if proc.returncode != 23: # if some Qt5 parts are missing: Show details | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Damn, I forgot to remove my testing code. Will follow up on this with a new commit... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought it has to be this way. Some more verbose logging in such a complex situation isn't bad. |
||
logger.debug(f"Qt5 probing stdout: {std_output}") | ||
logger.debug(f"Qt5 probing errout: {error_output}") | ||
|
||
return proc.returncode == 2 or (proc.returncode == 1 and systray_required is False) | ||
|
||
except FileNotFoundError: | ||
logger.error(f"Qt5 probing script not found: {cmd[0]}") | ||
raise | ||
|
||
except Exception as e: | ||
logger.error(f"Error: {repr(e)}") | ||
raise | ||
|
||
|
||
def preparePath(path): | ||
""" | ||
Removes trailing slash '/' from ``path``. | ||
|
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This info should be added to the README.md. There it is where distro maintainers pick up this information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I forgot that :-)
I will add this ASAP
I would also like to keep it in the CHANGES since this is what pkg maintainers do read first I guess (not the diff of the README)