class QHangupsMainWidget(QtGui.QWidget): """QHangups main widget (icon in system tray)""" startHangups = QtCore.pyqtSignal() stopHangups = QtCore.pyqtSignal() def __init__(self, cookies_path, parent=None): super().__init__(parent) self.set_language() self.cookies_path = cookies_path self.hangups_running = False self.client = None self.create_actions() self.create_menu() self.create_icon() self.update_status() # These are populated by on_connect when it's called. self.conv_list = None # hangups.ConversationList self.user_list = None # hangups.UserList self.notifier = None # hangups.notify.Notifier # Widgets self.conversations_dialog = QHangupsConversationsList(controller=self) self.messages_dialog = QHangupsConversations(controller=self) # Setup system tray icon doubleclick timer self.icon_doubleclick_timer = QtCore.QTimer(self) self.icon_doubleclick_timer.setSingleShot(True) self.icon_doubleclick_timer.timeout.connect( self.icon_doubleclick_timeout) # Handle signals on Unix # (add_signal_handler is not implemented on Windows) try: loop = asyncio.get_event_loop() for signum in (signal.SIGINT, signal.SIGTERM): loop.add_signal_handler(signum, lambda: self.quit(force=True)) except NotImplementedError: pass def create_actions(self): """Create actions and connect relevant signals""" self.startAction = QtGui.QAction(self) self.startAction.triggered.connect(self.hangups_start) self.stopAction = QtGui.QAction(self) self.stopAction.triggered.connect(self.hangups_stop) self.settingsAction = QtGui.QAction(self) self.settingsAction.triggered.connect(self.settings) self.aboutAction = QtGui.QAction(self) self.aboutAction.triggered.connect(self.about) self.quitAction = QtGui.QAction(self) self.quitAction.triggered.connect(self.quit) def create_menu(self): """Create menu and add items to it""" self.trayIconMenu = QtGui.QMenu(self) self.trayIconMenu.addAction(self.startAction) self.trayIconMenu.addAction(self.stopAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.settingsAction) self.trayIconMenu.addAction(self.aboutAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.quitAction) def create_icon(self): """Create system tray icon""" self.trayIcon = QtGui.QSystemTrayIcon(self) self.iconActive = QtGui.QIcon("{}/qhangups.svg".format( os.path.dirname(os.path.abspath(__file__)))) self.iconDisabled = QtGui.QIcon("{}/qhangups_disabled.svg".format( os.path.dirname(os.path.abspath(__file__)))) self.trayIcon.activated.connect(self.icon_activated) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.setIcon(self.iconDisabled) self.trayIcon.setToolTip("QHangups") self.trayIcon.show() def retranslateUi(self): """Retranslate GUI""" self.startAction.setText(self.tr("&Connect")) self.stopAction.setText(self.tr("&Disconnect")) self.settingsAction.setText(self.tr("S&ettings ...")) self.aboutAction.setText(self.tr("A&bout ...")) self.quitAction.setText(self.tr("&Quit")) def login(self, cookies_path): """Login to Google account""" try: cookies = hangups.auth.get_auth(self.get_credentials, self.get_pin, cookies_path) return cookies except hangups.GoogleAuthError: QtGui.QMessageBox.warning(self, self.tr("QHangups - Warning"), self.tr("Google login failed!")) return False def get_credentials(self): """Ask user for email and password (callback)""" email, ok = QtGui.QInputDialog.getText(self, self.tr("QHangups - Email"), self.tr("Email:"), QtGui.QLineEdit.Normal) if ok: password, ok = QtGui.QInputDialog.getText( self, self.tr("QHangups - Password"), self.tr(u"Password:"******"""Ask user for second factor PIN (callback)""" pin, ok = QtGui.QInputDialog.getText(self, self.tr("QHangups - PIN"), self.tr("PIN:"), QtGui.QLineEdit.Password) if ok: return pin else: return False def update_status(self): """Update GUI according to Hangups status""" if self.hangups_running: self.trayIcon.setIcon(self.iconActive) self.startAction.setEnabled(False) self.stopAction.setEnabled(True) else: self.trayIcon.setIcon(self.iconDisabled) self.startAction.setEnabled(True) self.stopAction.setEnabled(False) def hangups_start(self): """Connect to Hangouts""" cookies = self.login(self.cookies_path) if cookies: self.startHangups.emit() self.client = hangups.Client(cookies) self.client.on_connect.add_observer(self.on_connect) # Run Hangups event loop asyncio. async (self.client.connect()).add_done_callback( lambda future: future.result()) self.hangups_running = True self.update_status() def hangups_stop(self): """Disconnect from Hangouts""" self.stopHangups.emit() asyncio. async (self.client.disconnect()).add_done_callback( lambda future: future.result()) self.conv_list = None self.user_list = None self.notifier = None self.hangups_running = False self.client = None self.update_status() def about(self): """Show About dialog""" QtGui.QMessageBox.information( self, self.tr("About"), self.tr("QHangups {}".format(__version__))) def settings(self): """Show Settings dialog""" dialog = QHangupsSettings(self) if dialog.exec_(): self.set_language() if self.hangups_running: self.hangups_stop() self.hangups_start() def set_language(self): """Change language""" settings = QtCore.QSettings() language = settings.value("language") if not language: language = QtCore.QLocale.system().name().split("_")[0] lang_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "languages") lang_file = "qhangups_{}.qm".format(language) qt_lang_path = QtCore.QLibraryInfo.location( QtCore.QLibraryInfo.TranslationsPath) qt_lang_file = "qt_{}.qm".format(language) if os.path.isfile(os.path.join(lang_path, lang_file)): translator.load(lang_file, lang_path) qt_translator.load(qt_lang_file, qt_lang_path) else: translator.load("") qt_translator.load("") def icon_activated(self, reason): """Connect or disconnect from Hangouts by double-click on tray icon""" if reason == QtGui.QSystemTrayIcon.Trigger or reason == QtGui.QSystemTrayIcon.DoubleClick: if self.icon_doubleclick_timer.isActive(): self.icon_doubleclick_timer.stop() if self.hangups_running: self.hangups_stop() else: self.hangups_start() else: self.icon_doubleclick_timer.start( QtGui.qApp.doubleClickInterval()) def icon_doubleclick_timeout(self): """Open or close list of conversations after single-click on tray icon""" if self.conversations_dialog: if self.conversations_dialog.isVisible( ) and not self.conversations_dialog.isMinimized(): self.conversations_dialog.hide() else: self.conversations_dialog.showNormal() self.conversations_dialog.raise_() self.conversations_dialog.activateWindow() def quit(self, force=False): """Quit QHangups""" if self.hangups_running: if not force: reply = QtGui.QMessageBox.question( self, self.tr("QHangups - Quit"), self.tr("You are still connected to Google Hangouts. " "Do you really want to quit QHangups?"), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) if reply != QtGui.QMessageBox.Yes: return self.hangups_stop() loop = asyncio.get_event_loop() loop.stop() # QtGui.qApp.quit() def changeEvent(self, event): """Handle LanguageChange event""" if (event.type() == QtCore.QEvent.LanguageChange): print("Language changed") self.retranslateUi() super().changeEvent(event) def open_messages_dialog(self, conv_id, switch=True): """Open conversation in new tab""" self.messages_dialog.set_conv_tab(conv_id, switch=switch) self.messages_dialog.showNormal() if switch: self.messages_dialog.raise_() self.messages_dialog.activateWindow() def on_connect(self, initial_data): """Handle connecting for the first time (callback)""" print('Connected') self.user_list = hangups.UserList( self.client, initial_data.self_entity, initial_data.entities, initial_data.conversation_participants) self.conv_list = hangups.ConversationList( self.client, initial_data.conversation_states, self.user_list, initial_data.sync_timestamp) self.conv_list.on_event.add_observer(self.on_event) # Setup notifications self.notifier = Notifier(self.conv_list) # Setup conversations window self.messages_dialog.init_conversations(self.client, self.conv_list) # Setup conversations list window and show it self.conversations_dialog.init_conversations(self.client, self.conv_list) self.conversations_dialog.show() def on_event(self, conv_event): """Open conversation tab for new messages when they arrive (callback)""" if isinstance(conv_event, hangups.ChatMessageEvent): self.open_messages_dialog(conv_event.conversation_id, switch=False)
class QHangupsMainWidget(QtWidgets.QWidget): """QHangups main widget (icon in system tray)""" startHangups = QtCore.pyqtSignal() stopHangups = QtCore.pyqtSignal() def __init__(self, refresh_token_path, parent=None): super().__init__(parent) self.set_language() self.refresh_token_path = refresh_token_path self.hangups_running = False self.client = None self.create_actions() self.create_menu() self.create_icon() self.update_status() # These are populated by on_connect when it's called. self.conv_list = None # hangups.ConversationList self.user_list = None # hangups.UserList self.notifier = None # hangups.notify.Notifier # Widgets self.conversations_dialog = QHangupsConversationsList(controller=self) self.messages_dialog = QHangupsConversations(controller=self) # Setup system tray icon doubleclick timer self.icon_doubleclick_timer = QtCore.QTimer(self) self.icon_doubleclick_timer.setSingleShot(True) self.icon_doubleclick_timer.timeout.connect(self.icon_doubleclick_timeout) # Handle signals on Unix # (add_signal_handler is not implemented on Windows) try: loop = asyncio.get_event_loop() for signum in (signal.SIGINT, signal.SIGTERM): loop.add_signal_handler(signum, lambda: self.quit(force=True)) except NotImplementedError: pass def create_actions(self): """Create actions and connect relevant signals""" self.startAction = QtWidgets.QAction(self) self.startAction.triggered.connect(self.hangups_start) self.stopAction = QtWidgets.QAction(self) self.stopAction.triggered.connect(self.hangups_stop) self.settingsAction = QtWidgets.QAction(self) self.settingsAction.triggered.connect(self.settings) self.aboutAction = QtWidgets.QAction(self) self.aboutAction.triggered.connect(self.about) self.quitAction = QtWidgets.QAction(self) self.quitAction.triggered.connect(self.quit) def create_menu(self): """Create menu and add items to it""" self.trayIconMenu = QtWidgets.QMenu(self) self.trayIconMenu.addAction(self.startAction) self.trayIconMenu.addAction(self.stopAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.settingsAction) self.trayIconMenu.addAction(self.aboutAction) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.quitAction) def create_icon(self): """Create system tray icon""" self.trayIcon = QtWidgets.QSystemTrayIcon(self) self.iconActive = QtGui.QIcon("{}/qhangups.svg".format(os.path.dirname(os.path.abspath(__file__)))) self.iconDisabled = QtGui.QIcon("{}/qhangups_disabled.svg".format(os.path.dirname(os.path.abspath(__file__)))) # Workaround for Plasma 5 not showing SVG icons self.iconActive = QtGui.QIcon(self.iconActive.pixmap(128, 128)) self.iconDisabled = QtGui.QIcon(self.iconDisabled.pixmap(128, 128)) self.trayIcon.activated.connect(self.icon_activated) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.setIcon(self.iconDisabled) self.trayIcon.setToolTip("QHangups") self.trayIcon.show() def retranslateUi(self): """Retranslate GUI""" self.startAction.setText(self.tr("&Connect")) self.stopAction.setText(self.tr("&Disconnect")) self.settingsAction.setText(self.tr("S&ettings ...")) self.aboutAction.setText(self.tr("A&bout ...")) self.quitAction.setText(self.tr("&Quit")) def login(self, refresh_token_path): """Login to Google account""" try: cookies = hangups.auth.get_auth(self.get_credentials, refresh_token_path) return cookies except hangups.GoogleAuthError: QtWidgets.QMessageBox.warning(self, self.tr("QHangups - Warning"), self.tr("Google login failed!")) return False def get_credentials(self): """Ask user for OAuth 2 authorization code""" browser = QHangupsBrowser(hangups.auth.OAUTH2_LOGIN_URL, self).exec_() code, ok = QtWidgets.QInputDialog.getText(self, self.tr("QHangups - Authorization"), self.tr("Authorization code:"), QtWidgets.QLineEdit.Normal) if ok and code: return code else: return None def update_status(self): """Update GUI according to Hangups status""" if self.hangups_running: self.trayIcon.setIcon(self.iconActive) self.startAction.setEnabled(False) self.stopAction.setEnabled(True) else: self.trayIcon.setIcon(self.iconDisabled) self.startAction.setEnabled(True) self.stopAction.setEnabled(False) def hangups_start(self): """Connect to Hangouts""" cookies = self.login(self.refresh_token_path) if cookies: self.startHangups.emit() self.client = hangups.Client(cookies) self.client.on_connect.add_observer(self.on_connect) # Run Hangups event loop asyncio.async( self.client.connect() ).add_done_callback(lambda future: future.result()) self.hangups_running = True self.update_status() def hangups_stop(self): """Disconnect from Hangouts""" self.stopHangups.emit() asyncio.async( self.client.disconnect() ).add_done_callback(lambda future: future.result()) self.conv_list = None self.user_list = None self.notifier = None self.hangups_running = False self.client = None self.update_status() def about(self): """Show About dialog""" QtWidgets.QMessageBox.information(self, self.tr("About"), self.tr("QHangups {}".format(__version__))) def settings(self): """Show Settings dialog""" dialog = QHangupsSettings(self) if dialog.exec_(): self.set_language() if self.hangups_running: self.hangups_stop() self.hangups_start() def set_language(self): """Change language""" settings = QtCore.QSettings() language = settings.value("language") if not language: language = QtCore.QLocale.system().name().split("_")[0] lang_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "languages") lang_file = "qhangups_{}.qm".format(language) qt_lang_path = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath) qt_lang_file = "qt_{}.qm".format(language) if os.path.isfile(os.path.join(lang_path, lang_file)): translator.load(lang_file, lang_path) qt_translator.load(qt_lang_file, qt_lang_path) else: translator.load("") qt_translator.load("") def icon_activated(self, reason): """Connect or disconnect from Hangouts by double-click on tray icon""" if reason == QtWidgets.QSystemTrayIcon.Trigger or reason == QtWidgets.QSystemTrayIcon.DoubleClick: if self.icon_doubleclick_timer.isActive(): self.icon_doubleclick_timer.stop() if self.hangups_running: self.hangups_stop() else: self.hangups_start() else: self.icon_doubleclick_timer.start(QtWidgets.qApp.doubleClickInterval()) def icon_doubleclick_timeout(self): """Open or close list of conversations after single-click on tray icon""" if self.conversations_dialog: if self.conversations_dialog.isVisible() and not self.conversations_dialog.isMinimized(): self.conversations_dialog.hide() else: self.conversations_dialog.showNormal() self.conversations_dialog.raise_() self.conversations_dialog.activateWindow() def quit(self, force=False): """Quit QHangups""" if self.hangups_running: if not force: reply = QtWidgets.QMessageBox.question(self, self.tr("QHangups - Quit"), self.tr("You are still connected to Google Hangouts. " "Do you really want to quit QHangups?"), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply != QtWidgets.QMessageBox.Yes: return self.hangups_stop() loop = asyncio.get_event_loop() loop.stop() # QtWidgets.qApp.quit() def changeEvent(self, event): """Handle LanguageChange event""" if (event.type() == QtCore.QEvent.LanguageChange): print("Language changed") self.retranslateUi() super().changeEvent(event) def open_messages_dialog(self, conv_id, switch=True): """Open conversation in new tab""" self.messages_dialog.set_conv_tab(conv_id, switch=switch) self.messages_dialog.showNormal() if switch: self.messages_dialog.raise_() self.messages_dialog.activateWindow() @asyncio.coroutine def on_connect(self): """Handle connecting for the first time (callback)""" print('Connected') self.user_list, self.conv_list = ( yield from hangups.build_user_conversation_list(self.client) ) self.conv_list.on_event.add_observer(self.on_event) # Setup notifications self.notifier = Notifier(self.conv_list) # Setup conversations window self.messages_dialog.init_conversations(self.client, self.conv_list) # Setup conversations list window and show it self.conversations_dialog.init_conversations(self.client, self.conv_list) self.conversations_dialog.show() @asyncio.coroutine def on_event(self, conv_event): """Open conversation tab for new messages when they arrive (callback)""" if isinstance(conv_event, hangups.ChatMessageEvent): self.open_messages_dialog(conv_event.conversation_id, switch=False)