1import sys
2import os
3import signal
4import logging
5import time
6from threading import Event
7# This xinit import must happen before any GUI libraries are initialized.
8# pylint: disable=wrong-import-position,wrong-import-order,ungrouped-imports,unused-import
9import ulauncher.utils.xinit  # noqa: F401
10
11import gi
12
13# Fixes issue #488
14sys.path.append('/usr/lib/python3.8/site-packages')
15
16gi.require_version('Gtk', '3.0')
17# pylint: disable=wrong-import-position
18from gi.repository import Gtk
19import dbus
20import dbus.service
21from dbus.mainloop.glib import DBusGMainLoop
22
23from ulauncher.config import get_version, get_options, CACHE_DIR, CONFIG_DIR, DATA_DIR
24from ulauncher.utils.decorator.run_async import run_async
25from ulauncher.utils.wayland import is_wayland, is_wayland_compatibility_on
26from ulauncher.ui.windows.UlauncherWindow import UlauncherWindow
27from ulauncher.ui.AppIndicator import AppIndicator
28from ulauncher.utils.Settings import Settings
29from ulauncher.utils.setup_logging import setup_logging
30from ulauncher.api.version import api_version
31
32
33DBUS_SERVICE = 'net.launchpad.ulauncher'
34DBUS_PATH = '/net/launchpad/ulauncher'
35
36
37def _create_dirs():
38    if not os.path.exists(CONFIG_DIR):
39        os.makedirs(CONFIG_DIR)
40
41    if not os.path.exists(DATA_DIR):
42        os.makedirs(DATA_DIR)
43
44    # make sure ~/.cache/ulauncher exists
45    if not os.path.exists(CACHE_DIR):
46        os.makedirs(CACHE_DIR)
47
48
49class UlauncherDbusService(dbus.service.Object):
50    def __init__(self, window):
51        self.window = window
52        bus_name = dbus.service.BusName(DBUS_SERVICE, bus=dbus.SessionBus())
53        super().__init__(bus_name, DBUS_PATH)
54
55    @dbus.service.method(DBUS_SERVICE)
56    def toggle_window(self):
57        self.window.toggle_window()
58
59
60# pylint: disable=too-few-public-methods
61class SignalHandler:
62
63    _exit_event = None
64    _app_window = None
65    _logger = None
66
67    def __init__(self, app_window):
68        self._exit_event = Event()
69        self._app_window = app_window
70        self._logger = logging.getLogger('ulauncher')
71        signal.signal(signal.SIGINT, self._exit_gracefully)
72        signal.signal(signal.SIGTERM, self._exit_gracefully)
73        signal.signal(signal.SIGHUP, self._reload_configs)
74
75    def _reload_configs(self, *args):
76        self._logger.info('Received SIGHUP. Reloading configs')
77        self._app_window.init_theme()
78
79    def killed(self):
80        """
81        :rtype: bool
82        """
83        return self._exit_event.is_set()
84
85    def _exit_gracefully(self, *args):
86        self._exit_event.set()
87
88
89def main():
90    """
91    Main function that starts everything
92    """
93
94    # start DBus loop
95    DBusGMainLoop(set_as_default=True)
96    bus = dbus.SessionBus()
97    instance = bus.request_name(DBUS_SERVICE)
98
99    if instance != dbus.bus.REQUEST_NAME_REPLY_PRIMARY_OWNER:
100        print(
101            "DBus name already taken. Ulauncher is probably backgrounded. Did you mean `ulauncher-toggle`?",
102            file=sys.stderr
103        )
104        toggle_window = dbus.SessionBus().get_object(DBUS_SERVICE, DBUS_PATH).get_dbus_method("toggle_window")
105        toggle_window()
106        return
107
108    _create_dirs()
109
110    options = get_options()
111    setup_logging(options)
112    logger = logging.getLogger('ulauncher')
113    logger.info('Ulauncher version %s', get_version())
114    logger.info('Extension API version %s', api_version)
115    logger.info("GTK+ %s.%s.%s", Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version())
116    logger.info("Is Wayland: %s", is_wayland())
117    logger.info("Wayland compatibility: %s", ('on' if is_wayland_compatibility_on() else 'off'))
118
119    # log uncaught exceptions
120    def except_hook(exctype, value, tb):
121        logger.error("Uncaught exception", exc_info=(exctype, value, tb))
122
123    sys.excepthook = except_hook
124
125    window = UlauncherWindow.get_instance()
126    UlauncherDbusService(window)
127    if not options.hide_window:
128        window.show()
129
130    if Settings.get_instance().get_property('show-indicator-icon'):
131        AppIndicator.get_instance().show()
132
133    # workaround to make Ctrl+C quitting the app
134    signal_handler = SignalHandler(window)
135    gtk_thread = run_async(Gtk.main)()
136    try:
137        while gtk_thread.is_alive() and not signal_handler.killed():
138            time.sleep(0.5)
139    except KeyboardInterrupt:
140        logger.warning('On KeyboardInterrupt')
141    finally:
142        Gtk.main_quit()
143