]> jfr.im git - irc/weechat/qweechat.git/blob - qweechat/network.py
28ef673a6a3f5686272cadaa8800efee47ee9541
[irc/weechat/qweechat.git] / qweechat / network.py
1 # -*- coding: utf-8 -*-
2 #
3 # network.py - I/O with WeeChat/relay
4 #
5 # Copyright (C) 2011-2021 SĂ©bastien Helleu <flashcode@flashtux.org>
6 #
7 # This file is part of QWeeChat, a Qt remote GUI for WeeChat.
8 #
9 # QWeeChat is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # QWeeChat is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
21 #
22
23 """I/O with WeeChat/relay."""
24
25 import struct
26
27 from PySide6 import QtCore, QtNetwork
28
29 from qweechat import config
30
31
32 _PROTO_INIT_CMD = ['init password=%(password)s']
33
34 _PROTO_SYNC_CMDS = [
35 '(listbuffers) hdata buffer:gui_buffers(*) number,full_name,short_name,'
36 'type,nicklist,title,local_variables',
37
38 '(listlines) hdata buffer:gui_buffers(*)/own_lines/last_line(-%(lines)d)/'
39 'data date,displayed,prefix,message',
40
41 '(nicklist) nicklist',
42
43 'sync',
44
45 ''
46 ]
47
48 STATUS_DISCONNECTED = 'disconnected'
49 STATUS_CONNECTING = 'connecting'
50 STATUS_CONNECTED = 'connected'
51
52 NETWORK_STATUS = {
53 'disconnected': {
54 'label': 'Disconnected',
55 'color': '#aa0000',
56 'icon': 'dialog-close.png',
57 },
58 'connecting': {
59 'label': 'Connecting…',
60 'color': '#ff7f00',
61 'icon': 'dialog-warning.png',
62 },
63 'connected': {
64 'label': 'Connected',
65 'color': 'green',
66 'icon': 'dialog-ok-apply.png',
67 },
68 }
69
70 class Network(QtCore.QObject):
71 """I/O with WeeChat/relay."""
72
73 statusChanged = QtCore.Signal(str, str)
74 messageFromWeechat = QtCore.Signal(QtCore.QByteArray)
75
76 def __init__(self, *args):
77 super().__init__(*args)
78 self._server = None
79 self._port = None
80 self._ssl = None
81 self._password = None
82 self._lines = config.CONFIG_DEFAULT_RELAY_LINES
83 self._buffer = QtCore.QByteArray()
84 self._socket = QtNetwork.QSslSocket()
85 self._socket.connected.connect(self._socket_connected)
86 # self._socket.error.connect(self._socket_error)
87 self._socket.readyRead.connect(self._socket_read)
88 self._socket.disconnected.connect(self._socket_disconnected)
89
90 def _socket_connected(self):
91 """Slot: socket connected."""
92 self.statusChanged.emit(STATUS_CONNECTED, None)
93 if self._password:
94 self.send_to_weechat('\n'.join(_PROTO_INIT_CMD + _PROTO_SYNC_CMDS)
95 % {'password': str(self._password),
96 'lines': self._lines})
97
98 def _socket_error(self, error):
99 """Slot: socket error."""
100 self.statusChanged.emit(
101 STATUS_DISCONNECTED,
102 'Failed, error: %s' % self._socket.errorString())
103
104 def _socket_read(self):
105 """Slot: data available on socket."""
106 data = self._socket.readAll()
107 self._buffer.append(data)
108 while len(self._buffer) >= 4:
109 remainder = None
110 length = struct.unpack('>i', self._buffer[0:4].data())[0]
111 if len(self._buffer) < length:
112 # partial message, just wait for end of message
113 break
114 # more than one message?
115 if length < len(self._buffer):
116 # save beginning of another message
117 remainder = self._buffer[length:]
118 self._buffer = self._buffer[0:length]
119 self.messageFromWeechat.emit(self._buffer)
120 if not self.is_connected():
121 return
122 self._buffer.clear()
123 if remainder:
124 self._buffer.append(remainder)
125
126 def _socket_disconnected(self):
127 """Slot: socket disconnected."""
128 self._server = None
129 self._port = None
130 self._ssl = None
131 self._password = ""
132 self.statusChanged.emit(STATUS_DISCONNECTED, None)
133
134 def is_connected(self):
135 """Return True if the socket is connected, False otherwise."""
136 return self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState
137
138 def is_ssl(self):
139 """Return True if SSL is used, False otherwise."""
140 return self._ssl
141
142 def connect_weechat(self, server, port, ssl, password, lines):
143 """Connect to WeeChat."""
144 self._server = server
145 try:
146 self._port = int(port)
147 except ValueError:
148 self._port = 0
149 self._ssl = ssl
150 self._password = password
151 try:
152 self._lines = int(lines)
153 except ValueError:
154 self._lines = config.CONFIG_DEFAULT_RELAY_LINES
155 if self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState:
156 return
157 if self._socket.state() != QtNetwork.QAbstractSocket.UnconnectedState:
158 self._socket.abort()
159 if self._ssl:
160 self._socket.ignoreSslErrors()
161 self._socket.connectToHostEncrypted(self._server, self._port)
162 else:
163 self._socket.connectToHost(self._server, self._port)
164 self.statusChanged.emit(STATUS_CONNECTING, "")
165
166 def disconnect_weechat(self):
167 """Disconnect from WeeChat."""
168 if self._socket.state() == QtNetwork.QAbstractSocket.UnconnectedState:
169 return
170 if self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState:
171 self.send_to_weechat('quit\n')
172 self._socket.waitForBytesWritten(1000)
173 else:
174 self.statusChanged.emit(STATUS_DISCONNECTED, None)
175 self._socket.abort()
176
177 def send_to_weechat(self, message):
178 """Send a message to WeeChat."""
179 self._socket.write(message.encode('utf-8'))
180
181 def desync_weechat(self):
182 """Desynchronize from WeeChat."""
183 self.send_to_weechat('desync\n')
184
185 def sync_weechat(self):
186 """Synchronize with WeeChat."""
187 self.send_to_weechat('\n'.join(_PROTO_SYNC_CMDS))
188
189 def status_label(self, status):
190 """Return the label for a given status."""
191 return NETWORK_STATUS.get(status, {}).get('label', '')
192
193 def status_color(self, status):
194 """Return the color for a given status."""
195 return NETWORK_STATUS.get(status, {}).get('color', 'black')
196
197 def status_icon(self, status):
198 """Return the name of icon for a given status."""
199 return NETWORK_STATUS.get(status, {}).get('icon', '')
200
201 def get_options(self):
202 """Get connection options."""
203 return {
204 'server': self._server,
205 'port': self._port,
206 'ssl': 'on' if self._ssl else 'off',
207 'password': self._password,
208 'lines': str(self._lines),
209 }