X-Git-Url: https://jfr.im/git/irc/weechat/qweechat.git/blobdiff_plain/77df9d06f7d44caed27e29222413548358842772..ae648fd7f6e16a0c591637c270ca6322f7117a8e:/qweechat/qweechat.py diff --git a/qweechat/qweechat.py b/qweechat/qweechat.py index d78c54d..090b76e 100644 --- a/qweechat/qweechat.py +++ b/qweechat/qweechat.py @@ -2,7 +2,7 @@ # # qweechat.py - WeeChat remote GUI using Qt toolkit # -# Copyright (C) 2011-2014 Sébastien Helleu +# Copyright (C) 2011-2021 Sébastien Helleu # # This file is part of QWeeChat, a Qt remote GUI for WeeChat. # @@ -36,43 +36,37 @@ It requires requires WeeChat 0.3.7 or newer, running on local or remote host. import sys import traceback from pkg_resources import resource_filename -import qt_compat -QTCORE = qt_compat.import_module('QtCore') -QTGUI = qt_compat.import_module('QtGui') -import config -import weechat.protocol as protocol -from network import Network -from connection import ConnectionDialog -from buffer import BufferListWidget, Buffer -from debug import DebugDialog -from about import AboutDialog - -NAME = 'QWeeChat' -VERSION = '0.0.1-dev' -AUTHOR = 'Sébastien Helleu' -AUTHOR_MAIL = 'flashcode@flashtux.org' -WEECHAT_SITE = 'http://weechat.org/' -# number of lines in buffer for debug window -DEBUG_NUM_LINES = 50 +from PySide6 import QtCore, QtGui, QtWidgets + +from qweechat import config +from qweechat.about import AboutDialog +from qweechat.buffer import BufferListWidget, Buffer +from qweechat.connection import ConnectionDialog +from qweechat.network import Network, STATUS_DISCONNECTED, NETWORK_STATUS +from qweechat.preferences import PreferencesDialog +from qweechat.weechat import protocol + + +APP_NAME = 'QWeeChat' +AUTHOR = 'Sébastien Helleu' +WEECHAT_SITE = 'https://weechat.org/' -class MainWindow(QTGUI.QMainWindow): +class MainWindow(QtWidgets.QMainWindow): """Main window.""" def __init__(self, *args): - QTGUI.QMainWindow.__init__(*(self,) + args) + super().__init__(*args) self.config = config.read() self.resize(1000, 600) - self.setWindowTitle(NAME) - - self.debug_dialog = None - self.debug_lines = [] + self.setWindowTitle(APP_NAME) self.about_dialog = None self.connection_dialog = None + self.preferences_dialog = None # network self.network = Network() @@ -85,11 +79,11 @@ class MainWindow(QTGUI.QMainWindow): # default buffer self.buffers = [Buffer()] - self.stacked_buffers = QTGUI.QStackedWidget() + self.stacked_buffers = QtWidgets.QStackedWidget() self.stacked_buffers.addWidget(self.buffers[0].widget) # splitter with buffers + chat/input - splitter = QTGUI.QSplitter() + splitter = QtWidgets.QSplitter() splitter.addWidget(self.list_buffers) splitter.addWidget(self.stacked_buffers) @@ -101,34 +95,55 @@ class MainWindow(QTGUI.QMainWindow): # actions for menu and toolbar actions_def = { 'connect': [ - 'network-connect.png', 'Connect to WeeChat', - 'Ctrl+O', self.open_connection_dialog], + 'network-connect.png', + 'Connect to WeeChat', + 'Ctrl+O', + self.open_connection_dialog, + ], 'disconnect': [ - 'network-disconnect.png', 'Disconnect from WeeChat', - 'Ctrl+D', self.network.disconnect_weechat], + 'network-disconnect.png', + 'Disconnect from WeeChat', + 'Ctrl+D', + self.network.disconnect_weechat, + ], 'debug': [ - 'edit-find.png', 'Debug console window', - 'Ctrl+B', self.open_debug_dialog], + 'edit-find.png', + 'Open debug console window', + 'Ctrl+B', + self.network.open_debug_dialog, + ], 'preferences': [ - 'preferences-other.png', 'Preferences', - 'Ctrl+P', self.open_preferences_dialog], + 'preferences-other.png', + 'Change preferences', + 'Ctrl+P', + self.open_preferences_dialog, + ], 'about': [ - 'help-about.png', 'About', - 'Ctrl+H', self.open_about_dialog], + 'help-about.png', + 'About QWeeChat', + 'Ctrl+H', + self.open_about_dialog, + ], 'save connection': [ - 'document-save.png', 'Save connection configuration', - 'Ctrl+S', self.save_connection], + 'document-save.png', + 'Save connection configuration', + 'Ctrl+S', + self.save_connection, + ], 'quit': [ - 'application-exit.png', 'Quit application', - 'Ctrl+Q', self.close], + 'application-exit.png', + 'Quit application', + 'Ctrl+Q', + self.close, + ], } self.actions = {} for name, action in list(actions_def.items()): - self.actions[name] = QTGUI.QAction( - QTGUI.QIcon( + self.actions[name] = QtGui.QAction( + QtGui.QIcon( resource_filename(__name__, 'data/icons/%s' % action[0])), name.capitalize(), self) - self.actions[name].setStatusTip(action[1]) + self.actions[name].setToolTip(f'{action[1]} ({action[2]})') self.actions[name].setShortcut(action[2]) self.actions[name].triggered.connect(action[3]) @@ -144,19 +159,19 @@ class MainWindow(QTGUI.QMainWindow): menu_window.addAction(self.actions['debug']) menu_help = self.menu.addMenu('&Help') menu_help.addAction(self.actions['about']) - self.network_status = QTGUI.QLabel() + self.network_status = QtWidgets.QLabel() self.network_status.setFixedHeight(20) self.network_status.setFixedWidth(200) self.network_status.setContentsMargins(0, 0, 10, 0) - self.network_status.setAlignment(QTCORE.Qt.AlignRight) + self.network_status.setAlignment(QtCore.Qt.AlignRight) if hasattr(self.menu, 'setCornerWidget'): self.menu.setCornerWidget(self.network_status, - QTCORE.Qt.TopRightCorner) - self.network_status_set(self.network.status_disconnected) + QtCore.Qt.TopRightCorner) + self.network_status_set(STATUS_DISCONNECTED) # toolbar toolbar = self.addToolBar('toolBar') - toolbar.setToolButtonStyle(QTCORE.Qt.ToolButtonTextUnderIcon) + toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) toolbar.addActions([self.actions['connect'], self.actions['disconnect'], self.actions['debug'], @@ -168,16 +183,18 @@ class MainWindow(QTGUI.QMainWindow): # open debug dialog if self.config.getboolean('look', 'debug'): - self.open_debug_dialog() + self.network.open_debug_dialog() # auto-connect to relay if self.config.getboolean('relay', 'autoconnect'): - self.network.connect_weechat(self.config.get('relay', 'server'), - self.config.get('relay', 'port'), - self.config.getboolean('relay', - 'ssl'), - self.config.get('relay', 'password'), - self.config.get('relay', 'lines')) + self.network.connect_weechat( + hostname=self.config.get('relay', 'hostname', fallback=''), + port=self.config.get('relay', 'port', fallback=''), + ssl=self.config.getboolean('relay', 'ssl', fallback=''), + password=self.config.get('relay', 'password', fallback=''), + totp=None, + lines=self.config.get('relay', 'lines', fallback=''), + ) self.show() @@ -192,71 +209,29 @@ class MainWindow(QTGUI.QMainWindow): if self.network.is_connected(): message = 'input %s %s\n' % (full_name, text) self.network.send_to_weechat(message) - self.debug_display(0, '<==', message, forcecolor='#AA0000') + self.network.debug_print(0, '<==', message, forcecolor='#AA0000') def open_preferences_dialog(self): """Open a dialog with preferences.""" - pass # TODO + # TODO: implement the preferences dialog box + self.preferences_dialog = PreferencesDialog(self) def save_connection(self): """Save connection configuration.""" if self.network: options = self.network.get_options() - for option in options.keys(): + for option in options: self.config.set('relay', option, options[option]) - def debug_display(self, *args, **kwargs): - """Display a debug message.""" - self.debug_lines.append((args, kwargs)) - self.debug_lines = self.debug_lines[-DEBUG_NUM_LINES:] - if self.debug_dialog: - self.debug_dialog.chat.display(*args, **kwargs) - - def open_debug_dialog(self): - """Open a dialog with debug messages.""" - if not self.debug_dialog: - self.debug_dialog = DebugDialog(self) - self.debug_dialog.input.textSent.connect( - self.debug_input_text_sent) - self.debug_dialog.finished.connect(self._debug_dialog_closed) - self.debug_dialog.display_lines(self.debug_lines) - self.debug_dialog.chat.scroll_bottom() - - def debug_input_text_sent(self, text): - """Send debug buffer input to WeeChat.""" - if self.network.is_connected(): - text = str(text) - pos = text.find(')') - if text.startswith('(') and pos >= 0: - text = '(debug_%s)%s' % (text[1:pos], text[pos+1:]) - else: - text = '(debug) %s' % text - self.debug_display(0, '<==', text, forcecolor='#AA0000') - self.network.send_to_weechat(text + '\n') - - def _debug_dialog_closed(self, result): - """Called when debug dialog is closed.""" - self.debug_dialog = None - def open_about_dialog(self): """Open a dialog with info about QWeeChat.""" - messages = ['%s %s' % (NAME, VERSION), - '© 2011-2014 %s <%s>' - % (AUTHOR, AUTHOR_MAIL, AUTHOR_MAIL), - '', - 'Running with %s' % ('PySide' if qt_compat.uses_pyside - else 'PyQt4'), - '', - 'WeeChat site: %s' - % (WEECHAT_SITE, WEECHAT_SITE), - ''] - self.about_dialog = AboutDialog(NAME, messages, self) + self.about_dialog = AboutDialog(APP_NAME, AUTHOR, WEECHAT_SITE, self) def open_connection_dialog(self): """Open a dialog with connection settings.""" values = {} - for option in ('server', 'port', 'ssl', 'password', 'lines'): - values[option] = self.config.get('relay', option) + for option in ('hostname', 'port', 'ssl', 'password', 'lines'): + values[option] = self.config.get('relay', option, fallback='') self.connection_dialog = ConnectionDialog(values, self) self.connection_dialog.dialog_buttons.accepted.connect( self.connect_weechat) @@ -264,30 +239,28 @@ class MainWindow(QTGUI.QMainWindow): def connect_weechat(self): """Connect to WeeChat.""" self.network.connect_weechat( - self.connection_dialog.fields['server'].text(), - self.connection_dialog.fields['port'].text(), - self.connection_dialog.fields['ssl'].isChecked(), - self.connection_dialog.fields['password'].text(), - int(self.connection_dialog.fields['lines'].text())) + hostname=self.connection_dialog.fields['hostname'].text(), + port=self.connection_dialog.fields['port'].text(), + ssl=self.connection_dialog.fields['ssl'].isChecked(), + password=self.connection_dialog.fields['password'].text(), + totp=self.connection_dialog.fields['totp'].text(), + lines=int(self.connection_dialog.fields['lines'].text()), + ) self.connection_dialog.close() def _network_status_changed(self, status, extra): """Called when the network status has changed.""" if self.config.getboolean('look', 'statusbar'): self.statusBar().showMessage(status) - self.debug_display(0, '', status, forcecolor='#0000AA') + self.network.debug_print(0, '', status, forcecolor='#0000AA') self.network_status_set(status) def network_status_set(self, status): """Set the network status.""" pal = self.network_status.palette() - if status == self.network.status_connected: - pal.setColor(self.network_status.foregroundRole(), - QTGUI.QColor('green')) - else: - pal.setColor(self.network_status.foregroundRole(), - QTGUI.QColor('#aa0000')) - ssl = ' (SSL)' if status != self.network.status_disconnected \ + pal.setColor(self.network_status.foregroundRole(), + self.network.status_color(status)) + ssl = ' (SSL)' if status != STATUS_DISCONNECTED \ and self.network.is_ssl() else '' self.network_status.setPalette(pal) icon = self.network.status_icon(status) @@ -295,10 +268,10 @@ class MainWindow(QTGUI.QMainWindow): self.network_status.setText( ' %s' % (resource_filename(__name__, 'data/icons/%s' % icon), - status.capitalize() + ssl)) + self.network.status_label(status) + ssl)) else: self.network_status.setText(status.capitalize()) - if status == self.network.status_disconnected: + if status == STATUS_DISCONNECTED: self.actions['connect'].setEnabled(True) self.actions['disconnect'].setEnabled(False) else: @@ -307,30 +280,40 @@ class MainWindow(QTGUI.QMainWindow): def _network_weechat_msg(self, message): """Called when a message is received from WeeChat.""" - self.debug_display(0, '==>', - 'message (%d bytes):\n%s' - % (len(message), - protocol.hex_and_ascii(message, 20)), - forcecolor='#008800') + self.network.debug_print( + 0, '==>', + 'message (%d bytes):\n%s' + % (len(message), + protocol.hex_and_ascii(message.data(), 20)), + forcecolor='#008800', + ) try: proto = protocol.Protocol() - message = proto.decode(str(message)) + message = proto.decode(message.data()) if message.uncompressed: - self.debug_display( + self.network.debug_print( 0, '==>', 'message uncompressed (%d bytes):\n%s' % (message.size_uncompressed, protocol.hex_and_ascii(message.uncompressed, 20)), forcecolor='#008800') - self.debug_display(0, '', 'Message: %s' % message) + self.network.debug_print(0, '', 'Message: %s' % message) self.parse_message(message) - except: + except Exception: # noqa: E722 print('Error while decoding message from WeeChat:\n%s' % traceback.format_exc()) self.network.disconnect_weechat() + def _parse_handshake(self, message): + """Parse a WeeChat message with handshake response.""" + for obj in message.objects: + if obj.objtype != 'htb': + continue + self.network.init_with_handshake(obj.value) + break + def _parse_listbuffers(self, message): - """Parse a WeeChat with list of buffers.""" + """Parse a WeeChat message with list of buffers.""" for obj in message.objects: if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer': continue @@ -359,12 +342,15 @@ class MainWindow(QTGUI.QMainWindow): index = [i for i, b in enumerate(self.buffers) if b.pointer() == ptrbuf] if index: - lines.append((item['date'], item['prefix'], - item['message'])) + lines.append( + (index[0], + (item['date'], item['prefix'], + item['message'])) + ) if message.msgid == 'listlines': lines.reverse() for line in lines: - self.buffers[index[0]].widget.chat.display(*line) + self.buffers[line[0]].widget.chat.display(*line[1]) def _parse_nicklist(self, message): """Parse a WeeChat message with a buffer nicklist.""" @@ -458,6 +444,8 @@ class MainWindow(QTGUI.QMainWindow): elif message.msgid == '_buffer_title_changed': self.buffers[index].data['title'] = item['title'] self.buffers[index].update_title() + elif message.msgid == '_buffer_cleared': + self.buffers[index].widget.chat.clear() elif message.msgid.startswith('_buffer_localvar_'): self.buffers[index].data['local_variables'] = \ item['local_variables'] @@ -468,7 +456,9 @@ class MainWindow(QTGUI.QMainWindow): def parse_message(self, message): """Parse a WeeChat message.""" if message.msgid.startswith('debug'): - self.debug_display(0, '', '(debug message, ignored)') + self.network.debug_print(0, '', '(debug message, ignored)') + elif message.msgid == 'handshake': + self._parse_handshake(message) elif message.msgid == 'listbuffers': self._parse_listbuffers(message) elif message.msgid in ('listlines', '_buffer_line_added'): @@ -485,6 +475,8 @@ class MainWindow(QTGUI.QMainWindow): self.network.desync_weechat() elif message.msgid == '_upgrade_ended': self.network.sync_weechat() + else: + print(f"Unknown message with id {message.msgid}") def create_buffer(self, item): """Create a new buffer.""" @@ -499,9 +491,8 @@ class MainWindow(QTGUI.QMainWindow): def insert_buffer(self, index, buf): """Insert a buffer in list.""" self.buffers.insert(index, buf) - self.list_buffers.insertItem(index, '%d. %s' - % (buf.data['number'], - buf.data['full_name'].decode('utf-8'))) + self.list_buffers.insertItem(index, '%s' + % (buf.data['local_variables']['name'])) self.stacked_buffers.insertWidget(index, buf.widget) def remove_buffer(self, index): @@ -531,15 +522,21 @@ class MainWindow(QTGUI.QMainWindow): def closeEvent(self, event): """Called when QWeeChat window is closed.""" self.network.disconnect_weechat() - if self.debug_dialog: - self.debug_dialog.close() + if self.network.debug_dialog: + self.network.debug_dialog.close() config.write(self.config) - QTGUI.QMainWindow.closeEvent(self, event) + QtWidgets.QMainWindow.closeEvent(self, event) + + +def main(): + app = QtWidgets.QApplication(sys.argv) + app.setStyle(QtWidgets.QStyleFactory.create('Cleanlooks')) + app.setWindowIcon(QtGui.QIcon( + resource_filename(__name__, 'data/icons/weechat.png'))) + main_win = MainWindow() + main_win.show() + sys.exit(app.exec_()) -app = QTGUI.QApplication(sys.argv) -app.setStyle(QTGUI.QStyleFactory.create('Cleanlooks')) -app.setWindowIcon(QTGUI.QIcon( - resource_filename(__name__, 'data/icons/weechat_icon_32.png'))) -main = MainWindow() -sys.exit(app.exec_()) +if __name__ == '__main__': + main()