1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import os
import sys
from PyQt5.QtCore import Qt, QEvent, QLocale, QTranslator
from PyQt5.QtWidgets import QApplication, QMessageBox
from application import log
from application.notification import IObserver, NotificationCenter, NotificationData
from application.python import Null
from application.system import host, makedirs
from eventlib import api
from zope.interface import implementer
from sipsimple.application import SIPApplication
from sipsimple.account import Account, AccountManager, BonjourAccount
from sipsimple.addressbook import Contact, Group
from sipsimple.configuration.settings import SIPSimpleSettings
from sipsimple.configuration.backend.file import FileBackend
from sipsimple.payloads import XMLDocument
from sipsimple.storage import FileStorage
from sipsimple.threading import run_in_twisted_thread
from sipsimple.threading.green import run_in_green_thread
from blink.__info__ import __project__, __summary__, __webpage__, __version__, __date__, __author__, __email__, __license__, __copyright__
try:
from blink import branding
except ImportError:
branding = Null
from blink.chatwindow import ChatWindow
from blink.configuration.account import AccountExtension, BonjourAccountExtension
from blink.configuration.addressbook import ContactExtension, GroupExtension
from blink.configuration.settings import SIPSimpleSettingsExtension
from blink.logging import LogManager
from blink.mainwindow import MainWindow
from blink.presence import PresenceManager
from blink.resources import ApplicationData, Resources
from blink.sessions import SessionManager
from blink.update import UpdateManager
from blink.util import QSingleton, run_in_gui_thread
__all__ = ['Blink']
# 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)
if hasattr(sys, 'frozen'):
import httplib2
httplib2.CA_CERTS = os.environ['SSL_CERT_FILE'] = Resources.get('tls/cacerts.pem')
makedirs(ApplicationData.get('logs'))
sys.stdout.file = ApplicationData.get('logs/output.log')
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:
notification_center.post_notification(name='SystemIPAddressDidChange', sender=self, data=NotificationData(old_ip_address=current_address, new_ip_address=new_address))
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
@implementer(IObserver)
class Blink(QApplication, metaclass=QSingleton):
def __init__(self):
super(Blink, self).__init__(sys.argv)
self.setAttribute(Qt.AA_DontShowIconsInMenus, False)
self.sip_application = SIPApplication()
self.first_run = False
self.reinit = False
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)
self.setOrganizationDomain("ag-projects.com")
self.setOrganizationName("AG Projects")
self.setApplicationName("Blink")
self.setApplicationVersion(__version__)
self.main_window = MainWindow()
self.chat_window = ChatWindow()
self.main_window.__closed__ = True
self.chat_window.__closed__ = True
self.main_window.installEventFilter(self)
self.chat_window.installEventFilter(self)
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)
self.chat_window.addAction(self.main_window.received_files_window_action)
self.chat_window.addAction(self.main_window.screenshots_window_action)
self.ip_address_monitor = IPAddressMonitor()
self.log_manager = LogManager()
self.presence_manager = PresenceManager()
self.session_manager = SessionManager()
self.update_manager = UpdateManager()
# Prevent application from exiting after last window is closed if system tray was initialized
if self.main_window.system_tray_icon:
self.setQuitOnLastWindowClosed(False)
self.main_window.check_for_updates_action.triggered.connect(self.update_manager.check_for_updates)
self.main_window.check_for_updates_action.setVisible(self.update_manager != Null)
if getattr(sys, 'frozen', False):
XMLDocument.schema_path = Resources.get('xml-schemas')
Account.register_extension(AccountExtension)
BonjourAccount.register_extension(BonjourAccountExtension)
Contact.register_extension(ContactExtension)
Group.register_extension(GroupExtension)
SIPSimpleSettings.register_extension(SIPSimpleSettingsExtension)
notification_center = NotificationCenter()
notification_center.add_observer(self, sender=self.sip_application)
branding.setup(self)
def run(self):
self.first_run = not os.path.exists(ApplicationData.get('config'))
self.sip_application.start(FileStorage(ApplicationData.directory))
self.exec_()
self.update_manager.shutdown()
self.sip_application.stop()
self.sip_application.thread.join()
self.log_manager.stop()
if self.reinit:
os.execl(sys.executable, sys.executable, *sys.argv)
def quit(self):
self.chat_window.close()
self.main_window.close()
super(Blink, self).quit()
def restart(self):
self.reinit = True
self.quit()
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()
if watched is self.chat_window:
if event.type() == QEvent.WindowActivate:
try:
watched.send_pending_imdn_messages(watched.selected_session)
except KeyError:
pass
return False
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:
log.exception('Exception occurred while calling function %s in the GUI thread' % event.function.__name__)
def handle_notification(self, notification):
handler = getattr(self, '_NH_%s' % notification.name, Null)
handler(notification)
def _NH_SIPApplicationWillStart(self, notification):
self.log_manager.start()
self.presence_manager.start()
@run_in_gui_thread
def _NH_SIPApplicationDidStart(self, notification):
self.ip_address_monitor.start()
self.main_window.show()
accounts = AccountManager().get_accounts()
if not accounts or (self.first_run and accounts == [BonjourAccount()]):
self.main_window.preferences_window.show_create_account_dialog()
self.update_manager.initialize()
def _NH_SIPApplicationWillEnd(self, notification):
self.ip_address_monitor.stop()
def _NH_SIPApplicationDidEnd(self, notification):
self.presence_manager.stop()
@run_in_gui_thread
def _NH_SIPApplicationGotFatalError(self, notification):
log.error('Fatal error:\n{}'.format(notification.data.traceback))
QMessageBox.critical(self.main_window, "Fatal Error", "A fatal error occurred, {} will now exit.".format(self.applicationName()))
sys.exit(1)