]> jfr.im git - irc/weechat/qweechat.git/commitdiff
Migration to Qt6 using PySide6 and Python 3.
authorAbhilash Raj <redacted>
Tue, 1 Jun 2021 04:02:25 +0000 (21:02 -0700)
committerAbhilash Raj <redacted>
Tue, 1 Jun 2021 04:02:25 +0000 (21:02 -0700)
This is a bulk migraton of the codebase to work with Python3 and latest version
of Qt framework i.e. Qt6.

13 files changed:
qweechat/about.py
qweechat/buffer.py
qweechat/chat.py
qweechat/config.py
qweechat/connection.py
qweechat/debug.py
qweechat/input.py
qweechat/network.py
qweechat/qt_compat.py [deleted file]
qweechat/qweechat.py
qweechat/weechat/protocol.py
qweechat/weechat/testproto.py
qweechat/weechat/testproto.py.bak [new file with mode: 0644]

index b5c397eef31c55e1a38ce668fa9c9b2c8e739828..88dd0275cf1c1951b0c91d7c07691f973f1c4f13 100644 (file)
 # along with QWeeChat.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-import qt_compat
-
-QtCore = qt_compat.import_module('QtCore')
-QtGui = qt_compat.import_module('QtGui')
+from PySide6 import QtCore
+from PySide6 import QtWidgets as QtGui
 
 
 class AboutDialog(QtGui.QDialog):
@@ -44,7 +42,7 @@ class AboutDialog(QtGui.QDialog):
 
         vbox = QtGui.QVBoxLayout()
         for msg in messages:
-            label = QtGui.QLabel(msg.decode('utf-8'))
+            label = QtGui.QLabel(msg)
             label.setAlignment(QtCore.Qt.AlignHCenter)
             vbox.addWidget(label)
         vbox.addLayout(hbox)
index 81aa4b7c6ee448a534f1017cd392163ab4f25961..e581f8b594b8eb36a39f70625c610acdc952d2cd 100644 (file)
 #
 
 from pkg_resources import resource_filename
-import qt_compat
 from chat import ChatTextEdit
 from input import InputLineEdit
 import weechat.color as color
 
-QtCore = qt_compat.import_module('QtCore')
-QtGui = qt_compat.import_module('QtGui')
+from PySide6 import QtCore
+from PySide6 import QtWidgets
+from PySide6 import QtGui
 
 
-class GenericListWidget(QtGui.QListWidget):
+class GenericListWidget(QtWidgets.QListWidget):
     """Generic QListWidget with dynamic size."""
 
     def __init__(self, *args):
-        QtGui.QListWidget.__init__(*(self,) + args)
+        super().__init__(*args)
         self.setMaximumWidth(100)
         self.setTextElideMode(QtCore.Qt.ElideNone)
         self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
@@ -52,17 +52,17 @@ class GenericListWidget(QtGui.QListWidget):
 
     def clear(self, *args):
         """Re-implement clear to set dynamic size after clear."""
-        QtGui.QListWidget.clear(*(self,) + args)
+        QtWidgets.QListWidget.clear(*(self,) + args)
         self.auto_resize()
 
     def addItem(self, *args):
         """Re-implement addItem to set dynamic size after add."""
-        QtGui.QListWidget.addItem(*(self,) + args)
+        QtWidgets.QListWidget.addItem(*(self,) + args)
         self.auto_resize()
 
     def insertItem(self, *args):
         """Re-implement insertItem to set dynamic size after insert."""
-        QtGui.QListWidget.insertItem(*(self,) + args)
+        QtWidgets.QListWidget.insertItem(*(self,) + args)
         self.auto_resize()
 
 
@@ -70,7 +70,7 @@ class BufferListWidget(GenericListWidget):
     """Widget with list of buffers."""
 
     def __init__(self, *args):
-        GenericListWidget.__init__(*(self,) + args)
+        super().__init__(*args)
 
     def switch_prev_buffer(self):
         if self.currentRow() > 0:
@@ -85,23 +85,23 @@ class BufferListWidget(GenericListWidget):
             self.setCurrentRow(0)
 
 
-class BufferWidget(QtGui.QWidget):
+class BufferWidget(QtWidgets.QWidget):
     """
     Widget with (from top to bottom):
     title, chat + nicklist (optional) + prompt/input.
     """
 
     def __init__(self, display_nicklist=False):
-        QtGui.QWidget.__init__(self)
+        super().__init__()
 
         # title
-        self.title = QtGui.QLineEdit()
+        self.title = QtWidgets.QLineEdit()
         self.title.setFocusPolicy(QtCore.Qt.NoFocus)
 
         # splitter with chat + nicklist
-        self.chat_nicklist = QtGui.QSplitter()
-        self.chat_nicklist.setSizePolicy(QtGui.QSizePolicy.Expanding,
-                                         QtGui.QSizePolicy.Expanding)
+        self.chat_nicklist = QtWidgets.QSplitter()
+        self.chat_nicklist.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
+                                         QtWidgets.QSizePolicy.Expanding)
         self.chat = ChatTextEdit(debug=False)
         self.chat_nicklist.addWidget(self.chat)
         self.nicklist = GenericListWidget()
@@ -110,16 +110,16 @@ class BufferWidget(QtGui.QWidget):
         self.chat_nicklist.addWidget(self.nicklist)
 
         # prompt + input
-        self.hbox_edit = QtGui.QHBoxLayout()
+        self.hbox_edit = QtWidgets.QHBoxLayout()
         self.hbox_edit.setContentsMargins(0, 0, 0, 0)
         self.hbox_edit.setSpacing(0)
         self.input = InputLineEdit(self.chat)
         self.hbox_edit.addWidget(self.input)
-        prompt_input = QtGui.QWidget()
+        prompt_input = QtWidgets.QWidget()
         prompt_input.setLayout(self.hbox_edit)
 
         # vbox with title + chat/nicklist + prompt/input
-        vbox = QtGui.QVBoxLayout()
+        vbox = QtWidgets.QVBoxLayout()
         vbox.setContentsMargins(0, 0, 0, 0)
         vbox.setSpacing(0)
         vbox.addWidget(self.title)
@@ -139,7 +139,7 @@ class BufferWidget(QtGui.QWidget):
         if self.hbox_edit.count() > 1:
             self.hbox_edit.takeAt(0)
         if prompt is not None:
-            label = QtGui.QLabel(prompt)
+            label = QtWidgets.QLabel(prompt)
             label.setContentsMargins(0, 0, 5, 0)
             self.hbox_edit.insertWidget(0, label)
 
@@ -147,7 +147,7 @@ class BufferWidget(QtGui.QWidget):
 class Buffer(QtCore.QObject):
     """A WeeChat buffer."""
 
-    bufferInput = qt_compat.Signal(str, str)
+    bufferInput = QtCore.Signal(str, str)
 
     def __init__(self, data={}):
         QtCore.QObject.__init__(self)
@@ -243,6 +243,6 @@ class Buffer(QtCore.QObject):
                     pixmap = QtGui.QPixmap(8, 8)
                     pixmap.fill()
                     icon = QtGui.QIcon(pixmap)
-                item = QtGui.QListWidgetItem(icon, nick['name'])
+                item = QtWidgets.QListWidgetItem(icon, nick['name'])
                 self.widget.nicklist.addItem(item)
                 self.widget.nicklist.setVisible(True)
index b8cf8d58d856b1078a01fbf9aad28d4bead21e46..bd339ac4a4fe2c5dde7c8fbf47cf6708ee6f0385 100644 (file)
 #
 
 import datetime
-import qt_compat
 import config
 import weechat.color as color
 
-QtCore = qt_compat.import_module('QtCore')
-QtGui = qt_compat.import_module('QtGui')
+from PySide6 import QtCore
+from PySide6 import QtWidgets, QtGui
 
 
-class ChatTextEdit(QtGui.QTextEdit):
+class ChatTextEdit(QtWidgets.QTextEdit):
     """Chat area."""
 
     def __init__(self, debug, *args):
-        QtGui.QTextEdit.__init__(*(self,) + args)
+        QtWidgets.QTextEdit.__init__(*(self,) + args)
         self.debug = debug
         self.readOnly = True
         self.setFocusPolicy(QtCore.Qt.NoFocus)
@@ -77,9 +76,9 @@ class ChatTextEdit(QtGui.QTextEdit):
                 prefix = '\x01(F%s)%s' % (forcecolor, prefix)
             text = '\x01(F%s)%s' % (forcecolor, text)
         if prefix:
-            self._display_with_colors(str(prefix).decode('utf-8') + ' ')
+            self._display_with_colors(prefix + ' ')
         if text:
-            self._display_with_colors(str(text).decode('utf-8'))
+            self._display_with_colors(text)
             if text[-1:] != '\n':
                 self.insertPlainText('\n')
         else:
index 1613860903ba0a0a935286a7f316707ef5206a42..8fcc90199eb8ec38d65b940fe5065c0812ca407c 100644 (file)
@@ -20,7 +20,7 @@
 # along with QWeeChat.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-import ConfigParser
+import configparser
 import os
 
 CONFIG_DIR = '%s/.qweechat' % os.getenv('HOME')
@@ -91,7 +91,7 @@ config_color_options = []
 def read():
     """Read config file."""
     global config_color_options
-    config = ConfigParser.RawConfigParser()
+    config = configparser.RawConfigParser()
     if os.path.isfile(CONFIG_FILENAME):
         config.read(CONFIG_FILENAME)
 
@@ -123,7 +123,7 @@ def write(config):
     """Write config file."""
     if not os.path.exists(CONFIG_DIR):
         os.mkdir(CONFIG_DIR, 0o0755)
-    with open(CONFIG_FILENAME, 'wb') as cfg:
+    with open(CONFIG_FILENAME, 'w') as cfg:
         config.write(cfg)
 
 
index 8a0ee7159b4f66ac019c3e72eb7204c6a62fa01e..4529ae52e1eb391c41672058ecd4dff49b794f4f 100644 (file)
 # along with QWeeChat.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-import qt_compat
+import qt_compat
 
-QtGui = qt_compat.import_module('QtGui')
+# QtGui = qt_compat.import_module('QtGui')
+from PySide6 import QtGui, QtWidgets
 
 
-class ConnectionDialog(QtGui.QDialog):
+class ConnectionDialog(QtWidgets.QDialog):
     """Connection window."""
 
     def __init__(self, values, *args):
-        QtGui.QDialog.__init__(*(self,) + args)
+        super().__init__(*args)
         self.values = values
         self.setModal(True)
 
-        grid = QtGui.QGridLayout()
+        grid = QtWidgets.QGridLayout()
         grid.setSpacing(10)
 
         self.fields = {}
         for line, field in enumerate(('server', 'port', 'password', 'lines')):
-            grid.addWidget(QtGui.QLabel(field.capitalize()), line, 0)
-            line_edit = QtGui.QLineEdit()
+            grid.addWidget(QtWidgets.QLabel(field.capitalize()), line, 0)
+            line_edit = QtWidgets.QLineEdit()
             line_edit.setFixedWidth(200)
             if field == 'password':
-                line_edit.setEchoMode(QtGui.QLineEdit.Password)
+                line_edit.setEchoMode(QtWidgets.QLineEdit.Password)
             if field == 'lines':
                 validator = QtGui.QIntValidator(0, 2147483647, self)
                 line_edit.setValidator(validator)
@@ -51,14 +52,14 @@ class ConnectionDialog(QtGui.QDialog):
             grid.addWidget(line_edit, line, 1)
             self.fields[field] = line_edit
             if field == 'port':
-                ssl = QtGui.QCheckBox('SSL')
+                ssl = QtWidgets.QCheckBox('SSL')
                 ssl.setChecked(self.values['ssl'] == 'on')
                 grid.addWidget(ssl, line, 2)
                 self.fields['ssl'] = ssl
 
-        self.dialog_buttons = QtGui.QDialogButtonBox()
+        self.dialog_buttons = QtWidgets.QDialogButtonBox()
         self.dialog_buttons.setStandardButtons(
-            QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
+            QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
         self.dialog_buttons.rejected.connect(self.close)
 
         grid.addWidget(self.dialog_buttons, 4, 0, 1, 2)
index 3dd37d5e57479930249785f065aa36244e299336..3261bebb62f664a3b0e668b195ab6a1968e10a71 100644 (file)
 # along with QWeeChat.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-import qt_compat
 from chat import ChatTextEdit
 from input import InputLineEdit
 
-QtGui = qt_compat.import_module('QtGui')
+from PySide6 import QtWidgets as QtGui
 
 
 class DebugDialog(QtGui.QDialog):
index 5bde92284100447cefba1103bd02a0f3ce8fbd6c..7c85caf47583d01ba7e3e567b650edea1c30073e 100644 (file)
 # along with QWeeChat.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-import qt_compat
-
-QtCore = qt_compat.import_module('QtCore')
-QtGui = qt_compat.import_module('QtGui')
+from PySide6 import QtCore
+from PySide6 import QtWidgets as QtGui
 
 
 class InputLineEdit(QtGui.QLineEdit):
     """Input line."""
 
-    bufferSwitchPrev = qt_compat.Signal()
-    bufferSwitchNext = qt_compat.Signal()
-    textSent = qt_compat.Signal(str)
+    bufferSwitchPrev = QtCore.Signal()
+    bufferSwitchNext = QtCore.Signal()
+    textSent = QtCore.Signal(str)
 
     def __init__(self, scroll_widget):
-        QtGui.QLineEdit.__init__(self)
+        super().__init__(scroll_widget)
         self.scroll_widget = scroll_widget
         self._history = []
         self._history_index = -1
index 8c497ea9921287f4f499653393af83c17dcbcc14..362bb36a1fc9646b58b9ada2b2d19891226800e0 100644 (file)
 #
 
 import struct
-import qt_compat
 import config
+from PySide6 import QtCore, QtNetwork
+from PySide6.QtCore import QObject, Signal
 
-QtCore = qt_compat.import_module('QtCore')
-QtNetwork = qt_compat.import_module('QtNetwork')
+QtCore = qt_compat.import_module('QtCore')
+QtNetwork = qt_compat.import_module('QtNetwork')
 
 _PROTO_INIT_CMD = ['init password=%(password)s']
 
@@ -47,11 +48,11 @@ _PROTO_SYNC_CMDS = [
 class Network(QtCore.QObject):
     """I/O with WeeChat/relay."""
 
-    statusChanged = qt_compat.Signal(str, str)
-    messageFromWeechat = qt_compat.Signal(QtCore.QByteArray)
+    statusChanged = Signal(str, str)
+    messageFromWeechat = Signal(QtCore.QByteArray)
 
     def __init__(self, *args):
-        QtCore.QObject.__init__(*(self,) + args)
+        super().__init__(*args)
         self.status_disconnected = 'disconnected'
         self.status_connecting = 'connecting...'
         self.status_connected = 'connected'
@@ -63,13 +64,14 @@ class Network(QtCore.QObject):
         self._buffer = QtCore.QByteArray()
         self._socket = QtNetwork.QSslSocket()
         self._socket.connected.connect(self._socket_connected)
-        self._socket.error.connect(self._socket_error)
+        self._socket.error.connect(self._socket_error)
         self._socket.readyRead.connect(self._socket_read)
         self._socket.disconnected.connect(self._socket_disconnected)
 
     def _socket_connected(self):
         """Slot: socket connected."""
         self.statusChanged.emit(self.status_connected, None)
+        print('Connected, now sending password.')
         if self._password:
             self.send_to_weechat('\n'.join(_PROTO_INIT_CMD + _PROTO_SYNC_CMDS)
                                  % {'password': str(self._password),
@@ -87,7 +89,7 @@ class Network(QtCore.QObject):
         self._buffer.append(data)
         while len(self._buffer) >= 4:
             remainder = None
-            length = struct.unpack('>i', self._buffer[0:4])[0]
+            length = struct.unpack('>i', self._buffer[0:4].data())[0]
             if len(self._buffer) < length:
                 # partial message, just wait for end of message
                 break
@@ -108,7 +110,7 @@ class Network(QtCore.QObject):
         self._server = None
         self._port = None
         self._ssl = None
-        self._password = None
+        self._password = ""
         self.statusChanged.emit(self.status_disconnected, None)
 
     def is_connected(self):
@@ -122,6 +124,7 @@ class Network(QtCore.QObject):
     def connect_weechat(self, server, port, ssl, password, lines):
         """Connect to WeeChat."""
         self._server = server
+        print(f'Connecting to server {self._server}')
         try:
             self._port = int(port)
         except ValueError:
@@ -136,11 +139,13 @@ class Network(QtCore.QObject):
             return
         if self._socket.state() != QtNetwork.QAbstractSocket.UnconnectedState:
             self._socket.abort()
-        self._socket.connectToHost(self._server, self._port)
         if self._ssl:
             self._socket.ignoreSslErrors()
-            self._socket.startClientEncryption()
-        self.statusChanged.emit(self.status_connecting, None)
+            self._socket.connectToHostEncrypted(self._server, self._port)
+        else:
+            self._socket.connectToHost(self._server, self._port)
+        print('Got SSL connection')
+        self.statusChanged.emit(self.status_connecting, "")
 
     def disconnect_weechat(self):
         """Disconnect from WeeChat."""
diff --git a/qweechat/qt_compat.py b/qweechat/qt_compat.py
deleted file mode 100644 (file)
index 8940288..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# File downloaded from:
-#   https://github.com/epage/PythonUtils/blob/master/util/qt_compat.py
-# Author: epage
-# License: LGPL 2.1
-#
-
-from __future__ import with_statement
-from __future__ import division
-
-_TRY_PYSIDE = True
-uses_pyside = False
-
-try:
-    if not _TRY_PYSIDE:
-        raise ImportError()
-    import PySide.QtCore as _QtCore
-    QtCore = _QtCore
-    uses_pyside = True
-except ImportError:
-    import sip
-    sip.setapi('QString', 2)
-    sip.setapi('QVariant', 2)
-    import PyQt4.QtCore as _QtCore
-    QtCore = _QtCore
-    uses_pyside = False
-
-
-def _pyside_import_module(moduleName):
-    pyside = __import__('PySide', globals(), locals(), [moduleName], -1)
-    return getattr(pyside, moduleName)
-
-
-def _pyqt4_import_module(moduleName):
-    pyside = __import__('PyQt4', globals(), locals(), [moduleName], -1)
-    return getattr(pyside, moduleName)
-
-
-if uses_pyside:
-    import_module = _pyside_import_module
-
-    Signal = QtCore.Signal
-    Slot = QtCore.Slot
-    Property = QtCore.Property
-else:
-    import_module = _pyqt4_import_module
-
-    Signal = QtCore.pyqtSignal
-    Slot = QtCore.pyqtSlot
-    Property = QtCore.pyqtProperty
-
-
-if __name__ == "__main__":
-    pass
index 49e6b91ab1972c3987ee120f95fdbe02e027d87f..33e210abcab4712e6beb3a137083867cd856ca3b 100644 (file)
@@ -36,7 +36,7 @@ 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
+import qt_compat
 import config
 import weechat.protocol as protocol
 from network import Network
@@ -46,8 +46,14 @@ from debug import DebugDialog
 from about import AboutDialog
 from version import qweechat_version
 
-QtCore = qt_compat.import_module('QtCore')
-QtGui = qt_compat.import_module('QtGui')
+from PySide6.QtWidgets import (
+    QApplication, QLabel, QPushButton, QVBoxLayout, QWidget)
+from PySide6.QtCore import Qt, Slot
+from PySide6 import QtGui, QtWidgets, QtCore
+
+
+# QtCore = qt_compat.import_module('QtCore')
+# QtGui = qt_compat.import_module('QtGui')
 
 NAME = 'QWeeChat'
 AUTHOR = 'Sébastien Helleu'
@@ -58,11 +64,11 @@ WEECHAT_SITE = 'https://weechat.org/'
 DEBUG_NUM_LINES = 50
 
 
-class MainWindow(QtGui.QMainWindow):
+class MainWindow(QtWidgets.QMainWindow):
     """Main window."""
 
     def __init__(self, *args):
-        QtGui.QMainWindow.__init__(*(self,) + args)
+        super().__init__()
 
         self.config = config.read()
 
@@ -87,11 +93,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)
 
@@ -146,7 +152,7 @@ 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)
@@ -312,24 +318,24 @@ 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.debug_display(0, '==>',
+                           'message (%d bytes):\n%s'
+                           % (len(message),
+                              protocol.hex_and_ascii(message, 20)),
+                           forcecolor='#008800')
         try:
             proto = protocol.Protocol()
-            message = proto.decode(str(message))
+            message = proto.decode(message.data())
             if message.uncompressed:
                 self.debug_display(
                     0, '==>',
                     'message uncompressed (%d bytes):\n%s'
                     % (message.size_uncompressed,
                        protocol.hex_and_ascii(message.uncompressed, 20)),
-                    forcecolor='#008800')
+                                forcecolor='#008800')
             self.debug_display(0, '', 'Message: %s' % message)
             self.parse_message(message)
-        except:  # noqa: E722
+        except Exception:  # noqa: E722
             print('Error while decoding message from WeeChat:\n%s'
                   % traceback.format_exc())
             self.network.disconnect_weechat()
@@ -339,6 +345,7 @@ class MainWindow(QtGui.QMainWindow):
         for obj in message.objects:
             if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer':
                 continue
+            print('listbuffers object', obj.objtype, obj.value['path'])
             self.list_buffers.clear()
             while self.stacked_buffers.count() > 0:
                 buf = self.stacked_buffers.widget(0)
@@ -346,6 +353,7 @@ class MainWindow(QtGui.QMainWindow):
             self.buffers = []
             for item in obj.value['items']:
                 buf = self.create_buffer(item)
+                print(f'Creating buffer for {item}')
                 self.insert_buffer(len(self.buffers), buf)
             self.list_buffers.setCurrentRow(0)
             self.buffers[0].widget.input.setFocus()
@@ -477,6 +485,7 @@ class MainWindow(QtGui.QMainWindow):
 
     def parse_message(self, message):
         """Parse a WeeChat message."""
+        print(f'message.msgid = {message.msgid}')
         if message.msgid.startswith('debug'):
             self.debug_display(0, '', '(debug message, ignored)')
         elif message.msgid == 'listbuffers':
@@ -495,6 +504,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."""
@@ -511,7 +522,7 @@ class MainWindow(QtGui.QMainWindow):
         self.buffers.insert(index, buf)
         self.list_buffers.insertItem(index, '%d. %s'
                                      % (buf.data['number'],
-                                        buf.data['full_name'].decode('utf-8')))
+                                        buf.data['full_name']))
         self.stacked_buffers.insertWidget(index, buf.widget)
 
     def remove_buffer(self, index):
@@ -544,12 +555,13 @@ class MainWindow(QtGui.QMainWindow):
         if self.debug_dialog:
             self.debug_dialog.close()
         config.write(self.config)
-        QtGui.QMainWindow.closeEvent(self, event)
+        QtWidgets.QMainWindow.closeEvent(self, event)
 
 
-app = QtGui.QApplication(sys.argv)
-app.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
+app = QApplication(sys.argv)
+app.setStyle(QtWidgets.QStyleFactory.create('Cleanlooks'))
 app.setWindowIcon(QtGui.QIcon(
-    resource_filename(__name__, 'data/icons/weechat.png')))
+     resource_filename(__name__, 'data/icons/weechat.png')))
 main = MainWindow()
+main.show()
 sys.exit(app.exec_())
index 79b24c1dae23e8a747c739f2528ed6d5e152f0ba..6283b2b0a743d31a22f6fd3fc6e644e08faefbcc 100644 (file)
@@ -34,15 +34,10 @@ import collections
 import struct
 import zlib
 
-if hasattr(collections, 'OrderedDict'):
-    # python >= 2.7
-    class WeechatDict(collections.OrderedDict):
-        def __str__(self):
-            return '{%s}' % ', '.join(
-                ['%s: %s' % (repr(key), repr(self[key])) for key in self])
-else:
-    # python <= 2.6
-    WeechatDict = dict
+class WeechatDict(collections.OrderedDict):
+    def __str__(self):
+        return '{%s}' % ', '.join(
+            ['%s: %s' % (repr(key), repr(self[key])) for key in self])
 
 
 class WeechatObject:
@@ -151,7 +146,7 @@ class Protocol:
         if len(self.data) < 3:
             self.data = ''
             return ''
-        objtype = str(self.data[0:3])
+        objtype = bytes(self.data[0:3])
         self.data = self.data[3:]
         return objtype
 
@@ -196,14 +191,17 @@ class Protocol:
         value = self._obj_len_data(1)
         if value is None:
             return None
-        return int(str(value))
+        return int(value)
 
     def _obj_str(self):
         """Read a string in data (length on 4 bytes + content)."""
         value = self._obj_len_data(4)
         if value is None:
             return None
-        return str(value)
+        try:
+            return value.decode()
+        except AttributeError:
+            return value
 
     def _obj_buffer(self):
         """Read a buffer in data (length on 4 bytes + data)."""
@@ -214,22 +212,22 @@ class Protocol:
         value = self._obj_len_data(1)
         if value is None:
             return None
-        return '0x%s' % str(value)
+        return '0x%s' % value
 
     def _obj_time(self):
         """Read a time in data (length on 1 byte + value as string)."""
         value = self._obj_len_data(1)
         if value is None:
             return None
-        return int(str(value))
+        return int(value)
 
     def _obj_hashtable(self):
         """
         Read a hashtable in data
         (type for keys + type for values + count + items).
         """
-        type_keys = self._obj_type()
-        type_values = self._obj_type()
+        type_keys = self._obj_type().decode()
+        type_values = self._obj_type().decode()
         count = self._obj_int()
         hashtable = WeechatDict()
         for _ in range(count):
@@ -248,7 +246,7 @@ class Protocol:
         keys_types = []
         dict_keys = WeechatDict()
         for key in list_keys:
-            items = key.split(':')
+            items = list(item for item in key.split(':'))
             keys_types.append(items)
             dict_keys[items[0]] = items[1]
         items = []
@@ -259,6 +257,7 @@ class Protocol:
             for _ in enumerate(list_path):
                 pointers.append(self._obj_ptr())
             for key, objtype in keys_types:
+                objtype = objtype
                 item[key] = self._obj_cb[objtype]()
             item['__path'] = pointers
             items.append(item)
@@ -296,7 +295,7 @@ class Protocol:
 
     def _obj_array(self):
         """Read an array of values in data."""
-        type_values = self._obj_type()
+        type_values = self._obj_type().decode()
         count_values = self._obj_int()
         values = []
         for _ in range(count_values):
@@ -314,7 +313,7 @@ class Protocol:
         if compression:
             uncompressed = zlib.decompress(self.data[5:])
             size_uncompressed = len(uncompressed) + 5
-            uncompressed = '%s%s%s' % (struct.pack('>i', size_uncompressed),
+            uncompressed = b'%s%s%s' % (struct.pack('>i', size_uncompressed),
                                        struct.pack('b', 0), uncompressed)
             self.data = uncompressed
         else:
@@ -328,7 +327,7 @@ class Protocol:
         # read objects
         objects = WeechatObjects(separator=separator)
         while len(self.data) > 0:
-            objtype = self._obj_type()
+            objtype = self._obj_type().decode()
             value = self._obj_cb[objtype]()
             objects.append(WeechatObject(objtype, value, separator=separator))
         return WeechatMessage(size, size_uncompressed, compression,
@@ -344,13 +343,20 @@ def hex_and_ascii(data, bytes_per_line=10):
     for i in range(num_lines):
         str_hex = []
         str_ascii = []
-        for char in data[i*bytes_per_line:(i*bytes_per_line)+bytes_per_line]:
+        for j in range(bytes_per_line): # data[i*bytes_per_line:(i*bytes_per_line)+bytes_per_line]:
+            # We can't easily iterate over individual bytes, so we are going to
+            # do it this way.
+            index = (i*bytes_per_line) + j
+            char = data[index:index+1]
+            if not char:
+                char = b'x'
             byte = struct.unpack('B', char)[0]
-            str_hex.append('%02X' % int(byte))
+            str_hex.append(b'%02X' % int(byte))
             if byte >= 32 and byte <= 127:
                 str_ascii.append(char)
             else:
-                str_ascii.append('.')
-        fmt = '%%-%ds %%s' % ((bytes_per_line * 3) - 1)
-        lines.append(fmt % (' '.join(str_hex), ''.join(str_ascii)))
-    return '\n'.join(lines)
+                str_ascii.append(b'.')
+        fmt = b'%%-%ds %%s' % ((bytes_per_line * 3) - 1)
+        lines.append(fmt % (b' '.join(str_hex),
+                            b''.join(str_ascii)))
+    return b'\n'.join(lines)
index c90d538c6b3a79803f4813f88aaa4da90bb9be4c..06a0b539e3809d3ddd95ab1d6ba25d3952214763 100644 (file)
@@ -24,7 +24,7 @@
 Command-line program for testing WeeChat/relay protocol.
 """
 
-from __future__ import print_function
+
 
 import argparse
 import os
@@ -37,7 +37,8 @@ import time
 import traceback
 
 import protocol  # WeeChat/relay protocol
-from .. version import qweechat_version
+# from .. version import qweechat_version
+qweechat_version = '1.1'
 
 NAME = 'qweechat-testproto'
 
@@ -75,11 +76,11 @@ class TestProto(object):
         Return True if OK, False if error.
         """
         try:
-            for msg in messages.split('\n'):
-                if msg == 'quit':
+            for msg in messages.split(b'\n'):
+                if msg == b'quit':
                     self.has_quit = True
-                self.sock.sendall(msg + '\n')
-                print('\x1b[33m<-- ' + msg + '\x1b[0m')
+                self.sock.sendall(msg + b'\n')
+                sys.stdout.write((b'\x1b[33m<-- ' + msg + b'\x1b[0m\n').decode())
         except:  # noqa: E722
             traceback.print_exc()
             print('Failed to send message')
@@ -94,7 +95,7 @@ class TestProto(object):
         try:
             proto = protocol.Protocol()
             msgd = proto.decode(message,
-                                separator='\n' if self.args.debug > 0
+                                separator=b'\n' if self.args.debug > 0
                                 else ', ')
             print('')
             if self.args.debug >= 2 and msgd.uncompressed:
@@ -136,10 +137,10 @@ class TestProto(object):
         """
         if self.has_quit:
             return 0
-        message = ''
-        recvbuf = ''
-        prompt = '\x1b[36mrelay> \x1b[0m'
-        sys.stdout.write(prompt)
+        message = b''
+        recvbuf = b''
+        prompt = b'\x1b[36mrelay> \x1b[0m'
+        sys.stdout.write(prompt.decode())
         sys.stdout.flush()
         try:
             while not self.has_quit:
@@ -149,13 +150,14 @@ class TestProto(object):
                         buf = os.read(_file.fileno(), 4096)
                         if buf:
                             message += buf
-                            if '\n' in message:
-                                messages = message.split('\n')
-                                msgsent = '\n'.join(messages[:-1])
+                            if b'\n' in message:
+                                messages = message.split(b'\n')
+                                msgsent = b'\n'.join(messages[:-1])
                                 if msgsent and not self.send(msgsent):
                                     return 4
                                 message = messages[-1]
-                                sys.stdout.write(prompt + message)
+                                sys.stdout.write((prompt + message).decode())
+                                # sys.stdout.write(prompt + message)
                                 sys.stdout.flush()
                     else:
                         buf = _file.recv(4096)
@@ -178,12 +180,12 @@ class TestProto(object):
                                 if remainder:
                                     recvbuf = remainder
                                 else:
-                                    recvbuf = ''
-                            sys.stdout.write(prompt + message)
+                                    recvbuf = b''
+                            sys.stdout.write((prompt + message).decode())
                             sys.stdout.flush()
         except:  # noqa: E722
             traceback.print_exc()
-            self.send('quit')
+            self.send(b'quit')
         return 0
 
     def __del__(self):
@@ -220,7 +222,7 @@ The script returns:
                         help='debug mode: long objects view '
                         '(-dd: display raw messages)')
     parser.add_argument('-v', '--version', action='version',
-                        version=qweechat_version())
+                        version=qweechat_version)
     parser.add_argument('hostname',
                         help='hostname (or IP address) of machine running '
                         'WeeChat/relay')
diff --git a/qweechat/weechat/testproto.py.bak b/qweechat/weechat/testproto.py.bak
new file mode 100644 (file)
index 0000000..af8a396
--- /dev/null
@@ -0,0 +1,253 @@
+# -*- coding: utf-8 -*-
+#
+# testproto.py - command-line program for testing WeeChat/relay protocol
+#
+# Copyright (C) 2013-2021 Sébastien Helleu <flashcode@flashtux.org>
+#
+# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
+#
+# QWeeChat is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# QWeeChat is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with QWeeChat.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+"""
+Command-line program for testing WeeChat/relay protocol.
+"""
+
+from __future__ import print_function
+
+import argparse
+import os
+import select
+import shlex
+import socket
+import struct
+import sys
+import time
+import traceback
+
+import protocol  # WeeChat/relay protocol
+# from .. version import qweechat_version
+qweechat_version = '1.1'
+
+NAME = 'qweechat-testproto'
+
+
+class TestProto(object):
+    """Test of WeeChat/relay protocol."""
+
+    def __init__(self, args):
+        self.args = args
+        self.sock = None
+        self.has_quit = False
+        self.address = '{self.args.hostname}/{self.args.port} ' \
+            '(IPv{0})'.format(6 if self.args.ipv6 else 4, self=self)
+
+    def connect(self):
+        """
+        Connect to WeeChat/relay.
+        Return True if OK, False if error.
+        """
+        inet = socket.AF_INET6 if self.args.ipv6 else socket.AF_INET
+        try:
+            self.sock = socket.socket(inet, socket.SOCK_STREAM)
+            self.sock.connect((self.args.hostname, self.args.port))
+        except:  # noqa: E722
+            if self.sock:
+                self.sock.close()
+            print('Failed to connect to', self.address)
+            return False
+        print('Connected to', self.address)
+        return True
+
+    def send(self, messages):
+        """
+        Send a text message to WeeChat/relay.
+        Return True if OK, False if error.
+        """
+        try:
+            for msg in messages.split('\n'):
+                if msg == 'quit':
+                    self.has_quit = True
+                self.sock.sendall(msg + '\n')
+                print('\x1b[33m<-- ' + msg + '\x1b[0m')
+        except:  # noqa: E722
+            traceback.print_exc()
+            print('Failed to send message')
+            return False
+        return True
+
+    def decode(self, message):
+        """
+        Decode a binary message received from WeeChat/relay.
+        Return True if OK, False if error.
+        """
+        try:
+            proto = protocol.Protocol()
+            msgd = proto.decode(message,
+                                separator='\n' if self.args.debug > 0
+                                else ', ')
+            print('')
+            if self.args.debug >= 2 and msgd.uncompressed:
+                # display raw message
+                print('\x1b[32m--> message uncompressed ({0} bytes):\n'
+                      '{1}\x1b[0m'
+                      ''.format(msgd.size_uncompressed,
+                                protocol.hex_and_ascii(msgd.uncompressed, 20)))
+            # display decoded message
+            print('\x1b[32m--> {0}\x1b[0m'.format(msgd))
+        except:  # noqa: E722
+            traceback.print_exc()
+            print('Error while decoding message from WeeChat')
+            return False
+        return True
+
+    def send_stdin(self):
+        """
+        Send commands from standard input if some data is available.
+        Return True if OK (it's OK if stdin has no commands),
+        False if error.
+        """
+        inr = select.select([sys.stdin], [], [], 0)[0]
+        if inr:
+            data = os.read(sys.stdin.fileno(), 4096)
+            if data:
+                if not self.send(data.strip()):
+                    # self.sock.close()
+                    return False
+            # open stdin to read user commands
+            sys.stdin = open('/dev/tty')
+        return True
+
+    def mainloop(self):
+        """
+        Main loop: read keyboard, send commands, read socket,
+        decode/display binary messages received from WeeChat/relay.
+        Return 0 if OK, 4 if send error, 5 if decode error.
+        """
+        if self.has_quit:
+            return 0
+        message = ''
+        recvbuf = ''
+        prompt = '\x1b[36mrelay> \x1b[0m'
+        sys.stdout.write(prompt)
+        sys.stdout.flush()
+        try:
+            while not self.has_quit:
+                inr = select.select([sys.stdin, self.sock], [], [], 1)[0]
+                for _file in inr:
+                    if _file == sys.stdin:
+                        buf = os.read(_file.fileno(), 4096)
+                        if buf:
+                            message += buf
+                            if '\n' in message:
+                                messages = message.split('\n')
+                                msgsent = '\n'.join(messages[:-1])
+                                if msgsent and not self.send(msgsent):
+                                    return 4
+                                message = messages[-1]
+                                sys.stdout.write(prompt + message)
+                                sys.stdout.flush()
+                    else:
+                        buf = _file.recv(4096)
+                        if buf:
+                            recvbuf += buf
+                            while len(recvbuf) >= 4:
+                                remainder = None
+                                length = struct.unpack('>i', recvbuf[0:4])[0]
+                                if len(recvbuf) < length:
+                                    # partial message, just wait for the
+                                    # end of message
+                                    break
+                                # more than one message?
+                                if length < len(recvbuf):
+                                    # save beginning of another message
+                                    remainder = recvbuf[length:]
+                                    recvbuf = recvbuf[0:length]
+                                if not self.decode(recvbuf):
+                                    return 5
+                                if remainder:
+                                    recvbuf = remainder
+                                else:
+                                    recvbuf = ''
+                            sys.stdout.write(prompt + message)
+                            sys.stdout.flush()
+        except:  # noqa: E722
+            traceback.print_exc()
+            self.send('quit')
+        return 0
+
+    def __del__(self):
+        print('Closing connection with', self.address)
+        time.sleep(0.5)
+        self.sock.close()
+
+
+def main():
+    """Main function."""
+    # parse command line arguments
+    parser = argparse.ArgumentParser(
+        formatter_class=argparse.RawDescriptionHelpFormatter,
+        fromfile_prefix_chars='@',
+        description='Command-line program for testing WeeChat/relay protocol.',
+        epilog='''
+Environment variable "QWEECHAT_PROTO_OPTIONS" can be set with default options.
+Argument "@file.txt" can be used to read default options in a file.
+
+Some commands can be piped to the script, for example:
+  echo "init password=xxxx" | {name} localhost 5000
+  {name} localhost 5000 < commands.txt
+
+The script returns:
+  0: OK
+  2: wrong arguments (command line)
+  3: connection error
+  4: send error (message sent to WeeChat)
+  5: decode error (message received from WeeChat)
+'''.format(name=NAME))
+    parser.add_argument('-6', '--ipv6', action='store_true',
+                        help='connect using IPv6')
+    parser.add_argument('-d', '--debug', action='count', default=0,
+                        help='debug mode: long objects view '
+                        '(-dd: display raw messages)')
+    parser.add_argument('-v', '--version', action='version',
+                        version=qweechat_version)
+    parser.add_argument('hostname',
+                        help='hostname (or IP address) of machine running '
+                        'WeeChat/relay')
+    parser.add_argument('port', type=int,
+                        help='port of machine running WeeChat/relay')
+    if len(sys.argv) == 1:
+        parser.print_help()
+        sys.exit(0)
+    _args = parser.parse_args(
+        shlex.split(os.getenv('QWEECHAT_PROTO_OPTIONS') or '') + sys.argv[1:])
+
+    test = TestProto(_args)
+
+    # connect to WeeChat/relay
+    if not test.connect():
+        sys.exit(3)
+
+    # send commands from standard input if some data is available
+    if not test.send_stdin():
+        sys.exit(4)
+
+    # main loop (wait commands, display messages received)
+    returncode = test.mainloop()
+    del test
+    sys.exit(returncode)
+
+
+if __name__ == "__main__":
+    main()