]> jfr.im git - irc/weechat/qweechat.git/blame - src/qweechat/qweechat.py
Add SSL support
[irc/weechat/qweechat.git] / src / qweechat / qweechat.py
CommitLineData
7dcf23b1
SH
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#
beaa8775 4# Copyright (C) 2011-2012 Sebastien Helleu <flashcode@flashtux.org>
7dcf23b1
SH
5#
6# This file is part of QWeeChat, a Qt remote GUI for WeeChat.
7#
8# QWeeChat is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 3 of the License, or
11# (at your option) any later version.
12#
13# QWeeChat is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with QWeeChat. If not, see <http://www.gnu.org/licenses/>.
20#
21
22#
23# QWeeChat - WeeChat remote GUI using Qt toolkit.
24# (this script requires WeeChat 0.3.7 or newer, running on local or remote host)
25#
26# History:
27#
28# 2011-05-27, Sebastien Helleu <flashcode@flashtux.org>:
29# start dev
30#
31
32import sys, struct
33import qt_compat
34QtCore = qt_compat.import_module('QtCore')
35QtGui = qt_compat.import_module('QtGui')
36import config
37import weechat.protocol as protocol
7dcf23b1
SH
38from network import Network
39from connection import ConnectionDialog
40from buffer import BufferListWidget, Buffer
41from debug import DebugDialog
42from about import AboutDialog
43
f4848c2e 44NAME = 'QWeeChat'
8320f3a8 45VERSION = '0.0.1-dev'
7dcf23b1
SH
46AUTHOR = 'Sébastien Helleu'
47AUTHOR_MAIL= 'flashcode@flashtux.org'
48WEECHAT_SITE = 'http://www.weechat.org/'
49
cc80cd81
SH
50# number of lines in buffer for debug window
51DEBUG_NUM_LINES = 50
52
7dcf23b1
SH
53
54class MainWindow(QtGui.QMainWindow):
55 """Main window."""
56
57 def __init__(self, *args):
67ae3204 58 QtGui.QMainWindow.__init__(*(self,) + args)
7dcf23b1
SH
59
60 self.config = config.read()
61
7dcf23b1
SH
62 self.resize(1000, 600)
63 self.setWindowTitle(NAME)
64
cc80cd81
SH
65 self.debug_dialog = None
66 self.debug_lines = []
67
7dcf23b1
SH
68 # network
69 self.network = Network()
70 self.network.statusChanged.connect(self.network_status_changed)
71 self.network.messageFromWeechat.connect(self.network_message_from_weechat)
72
73 # list of buffers
74 self.list_buffers = BufferListWidget()
75 self.list_buffers.currentRowChanged.connect(self.buffer_switch)
76
77 # default buffer
78 self.buffers = [Buffer()]
79 self.stacked_buffers = QtGui.QStackedWidget()
80 self.stacked_buffers.addWidget(self.buffers[0].widget)
81
82 # splitter with buffers + chat/input
83 splitter = QtGui.QSplitter()
84 splitter.addWidget(self.list_buffers)
85 splitter.addWidget(self.stacked_buffers)
86
87 self.setCentralWidget(splitter)
88
89 if self.config.getboolean('look', 'statusbar'):
90 self.statusBar().visible = True
91
92 # actions for menu and toolbar
93 actions_def = {'connect' : ['network-connect.png', 'Connect to WeeChat', 'Ctrl+O', self.open_connection_dialog],
94 'disconnect' : ['network-disconnect.png', 'Disconnect from WeeChat', 'Ctrl+D', self.network.disconnect_weechat],
95 'debug' : ['edit-find.png', 'Debug console window', 'Ctrl+B', self.open_debug_dialog],
96 'preferences': ['preferences-other.png', 'Preferences', 'Ctrl+P', self.open_preferences_dialog],
97 'about' : ['help-about.png', 'About', 'Ctrl+H', self.open_about_dialog],
98 'quit' : ['application-exit.png', 'Quit application', 'Ctrl+Q', self.close],
99 }
100 self.actions = {}
773ee7bb 101 for name, action in list(actions_def.items()):
7dcf23b1
SH
102 self.actions[name] = QtGui.QAction(QtGui.QIcon('data/icons/%s' % action[0]), name.capitalize(), self)
103 self.actions[name].setStatusTip(action[1])
104 self.actions[name].setShortcut(action[2])
105 self.actions[name].triggered.connect(action[3])
106
107 # menu
108 self.menu = self.menuBar()
109 menu_file = self.menu.addMenu('&File')
110 menu_file.addActions([self.actions['connect'], self.actions['disconnect'],
111 self.actions['preferences'], self.actions['quit']])
112 menu_window = self.menu.addMenu('&Window')
113 menu_window.addAction(self.actions['debug'])
114 menu_help = self.menu.addMenu('&Help')
115 menu_help.addAction(self.actions['about'])
116 self.network_status = QtGui.QLabel()
117 self.network_status.setFixedHeight(20)
118 self.network_status.setFixedWidth(200)
119 self.network_status.setContentsMargins(0, 0, 10, 0)
120 self.network_status.setAlignment(QtCore.Qt.AlignRight)
121 if hasattr(self.menu, 'setCornerWidget'):
122 self.menu.setCornerWidget(self.network_status, QtCore.Qt.TopRightCorner)
123 self.network_status_set(self.network.status_disconnected, None)
124
125 # toolbar
126 toolbar = self.addToolBar('toolBar')
127 toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
128 toolbar.addActions([self.actions['connect'], self.actions['disconnect'],
129 self.actions['debug'], self.actions['preferences'],
130 self.actions['about'], self.actions['quit']])
131
132 self.buffers[0].widget.input.setFocus()
133
134 # open debug dialog
135 if self.config.getboolean('look', 'debug'):
136 self.open_debug_dialog()
137
138 # auto-connect to relay
139 if self.config.getboolean('relay', 'autoconnect'):
140 self.network.connect_weechat(self.config.get('relay', 'server'),
141 self.config.get('relay', 'port'),
77b25057 142 self.config.get('relay', 'ssl') == 'on',
7dcf23b1
SH
143 self.config.get('relay', 'password'))
144
145 self.show()
146
147 def buffer_switch(self, index):
148 if index >= 0:
149 self.stacked_buffers.setCurrentIndex(index)
150 self.stacked_buffers.widget(index).input.setFocus()
151
152 def buffer_input(self, full_name, text):
153 if self.network.is_connected():
154 message = 'input %s %s\n' % (full_name, text)
155 self.network.send_to_weechat(message)
3a5ec0c1 156 self.debug_display(0, '<==', message, forcecolor='#AA0000')
7dcf23b1
SH
157
158 def open_preferences_dialog(self):
159 pass # TODO
160
cc80cd81
SH
161 def debug_display(self, *args, **kwargs):
162 self.debug_lines.append((args, kwargs))
163 self.debug_lines = self.debug_lines[-DEBUG_NUM_LINES:]
164 if self.debug_dialog:
165 self.debug_dialog.chat.display(*args, **kwargs)
166
7dcf23b1 167 def open_debug_dialog(self):
cc80cd81
SH
168 if not self.debug_dialog:
169 self.debug_dialog = DebugDialog(self)
170 self.debug_dialog.input.textSent.connect(self.debug_input_text_sent)
171 self.debug_dialog.finished.connect(self.debug_dialog_closed)
172 self.debug_dialog.display_lines(self.debug_lines)
173 self.debug_dialog.chat.scroll_bottom()
7dcf23b1
SH
174
175 def debug_input_text_sent(self, text):
176 if self.network.is_connected():
177 text = str(text)
178 pos = text.find(')')
179 if text.startswith('(') and pos >= 0:
180 text = '(debug_%s)%s' % (text[1:pos], text[pos+1:])
181 else:
182 text = '(debug) %s' % text
3a5ec0c1 183 self.debug_display(0, '<==', text, forcecolor='#AA0000')
7dcf23b1 184 self.network.send_to_weechat(text + '\n')
7dcf23b1
SH
185
186 def debug_dialog_closed(self, result):
cc80cd81 187 self.debug_dialog = None
7dcf23b1
SH
188
189 def open_about_dialog(self):
190 messages = ['<b>%s</b> %s' % (NAME, VERSION),
d1b4884d 191 '&copy; 2011-2012 %s &lt;<a href="mailto:%s">%s</a>&gt;' % (AUTHOR, AUTHOR_MAIL, AUTHOR_MAIL),
7dcf23b1
SH
192 '',
193 'WeeChat site: <a href="%s">%s</a>' % (WEECHAT_SITE, WEECHAT_SITE),
194 '']
195 self.about_dialog = AboutDialog(NAME, messages, self)
196
197 def open_connection_dialog(self):
198 values = {}
77b25057 199 for option in ('server', 'port', 'ssl', 'password'):
7dcf23b1
SH
200 values[option] = self.config.get('relay', option)
201 self.connection_dialog = ConnectionDialog(values, self)
202 self.connection_dialog.dialog_buttons.accepted.connect(self.connect_weechat)
203
204 def connect_weechat(self):
205 self.network.connect_weechat(self.connection_dialog.fields['server'].text(),
206 self.connection_dialog.fields['port'].text(),
77b25057 207 self.connection_dialog.fields['ssl'].isChecked(),
7dcf23b1
SH
208 self.connection_dialog.fields['password'].text())
209 self.connection_dialog.close()
210
211 def network_status_changed(self, status, extra):
212 if self.config.getboolean('look', 'statusbar'):
213 self.statusBar().showMessage(status)
3a5ec0c1 214 self.debug_display(0, '', status, forcecolor='#0000AA')
7dcf23b1
SH
215 self.network_status_set(status, extra)
216
217 def network_status_set(self, status, extra):
218 pal = self.network_status.palette()
77b25057 219 if status == self.network.status_connected:
7dcf23b1
SH
220 pal.setColor(self.network_status.foregroundRole(), QtGui.QColor('green'))
221 else:
222 pal.setColor(self.network_status.foregroundRole(), QtGui.QColor('#aa0000'))
77b25057 223 ssl = ' (SSL)' if status != self.network.status_disconnected and self.network.is_ssl() else ''
7dcf23b1
SH
224 self.network_status.setPalette(pal)
225 icon = self.network.status_icon(status)
226 if icon:
77b25057 227 self.network_status.setText('<img src="data/icons/%s"> %s' % (icon, status.capitalize() + ssl))
7dcf23b1
SH
228 else:
229 self.network_status.setText(status.capitalize())
230 if status == self.network.status_disconnected:
231 self.actions['connect'].setEnabled(True)
232 self.actions['disconnect'].setEnabled(False)
233 else:
234 self.actions['connect'].setEnabled(False)
235 self.actions['disconnect'].setEnabled(True)
236
237 def network_message_from_weechat(self, message):
cc80cd81
SH
238 self.debug_display(0, '==>',
239 'message (%d bytes):\n%s'
240 % (len(message), protocol.hex_and_ascii(message, 20)),
3a5ec0c1 241 forcecolor='#008800')
77b25057
SH
242 try:
243 proto = protocol.Protocol()
244 message = proto.decode(str(message))
245 if message.uncompressed:
246 self.debug_display(0, '==>',
247 'message uncompressed (%d bytes):\n%s'
248 % (message.size_uncompressed,
249 protocol.hex_and_ascii(message.uncompressed, 20)),
250 forcecolor='#008800')
251 self.debug_display(0, '', 'Message: %s' % message)
252 self.parse_message(message)
253 except:
254 print("Error while decoding message from WeeChat")
255 self.network.disconnect_weechat()
7dcf23b1
SH
256
257 def parse_message(self, message):
258 if message.msgid.startswith('debug'):
cc80cd81 259 self.debug_display(0, '', '(debug message, ignored)')
7dcf23b1
SH
260 return
261 if message.msgid == 'listbuffers':
262 for obj in message.objects:
263 if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer':
264 self.list_buffers.clear()
265 while self.stacked_buffers.count() > 0:
266 buf = self.stacked_buffers.widget(0)
267 self.stacked_buffers.removeWidget(buf)
268 self.buffers = []
269 for item in obj.value['items']:
ca67c3a7
SH
270 buf = self.create_buffer(item)
271 self.insert_buffer(len(self.buffers), buf)
7dcf23b1
SH
272 self.list_buffers.setCurrentRow(0)
273 self.buffers[0].widget.input.setFocus()
ca67c3a7 274 elif message.msgid in ('listlines', '_buffer_line_added'):
7dcf23b1
SH
275 for obj in message.objects:
276 if obj.objtype == 'hda' and obj.value['path'][-1] == 'line_data':
277 for item in obj.value['items']:
ca67c3a7
SH
278 if message.msgid == 'listlines':
279 ptrbuf = item['__path'][0]
280 else:
281 ptrbuf = item['buffer']
282 index = [i for i, b in enumerate(self.buffers) if b.pointer() == ptrbuf]
283 if index:
284 self.buffers[index[0]].widget.chat.display(item['date'],
3a5ec0c1
SH
285 item['prefix'],
286 item['message'])
ca67c3a7
SH
287 elif message.msgid in ('_nicklist', 'nicklist'):
288 buffer_nicklist = {}
7dcf23b1 289 for obj in message.objects:
ca67c3a7 290 if obj.objtype == 'hda' and obj.value['path'][-1] == 'nicklist_item':
7dcf23b1 291 for item in obj.value['items']:
ca67c3a7
SH
292 index = [i for i, b in enumerate(self.buffers) if b.pointer() == item['__path'][0]]
293 if index:
294 if not item['__path'][0] in buffer_nicklist:
295 self.buffers[index[0]].remove_all_nicks()
296 buffer_nicklist[item['__path'][0]] = True
297 if not item['group'] and item['visible']:
298 self.buffers[index[0]].add_nick(item['prefix'], item['name'])
299 elif message.msgid == '_buffer_opened':
300 for obj in message.objects:
301 if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer':
302 for item in obj.value['items']:
303 buf = self.create_buffer(item)
304 index = self.find_buffer_index_for_insert(item['next_buffer'])
305 self.insert_buffer(index, buf)
306 elif message.msgid.startswith('_buffer_'):
307 for obj in message.objects:
308 if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer':
309 for item in obj.value['items']:
310 index = [i for i, b in enumerate(self.buffers) if b.pointer() == item['__path'][0]]
311 if index:
312 index = index[0]
97936653
SH
313 if message.msgid == '_buffer_type_changed':
314 self.buffers[index].data['type'] = item['type']
315 elif message.msgid in ('_buffer_moved', '_buffer_merged', '_buffer_unmerged'):
ca67c3a7
SH
316 buf = self.buffers[index]
317 buf.data['number'] = item['number']
318 self.remove_buffer(index)
319 index2 = self.find_buffer_index_for_insert(item['next_buffer'])
320 self.insert_buffer(index2, buf)
321 elif message.msgid == '_buffer_renamed':
322 self.buffers[index].data['full_name'] = item['full_name']
323 self.buffers[index].data['short_name'] = item['short_name']
324 elif message.msgid == '_buffer_title_changed':
325 self.buffers[index].data['title'] = item['title']
c728febd
SH
326 self.buffers[index].update_title()
327 elif message.msgid.startswith('_buffer_localvar_'):
328 self.buffers[index].data['local_variables'] = item['local_variables']
329 self.buffers[index].update_prompt()
ca67c3a7
SH
330 elif message.msgid == '_buffer_closing':
331 self.remove_buffer(index)
beaa8775
SH
332 elif message.msgid == '_upgrade':
333 self.network.desync_weechat()
334 elif message.msgid == '_upgrade_ended':
335 self.network.sync_weechat()
ca67c3a7
SH
336
337 def create_buffer(self, item):
338 buf = Buffer(item)
339 buf.bufferInput.connect(self.buffer_input)
340 buf.widget.input.bufferSwitchPrev.connect(self.list_buffers.switch_prev_buffer)
341 buf.widget.input.bufferSwitchNext.connect(self.list_buffers.switch_next_buffer)
342 return buf
343
344 def insert_buffer(self, index, buf):
345 self.buffers.insert(index, buf)
346 self.list_buffers.insertItem(index, '%d. %s' % (buf.data['number'], buf.data['full_name']))
347 self.stacked_buffers.insertWidget(index, buf.widget)
348
349 def remove_buffer(self, index):
350 if self.list_buffers.currentRow == index and index > 0:
351 self.list_buffers.setCurrentRow(index - 1)
352 self.list_buffers.takeItem(index)
353 self.stacked_buffers.removeWidget(self.stacked_buffers.widget(index))
354 self.buffers.pop(index)
355
356 def find_buffer_index_for_insert(self, next_buffer):
357 index = -1
358 if next_buffer == '0x0':
359 index = len(self.buffers)
360 else:
361 index = [i for i, b in enumerate(self.buffers) if b.pointer() == next_buffer]
362 if index:
363 index = index[0]
364 if index < 0:
365 print('Warning: unable to find position for buffer, using end of list by default')
366 index = len(self.buffers)
367 return index
7dcf23b1
SH
368
369 def closeEvent(self, event):
370 self.network.disconnect_weechat()
cc80cd81
SH
371 if self.debug_dialog:
372 self.debug_dialog.close()
7dcf23b1
SH
373 config.write(self.config)
374 QtGui.QMainWindow.closeEvent(self, event)
375
376
377app = QtGui.QApplication(sys.argv)
378app.setStyle(QtGui.QStyleFactory.create('Cleanlooks'))
93865c21 379app.setWindowIcon(QtGui.QIcon('data/icons/weechat_icon_32.png'))
7dcf23b1
SH
380main = MainWindow()
381sys.exit(app.exec_())