Migration to Qt6 using PySide6 and Python 3.

This is a bulk migraton of the codebase to work with Python3 and latest version
of Qt framework i.e. Qt6.
This commit is contained in:
Abhilash Raj 2021-05-31 21:02:25 -07:00
parent 8335009dae
commit 1f27a20e9e
No known key found for this signature in database
GPG Key ID: 9D9B2BA061D0A67C
13 changed files with 408 additions and 190 deletions

View File

@ -20,10 +20,8 @@
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>. # along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
# #
import qt_compat from PySide6 import QtCore
from PySide6 import QtWidgets as QtGui
QtCore = qt_compat.import_module('QtCore')
QtGui = qt_compat.import_module('QtGui')
class AboutDialog(QtGui.QDialog): class AboutDialog(QtGui.QDialog):
@ -44,7 +42,7 @@ class AboutDialog(QtGui.QDialog):
vbox = QtGui.QVBoxLayout() vbox = QtGui.QVBoxLayout()
for msg in messages: for msg in messages:
label = QtGui.QLabel(msg.decode('utf-8')) label = QtGui.QLabel(msg)
label.setAlignment(QtCore.Qt.AlignHCenter) label.setAlignment(QtCore.Qt.AlignHCenter)
vbox.addWidget(label) vbox.addWidget(label)
vbox.addLayout(hbox) vbox.addLayout(hbox)

View File

@ -21,20 +21,20 @@
# #
from pkg_resources import resource_filename from pkg_resources import resource_filename
import qt_compat
from chat import ChatTextEdit from chat import ChatTextEdit
from input import InputLineEdit from input import InputLineEdit
import weechat.color as color import weechat.color as color
QtCore = qt_compat.import_module('QtCore') from PySide6 import QtCore
QtGui = qt_compat.import_module('QtGui') from PySide6 import QtWidgets
from PySide6 import QtGui
class GenericListWidget(QtGui.QListWidget): class GenericListWidget(QtWidgets.QListWidget):
"""Generic QListWidget with dynamic size.""" """Generic QListWidget with dynamic size."""
def __init__(self, *args): def __init__(self, *args):
QtGui.QListWidget.__init__(*(self,) + args) super().__init__(*args)
self.setMaximumWidth(100) self.setMaximumWidth(100)
self.setTextElideMode(QtCore.Qt.ElideNone) self.setTextElideMode(QtCore.Qt.ElideNone)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
@ -52,17 +52,17 @@ class GenericListWidget(QtGui.QListWidget):
def clear(self, *args): def clear(self, *args):
"""Re-implement clear to set dynamic size after clear.""" """Re-implement clear to set dynamic size after clear."""
QtGui.QListWidget.clear(*(self,) + args) QtWidgets.QListWidget.clear(*(self,) + args)
self.auto_resize() self.auto_resize()
def addItem(self, *args): def addItem(self, *args):
"""Re-implement addItem to set dynamic size after add.""" """Re-implement addItem to set dynamic size after add."""
QtGui.QListWidget.addItem(*(self,) + args) QtWidgets.QListWidget.addItem(*(self,) + args)
self.auto_resize() self.auto_resize()
def insertItem(self, *args): def insertItem(self, *args):
"""Re-implement insertItem to set dynamic size after insert.""" """Re-implement insertItem to set dynamic size after insert."""
QtGui.QListWidget.insertItem(*(self,) + args) QtWidgets.QListWidget.insertItem(*(self,) + args)
self.auto_resize() self.auto_resize()
@ -70,7 +70,7 @@ class BufferListWidget(GenericListWidget):
"""Widget with list of buffers.""" """Widget with list of buffers."""
def __init__(self, *args): def __init__(self, *args):
GenericListWidget.__init__(*(self,) + args) super().__init__(*args)
def switch_prev_buffer(self): def switch_prev_buffer(self):
if self.currentRow() > 0: if self.currentRow() > 0:
@ -85,23 +85,23 @@ class BufferListWidget(GenericListWidget):
self.setCurrentRow(0) self.setCurrentRow(0)
class BufferWidget(QtGui.QWidget): class BufferWidget(QtWidgets.QWidget):
""" """
Widget with (from top to bottom): Widget with (from top to bottom):
title, chat + nicklist (optional) + prompt/input. title, chat + nicklist (optional) + prompt/input.
""" """
def __init__(self, display_nicklist=False): def __init__(self, display_nicklist=False):
QtGui.QWidget.__init__(self) super().__init__()
# title # title
self.title = QtGui.QLineEdit() self.title = QtWidgets.QLineEdit()
self.title.setFocusPolicy(QtCore.Qt.NoFocus) self.title.setFocusPolicy(QtCore.Qt.NoFocus)
# splitter with chat + nicklist # splitter with chat + nicklist
self.chat_nicklist = QtGui.QSplitter() self.chat_nicklist = QtWidgets.QSplitter()
self.chat_nicklist.setSizePolicy(QtGui.QSizePolicy.Expanding, self.chat_nicklist.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding) QtWidgets.QSizePolicy.Expanding)
self.chat = ChatTextEdit(debug=False) self.chat = ChatTextEdit(debug=False)
self.chat_nicklist.addWidget(self.chat) self.chat_nicklist.addWidget(self.chat)
self.nicklist = GenericListWidget() self.nicklist = GenericListWidget()
@ -110,16 +110,16 @@ class BufferWidget(QtGui.QWidget):
self.chat_nicklist.addWidget(self.nicklist) self.chat_nicklist.addWidget(self.nicklist)
# prompt + input # prompt + input
self.hbox_edit = QtGui.QHBoxLayout() self.hbox_edit = QtWidgets.QHBoxLayout()
self.hbox_edit.setContentsMargins(0, 0, 0, 0) self.hbox_edit.setContentsMargins(0, 0, 0, 0)
self.hbox_edit.setSpacing(0) self.hbox_edit.setSpacing(0)
self.input = InputLineEdit(self.chat) self.input = InputLineEdit(self.chat)
self.hbox_edit.addWidget(self.input) self.hbox_edit.addWidget(self.input)
prompt_input = QtGui.QWidget() prompt_input = QtWidgets.QWidget()
prompt_input.setLayout(self.hbox_edit) prompt_input.setLayout(self.hbox_edit)
# vbox with title + chat/nicklist + prompt/input # vbox with title + chat/nicklist + prompt/input
vbox = QtGui.QVBoxLayout() vbox = QtWidgets.QVBoxLayout()
vbox.setContentsMargins(0, 0, 0, 0) vbox.setContentsMargins(0, 0, 0, 0)
vbox.setSpacing(0) vbox.setSpacing(0)
vbox.addWidget(self.title) vbox.addWidget(self.title)
@ -139,7 +139,7 @@ class BufferWidget(QtGui.QWidget):
if self.hbox_edit.count() > 1: if self.hbox_edit.count() > 1:
self.hbox_edit.takeAt(0) self.hbox_edit.takeAt(0)
if prompt is not None: if prompt is not None:
label = QtGui.QLabel(prompt) label = QtWidgets.QLabel(prompt)
label.setContentsMargins(0, 0, 5, 0) label.setContentsMargins(0, 0, 5, 0)
self.hbox_edit.insertWidget(0, label) self.hbox_edit.insertWidget(0, label)
@ -147,7 +147,7 @@ class BufferWidget(QtGui.QWidget):
class Buffer(QtCore.QObject): class Buffer(QtCore.QObject):
"""A WeeChat buffer.""" """A WeeChat buffer."""
bufferInput = qt_compat.Signal(str, str) bufferInput = QtCore.Signal(str, str)
def __init__(self, data={}): def __init__(self, data={}):
QtCore.QObject.__init__(self) QtCore.QObject.__init__(self)
@ -243,6 +243,6 @@ class Buffer(QtCore.QObject):
pixmap = QtGui.QPixmap(8, 8) pixmap = QtGui.QPixmap(8, 8)
pixmap.fill() pixmap.fill()
icon = QtGui.QIcon(pixmap) icon = QtGui.QIcon(pixmap)
item = QtGui.QListWidgetItem(icon, nick['name']) item = QtWidgets.QListWidgetItem(icon, nick['name'])
self.widget.nicklist.addItem(item) self.widget.nicklist.addItem(item)
self.widget.nicklist.setVisible(True) self.widget.nicklist.setVisible(True)

View File

@ -21,19 +21,18 @@
# #
import datetime import datetime
import qt_compat
import config import config
import weechat.color as color import weechat.color as color
QtCore = qt_compat.import_module('QtCore') from PySide6 import QtCore
QtGui = qt_compat.import_module('QtGui') from PySide6 import QtWidgets, QtGui
class ChatTextEdit(QtGui.QTextEdit): class ChatTextEdit(QtWidgets.QTextEdit):
"""Chat area.""" """Chat area."""
def __init__(self, debug, *args): def __init__(self, debug, *args):
QtGui.QTextEdit.__init__(*(self,) + args) QtWidgets.QTextEdit.__init__(*(self,) + args)
self.debug = debug self.debug = debug
self.readOnly = True self.readOnly = True
self.setFocusPolicy(QtCore.Qt.NoFocus) self.setFocusPolicy(QtCore.Qt.NoFocus)
@ -77,9 +76,9 @@ class ChatTextEdit(QtGui.QTextEdit):
prefix = '\x01(F%s)%s' % (forcecolor, prefix) prefix = '\x01(F%s)%s' % (forcecolor, prefix)
text = '\x01(F%s)%s' % (forcecolor, text) text = '\x01(F%s)%s' % (forcecolor, text)
if prefix: if prefix:
self._display_with_colors(str(prefix).decode('utf-8') + ' ') self._display_with_colors(prefix + ' ')
if text: if text:
self._display_with_colors(str(text).decode('utf-8')) self._display_with_colors(text)
if text[-1:] != '\n': if text[-1:] != '\n':
self.insertPlainText('\n') self.insertPlainText('\n')
else: else:

View File

@ -20,7 +20,7 @@
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>. # along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
# #
import ConfigParser import configparser
import os import os
CONFIG_DIR = '%s/.qweechat' % os.getenv('HOME') CONFIG_DIR = '%s/.qweechat' % os.getenv('HOME')
@ -91,7 +91,7 @@ config_color_options = []
def read(): def read():
"""Read config file.""" """Read config file."""
global config_color_options global config_color_options
config = ConfigParser.RawConfigParser() config = configparser.RawConfigParser()
if os.path.isfile(CONFIG_FILENAME): if os.path.isfile(CONFIG_FILENAME):
config.read(CONFIG_FILENAME) config.read(CONFIG_FILENAME)
@ -123,7 +123,7 @@ def write(config):
"""Write config file.""" """Write config file."""
if not os.path.exists(CONFIG_DIR): if not os.path.exists(CONFIG_DIR):
os.mkdir(CONFIG_DIR, 0o0755) os.mkdir(CONFIG_DIR, 0o0755)
with open(CONFIG_FILENAME, 'wb') as cfg: with open(CONFIG_FILENAME, 'w') as cfg:
config.write(cfg) config.write(cfg)

View File

@ -20,29 +20,30 @@
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>. # 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.""" """Connection window."""
def __init__(self, values, *args): def __init__(self, values, *args):
QtGui.QDialog.__init__(*(self,) + args) super().__init__(*args)
self.values = values self.values = values
self.setModal(True) self.setModal(True)
grid = QtGui.QGridLayout() grid = QtWidgets.QGridLayout()
grid.setSpacing(10) grid.setSpacing(10)
self.fields = {} self.fields = {}
for line, field in enumerate(('server', 'port', 'password', 'lines')): for line, field in enumerate(('server', 'port', 'password', 'lines')):
grid.addWidget(QtGui.QLabel(field.capitalize()), line, 0) grid.addWidget(QtWidgets.QLabel(field.capitalize()), line, 0)
line_edit = QtGui.QLineEdit() line_edit = QtWidgets.QLineEdit()
line_edit.setFixedWidth(200) line_edit.setFixedWidth(200)
if field == 'password': if field == 'password':
line_edit.setEchoMode(QtGui.QLineEdit.Password) line_edit.setEchoMode(QtWidgets.QLineEdit.Password)
if field == 'lines': if field == 'lines':
validator = QtGui.QIntValidator(0, 2147483647, self) validator = QtGui.QIntValidator(0, 2147483647, self)
line_edit.setValidator(validator) line_edit.setValidator(validator)
@ -51,14 +52,14 @@ class ConnectionDialog(QtGui.QDialog):
grid.addWidget(line_edit, line, 1) grid.addWidget(line_edit, line, 1)
self.fields[field] = line_edit self.fields[field] = line_edit
if field == 'port': if field == 'port':
ssl = QtGui.QCheckBox('SSL') ssl = QtWidgets.QCheckBox('SSL')
ssl.setChecked(self.values['ssl'] == 'on') ssl.setChecked(self.values['ssl'] == 'on')
grid.addWidget(ssl, line, 2) grid.addWidget(ssl, line, 2)
self.fields['ssl'] = ssl self.fields['ssl'] = ssl
self.dialog_buttons = QtGui.QDialogButtonBox() self.dialog_buttons = QtWidgets.QDialogButtonBox()
self.dialog_buttons.setStandardButtons( self.dialog_buttons.setStandardButtons(
QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
self.dialog_buttons.rejected.connect(self.close) self.dialog_buttons.rejected.connect(self.close)
grid.addWidget(self.dialog_buttons, 4, 0, 1, 2) grid.addWidget(self.dialog_buttons, 4, 0, 1, 2)

View File

@ -20,11 +20,10 @@
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>. # along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
# #
import qt_compat
from chat import ChatTextEdit from chat import ChatTextEdit
from input import InputLineEdit from input import InputLineEdit
QtGui = qt_compat.import_module('QtGui') from PySide6 import QtWidgets as QtGui
class DebugDialog(QtGui.QDialog): class DebugDialog(QtGui.QDialog):

View File

@ -20,21 +20,19 @@
# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>. # along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
# #
import qt_compat from PySide6 import QtCore
from PySide6 import QtWidgets as QtGui
QtCore = qt_compat.import_module('QtCore')
QtGui = qt_compat.import_module('QtGui')
class InputLineEdit(QtGui.QLineEdit): class InputLineEdit(QtGui.QLineEdit):
"""Input line.""" """Input line."""
bufferSwitchPrev = qt_compat.Signal() bufferSwitchPrev = QtCore.Signal()
bufferSwitchNext = qt_compat.Signal() bufferSwitchNext = QtCore.Signal()
textSent = qt_compat.Signal(str) textSent = QtCore.Signal(str)
def __init__(self, scroll_widget): def __init__(self, scroll_widget):
QtGui.QLineEdit.__init__(self) super().__init__(scroll_widget)
self.scroll_widget = scroll_widget self.scroll_widget = scroll_widget
self._history = [] self._history = []
self._history_index = -1 self._history_index = -1

View File

@ -21,11 +21,12 @@
# #
import struct import struct
import qt_compat
import config import config
from PySide6 import QtCore, QtNetwork
from PySide6.QtCore import QObject, Signal
QtCore = qt_compat.import_module('QtCore') # QtCore = qt_compat.import_module('QtCore')
QtNetwork = qt_compat.import_module('QtNetwork') # QtNetwork = qt_compat.import_module('QtNetwork')
_PROTO_INIT_CMD = ['init password=%(password)s'] _PROTO_INIT_CMD = ['init password=%(password)s']
@ -47,11 +48,11 @@ _PROTO_SYNC_CMDS = [
class Network(QtCore.QObject): class Network(QtCore.QObject):
"""I/O with WeeChat/relay.""" """I/O with WeeChat/relay."""
statusChanged = qt_compat.Signal(str, str) statusChanged = Signal(str, str)
messageFromWeechat = qt_compat.Signal(QtCore.QByteArray) messageFromWeechat = Signal(QtCore.QByteArray)
def __init__(self, *args): def __init__(self, *args):
QtCore.QObject.__init__(*(self,) + args) super().__init__(*args)
self.status_disconnected = 'disconnected' self.status_disconnected = 'disconnected'
self.status_connecting = 'connecting...' self.status_connecting = 'connecting...'
self.status_connected = 'connected' self.status_connected = 'connected'
@ -63,13 +64,14 @@ class Network(QtCore.QObject):
self._buffer = QtCore.QByteArray() self._buffer = QtCore.QByteArray()
self._socket = QtNetwork.QSslSocket() self._socket = QtNetwork.QSslSocket()
self._socket.connected.connect(self._socket_connected) 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.readyRead.connect(self._socket_read)
self._socket.disconnected.connect(self._socket_disconnected) self._socket.disconnected.connect(self._socket_disconnected)
def _socket_connected(self): def _socket_connected(self):
"""Slot: socket connected.""" """Slot: socket connected."""
self.statusChanged.emit(self.status_connected, None) self.statusChanged.emit(self.status_connected, None)
print('Connected, now sending password.')
if self._password: if self._password:
self.send_to_weechat('\n'.join(_PROTO_INIT_CMD + _PROTO_SYNC_CMDS) self.send_to_weechat('\n'.join(_PROTO_INIT_CMD + _PROTO_SYNC_CMDS)
% {'password': str(self._password), % {'password': str(self._password),
@ -87,7 +89,7 @@ class Network(QtCore.QObject):
self._buffer.append(data) self._buffer.append(data)
while len(self._buffer) >= 4: while len(self._buffer) >= 4:
remainder = None 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: if len(self._buffer) < length:
# partial message, just wait for end of message # partial message, just wait for end of message
break break
@ -108,7 +110,7 @@ class Network(QtCore.QObject):
self._server = None self._server = None
self._port = None self._port = None
self._ssl = None self._ssl = None
self._password = None self._password = ""
self.statusChanged.emit(self.status_disconnected, None) self.statusChanged.emit(self.status_disconnected, None)
def is_connected(self): def is_connected(self):
@ -122,6 +124,7 @@ class Network(QtCore.QObject):
def connect_weechat(self, server, port, ssl, password, lines): def connect_weechat(self, server, port, ssl, password, lines):
"""Connect to WeeChat.""" """Connect to WeeChat."""
self._server = server self._server = server
print(f'Connecting to server {self._server}')
try: try:
self._port = int(port) self._port = int(port)
except ValueError: except ValueError:
@ -136,11 +139,13 @@ class Network(QtCore.QObject):
return return
if self._socket.state() != QtNetwork.QAbstractSocket.UnconnectedState: if self._socket.state() != QtNetwork.QAbstractSocket.UnconnectedState:
self._socket.abort() self._socket.abort()
self._socket.connectToHost(self._server, self._port)
if self._ssl: if self._ssl:
self._socket.ignoreSslErrors() self._socket.ignoreSslErrors()
self._socket.startClientEncryption() self._socket.connectToHostEncrypted(self._server, self._port)
self.statusChanged.emit(self.status_connecting, None) else:
self._socket.connectToHost(self._server, self._port)
print('Got SSL connection')
self.statusChanged.emit(self.status_connecting, "")
def disconnect_weechat(self): def disconnect_weechat(self):
"""Disconnect from WeeChat.""" """Disconnect from WeeChat."""

View File

@ -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

View File

@ -36,7 +36,7 @@ It requires requires WeeChat 0.3.7 or newer, running on local or remote host.
import sys import sys
import traceback import traceback
from pkg_resources import resource_filename from pkg_resources import resource_filename
import qt_compat # import qt_compat
import config import config
import weechat.protocol as protocol import weechat.protocol as protocol
from network import Network from network import Network
@ -46,8 +46,14 @@ from debug import DebugDialog
from about import AboutDialog from about import AboutDialog
from version import qweechat_version from version import qweechat_version
QtCore = qt_compat.import_module('QtCore') from PySide6.QtWidgets import (
QtGui = qt_compat.import_module('QtGui') 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' NAME = 'QWeeChat'
AUTHOR = 'Sébastien Helleu' AUTHOR = 'Sébastien Helleu'
@ -58,11 +64,11 @@ WEECHAT_SITE = 'https://weechat.org/'
DEBUG_NUM_LINES = 50 DEBUG_NUM_LINES = 50
class MainWindow(QtGui.QMainWindow): class MainWindow(QtWidgets.QMainWindow):
"""Main window.""" """Main window."""
def __init__(self, *args): def __init__(self, *args):
QtGui.QMainWindow.__init__(*(self,) + args) super().__init__()
self.config = config.read() self.config = config.read()
@ -87,11 +93,11 @@ class MainWindow(QtGui.QMainWindow):
# default buffer # default buffer
self.buffers = [Buffer()] self.buffers = [Buffer()]
self.stacked_buffers = QtGui.QStackedWidget() self.stacked_buffers = QtWidgets.QStackedWidget()
self.stacked_buffers.addWidget(self.buffers[0].widget) self.stacked_buffers.addWidget(self.buffers[0].widget)
# splitter with buffers + chat/input # splitter with buffers + chat/input
splitter = QtGui.QSplitter() splitter = QtWidgets.QSplitter()
splitter.addWidget(self.list_buffers) splitter.addWidget(self.list_buffers)
splitter.addWidget(self.stacked_buffers) splitter.addWidget(self.stacked_buffers)
@ -146,7 +152,7 @@ class MainWindow(QtGui.QMainWindow):
menu_window.addAction(self.actions['debug']) menu_window.addAction(self.actions['debug'])
menu_help = self.menu.addMenu('&Help') menu_help = self.menu.addMenu('&Help')
menu_help.addAction(self.actions['about']) menu_help.addAction(self.actions['about'])
self.network_status = QtGui.QLabel() self.network_status = QtWidgets.QLabel()
self.network_status.setFixedHeight(20) self.network_status.setFixedHeight(20)
self.network_status.setFixedWidth(200) self.network_status.setFixedWidth(200)
self.network_status.setContentsMargins(0, 0, 10, 0) self.network_status.setContentsMargins(0, 0, 10, 0)
@ -312,24 +318,24 @@ class MainWindow(QtGui.QMainWindow):
def _network_weechat_msg(self, message): def _network_weechat_msg(self, message):
"""Called when a message is received from WeeChat.""" """Called when a message is received from WeeChat."""
self.debug_display(0, '==>', # self.debug_display(0, '==>',
'message (%d bytes):\n%s' # 'message (%d bytes):\n%s'
% (len(message), # % (len(message),
protocol.hex_and_ascii(message, 20)), # protocol.hex_and_ascii(message, 20)),
forcecolor='#008800') # forcecolor='#008800')
try: try:
proto = protocol.Protocol() proto = protocol.Protocol()
message = proto.decode(str(message)) message = proto.decode(message.data())
if message.uncompressed: if message.uncompressed:
self.debug_display( self.debug_display(
0, '==>', 0, '==>',
'message uncompressed (%d bytes):\n%s' 'message uncompressed (%d bytes):\n%s'
% (message.size_uncompressed, % (message.size_uncompressed,
protocol.hex_and_ascii(message.uncompressed, 20)), protocol.hex_and_ascii(message.uncompressed, 20)),
forcecolor='#008800') forcecolor='#008800')
self.debug_display(0, '', 'Message: %s' % message) self.debug_display(0, '', 'Message: %s' % message)
self.parse_message(message) self.parse_message(message)
except: # noqa: E722 except Exception: # noqa: E722
print('Error while decoding message from WeeChat:\n%s' print('Error while decoding message from WeeChat:\n%s'
% traceback.format_exc()) % traceback.format_exc())
self.network.disconnect_weechat() self.network.disconnect_weechat()
@ -339,6 +345,7 @@ class MainWindow(QtGui.QMainWindow):
for obj in message.objects: for obj in message.objects:
if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer': if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer':
continue continue
print('listbuffers object', obj.objtype, obj.value['path'])
self.list_buffers.clear() self.list_buffers.clear()
while self.stacked_buffers.count() > 0: while self.stacked_buffers.count() > 0:
buf = self.stacked_buffers.widget(0) buf = self.stacked_buffers.widget(0)
@ -346,6 +353,7 @@ class MainWindow(QtGui.QMainWindow):
self.buffers = [] self.buffers = []
for item in obj.value['items']: for item in obj.value['items']:
buf = self.create_buffer(item) buf = self.create_buffer(item)
print(f'Creating buffer for {item}')
self.insert_buffer(len(self.buffers), buf) self.insert_buffer(len(self.buffers), buf)
self.list_buffers.setCurrentRow(0) self.list_buffers.setCurrentRow(0)
self.buffers[0].widget.input.setFocus() self.buffers[0].widget.input.setFocus()
@ -477,6 +485,7 @@ class MainWindow(QtGui.QMainWindow):
def parse_message(self, message): def parse_message(self, message):
"""Parse a WeeChat message.""" """Parse a WeeChat message."""
print(f'message.msgid = {message.msgid}')
if message.msgid.startswith('debug'): if message.msgid.startswith('debug'):
self.debug_display(0, '', '(debug message, ignored)') self.debug_display(0, '', '(debug message, ignored)')
elif message.msgid == 'listbuffers': elif message.msgid == 'listbuffers':
@ -495,6 +504,8 @@ class MainWindow(QtGui.QMainWindow):
self.network.desync_weechat() self.network.desync_weechat()
elif message.msgid == '_upgrade_ended': elif message.msgid == '_upgrade_ended':
self.network.sync_weechat() self.network.sync_weechat()
else:
print(f"Unknown message with id {message.msgid}")
def create_buffer(self, item): def create_buffer(self, item):
"""Create a new buffer.""" """Create a new buffer."""
@ -511,7 +522,7 @@ class MainWindow(QtGui.QMainWindow):
self.buffers.insert(index, buf) self.buffers.insert(index, buf)
self.list_buffers.insertItem(index, '%d. %s' self.list_buffers.insertItem(index, '%d. %s'
% (buf.data['number'], % (buf.data['number'],
buf.data['full_name'].decode('utf-8'))) buf.data['full_name']))
self.stacked_buffers.insertWidget(index, buf.widget) self.stacked_buffers.insertWidget(index, buf.widget)
def remove_buffer(self, index): def remove_buffer(self, index):
@ -544,12 +555,13 @@ class MainWindow(QtGui.QMainWindow):
if self.debug_dialog: if self.debug_dialog:
self.debug_dialog.close() self.debug_dialog.close()
config.write(self.config) config.write(self.config)
QtGui.QMainWindow.closeEvent(self, event) QtWidgets.QMainWindow.closeEvent(self, event)
app = QtGui.QApplication(sys.argv) app = QApplication(sys.argv)
app.setStyle(QtGui.QStyleFactory.create('Cleanlooks')) app.setStyle(QtWidgets.QStyleFactory.create('Cleanlooks'))
app.setWindowIcon(QtGui.QIcon( app.setWindowIcon(QtGui.QIcon(
resource_filename(__name__, 'data/icons/weechat.png'))) resource_filename(__name__, 'data/icons/weechat.png')))
main = MainWindow() main = MainWindow()
main.show()
sys.exit(app.exec_()) sys.exit(app.exec_())

View File

@ -34,15 +34,10 @@ import collections
import struct import struct
import zlib import zlib
if hasattr(collections, 'OrderedDict'): class WeechatDict(collections.OrderedDict):
# python >= 2.7 def __str__(self):
class WeechatDict(collections.OrderedDict): return '{%s}' % ', '.join(
def __str__(self): ['%s: %s' % (repr(key), repr(self[key])) for key in self])
return '{%s}' % ', '.join(
['%s: %s' % (repr(key), repr(self[key])) for key in self])
else:
# python <= 2.6
WeechatDict = dict
class WeechatObject: class WeechatObject:
@ -151,7 +146,7 @@ class Protocol:
if len(self.data) < 3: if len(self.data) < 3:
self.data = '' self.data = ''
return '' return ''
objtype = str(self.data[0:3]) objtype = bytes(self.data[0:3])
self.data = self.data[3:] self.data = self.data[3:]
return objtype return objtype
@ -196,14 +191,17 @@ class Protocol:
value = self._obj_len_data(1) value = self._obj_len_data(1)
if value is None: if value is None:
return None return None
return int(str(value)) return int(value)
def _obj_str(self): def _obj_str(self):
"""Read a string in data (length on 4 bytes + content).""" """Read a string in data (length on 4 bytes + content)."""
value = self._obj_len_data(4) value = self._obj_len_data(4)
if value is None: if value is None:
return None return None
return str(value) try:
return value.decode()
except AttributeError:
return value
def _obj_buffer(self): def _obj_buffer(self):
"""Read a buffer in data (length on 4 bytes + data).""" """Read a buffer in data (length on 4 bytes + data)."""
@ -214,22 +212,22 @@ class Protocol:
value = self._obj_len_data(1) value = self._obj_len_data(1)
if value is None: if value is None:
return None return None
return '0x%s' % str(value) return '0x%s' % value
def _obj_time(self): def _obj_time(self):
"""Read a time in data (length on 1 byte + value as string).""" """Read a time in data (length on 1 byte + value as string)."""
value = self._obj_len_data(1) value = self._obj_len_data(1)
if value is None: if value is None:
return None return None
return int(str(value)) return int(value)
def _obj_hashtable(self): def _obj_hashtable(self):
""" """
Read a hashtable in data Read a hashtable in data
(type for keys + type for values + count + items). (type for keys + type for values + count + items).
""" """
type_keys = self._obj_type() type_keys = self._obj_type().decode()
type_values = self._obj_type() type_values = self._obj_type().decode()
count = self._obj_int() count = self._obj_int()
hashtable = WeechatDict() hashtable = WeechatDict()
for _ in range(count): for _ in range(count):
@ -248,7 +246,7 @@ class Protocol:
keys_types = [] keys_types = []
dict_keys = WeechatDict() dict_keys = WeechatDict()
for key in list_keys: for key in list_keys:
items = key.split(':') items = list(item for item in key.split(':'))
keys_types.append(items) keys_types.append(items)
dict_keys[items[0]] = items[1] dict_keys[items[0]] = items[1]
items = [] items = []
@ -259,6 +257,7 @@ class Protocol:
for _ in enumerate(list_path): for _ in enumerate(list_path):
pointers.append(self._obj_ptr()) pointers.append(self._obj_ptr())
for key, objtype in keys_types: for key, objtype in keys_types:
objtype = objtype
item[key] = self._obj_cb[objtype]() item[key] = self._obj_cb[objtype]()
item['__path'] = pointers item['__path'] = pointers
items.append(item) items.append(item)
@ -296,7 +295,7 @@ class Protocol:
def _obj_array(self): def _obj_array(self):
"""Read an array of values in data.""" """Read an array of values in data."""
type_values = self._obj_type() type_values = self._obj_type().decode()
count_values = self._obj_int() count_values = self._obj_int()
values = [] values = []
for _ in range(count_values): for _ in range(count_values):
@ -314,7 +313,7 @@ class Protocol:
if compression: if compression:
uncompressed = zlib.decompress(self.data[5:]) uncompressed = zlib.decompress(self.data[5:])
size_uncompressed = len(uncompressed) + 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) struct.pack('b', 0), uncompressed)
self.data = uncompressed self.data = uncompressed
else: else:
@ -328,7 +327,7 @@ class Protocol:
# read objects # read objects
objects = WeechatObjects(separator=separator) objects = WeechatObjects(separator=separator)
while len(self.data) > 0: while len(self.data) > 0:
objtype = self._obj_type() objtype = self._obj_type().decode()
value = self._obj_cb[objtype]() value = self._obj_cb[objtype]()
objects.append(WeechatObject(objtype, value, separator=separator)) objects.append(WeechatObject(objtype, value, separator=separator))
return WeechatMessage(size, size_uncompressed, compression, 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): for i in range(num_lines):
str_hex = [] str_hex = []
str_ascii = [] 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] 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: if byte >= 32 and byte <= 127:
str_ascii.append(char) str_ascii.append(char)
else: else:
str_ascii.append('.') str_ascii.append(b'.')
fmt = '%%-%ds %%s' % ((bytes_per_line * 3) - 1) fmt = b'%%-%ds %%s' % ((bytes_per_line * 3) - 1)
lines.append(fmt % (' '.join(str_hex), ''.join(str_ascii))) lines.append(fmt % (b' '.join(str_hex),
return '\n'.join(lines) b''.join(str_ascii)))
return b'\n'.join(lines)

View File

@ -24,7 +24,7 @@
Command-line program for testing WeeChat/relay protocol. Command-line program for testing WeeChat/relay protocol.
""" """
from __future__ import print_function
import argparse import argparse
import os import os
@ -37,7 +37,8 @@ import time
import traceback import traceback
import protocol # WeeChat/relay protocol import protocol # WeeChat/relay protocol
from .. version import qweechat_version # from .. version import qweechat_version
qweechat_version = '1.1'
NAME = 'qweechat-testproto' NAME = 'qweechat-testproto'
@ -75,11 +76,11 @@ class TestProto(object):
Return True if OK, False if error. Return True if OK, False if error.
""" """
try: try:
for msg in messages.split('\n'): for msg in messages.split(b'\n'):
if msg == 'quit': if msg == b'quit':
self.has_quit = True self.has_quit = True
self.sock.sendall(msg + '\n') self.sock.sendall(msg + b'\n')
print('\x1b[33m<-- ' + msg + '\x1b[0m') sys.stdout.write((b'\x1b[33m<-- ' + msg + b'\x1b[0m\n').decode())
except: # noqa: E722 except: # noqa: E722
traceback.print_exc() traceback.print_exc()
print('Failed to send message') print('Failed to send message')
@ -94,7 +95,7 @@ class TestProto(object):
try: try:
proto = protocol.Protocol() proto = protocol.Protocol()
msgd = proto.decode(message, msgd = proto.decode(message,
separator='\n' if self.args.debug > 0 separator=b'\n' if self.args.debug > 0
else ', ') else ', ')
print('') print('')
if self.args.debug >= 2 and msgd.uncompressed: if self.args.debug >= 2 and msgd.uncompressed:
@ -136,10 +137,10 @@ class TestProto(object):
""" """
if self.has_quit: if self.has_quit:
return 0 return 0
message = '' message = b''
recvbuf = '' recvbuf = b''
prompt = '\x1b[36mrelay> \x1b[0m' prompt = b'\x1b[36mrelay> \x1b[0m'
sys.stdout.write(prompt) sys.stdout.write(prompt.decode())
sys.stdout.flush() sys.stdout.flush()
try: try:
while not self.has_quit: while not self.has_quit:
@ -149,13 +150,14 @@ class TestProto(object):
buf = os.read(_file.fileno(), 4096) buf = os.read(_file.fileno(), 4096)
if buf: if buf:
message += buf message += buf
if '\n' in message: if b'\n' in message:
messages = message.split('\n') messages = message.split(b'\n')
msgsent = '\n'.join(messages[:-1]) msgsent = b'\n'.join(messages[:-1])
if msgsent and not self.send(msgsent): if msgsent and not self.send(msgsent):
return 4 return 4
message = messages[-1] message = messages[-1]
sys.stdout.write(prompt + message) sys.stdout.write((prompt + message).decode())
# sys.stdout.write(prompt + message)
sys.stdout.flush() sys.stdout.flush()
else: else:
buf = _file.recv(4096) buf = _file.recv(4096)
@ -178,12 +180,12 @@ class TestProto(object):
if remainder: if remainder:
recvbuf = remainder recvbuf = remainder
else: else:
recvbuf = '' recvbuf = b''
sys.stdout.write(prompt + message) sys.stdout.write((prompt + message).decode())
sys.stdout.flush() sys.stdout.flush()
except: # noqa: E722 except: # noqa: E722
traceback.print_exc() traceback.print_exc()
self.send('quit') self.send(b'quit')
return 0 return 0
def __del__(self): def __del__(self):
@ -220,7 +222,7 @@ The script returns:
help='debug mode: long objects view ' help='debug mode: long objects view '
'(-dd: display raw messages)') '(-dd: display raw messages)')
parser.add_argument('-v', '--version', action='version', parser.add_argument('-v', '--version', action='version',
version=qweechat_version()) version=qweechat_version)
parser.add_argument('hostname', parser.add_argument('hostname',
help='hostname (or IP address) of machine running ' help='hostname (or IP address) of machine running '
'WeeChat/relay') 'WeeChat/relay')

View File

@ -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()