__init__.py 9.67 KB
Newer Older
Dan Pascu's avatar
Dan Pascu committed
1

2
import os
Dan Pascu's avatar
Dan Pascu committed
3
import sys
4

5
from PyQt5.QtCore import Qt, QEvent, QLocale, QTranslator
Dan Pascu's avatar
Dan Pascu committed
6
from PyQt5.QtWidgets import QApplication, QMessageBox
Dan Pascu's avatar
Dan Pascu committed
7

8
from application import log
9
from application.notification import IObserver, NotificationCenter, NotificationData
10
from application.python import Null
Dan Pascu's avatar
Dan Pascu committed
11
from application.system import host, makedirs
12
from eventlib import api
13
from zope.interface import implementer
14

15
from sipsimple.application import SIPApplication
16
from sipsimple.account import Account, AccountManager, BonjourAccount
17
from sipsimple.addressbook import Contact, Group
18
from sipsimple.configuration.settings import SIPSimpleSettings
19
from sipsimple.configuration.backend.file import FileBackend
Dan Pascu's avatar
Dan Pascu committed
20
from sipsimple.payloads import XMLDocument
21
from sipsimple.storage import FileStorage
Dan Pascu's avatar
Dan Pascu committed
22 23
from sipsimple.threading import run_in_twisted_thread
from sipsimple.threading.green import run_in_green_thread
24

Dan Pascu's avatar
Dan Pascu committed
25 26
from blink.__info__ import __project__, __summary__, __webpage__, __version__, __date__, __author__, __email__, __license__, __copyright__

27 28 29 30
try:
    from blink import branding
except ImportError:
    branding = Null
31

32
from blink.chatwindow import ChatWindow
33
from blink.configuration.account import AccountExtension, BonjourAccountExtension
34
from blink.configuration.addressbook import ContactExtension, GroupExtension
35
from blink.configuration.settings import SIPSimpleSettingsExtension
36
from blink.logging import LogManager
Dan Pascu's avatar
Dan Pascu committed
37
from blink.mainwindow import MainWindow
38
from blink.presence import PresenceManager
Dan Pascu's avatar
Dan Pascu committed
39
from blink.resources import ApplicationData, Resources
40
from blink.sessions import SessionManager
41
from blink.update import UpdateManager
42
from blink.util import QSingleton, run_in_gui_thread
Dan Pascu's avatar
Dan Pascu committed
43

44

45 46
__all__ = ['Blink']

47 48 49 50 51
# Handle high resolution displays:
if hasattr(Qt, 'AA_EnableHighDpiScaling'):
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
if hasattr(Qt, 'AA_UseHighDpiPixmaps'):
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
52

Dan Pascu's avatar
Dan Pascu committed
53
if hasattr(sys, 'frozen'):
54
    import httplib2
55
    httplib2.CA_CERTS = os.environ['SSL_CERT_FILE'] = Resources.get('tls/cacerts.pem')
56 57
    makedirs(ApplicationData.get('logs'))
    sys.stdout.file = ApplicationData.get('logs/output.log')
58 59


Dan Pascu's avatar
Dan Pascu committed
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
class IPAddressMonitor(object):
    """
    An object which monitors the IP address used for the default route of the
    host and posts a SystemIPAddressDidChange notification when a change is
    detected.
    """

    def __init__(self):
        self.greenlet = None

    @run_in_green_thread
    def start(self):
        notification_center = NotificationCenter()

        if self.greenlet is not None:
            return
        self.greenlet = api.getcurrent()

        current_address = host.default_ip
        while True:
            new_address = host.default_ip
            # make sure the address stabilized
            api.sleep(5)
            if new_address != host.default_ip:
                continue
            if new_address != current_address:
86
                notification_center.post_notification(name='SystemIPAddressDidChange', sender=self, data=NotificationData(old_ip_address=current_address, new_ip_address=new_address))
Dan Pascu's avatar
Dan Pascu committed
87 88 89 90 91 92 93 94 95 96
                current_address = new_address
            api.sleep(5)

    @run_in_twisted_thread
    def stop(self):
        if self.greenlet is not None:
            api.kill(self.greenlet, api.GreenletExit())
            self.greenlet = None


97
@implementer(IObserver)
Adrian Georgescu's avatar
Adrian Georgescu committed
98
class Blink(QApplication, metaclass=QSingleton):
99

Dan Pascu's avatar
Dan Pascu committed
100
    def __init__(self):
101
        super(Blink, self).__init__(sys.argv)
102
        self.setAttribute(Qt.AA_DontShowIconsInMenus, False)
Dan Pascu's avatar
Dan Pascu committed
103
        self.sip_application = SIPApplication()
104
        self.first_run = False
Tijmen de Mes's avatar
Tijmen de Mes committed
105
        self.reinit = False
106

107 108 109 110 111 112 113 114 115 116 117 118 119 120
        translator = QTranslator(self)
        system_language = QLocale.system().name().split('_')[0]
        language = system_language
        if os.path.exists(ApplicationData.get('config')):
            pre_loaded_settings = FileBackend(ApplicationData.get('config')).load()
            try:
                language = pre_loaded_settings['BlinkSettings']['interface']['language']
            except KeyError:
                pass
            if language == 'default':
                language = system_language
            if translator.load(Resources.get(f'i18n/blink_{language}')):
                self.installTranslator(translator)

121 122 123 124 125
        self.setOrganizationDomain("ag-projects.com")
        self.setOrganizationName("AG Projects")
        self.setApplicationName("Blink")
        self.setApplicationVersion(__version__)

Dan Pascu's avatar
Dan Pascu committed
126
        self.main_window = MainWindow()
127
        self.chat_window = ChatWindow()
128 129 130 131
        self.main_window.__closed__ = True
        self.chat_window.__closed__ = True
        self.main_window.installEventFilter(self)
        self.chat_window.installEventFilter(self)
132

133 134 135 136 137 138 139 140 141 142
        self.main_window.addAction(self.chat_window.control_button.actions.main_window)
        self.chat_window.addAction(self.main_window.quit_action)
        self.chat_window.addAction(self.main_window.help_action)
        self.chat_window.addAction(self.main_window.redial_action)
        self.chat_window.addAction(self.main_window.join_conference_action)
        self.chat_window.addAction(self.main_window.mute_action)
        self.chat_window.addAction(self.main_window.silent_action)
        self.chat_window.addAction(self.main_window.preferences_action)
        self.chat_window.addAction(self.main_window.transfers_window_action)
        self.chat_window.addAction(self.main_window.logs_window_action)
Dan Pascu's avatar
Dan Pascu committed
143 144
        self.chat_window.addAction(self.main_window.received_files_window_action)
        self.chat_window.addAction(self.main_window.screenshots_window_action)
145

Dan Pascu's avatar
Dan Pascu committed
146
        self.ip_address_monitor = IPAddressMonitor()
147
        self.log_manager = LogManager()
148
        self.presence_manager = PresenceManager()
149 150
        self.session_manager = SessionManager()
        self.update_manager = UpdateManager()
Dan Pascu's avatar
Dan Pascu committed
151

152 153 154 155
        # Prevent application from exiting after last window is closed if system tray was initialized
        if self.main_window.system_tray_icon:
            self.setQuitOnLastWindowClosed(False)

156
        self.main_window.check_for_updates_action.triggered.connect(self.update_manager.check_for_updates)
157
        self.main_window.check_for_updates_action.setVisible(self.update_manager != Null)
158

Dan Pascu's avatar
Dan Pascu committed
159 160 161
        if getattr(sys, 'frozen', False):
            XMLDocument.schema_path = Resources.get('xml-schemas')

162 163
        Account.register_extension(AccountExtension)
        BonjourAccount.register_extension(BonjourAccountExtension)
164 165
        Contact.register_extension(ContactExtension)
        Group.register_extension(GroupExtension)
166 167
        SIPSimpleSettings.register_extension(SIPSimpleSettingsExtension)

168 169 170
        notification_center = NotificationCenter()
        notification_center.add_observer(self, sender=self.sip_application)

Saul Ibarra's avatar
Saul Ibarra committed
171 172
        branding.setup(self)

Dan Pascu's avatar
Dan Pascu committed
173
    def run(self):
174 175
        self.first_run = not os.path.exists(ApplicationData.get('config'))
        self.sip_application.start(FileStorage(ApplicationData.directory))
176
        self.exec_()
177
        self.update_manager.shutdown()
Dan Pascu's avatar
Dan Pascu committed
178 179
        self.sip_application.stop()
        self.sip_application.thread.join()
180
        self.log_manager.stop()
Tijmen de Mes's avatar
Tijmen de Mes committed
181 182
        if self.reinit:
            os.execl(sys.executable, sys.executable, *sys.argv)
Dan Pascu's avatar
Dan Pascu committed
183

184 185 186 187 188
    def quit(self):
        self.chat_window.close()
        self.main_window.close()
        super(Blink, self).quit()

Tijmen de Mes's avatar
Tijmen de Mes committed
189 190 191 192
    def restart(self):
        self.reinit = True
        self.quit()

193 194 195 196 197 198 199 200 201 202 203
    def eventFilter(self, watched, event):
        if watched in (self.main_window, self.chat_window):
            if event.type() == QEvent.Show:
                watched.__closed__ = False
            elif event.type() == QEvent.Close:
                watched.__closed__ = True
                if self.main_window.__closed__ and self.chat_window.__closed__:
                    # close auxiliary windows
                    self.main_window.conference_dialog.close()
                    self.main_window.filetransfer_window.close()
                    self.main_window.preferences_window.close()
Tijmen de Mes's avatar
Tijmen de Mes committed
204 205
        if watched is self.chat_window:
            if event.type() == QEvent.WindowActivate:
206 207 208 209
                try:
                    watched.send_pending_imdn_messages(watched.selected_session)
                except KeyError:
                    pass
210 211
        return False

212 213 214 215 216 217 218 219
    def customEvent(self, event):
        handler = getattr(self, '_EH_%s' % event.name, Null)
        handler(event)

    def _EH_CallFunctionEvent(self, event):
        try:
            event.function(*event.args, **event.kw)
        except:
220
            log.exception('Exception occurred while calling function %s in the GUI thread' % event.function.__name__)
221

222 223 224 225
    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

226
    def _NH_SIPApplicationWillStart(self, notification):
227
        self.log_manager.start()
228
        self.presence_manager.start()
229

230 231
    @run_in_gui_thread
    def _NH_SIPApplicationDidStart(self, notification):
Dan Pascu's avatar
Dan Pascu committed
232
        self.ip_address_monitor.start()
233
        self.main_window.show()
Dan Pascu's avatar
Dan Pascu committed
234
        accounts = AccountManager().get_accounts()
235
        if not accounts or (self.first_run and accounts == [BonjourAccount()]):
236
            self.main_window.preferences_window.show_create_account_dialog()
237
        self.update_manager.initialize()
238

Dan Pascu's avatar
Dan Pascu committed
239 240 241
    def _NH_SIPApplicationWillEnd(self, notification):
        self.ip_address_monitor.stop()

242 243 244
    def _NH_SIPApplicationDidEnd(self, notification):
        self.presence_manager.stop()

245 246 247
    @run_in_gui_thread
    def _NH_SIPApplicationGotFatalError(self, notification):
        log.error('Fatal error:\n{}'.format(notification.data.traceback))
Adrian Georgescu's avatar
Adrian Georgescu committed
248
        QMessageBox.critical(self.main_window, "Fatal Error", "A fatal error occurred, {} will now exit.".format(self.applicationName()))
249
        sys.exit(1)