Code refactoring, fix setup.py
All changes: - full PEP8 compliance - move sources from src/qweechat/ to qweechat/ - move data from data/icons/ to qweechat/data/icons/ - sources validated with PEP8 - use setuptools in setup.py, fix path of data files
This commit is contained in:
		
							parent
							
								
									42f3541246
								
							
						
					
					
						commit
						77df9d06f7
					
				
					 33 changed files with 953 additions and 728 deletions
				
			
		|  | @ -1,53 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # about.py - about dialog box | ||||
| # | ||||
| # Copyright (C) 2011-2014 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/>. | ||||
| # | ||||
| 
 | ||||
| import qt_compat | ||||
| QtCore = qt_compat.import_module('QtCore') | ||||
| QtGui = qt_compat.import_module('QtGui') | ||||
| 
 | ||||
| 
 | ||||
| class AboutDialog(QtGui.QDialog): | ||||
|     """About dialog.""" | ||||
| 
 | ||||
|     def __init__(self, name, messages, *args): | ||||
|         QtGui.QDialog.__init__(*(self,) + args) | ||||
|         self.setModal(True) | ||||
|         self.setWindowTitle(name) | ||||
| 
 | ||||
|         close_button = QtGui.QPushButton('Close') | ||||
|         close_button.pressed.connect(self.close) | ||||
| 
 | ||||
|         hbox = QtGui.QHBoxLayout() | ||||
|         hbox.addStretch(1) | ||||
|         hbox.addWidget(close_button) | ||||
|         hbox.addStretch(1) | ||||
| 
 | ||||
|         vbox = QtGui.QVBoxLayout() | ||||
|         for msg in messages: | ||||
|             label = QtGui.QLabel(msg.decode('utf-8')) | ||||
|             label.setAlignment(QtCore.Qt.AlignHCenter) | ||||
|             vbox.addWidget(label) | ||||
|         vbox.addLayout(hbox) | ||||
| 
 | ||||
|         self.setLayout(vbox) | ||||
|         self.show() | ||||
|  | @ -1,226 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # buffer.py - management of WeeChat buffers/nicklist | ||||
| # | ||||
| # Copyright (C) 2011-2014 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/>. | ||||
| # | ||||
| 
 | ||||
| import qt_compat | ||||
| QtCore = qt_compat.import_module('QtCore') | ||||
| QtGui = qt_compat.import_module('QtGui') | ||||
| from chat import ChatTextEdit | ||||
| from input import InputLineEdit | ||||
| import weechat.color as color | ||||
| 
 | ||||
| 
 | ||||
| class GenericListWidget(QtGui.QListWidget): | ||||
|     """Generic QListWidget with dynamic size.""" | ||||
| 
 | ||||
|     def __init__(self, *args): | ||||
|         QtGui.QListWidget.__init__(*(self,) + args) | ||||
|         self.setMaximumWidth(100) | ||||
|         self.setTextElideMode(QtCore.Qt.ElideNone) | ||||
|         self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) | ||||
|         self.setFocusPolicy(QtCore.Qt.NoFocus) | ||||
|         pal = self.palette() | ||||
|         pal.setColor(QtGui.QPalette.Highlight, QtGui.QColor('#ddddff')) | ||||
|         pal.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor('black')) | ||||
|         self.setPalette(pal) | ||||
| 
 | ||||
|     def auto_resize(self): | ||||
|         size = self.sizeHintForColumn(0) | ||||
|         if size > 0: | ||||
|             size += 4 | ||||
|         self.setMaximumWidth(size) | ||||
| 
 | ||||
|     def clear(self, *args): | ||||
|         """Re-implement clear to set dynamic size after clear.""" | ||||
|         QtGui.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) | ||||
|         self.auto_resize() | ||||
| 
 | ||||
|     def insertItem(self, *args): | ||||
|         """Re-implement insertItem to set dynamic size after insert.""" | ||||
|         QtGui.QListWidget.insertItem(*(self,) + args) | ||||
|         self.auto_resize() | ||||
| 
 | ||||
| 
 | ||||
| class BufferListWidget(GenericListWidget): | ||||
|     """Widget with list of buffers.""" | ||||
| 
 | ||||
|     def __init__(self, *args): | ||||
|         GenericListWidget.__init__(*(self,) + args) | ||||
| 
 | ||||
|     def switch_prev_buffer(self): | ||||
|         if self.currentRow() > 0: | ||||
|             self.setCurrentRow(self.currentRow() - 1) | ||||
|         else: | ||||
|             self.setCurrentRow(self.count() - 1) | ||||
| 
 | ||||
|     def switch_next_buffer(self): | ||||
|         if self.currentRow() < self.count() - 1: | ||||
|             self.setCurrentRow(self.currentRow() + 1) | ||||
|         else: | ||||
|             self.setCurrentRow(0) | ||||
| 
 | ||||
| 
 | ||||
| class BufferWidget(QtGui.QWidget): | ||||
|     """Widget with (from top to bottom): title, chat + nicklist (optional) + prompt/input.""" | ||||
| 
 | ||||
|     def __init__(self, display_nicklist=False): | ||||
|         QtGui.QWidget.__init__(self) | ||||
| 
 | ||||
|         # title | ||||
|         self.title = QtGui.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 = ChatTextEdit(debug=False) | ||||
|         self.chat_nicklist.addWidget(self.chat) | ||||
|         self.nicklist = GenericListWidget() | ||||
|         if not display_nicklist: | ||||
|             self.nicklist.setVisible(False) | ||||
|         self.chat_nicklist.addWidget(self.nicklist) | ||||
| 
 | ||||
|         # prompt + input | ||||
|         self.hbox_edit = QtGui.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.setLayout(self.hbox_edit) | ||||
| 
 | ||||
|         # vbox with title + chat/nicklist + prompt/input | ||||
|         vbox = QtGui.QVBoxLayout() | ||||
|         vbox.setContentsMargins(0, 0, 0, 0) | ||||
|         vbox.setSpacing(0) | ||||
|         vbox.addWidget(self.title) | ||||
|         vbox.addWidget(self.chat_nicklist) | ||||
|         vbox.addWidget(prompt_input) | ||||
| 
 | ||||
|         self.setLayout(vbox) | ||||
| 
 | ||||
|     def set_title(self, title): | ||||
|         """Set buffer title.""" | ||||
|         self.title.clear() | ||||
|         if not title is None: | ||||
|             self.title.setText(title) | ||||
| 
 | ||||
|     def set_prompt(self, prompt): | ||||
|         """Set prompt.""" | ||||
|         if self.hbox_edit.count() > 1: | ||||
|             self.hbox_edit.takeAt(0) | ||||
|         if not prompt is None: | ||||
|             label = QtGui.QLabel(prompt) | ||||
|             label.setContentsMargins(0, 0, 5, 0) | ||||
|             self.hbox_edit.insertWidget(0, label) | ||||
| 
 | ||||
| 
 | ||||
| class Buffer(QtCore.QObject): | ||||
|     """A WeeChat buffer.""" | ||||
| 
 | ||||
|     bufferInput = qt_compat.Signal(str, str) | ||||
| 
 | ||||
|     def __init__(self, data={}): | ||||
|         QtCore.QObject.__init__(self) | ||||
|         self.data = data | ||||
|         self.nicklist = {} | ||||
|         self.widget = BufferWidget(display_nicklist=self.data.get('nicklist', 0)) | ||||
|         self.update_title() | ||||
|         self.update_prompt() | ||||
|         self.widget.input.textSent.connect(self.input_text_sent) | ||||
| 
 | ||||
|     def pointer(self): | ||||
|         """Return pointer on buffer.""" | ||||
|         return self.data.get('__path', [''])[0] | ||||
| 
 | ||||
|     def update_title(self): | ||||
|         """Update title.""" | ||||
|         try: | ||||
|             self.widget.set_title(color.remove(self.data['title'].decode('utf-8'))) | ||||
|         except: | ||||
|             self.widget.set_title(None) | ||||
| 
 | ||||
|     def update_prompt(self): | ||||
|         """Update prompt.""" | ||||
|         try: | ||||
|             self.widget.set_prompt(self.data['local_variables']['nick']) | ||||
|         except: | ||||
|             self.widget.set_prompt(None) | ||||
| 
 | ||||
|     def input_text_sent(self, text): | ||||
|         """Called when text has to be sent to buffer.""" | ||||
|         if self.data: | ||||
|             self.bufferInput.emit(self.data['full_name'], text) | ||||
| 
 | ||||
|     def nicklist_add_item(self, parent, group, prefix, name, visible): | ||||
|         """Add a group/nick in nicklist.""" | ||||
|         if group: | ||||
|             self.nicklist[name] = { 'visible': visible, | ||||
|                                     'nicks': [] } | ||||
|         else: | ||||
|             self.nicklist[parent]['nicks'].append({ 'prefix': prefix, | ||||
|                                                     'name': name, | ||||
|                                                     'visible': visible }) | ||||
| 
 | ||||
|     def nicklist_remove_item(self, parent, group, name): | ||||
|         """Remove a group/nick from nicklist.""" | ||||
|         if group: | ||||
|             if name in self.nicklist: | ||||
|                 del self.nicklist[name] | ||||
|         else: | ||||
|             if parent in self.nicklist: | ||||
|                 self.nicklist[parent]['nicks'] = [nick for nick in self.nicklist[parent]['nicks'] if nick['name'] != name] | ||||
| 
 | ||||
|     def nicklist_update_item(self, parent, group, prefix, name, visible): | ||||
|         """Update a group/nick in nicklist.""" | ||||
|         if group: | ||||
|             if name in self.nicklist: | ||||
|                 self.nicklist[name]['visible'] = visible | ||||
|         else: | ||||
|             if parent in self.nicklist: | ||||
|                 for nick in self.nicklist[parent]['nicks']: | ||||
|                     if nick['name'] == name: | ||||
|                         nick['prefix'] = prefix | ||||
|                         nick['visible'] = visible | ||||
|                         break | ||||
| 
 | ||||
|     def nicklist_refresh(self): | ||||
|         """Refresh nicklist.""" | ||||
|         self.widget.nicklist.clear() | ||||
|         for group in sorted(self.nicklist): | ||||
|             for nick in sorted(self.nicklist[group]['nicks'], key=lambda n:n['name']): | ||||
|                 prefix_color = { '': '', ' ': '', '+': 'yellow' } | ||||
|                 color = prefix_color.get(nick['prefix'], 'green') | ||||
|                 if color: | ||||
|                     icon = QtGui.QIcon('data/icons/bullet_%s_8x8.png' % color) | ||||
|                 else: | ||||
|                     pixmap = QtGui.QPixmap(8, 8) | ||||
|                     pixmap.fill() | ||||
|                     icon = QtGui.QIcon(pixmap) | ||||
|                 item = QtGui.QListWidgetItem(icon, nick['name']) | ||||
|                 self.widget.nicklist.addItem(item) | ||||
|                 self.widget.nicklist.setVisible(True) | ||||
|  | @ -1,122 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # chat.py - chat area | ||||
| # | ||||
| # Copyright (C) 2011-2014 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/>. | ||||
| # | ||||
| 
 | ||||
| import datetime | ||||
| import qt_compat | ||||
| QtCore = qt_compat.import_module('QtCore') | ||||
| QtGui = qt_compat.import_module('QtGui') | ||||
| import config | ||||
| import weechat.color as color | ||||
| 
 | ||||
| 
 | ||||
| class ChatTextEdit(QtGui.QTextEdit): | ||||
|     """Chat area.""" | ||||
| 
 | ||||
|     def __init__(self, debug, *args): | ||||
|         QtGui.QTextEdit.__init__(*(self,) + args) | ||||
|         self.debug = debug | ||||
|         self.readOnly = True | ||||
|         self.setFocusPolicy(QtCore.Qt.NoFocus) | ||||
|         self.setFontFamily('monospace') | ||||
|         self._textcolor = self.textColor() | ||||
|         self._bgcolor = QtGui.QColor('#FFFFFF') | ||||
|         self._setcolorcode = { 'F': (self.setTextColor, self._textcolor), | ||||
|                                'B': (self.setTextBackgroundColor, self._bgcolor) } | ||||
|         self._setfont = { '*': self.setFontWeight, | ||||
|                           '_': self.setFontUnderline, | ||||
|                           '/': self.setFontItalic } | ||||
|         self._fontvalues = { False: { '*': QtGui.QFont.Normal, '_': False, '/': False }, | ||||
|                              True: { '*': QtGui.QFont.Bold, '_': True, '/': True } } | ||||
|         self._color = color.Color(config.color_options(), self.debug) | ||||
| 
 | ||||
|     def display(self, time, prefix, text, forcecolor=None): | ||||
|         if time == 0: | ||||
|             d = datetime.datetime.now() | ||||
|         else: | ||||
|             d = datetime.datetime.fromtimestamp(float(time)) | ||||
|         self.setTextColor(QtGui.QColor('#999999')) | ||||
|         self.insertPlainText(d.strftime('%H:%M ')) | ||||
|         prefix = self._color.convert(prefix) | ||||
|         text = self._color.convert(text) | ||||
|         if forcecolor: | ||||
|             if prefix: | ||||
|                 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') + ' ') | ||||
|         if text: | ||||
|             self._display_with_colors(str(text).decode('utf-8')) | ||||
|             if text[-1:] != '\n': | ||||
|                 self.insertPlainText('\n') | ||||
|         else: | ||||
|             self.insertPlainText('\n') | ||||
|         self.scroll_bottom() | ||||
| 
 | ||||
|     def _display_with_colors(self, string): | ||||
|         self.setTextColor(self._textcolor) | ||||
|         self.setTextBackgroundColor(self._bgcolor) | ||||
|         self._reset_attributes() | ||||
|         items = string.split('\x01') | ||||
|         for i, item in enumerate(items): | ||||
|             if i > 0 and item.startswith('('): | ||||
|                 pos = item.find(')') | ||||
|                 if pos >= 2: | ||||
|                     action = item[1] | ||||
|                     code = item[2:pos] | ||||
|                     if action == '+': | ||||
|                         # set attribute | ||||
|                         self._set_attribute(code[0], True) | ||||
|                     elif action == '-': | ||||
|                         # remove attribute | ||||
|                         self._set_attribute(code[0], False) | ||||
|                     else: | ||||
|                         # reset attributes and color | ||||
|                         if code == 'r': | ||||
|                             self._reset_attributes() | ||||
|                             self._setcolorcode[action][0](self._setcolorcode[action][1]) | ||||
|                         else: | ||||
|                             # set attributes + color | ||||
|                             while code.startswith(('*', '!', '/', '_', '|', 'r')): | ||||
|                                 if code[0] == 'r': | ||||
|                                     self._reset_attributes() | ||||
|                                 elif code[0] in self._setfont: | ||||
|                                     self._set_attribute(code[0], not self._font[code[0]]) | ||||
|                                 code = code[1:] | ||||
|                             if code: | ||||
|                                 self._setcolorcode[action][0](QtGui.QColor(code)) | ||||
|                     item = item[pos+1:] | ||||
|             if len(item) > 0: | ||||
|                 self.insertPlainText(item) | ||||
| 
 | ||||
|     def _reset_attributes(self): | ||||
|         self._font = {} | ||||
|         for attr in self._setfont: | ||||
|             self._set_attribute(attr, False) | ||||
| 
 | ||||
|     def _set_attribute(self, attr, value): | ||||
|         self._font[attr] = value | ||||
|         self._setfont[attr](self._fontvalues[self._font[attr]][attr]) | ||||
| 
 | ||||
|     def scroll_bottom(self): | ||||
|         bar = self.verticalScrollBar() | ||||
|         bar.setValue(bar.maximum()) | ||||
|  | @ -1,130 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # config.py - configuration for QWeeChat (~/.qweechat/qweechat.conf) | ||||
| # | ||||
| # Copyright (C) 2011-2014 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/>. | ||||
| # | ||||
| 
 | ||||
| import os, ConfigParser | ||||
| import weechat.color as color | ||||
| 
 | ||||
| CONFIG_DIR = '%s/.qweechat' % os.getenv('HOME') | ||||
| CONFIG_FILENAME = '%s/qweechat.conf' % CONFIG_DIR | ||||
| 
 | ||||
| CONFIG_DEFAULT_RELAY_LINES = 50 | ||||
| 
 | ||||
| CONFIG_DEFAULT_SECTIONS = ('relay', 'look', 'color') | ||||
| CONFIG_DEFAULT_OPTIONS = (('relay.server', ''), | ||||
|                           ('relay.port', ''), | ||||
|                           ('relay.ssl', 'off'), | ||||
|                           ('relay.password', ''), | ||||
|                           ('relay.autoconnect', 'off'), | ||||
|                           ('relay.lines', str(CONFIG_DEFAULT_RELAY_LINES)), | ||||
|                           ('look.debug', 'off'), | ||||
|                           ('look.statusbar', 'off')) | ||||
| 
 | ||||
| # Default colors for WeeChat color options (option name, #rgb value) | ||||
| CONFIG_DEFAULT_COLOR_OPTIONS = (('separator', '#000066'), # 0 | ||||
|                                 ('chat', '#000000'), # 1 | ||||
|                                 ('chat_time', '#999999'), # 2 | ||||
|                                 ('chat_time_delimiters', '#000000'), # 3 | ||||
|                                 ('chat_prefix_error', '#FF6633'), # 4 | ||||
|                                 ('chat_prefix_network', '#990099'), # 5 | ||||
|                                 ('chat_prefix_action', '#000000'), # 6 | ||||
|                                 ('chat_prefix_join', '#00CC00'), # 7 | ||||
|                                 ('chat_prefix_quit', '#CC0000'), # 8 | ||||
|                                 ('chat_prefix_more', '#CC00FF'), # 9 | ||||
|                                 ('chat_prefix_suffix', '#330099'), # 10 | ||||
|                                 ('chat_buffer', '#000000'), # 11 | ||||
|                                 ('chat_server', '#000000'), # 12 | ||||
|                                 ('chat_channel', '#000000'), # 13 | ||||
|                                 ('chat_nick', '#000000'), # 14 | ||||
|                                 ('chat_nick_self', '*#000000'), # 15 | ||||
|                                 ('chat_nick_other', '#000000'), # 16 | ||||
|                                 ('', '#000000'), # 17 (nick1 -- obsolete) | ||||
|                                 ('', '#000000'), # 18 (nick2 -- obsolete) | ||||
|                                 ('', '#000000'), # 19 (nick3 -- obsolete) | ||||
|                                 ('', '#000000'), # 20 (nick4 -- obsolete) | ||||
|                                 ('', '#000000'), # 21 (nick5 -- obsolete) | ||||
|                                 ('', '#000000'), # 22 (nick6 -- obsolete) | ||||
|                                 ('', '#000000'), # 23 (nick7 -- obsolete) | ||||
|                                 ('', '#000000'), # 24 (nick8 -- obsolete) | ||||
|                                 ('', '#000000'), # 25 (nick9 -- obsolete) | ||||
|                                 ('', '#000000'), # 26 (nick10 -- obsolete) | ||||
|                                 ('chat_host', '#666666'), # 27 | ||||
|                                 ('chat_delimiters', '#9999FF'), # 28 | ||||
|                                 ('chat_highlight', '#3399CC'), # 29 | ||||
|                                 ('chat_read_marker', '#000000'), # 30 | ||||
|                                 ('chat_text_found', '#000000'), # 31 | ||||
|                                 ('chat_value', '#000000'), # 32 | ||||
|                                 ('chat_prefix_buffer', '#000000'), # 33 | ||||
|                                 ('chat_tags', '#000000'), # 34 | ||||
|                                 ('chat_inactive_window', '#000000'), # 35 | ||||
|                                 ('chat_inactive_buffer', '#000000'), # 36 | ||||
|                                 ('chat_prefix_buffer_inactive_buffer', '#000000'), # 37 | ||||
|                                 ('chat_nick_offline', '#000000'), # 38 | ||||
|                                 ('chat_nick_offline_highlight', '#000000'), # 39 | ||||
|                                 ('chat_nick_prefix', '#000000'), # 40 | ||||
|                                 ('chat_nick_suffix', '#000000'), # 41 | ||||
|                                 ('emphasis', '#000000'), # 42 | ||||
|                                 ('chat_day_change', '#000000'), #43 | ||||
|                             ) | ||||
| config_color_options = [] | ||||
| 
 | ||||
| 
 | ||||
| def read(): | ||||
|     """Read config file.""" | ||||
|     global config_color_options | ||||
|     config = ConfigParser.RawConfigParser() | ||||
|     if os.path.isfile(CONFIG_FILENAME): | ||||
|         config.read(CONFIG_FILENAME) | ||||
| 
 | ||||
|     # add missing sections/options | ||||
|     for section in CONFIG_DEFAULT_SECTIONS: | ||||
|         if not config.has_section(section): | ||||
|             config.add_section(section) | ||||
|     for option in reversed(CONFIG_DEFAULT_OPTIONS): | ||||
|         section, name = option[0].split('.', 1) | ||||
|         if not config.has_option(section, name): | ||||
|             config.set(section, name, option[1]) | ||||
|     section = 'color' | ||||
|     for option in reversed(CONFIG_DEFAULT_COLOR_OPTIONS): | ||||
|         if option[0] and not config.has_option(section, option[0]): | ||||
|             config.set(section, option[0], option[1]) | ||||
| 
 | ||||
|     # build list of color options | ||||
|     config_color_options = [] | ||||
|     for option in CONFIG_DEFAULT_COLOR_OPTIONS: | ||||
|         if option[0]: | ||||
|             config_color_options.append(config.get('color', option[0])) | ||||
|         else: | ||||
|             config_color_options.append('#000000') | ||||
| 
 | ||||
|     return config | ||||
| 
 | ||||
| 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: | ||||
|         config.write(cfg) | ||||
| 
 | ||||
| def color_options(): | ||||
|     global config_color_options | ||||
|     return config_color_options | ||||
|  | @ -1,65 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # connection.py - connection window | ||||
| # | ||||
| # Copyright (C) 2011-2014 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/>. | ||||
| # | ||||
| 
 | ||||
| import qt_compat | ||||
| QtGui = qt_compat.import_module('QtGui') | ||||
| 
 | ||||
| 
 | ||||
| class ConnectionDialog(QtGui.QDialog): | ||||
|     """Connection window.""" | ||||
| 
 | ||||
|     def __init__(self, values, *args): | ||||
|         QtGui.QDialog.__init__(*(self,) + args) | ||||
|         self.values = values | ||||
|         self.setModal(True) | ||||
| 
 | ||||
|         grid = QtGui.QGridLayout() | ||||
|         grid.setSpacing(10) | ||||
| 
 | ||||
|         self.fields = {} | ||||
|         for y, field in enumerate(('server', 'port', 'password', 'lines')): | ||||
|             grid.addWidget(QtGui.QLabel(field.capitalize()), y, 0) | ||||
|             lineEdit = QtGui.QLineEdit() | ||||
|             lineEdit.setFixedWidth(200) | ||||
|             if field == 'password': | ||||
|                 lineEdit.setEchoMode(QtGui.QLineEdit.Password) | ||||
|             if field == 'lines': | ||||
|                 validator = QtGui.QIntValidator(0, 2147483647, self) | ||||
|                 lineEdit.setValidator(validator) | ||||
|                 lineEdit.setFixedWidth(80) | ||||
|             lineEdit.insert(self.values[field]) | ||||
|             grid.addWidget(lineEdit, y, 1) | ||||
|             self.fields[field] = lineEdit | ||||
|             if field == 'port': | ||||
|                 ssl = QtGui.QCheckBox('SSL') | ||||
|                 ssl.setChecked(self.values['ssl'] == 'on') | ||||
|                 grid.addWidget(ssl, y, 2) | ||||
|                 self.fields['ssl'] = ssl | ||||
| 
 | ||||
|         self.dialog_buttons = QtGui.QDialogButtonBox() | ||||
|         self.dialog_buttons.setStandardButtons(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel) | ||||
|         self.dialog_buttons.rejected.connect(self.close) | ||||
| 
 | ||||
|         grid.addWidget(self.dialog_buttons, 4, 0, 1, 2) | ||||
|         self.setLayout(grid) | ||||
|         self.show() | ||||
|  | @ -1,50 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # debug.py - debug window | ||||
| # | ||||
| # Copyright (C) 2011-2014 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/>. | ||||
| # | ||||
| 
 | ||||
| import qt_compat | ||||
| QtGui = qt_compat.import_module('QtGui') | ||||
| from chat import ChatTextEdit | ||||
| from input import InputLineEdit | ||||
| 
 | ||||
| 
 | ||||
| class DebugDialog(QtGui.QDialog): | ||||
|     """Debug dialog.""" | ||||
| 
 | ||||
|     def __init__(self, *args): | ||||
|         QtGui.QDialog.__init__(*(self,) + args) | ||||
|         self.resize(640, 480) | ||||
|         self.setWindowTitle('Debug console') | ||||
| 
 | ||||
|         self.chat = ChatTextEdit(debug=True) | ||||
|         self.input = InputLineEdit(self.chat) | ||||
| 
 | ||||
|         vbox = QtGui.QVBoxLayout() | ||||
|         vbox.addWidget(self.chat) | ||||
|         vbox.addWidget(self.input) | ||||
| 
 | ||||
|         self.setLayout(vbox) | ||||
|         self.show() | ||||
| 
 | ||||
|     def display_lines(self, lines): | ||||
|         for line in lines: | ||||
|             self.chat.display(*line[0], **line[1]) | ||||
|  | @ -1,90 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # input.py - input line for chat and debug window | ||||
| # | ||||
| # Copyright (C) 2011-2014 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/>. | ||||
| # | ||||
| 
 | ||||
| import qt_compat | ||||
| QtCore = qt_compat.import_module('QtCore') | ||||
| QtGui = qt_compat.import_module('QtGui') | ||||
| 
 | ||||
| 
 | ||||
| class InputLineEdit(QtGui.QLineEdit): | ||||
|     """Input line.""" | ||||
| 
 | ||||
|     bufferSwitchPrev = qt_compat.Signal() | ||||
|     bufferSwitchNext = qt_compat.Signal() | ||||
|     textSent = qt_compat.Signal(str) | ||||
| 
 | ||||
|     def __init__(self, scroll_widget): | ||||
|         QtGui.QLineEdit.__init__(self) | ||||
|         self.scroll_widget = scroll_widget | ||||
|         self._history = [] | ||||
|         self._history_index = -1 | ||||
|         self.returnPressed.connect(self._input_return_pressed) | ||||
| 
 | ||||
|     def keyPressEvent(self, event): | ||||
|         key = event.key() | ||||
|         modifiers = event.modifiers() | ||||
|         bar = self.scroll_widget.verticalScrollBar() | ||||
|         if modifiers == QtCore.Qt.ControlModifier and key == QtCore.Qt.Key_PageUp: | ||||
|             self.bufferSwitchPrev.emit() | ||||
|         elif modifiers == QtCore.Qt.AltModifier and key in (QtCore.Qt.Key_Left, QtCore.Qt.Key_Up): | ||||
|             self.bufferSwitchPrev.emit() | ||||
|         elif modifiers == QtCore.Qt.ControlModifier and key == QtCore.Qt.Key_PageDown: | ||||
|             self.bufferSwitchNext.emit() | ||||
|         elif modifiers == QtCore.Qt.AltModifier and key in (QtCore.Qt.Key_Right, QtCore.Qt.Key_Down): | ||||
|             self.bufferSwitchNext.emit() | ||||
|         elif modifiers == QtCore.Qt.AltModifier and key == QtCore.Qt.Key_PageUp: | ||||
|             bar.setValue(bar.value() - (bar.pageStep() / 10)) | ||||
|         elif modifiers == QtCore.Qt.AltModifier and key == QtCore.Qt.Key_PageDown: | ||||
|             bar.setValue(bar.value() + (bar.pageStep() / 10)) | ||||
|         elif key == QtCore.Qt.Key_PageUp: | ||||
|             bar.setValue(bar.value() - bar.pageStep()) | ||||
|         elif key == QtCore.Qt.Key_PageDown: | ||||
|             bar.setValue(bar.value() + bar.pageStep()) | ||||
|         elif modifiers == QtCore.Qt.AltModifier and key == QtCore.Qt.Key_Home: | ||||
|             bar.setValue(bar.minimum()) | ||||
|         elif modifiers == QtCore.Qt.AltModifier and key == QtCore.Qt.Key_End: | ||||
|             bar.setValue(bar.maximum()) | ||||
|         elif key == QtCore.Qt.Key_Up: | ||||
|             self._history_navigate(-1) | ||||
|         elif key == QtCore.Qt.Key_Down: | ||||
|             self._history_navigate(1) | ||||
|         else: | ||||
|             QtGui.QLineEdit.keyPressEvent(self, event) | ||||
| 
 | ||||
|     def _input_return_pressed(self): | ||||
|         self._history.append(self.text().encode('utf-8')) | ||||
|         self._history_index = len(self._history) | ||||
|         self.textSent.emit(self.text()) | ||||
|         self.clear() | ||||
| 
 | ||||
|     def _history_navigate(self, direction): | ||||
|         if self._history: | ||||
|             self._history_index += direction | ||||
|             if self._history_index < 0: | ||||
|                 self._history_index = 0 | ||||
|                 return | ||||
|             if self._history_index > len(self._history) - 1: | ||||
|                 self._history_index = len(self._history) | ||||
|                 self.clear() | ||||
|                 return | ||||
|             self.setText(self._history[self._history_index]) | ||||
|  | @ -1,160 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # network.py - I/O with WeeChat/relay | ||||
| # | ||||
| # Copyright (C) 2011-2014 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/>. | ||||
| # | ||||
| 
 | ||||
| import struct | ||||
| import qt_compat | ||||
| QtCore = qt_compat.import_module('QtCore') | ||||
| QtNetwork = qt_compat.import_module('QtNetwork') | ||||
| import config | ||||
| 
 | ||||
| _PROTO_INIT_CMD  = ['init password=%(password)s'] | ||||
| _PROTO_SYNC_CMDS = ['(listbuffers) hdata buffer:gui_buffers(*) number,full_name,short_name,type,nicklist,title,local_variables', | ||||
|                     '(listlines) hdata buffer:gui_buffers(*)/own_lines/last_line(-%(lines)d)/data date,displayed,prefix,message', | ||||
|                     '(nicklist) nicklist', | ||||
|                     'sync', | ||||
|                     ''] | ||||
| 
 | ||||
| 
 | ||||
| class Network(QtCore.QObject): | ||||
|     """I/O with WeeChat/relay.""" | ||||
| 
 | ||||
|     statusChanged = qt_compat.Signal(str, str) | ||||
|     messageFromWeechat = qt_compat.Signal(QtCore.QByteArray) | ||||
| 
 | ||||
|     def __init__(self, *args): | ||||
|         QtCore.QObject.__init__(*(self,) + args) | ||||
|         self.status_disconnected = 'disconnected' | ||||
|         self.status_connecting = 'connecting...' | ||||
|         self.status_connected = 'connected' | ||||
|         self._server = None | ||||
|         self._port = None | ||||
|         self._ssl = None | ||||
|         self._password = None | ||||
|         self._lines = config.CONFIG_DEFAULT_RELAY_LINES | ||||
|         self._buffer = QtCore.QByteArray() | ||||
|         self._socket = QtNetwork.QSslSocket() | ||||
|         self._socket.connected.connect(self._socket_connected) | ||||
|         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) | ||||
|         if self._password: | ||||
|             self.send_to_weechat('\n'.join(_PROTO_INIT_CMD + _PROTO_SYNC_CMDS) | ||||
|                                  % {'password': str(self._password), | ||||
|                                     'lines': self._lines}) | ||||
| 
 | ||||
|     def _socket_error(self, error): | ||||
|         """Slot: socket error.""" | ||||
|         self.statusChanged.emit(self.status_disconnected, 'Failed, error: %s' % self._socket.errorString()) | ||||
| 
 | ||||
|     def _socket_read(self): | ||||
|         """Slot: data available on socket.""" | ||||
|         bytes = self._socket.readAll() | ||||
|         self._buffer.append(bytes) | ||||
|         while len(self._buffer) >= 4: | ||||
|             remainder = None | ||||
|             length = struct.unpack('>i', self._buffer[0:4])[0] | ||||
|             if len(self._buffer) < length: | ||||
|                 # partial message, just wait for end of message | ||||
|                 break | ||||
|             # more than one message? | ||||
|             if length < len(self._buffer): | ||||
|                 # save beginning of another message | ||||
|                 remainder = self._buffer[length:] | ||||
|                 self._buffer = self._buffer[0:length] | ||||
|             self.messageFromWeechat.emit(self._buffer) | ||||
|             if not self.is_connected(): | ||||
|                 return | ||||
|             self._buffer.clear() | ||||
|             if remainder: | ||||
|                 self._buffer.append(remainder) | ||||
| 
 | ||||
|     def _socket_disconnected(self): | ||||
|         """Slot: socket disconnected.""" | ||||
|         self._server = None | ||||
|         self._port = None | ||||
|         self._ssl = None | ||||
|         self._password = None | ||||
|         self.statusChanged.emit(self.status_disconnected, None) | ||||
| 
 | ||||
|     def is_connected(self): | ||||
|         return self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState | ||||
| 
 | ||||
|     def is_ssl(self): | ||||
|         return self._ssl | ||||
| 
 | ||||
|     def connect_weechat(self, server, port, ssl, password, lines): | ||||
|         self._server = server | ||||
|         try: | ||||
|             self._port = int(port) | ||||
|         except: | ||||
|             self._port = 0 | ||||
|         self._ssl = ssl | ||||
|         self._password = password | ||||
|         try: | ||||
|             self._lines = int(lines) | ||||
|         except: | ||||
|             self._lines = config.CONFIG_DEFAULT_RELAY_LINES | ||||
|         if self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState: | ||||
|             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) | ||||
| 
 | ||||
|     def disconnect_weechat(self): | ||||
|         if self._socket.state() != QtNetwork.QAbstractSocket.UnconnectedState: | ||||
|             if self._socket.state() == QtNetwork.QAbstractSocket.ConnectedState: | ||||
|                 self.send_to_weechat('quit\n') | ||||
|                 self._socket.waitForBytesWritten(1000) | ||||
|             else: | ||||
|                 self.statusChanged.emit(self.status_disconnected, None) | ||||
|             self._socket.abort() | ||||
| 
 | ||||
|     def send_to_weechat(self, message): | ||||
|         self._socket.write(message.encode('utf-8')) | ||||
| 
 | ||||
|     def desync_weechat(self): | ||||
|         self.send_to_weechat('desync\n') | ||||
| 
 | ||||
|     def sync_weechat(self): | ||||
|         self.send_to_weechat('\n'.join(_PROTO_SYNC_CMDS)) | ||||
| 
 | ||||
|     def status_icon(self, status): | ||||
|         icon = {self.status_disconnected: 'dialog-close.png', | ||||
|                 self.status_connecting: 'dialog-close.png', | ||||
|                 self.status_connected: 'dialog-ok-apply.png'} | ||||
|         return icon.get(status, '') | ||||
| 
 | ||||
|     def get_options(self): | ||||
|         return {'server': self._server, | ||||
|                 'port': self._port, | ||||
|                 'ssl': 'on' if self._ssl else 'off', | ||||
|                 'password': self._password, | ||||
|                 'lines': str(self._lines)} | ||||
|  | @ -1,54 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # | ||||
| # 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 | ||||
|  | @ -1,419 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # qweechat.py - WeeChat remote GUI using Qt toolkit | ||||
| # | ||||
| # Copyright (C) 2011-2014 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/>. | ||||
| # | ||||
| 
 | ||||
| # | ||||
| # This script requires WeeChat 0.3.7 or newer, running on local or remote host. | ||||
| # | ||||
| # History: | ||||
| # | ||||
| # 2011-05-27, Sébastien Helleu <flashcode@flashtux.org>: | ||||
| #     start dev | ||||
| # | ||||
| 
 | ||||
| import sys, struct, traceback | ||||
| import qt_compat | ||||
| QtCore = qt_compat.import_module('QtCore') | ||||
| QtGui = qt_compat.import_module('QtGui') | ||||
| import config | ||||
| import weechat.protocol as protocol | ||||
| from network import Network | ||||
| from connection import ConnectionDialog | ||||
| from buffer import BufferListWidget, Buffer | ||||
| from debug import DebugDialog | ||||
| from about import AboutDialog | ||||
| 
 | ||||
| NAME = 'QWeeChat' | ||||
| VERSION = '0.0.1-dev' | ||||
| AUTHOR = 'Sébastien Helleu' | ||||
| AUTHOR_MAIL= 'flashcode@flashtux.org' | ||||
| WEECHAT_SITE = 'http://weechat.org/' | ||||
| 
 | ||||
| # number of lines in buffer for debug window | ||||
| DEBUG_NUM_LINES = 50 | ||||
| 
 | ||||
| 
 | ||||
| class MainWindow(QtGui.QMainWindow): | ||||
|     """Main window.""" | ||||
| 
 | ||||
|     def __init__(self, *args): | ||||
|         QtGui.QMainWindow.__init__(*(self,) + args) | ||||
| 
 | ||||
|         self.config = config.read() | ||||
| 
 | ||||
|         self.resize(1000, 600) | ||||
|         self.setWindowTitle(NAME) | ||||
| 
 | ||||
|         self.debug_dialog = None | ||||
|         self.debug_lines = [] | ||||
| 
 | ||||
|         # network | ||||
|         self.network = Network() | ||||
|         self.network.statusChanged.connect(self.network_status_changed) | ||||
|         self.network.messageFromWeechat.connect(self.network_message_from_weechat) | ||||
| 
 | ||||
|         # list of buffers | ||||
|         self.list_buffers = BufferListWidget() | ||||
|         self.list_buffers.currentRowChanged.connect(self.buffer_switch) | ||||
| 
 | ||||
|         # default buffer | ||||
|         self.buffers = [Buffer()] | ||||
|         self.stacked_buffers = QtGui.QStackedWidget() | ||||
|         self.stacked_buffers.addWidget(self.buffers[0].widget) | ||||
| 
 | ||||
|         # splitter with buffers + chat/input | ||||
|         splitter = QtGui.QSplitter() | ||||
|         splitter.addWidget(self.list_buffers) | ||||
|         splitter.addWidget(self.stacked_buffers) | ||||
| 
 | ||||
|         self.setCentralWidget(splitter) | ||||
| 
 | ||||
|         if self.config.getboolean('look', 'statusbar'): | ||||
|             self.statusBar().visible = True | ||||
| 
 | ||||
|         # actions for menu and toolbar | ||||
|         actions_def = {'connect'        : ['network-connect.png', 'Connect to WeeChat', 'Ctrl+O', self.open_connection_dialog], | ||||
|                        'disconnect'     : ['network-disconnect.png', 'Disconnect from WeeChat', 'Ctrl+D', self.network.disconnect_weechat], | ||||
|                        'debug'          : ['edit-find.png', 'Debug console window', 'Ctrl+B', self.open_debug_dialog], | ||||
|                        'preferences'    : ['preferences-other.png', 'Preferences', 'Ctrl+P', self.open_preferences_dialog], | ||||
|                        'about'          : ['help-about.png', 'About', 'Ctrl+H', self.open_about_dialog], | ||||
|                        'save connection': ['document-save.png', 'Save connection configuration', 'Ctrl+S', self.save_connection], | ||||
|                        'quit'           : ['application-exit.png', 'Quit application', 'Ctrl+Q', self.close], | ||||
|                        } | ||||
|         self.actions = {} | ||||
|         for name, action in list(actions_def.items()): | ||||
|             self.actions[name] = QtGui.QAction(QtGui.QIcon('data/icons/%s' % action[0]), name.capitalize(), self) | ||||
|             self.actions[name].setStatusTip(action[1]) | ||||
|             self.actions[name].setShortcut(action[2]) | ||||
|             self.actions[name].triggered.connect(action[3]) | ||||
| 
 | ||||
|         # menu | ||||
|         self.menu = self.menuBar() | ||||
|         menu_file = self.menu.addMenu('&File') | ||||
|         menu_file.addActions([self.actions['connect'], self.actions['disconnect'], | ||||
|                               self.actions['preferences'], self.actions['save connection'], self.actions['quit']]) | ||||
|         menu_window = self.menu.addMenu('&Window') | ||||
|         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.setFixedHeight(20) | ||||
|         self.network_status.setFixedWidth(200) | ||||
|         self.network_status.setContentsMargins(0, 0, 10, 0) | ||||
|         self.network_status.setAlignment(QtCore.Qt.AlignRight) | ||||
|         if hasattr(self.menu, 'setCornerWidget'): | ||||
|             self.menu.setCornerWidget(self.network_status, QtCore.Qt.TopRightCorner) | ||||
|         self.network_status_set(self.network.status_disconnected, None) | ||||
| 
 | ||||
|         # toolbar | ||||
|         toolbar = self.addToolBar('toolBar') | ||||
|         toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) | ||||
|         toolbar.addActions([self.actions['connect'], self.actions['disconnect'], | ||||
|                             self.actions['debug'], self.actions['preferences'], | ||||
|                             self.actions['about'], self.actions['quit']]) | ||||
| 
 | ||||
|         self.buffers[0].widget.input.setFocus() | ||||
| 
 | ||||
|         # open debug dialog | ||||
|         if self.config.getboolean('look', 'debug'): | ||||
|             self.open_debug_dialog() | ||||
| 
 | ||||
|         # auto-connect to relay | ||||
|         if self.config.getboolean('relay', 'autoconnect'): | ||||
|             self.network.connect_weechat(self.config.get('relay', 'server'), | ||||
|                                          self.config.get('relay', 'port'), | ||||
|                                          self.config.getboolean('relay', 'ssl'), | ||||
|                                          self.config.get('relay', 'password'), | ||||
|                                          self.config.get('relay', 'lines')) | ||||
| 
 | ||||
|         self.show() | ||||
| 
 | ||||
|     def buffer_switch(self, index): | ||||
|         if index >= 0: | ||||
|             self.stacked_buffers.setCurrentIndex(index) | ||||
|             self.stacked_buffers.widget(index).input.setFocus() | ||||
| 
 | ||||
|     def buffer_input(self, full_name, text): | ||||
|         if self.network.is_connected(): | ||||
|             message = 'input %s %s\n' % (full_name, text) | ||||
|             self.network.send_to_weechat(message) | ||||
|             self.debug_display(0, '<==', message, forcecolor='#AA0000') | ||||
| 
 | ||||
|     def open_preferences_dialog(self): | ||||
|         pass # TODO | ||||
| 
 | ||||
|     def save_connection(self): | ||||
|         if self.network: | ||||
|             options = self.network.get_options() | ||||
|             for option in options.keys(): | ||||
|                 self.config.set('relay', option, options[option]) | ||||
| 
 | ||||
|     def debug_display(self, *args, **kwargs): | ||||
|         self.debug_lines.append((args, kwargs)) | ||||
|         self.debug_lines = self.debug_lines[-DEBUG_NUM_LINES:] | ||||
|         if self.debug_dialog: | ||||
|             self.debug_dialog.chat.display(*args, **kwargs) | ||||
| 
 | ||||
|     def open_debug_dialog(self): | ||||
|         if not self.debug_dialog: | ||||
|             self.debug_dialog = DebugDialog(self) | ||||
|             self.debug_dialog.input.textSent.connect(self.debug_input_text_sent) | ||||
|             self.debug_dialog.finished.connect(self.debug_dialog_closed) | ||||
|             self.debug_dialog.display_lines(self.debug_lines) | ||||
|             self.debug_dialog.chat.scroll_bottom() | ||||
| 
 | ||||
|     def debug_input_text_sent(self, text): | ||||
|         if self.network.is_connected(): | ||||
|             text = str(text) | ||||
|             pos = text.find(')') | ||||
|             if text.startswith('(') and pos >= 0: | ||||
|                 text = '(debug_%s)%s' % (text[1:pos], text[pos+1:]) | ||||
|             else: | ||||
|                 text = '(debug) %s' % text | ||||
|             self.debug_display(0, '<==', text, forcecolor='#AA0000') | ||||
|             self.network.send_to_weechat(text + '\n') | ||||
| 
 | ||||
|     def debug_dialog_closed(self, result): | ||||
|         self.debug_dialog = None | ||||
| 
 | ||||
|     def open_about_dialog(self): | ||||
|         messages = ['<b>%s</b> %s' % (NAME, VERSION), | ||||
|                     '© 2011-2014 %s <<a href="mailto:%s">%s</a>>' % (AUTHOR, AUTHOR_MAIL, AUTHOR_MAIL), | ||||
|                     '', | ||||
|                     'Running with %s' % ('PySide' if qt_compat.uses_pyside else 'PyQt4'), | ||||
|                     '', | ||||
|                     'WeeChat site: <a href="%s">%s</a>' % (WEECHAT_SITE, WEECHAT_SITE), | ||||
|                     ''] | ||||
|         self.about_dialog = AboutDialog(NAME, messages, self) | ||||
| 
 | ||||
|     def open_connection_dialog(self): | ||||
|         values = {} | ||||
|         for option in ('server', 'port', 'ssl', 'password', 'lines'): | ||||
|             values[option] = self.config.get('relay', option) | ||||
|         self.connection_dialog = ConnectionDialog(values, self) | ||||
|         self.connection_dialog.dialog_buttons.accepted.connect(self.connect_weechat) | ||||
| 
 | ||||
|     def connect_weechat(self): | ||||
|         self.network.connect_weechat(self.connection_dialog.fields['server'].text(), | ||||
|                                      self.connection_dialog.fields['port'].text(), | ||||
|                                      self.connection_dialog.fields['ssl'].isChecked(), | ||||
|                                      self.connection_dialog.fields['password'].text(), | ||||
|                                      int(self.connection_dialog.fields['lines'].text())) | ||||
|         self.connection_dialog.close() | ||||
| 
 | ||||
|     def network_status_changed(self, status, extra): | ||||
|         if self.config.getboolean('look', 'statusbar'): | ||||
|             self.statusBar().showMessage(status) | ||||
|         self.debug_display(0, '', status, forcecolor='#0000AA') | ||||
|         self.network_status_set(status, extra) | ||||
| 
 | ||||
|     def network_status_set(self, status, extra): | ||||
|         pal = self.network_status.palette() | ||||
|         if status == self.network.status_connected: | ||||
|             pal.setColor(self.network_status.foregroundRole(), QtGui.QColor('green')) | ||||
|         else: | ||||
|             pal.setColor(self.network_status.foregroundRole(), QtGui.QColor('#aa0000')) | ||||
|         ssl = ' (SSL)' if status != self.network.status_disconnected and self.network.is_ssl() else '' | ||||
|         self.network_status.setPalette(pal) | ||||
|         icon = self.network.status_icon(status) | ||||
|         if icon: | ||||
|             self.network_status.setText('<img src="data/icons/%s"> %s' % (icon, status.capitalize() + ssl)) | ||||
|         else: | ||||
|             self.network_status.setText(status.capitalize()) | ||||
|         if status == self.network.status_disconnected: | ||||
|             self.actions['connect'].setEnabled(True) | ||||
|             self.actions['disconnect'].setEnabled(False) | ||||
|         else: | ||||
|             self.actions['connect'].setEnabled(False) | ||||
|             self.actions['disconnect'].setEnabled(True) | ||||
| 
 | ||||
|     def network_message_from_weechat(self, message): | ||||
|         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)) | ||||
|             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') | ||||
|             self.debug_display(0, '', 'Message: %s' % message) | ||||
|             self.parse_message(message) | ||||
|         except: | ||||
|             print('Error while decoding message from WeeChat:\n%s' % traceback.format_exc()) | ||||
|             self.network.disconnect_weechat() | ||||
| 
 | ||||
|     def parse_message(self, message): | ||||
|         if message.msgid.startswith('debug'): | ||||
|             self.debug_display(0, '', '(debug message, ignored)') | ||||
|             return | ||||
|         if message.msgid == 'listbuffers': | ||||
|             for obj in message.objects: | ||||
|                 if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer': | ||||
|                     self.list_buffers.clear() | ||||
|                     while self.stacked_buffers.count() > 0: | ||||
|                         buf = self.stacked_buffers.widget(0) | ||||
|                         self.stacked_buffers.removeWidget(buf) | ||||
|                     self.buffers = [] | ||||
|                     for item in obj.value['items']: | ||||
|                         buf = self.create_buffer(item) | ||||
|                         self.insert_buffer(len(self.buffers), buf) | ||||
|                     self.list_buffers.setCurrentRow(0) | ||||
|                     self.buffers[0].widget.input.setFocus() | ||||
|         elif message.msgid in ('listlines', '_buffer_line_added'): | ||||
|             for obj in message.objects: | ||||
|                 lines = [] | ||||
|                 if obj.objtype == 'hda' and obj.value['path'][-1] == 'line_data': | ||||
|                     for item in obj.value['items']: | ||||
|                         if message.msgid == 'listlines': | ||||
|                             ptrbuf = item['__path'][0] | ||||
|                         else: | ||||
|                             ptrbuf = item['buffer'] | ||||
|                         index = [i for i, b in enumerate(self.buffers) if b.pointer() == ptrbuf] | ||||
|                         if index: | ||||
|                             lines.append((item['date'], item['prefix'], item['message'])) | ||||
|                 if message.msgid == 'listlines': | ||||
|                     lines.reverse() | ||||
|                 for line in lines: | ||||
|                     self.buffers[index[0]].widget.chat.display(*line) | ||||
|         elif message.msgid in ('_nicklist', 'nicklist'): | ||||
|             buffer_refresh = {} | ||||
|             for obj in message.objects: | ||||
|                 if obj.objtype == 'hda' and obj.value['path'][-1] == 'nicklist_item': | ||||
|                     group = '__root' | ||||
|                     for item in obj.value['items']: | ||||
|                         index = [i for i, b in enumerate(self.buffers) if b.pointer() == item['__path'][0]] | ||||
|                         if index: | ||||
|                             if not index[0] in buffer_refresh: | ||||
|                                 self.buffers[index[0]].nicklist = {} | ||||
|                             buffer_refresh[index[0]] = True | ||||
|                             if item['group']: | ||||
|                                 group = item['name'] | ||||
|                             self.buffers[index[0]].nicklist_add_item(group, item['group'], item['prefix'], item['name'], item['visible']) | ||||
|             for index in buffer_refresh: | ||||
|                 self.buffers[index].nicklist_refresh() | ||||
|         elif message.msgid == '_nicklist_diff': | ||||
|             buffer_refresh = {} | ||||
|             for obj in message.objects: | ||||
|                 if obj.objtype == 'hda' and obj.value['path'][-1] == 'nicklist_item': | ||||
|                     group = '__root' | ||||
|                     for item in obj.value['items']: | ||||
|                         index = [i for i, b in enumerate(self.buffers) if b.pointer() == item['__path'][0]] | ||||
|                         if index: | ||||
|                             buffer_refresh[index[0]] = True | ||||
|                             if item['_diff'] == ord('^'): | ||||
|                                 group = item['name'] | ||||
|                             elif item['_diff'] == ord('+'): | ||||
|                                 self.buffers[index[0]].nicklist_add_item(group, item['group'], item['prefix'], item['name'], item['visible']) | ||||
|                             elif item['_diff'] == ord('-'): | ||||
|                                 self.buffers[index[0]].nicklist_remove_item(group, item['group'], item['name']) | ||||
|                             elif item['_diff'] == ord('*'): | ||||
|                                 self.buffers[index[0]].nicklist_update_item(group, item['group'], item['prefix'], item['name'], item['visible']) | ||||
|             for index in buffer_refresh: | ||||
|                 self.buffers[index].nicklist_refresh() | ||||
|         elif message.msgid == '_buffer_opened': | ||||
|             for obj in message.objects: | ||||
|                 if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer': | ||||
|                     for item in obj.value['items']: | ||||
|                         buf = self.create_buffer(item) | ||||
|                         index = self.find_buffer_index_for_insert(item['next_buffer']) | ||||
|                         self.insert_buffer(index, buf) | ||||
|         elif message.msgid.startswith('_buffer_'): | ||||
|             for obj in message.objects: | ||||
|                 if obj.objtype == 'hda' and obj.value['path'][-1] == 'buffer': | ||||
|                     for item in obj.value['items']: | ||||
|                         index = [i for i, b in enumerate(self.buffers) if b.pointer() == item['__path'][0]] | ||||
|                         if index: | ||||
|                             index = index[0] | ||||
|                             if message.msgid == '_buffer_type_changed': | ||||
|                                 self.buffers[index].data['type'] = item['type'] | ||||
|                             elif message.msgid in ('_buffer_moved', '_buffer_merged', '_buffer_unmerged'): | ||||
|                                 buf = self.buffers[index] | ||||
|                                 buf.data['number'] = item['number'] | ||||
|                                 self.remove_buffer(index) | ||||
|                                 index2 = self.find_buffer_index_for_insert(item['next_buffer']) | ||||
|                                 self.insert_buffer(index2, buf) | ||||
|                             elif message.msgid == '_buffer_renamed': | ||||
|                                 self.buffers[index].data['full_name'] = item['full_name'] | ||||
|                                 self.buffers[index].data['short_name'] = item['short_name'] | ||||
|                             elif message.msgid == '_buffer_title_changed': | ||||
|                                 self.buffers[index].data['title'] = item['title'] | ||||
|                                 self.buffers[index].update_title() | ||||
|                             elif message.msgid.startswith('_buffer_localvar_'): | ||||
|                                 self.buffers[index].data['local_variables'] = item['local_variables'] | ||||
|                                 self.buffers[index].update_prompt() | ||||
|                             elif message.msgid == '_buffer_closing': | ||||
|                                 self.remove_buffer(index) | ||||
|         elif message.msgid == '_upgrade': | ||||
|             self.network.desync_weechat() | ||||
|         elif message.msgid == '_upgrade_ended': | ||||
|             self.network.sync_weechat() | ||||
| 
 | ||||
|     def create_buffer(self, item): | ||||
|         buf = Buffer(item) | ||||
|         buf.bufferInput.connect(self.buffer_input) | ||||
|         buf.widget.input.bufferSwitchPrev.connect(self.list_buffers.switch_prev_buffer) | ||||
|         buf.widget.input.bufferSwitchNext.connect(self.list_buffers.switch_next_buffer) | ||||
|         return buf | ||||
| 
 | ||||
|     def insert_buffer(self, index, buf): | ||||
|         self.buffers.insert(index, buf) | ||||
|         self.list_buffers.insertItem(index, '%d. %s' % (buf.data['number'], buf.data['full_name'].decode('utf-8'))) | ||||
|         self.stacked_buffers.insertWidget(index, buf.widget) | ||||
| 
 | ||||
|     def remove_buffer(self, index): | ||||
|         if self.list_buffers.currentRow == index and index > 0: | ||||
|             self.list_buffers.setCurrentRow(index - 1) | ||||
|         self.list_buffers.takeItem(index) | ||||
|         self.stacked_buffers.removeWidget(self.stacked_buffers.widget(index)) | ||||
|         self.buffers.pop(index) | ||||
| 
 | ||||
|     def find_buffer_index_for_insert(self, next_buffer): | ||||
|         index = -1 | ||||
|         if next_buffer == '0x0': | ||||
|             index = len(self.buffers) | ||||
|         else: | ||||
|             index = [i for i, b in enumerate(self.buffers) if b.pointer() == next_buffer] | ||||
|             if index: | ||||
|                 index = index[0] | ||||
|         if index < 0: | ||||
|             print('Warning: unable to find position for buffer, using end of list by default') | ||||
|             index = len(self.buffers) | ||||
|         return index | ||||
| 
 | ||||
|     def closeEvent(self, event): | ||||
|         self.network.disconnect_weechat() | ||||
|         if self.debug_dialog: | ||||
|             self.debug_dialog.close() | ||||
|         config.write(self.config) | ||||
|         QtGui.QMainWindow.closeEvent(self, event) | ||||
| 
 | ||||
| 
 | ||||
| app = QtGui.QApplication(sys.argv) | ||||
| app.setStyle(QtGui.QStyleFactory.create('Cleanlooks')) | ||||
| app.setWindowIcon(QtGui.QIcon('data/icons/weechat_icon_32.png')) | ||||
| main = MainWindow() | ||||
| sys.exit(app.exec_()) | ||||
|  | @ -1,171 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # color.py - remove/replace colors in WeeChat strings | ||||
| # | ||||
| # Copyright (C) 2011-2014 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/>. | ||||
| # | ||||
| 
 | ||||
| import re | ||||
| 
 | ||||
| RE_COLOR_ATTRS = r'[*!/_|]*' | ||||
| RE_COLOR_STD = r'(?:%s\d{2})' % RE_COLOR_ATTRS | ||||
| RE_COLOR_EXT = r'(?:@%s\d{5})' % RE_COLOR_ATTRS | ||||
| RE_COLOR_ANY = r'(?:%s|%s)' % (RE_COLOR_STD, RE_COLOR_EXT) | ||||
| # \x19: color code, \x1A: set attribute, \x1B: remove attribute, \x1C: reset | ||||
| RE_COLOR = re.compile(r'(\x19(?:\d{2}|F%s|B\d{2}|B@\d{5}|E|\\*%s(,%s)?|@\d{5}|b.|\x1C))|\x1A.|\x1B.|\x1C' | ||||
|                       % (RE_COLOR_ANY, RE_COLOR_ANY, RE_COLOR_ANY)) | ||||
| 
 | ||||
| TERMINAL_COLORS = \ | ||||
|     '000000cd000000cd00cdcd000000cdcd00cd00cdcde5e5e54d4d4dff000000ff00ffff000000ffff00ff00ffffffffff' \ | ||||
|     '00000000002a0000550000800000aa0000d4002a00002a2a002a55002a80002aaa002ad400550000552a005555005580' \ | ||||
|     '0055aa0055d400800000802a0080550080800080aa0080d400aa0000aa2a00aa5500aa8000aaaa00aad400d40000d42a' \ | ||||
|     '00d45500d48000d4aa00d4d42a00002a002a2a00552a00802a00aa2a00d42a2a002a2a2a2a2a552a2a802a2aaa2a2ad4' \ | ||||
|     '2a55002a552a2a55552a55802a55aa2a55d42a80002a802a2a80552a80802a80aa2a80d42aaa002aaa2a2aaa552aaa80' \ | ||||
|     '2aaaaa2aaad42ad4002ad42a2ad4552ad4802ad4aa2ad4d455000055002a5500555500805500aa5500d4552a00552a2a' \ | ||||
|     '552a55552a80552aaa552ad455550055552a5555555555805555aa5555d455800055802a5580555580805580aa5580d4' \ | ||||
|     '55aa0055aa2a55aa5555aa8055aaaa55aad455d40055d42a55d45555d48055d4aa55d4d480000080002a800055800080' \ | ||||
|     '8000aa8000d4802a00802a2a802a55802a80802aaa802ad480550080552a8055558055808055aa8055d480800080802a' \ | ||||
|     '8080558080808080aa8080d480aa0080aa2a80aa5580aa8080aaaa80aad480d40080d42a80d45580d48080d4aa80d4d4' \ | ||||
|     'aa0000aa002aaa0055aa0080aa00aaaa00d4aa2a00aa2a2aaa2a55aa2a80aa2aaaaa2ad4aa5500aa552aaa5555aa5580' \ | ||||
|     'aa55aaaa55d4aa8000aa802aaa8055aa8080aa80aaaa80d4aaaa00aaaa2aaaaa55aaaa80aaaaaaaaaad4aad400aad42a' \ | ||||
|     'aad455aad480aad4aaaad4d4d40000d4002ad40055d40080d400aad400d4d42a00d42a2ad42a55d42a80d42aaad42ad4' \ | ||||
|     'd45500d4552ad45555d45580d455aad455d4d48000d4802ad48055d48080d480aad480d4d4aa00d4aa2ad4aa55d4aa80' \ | ||||
|     'd4aaaad4aad4d4d400d4d42ad4d455d4d480d4d4aad4d4d40808081212121c1c1c2626263030303a3a3a4444444e4e4e' \ | ||||
|     '5858586262626c6c6c7676768080808a8a8a9494949e9e9ea8a8a8b2b2b2bcbcbcc6c6c6d0d0d0dadadae4e4e4eeeeee' | ||||
| 
 | ||||
| # WeeChat basic colors (color name, index in terminal colors) | ||||
| WEECHAT_BASIC_COLORS = (('default', 0), ('black', 0), ('darkgray', 8), ('red', 1), | ||||
|                         ('lightred', 9), ('green', 2), ('lightgreen', 10), ('brown', 3), | ||||
|                         ('yellow', 11), ('blue', 4), ('lightblue', 12), ('magenta', 5), | ||||
|                         ('lightmagenta', 13), ('cyan', 6), ('lightcyan', 14), ('gray', 7), | ||||
|                         ('white', 0)) | ||||
| 
 | ||||
| class Color(): | ||||
|     def __init__(self, color_options, debug=False): | ||||
|         self.color_options = color_options | ||||
|         self.debug = debug | ||||
| 
 | ||||
|     def _rgb_color(self, index): | ||||
|         color = TERMINAL_COLORS[index*6:(index*6)+6] | ||||
|         r = int(color[0:2], 16) * 0.85 | ||||
|         g = int(color[2:4], 16) * 0.85 | ||||
|         b = int(color[4:6], 16) * 0.85 | ||||
|         return '%02x%02x%02x' % (r, g, b) | ||||
| 
 | ||||
|     def _convert_weechat_color(self, color): | ||||
|         try: | ||||
|             index = int(color) | ||||
|             return '\x01(Fr%s)' % self.color_options[index] | ||||
|         except: | ||||
|             print('Error decoding WeeChat color "%s"' % color) | ||||
|             return '' | ||||
| 
 | ||||
|     def _convert_terminal_color(self, fg_bg, attrs, color): | ||||
|         try: | ||||
|             index = int(color) | ||||
|             return '\x01(%s%s#%s)' % (fg_bg, attrs, self._rgb_color(index)) | ||||
|         except: | ||||
|             print('Error decoding terminal color "%s"' % color) | ||||
|             return '' | ||||
| 
 | ||||
|     def _convert_color_attr(self, fg_bg, color): | ||||
|         extended = False | ||||
|         if color[0].startswith('@'): | ||||
|             extended = True | ||||
|             color = color[1:] | ||||
|         attrs = '' | ||||
|         keep_attrs = False | ||||
|         while color.startswith(('*', '!', '/', '_', '|')): | ||||
|             if color[0] == '|': | ||||
|                 keep_attrs = True | ||||
|             attrs += color[0] | ||||
|             color = color[1:] | ||||
|         if extended: | ||||
|             return self._convert_terminal_color(fg_bg, attrs, color) | ||||
|         try: | ||||
|             index = int(color) | ||||
|             return self._convert_terminal_color(fg_bg, attrs, WEECHAT_BASIC_COLORS[index][1]) | ||||
|         except: | ||||
|             print('Error decoding color "%s"' % color) | ||||
|             return '' | ||||
| 
 | ||||
|     def _attrcode_to_char(self, code): | ||||
|         codes = { '\x01': '*', '\x02': '!', '\x03': '/', '\x04': '_' } | ||||
|         return codes.get(code, '') | ||||
| 
 | ||||
|     def _convert_color(self, match): | ||||
|         color = match.group(0) | ||||
|         if color[0] == '\x19': | ||||
|             if color[1] == 'b': | ||||
|                 # bar code, ignored | ||||
|                 return '' | ||||
|             elif color[1] == '\x1C': | ||||
|                 # reset | ||||
|                 return '\x01(Fr)\x01(Br)' | ||||
|             elif color[1] in ('F', 'B'): | ||||
|                 # foreground or background | ||||
|                 return self._convert_color_attr(color[1], color[2:]) | ||||
|             elif color[1] == '*': | ||||
|                 # foreground with optional background | ||||
|                 items = color[2:].split(',') | ||||
|                 s = self._convert_color_attr('F', items[0]) | ||||
|                 if len(items) > 1: | ||||
|                     s += self._convert_color_attr('B', items[1]) | ||||
|                 return s | ||||
|             elif color[1] == '@': | ||||
|                 # direct ncurses pair number, ignored | ||||
|                 return '' | ||||
|             elif color[1] == 'E': | ||||
|                 # text emphasis, ignored | ||||
|                 return '' | ||||
|             if color[1:].isdigit(): | ||||
|                 return self._convert_weechat_color(int(color[1:])) | ||||
|             # color code | ||||
|             pass | ||||
|         elif color[0] == '\x1A': | ||||
|             # set attribute | ||||
|             return '\x01(+%s)' % self._attrcode_to_char(color[1]) | ||||
|         elif color[0] == '\x1B': | ||||
|             # remove attribute | ||||
|             return '\x01(-%s)' % self._attrcode_to_char(color[1]) | ||||
|         elif color[0] == '\x1C': | ||||
|             # reset | ||||
|             return '\x01(Fr)\x01(Br)' | ||||
|         # should never be executed! | ||||
|         return match.group(0) | ||||
| 
 | ||||
|     def _convert_color_debug(self, match): | ||||
|         group = match.group(0) | ||||
|         for code in (0x01, 0x02, 0x03, 0x04, 0x19, 0x1A, 0x1B): | ||||
|             group = group.replace(chr(code), '<x%02X>' % code) | ||||
|         return group | ||||
| 
 | ||||
|     def convert(self, text): | ||||
|         if not text: | ||||
|             return '' | ||||
|         if self.debug: | ||||
|             return RE_COLOR.sub(self._convert_color_debug, text) | ||||
|         else: | ||||
|             return RE_COLOR.sub(self._convert_color, text) | ||||
| 
 | ||||
| def remove(text): | ||||
|     """Remove colors in a WeeChat string.""" | ||||
|     if not text: | ||||
|         return '' | ||||
|     return re.sub(RE_COLOR, '', text) | ||||
|  | @ -1,325 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # protocol.py - decode binary messages received from WeeChat/relay | ||||
| # | ||||
| # Copyright (C) 2011-2014 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/>. | ||||
| # | ||||
| 
 | ||||
| # | ||||
| # For info about protocol and format of messages, please read document | ||||
| # "WeeChat Relay Protocol", available at:  http://weechat.org/doc/ | ||||
| # | ||||
| # History: | ||||
| # | ||||
| # 2011-11-23, Sébastien Helleu <flashcode@flashtux.org>: | ||||
| #     start dev | ||||
| # | ||||
| 
 | ||||
| import collections, struct, 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 WeechatObject: | ||||
|     def __init__(self, objtype, value, separator='\n'): | ||||
|         self.objtype = objtype; | ||||
|         self.value = value | ||||
|         self.separator = separator | ||||
|         self.indent = '  ' if separator == '\n' else '' | ||||
|         self.separator1 = '\n%s' % self.indent if separator == '\n' else '' | ||||
| 
 | ||||
|     def _str_value(self, v): | ||||
|         if type(v) is str and not v is None: | ||||
|             return '\'%s\'' % v | ||||
|         return str(v) | ||||
| 
 | ||||
|     def _str_value_hdata(self): | ||||
|         lines = ['%skeys: %s%s%spath: %s' % (self.separator1, str(self.value['keys']), self.separator, self.indent, str(self.value['path']))] | ||||
|         for i, item in enumerate(self.value['items']): | ||||
|             lines.append('  item %d:%s%s' % ((i + 1), self.separator, | ||||
|                                              self.separator.join(['%s%s: %s' % (self.indent * 2, key, self._str_value(value)) for key, value in item.items()]))) | ||||
|         return '\n'.join(lines) | ||||
| 
 | ||||
|     def _str_value_infolist(self): | ||||
|         lines = ['%sname: %s' % (self.separator1, self.value['name'])] | ||||
|         for i, item in enumerate(self.value['items']): | ||||
|             lines.append('  item %d:%s%s' % ((i + 1), self.separator, | ||||
|                                              self.separator.join(['%s%s: %s' % (self.indent * 2, key, self._str_value(value)) for key, value in item.items()]))) | ||||
|         return '\n'.join(lines) | ||||
| 
 | ||||
|     def _str_value_other(self): | ||||
|         return self._str_value(self.value) | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         self._obj_cb = {'hda': self._str_value_hdata, | ||||
|                         'inl': self._str_value_infolist, | ||||
|                         } | ||||
|         return '%s: %s' % (self.objtype, self._obj_cb.get(self.objtype, self._str_value_other)()) | ||||
| 
 | ||||
| 
 | ||||
| class WeechatObjects(list): | ||||
|     def __init__(self, separator='\n'): | ||||
|         self.separator = separator | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         return self.separator.join([str(obj) for obj in self]) | ||||
| 
 | ||||
| 
 | ||||
| class WeechatMessage: | ||||
|     def __init__(self, size, size_uncompressed, compression, uncompressed, msgid, objects): | ||||
|         self.size = size | ||||
|         self.size_uncompressed = size_uncompressed | ||||
|         self.compression = compression | ||||
|         self.uncompressed = uncompressed | ||||
|         self.msgid = msgid | ||||
|         self.objects = objects | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         if self.compression != 0: | ||||
|             return 'size: %d/%d (%d%%), id=\'%s\', objects:\n%s' % ( | ||||
|                 self.size, self.size_uncompressed, | ||||
|                 100 - ((self.size * 100) // self.size_uncompressed), | ||||
|                 self.msgid, self.objects) | ||||
|         else: | ||||
|             return 'size: %d, id=\'%s\', objects:\n%s' % (self.size, self.msgid, self.objects) | ||||
| 
 | ||||
| 
 | ||||
| class Protocol: | ||||
|     """Decode binary message received from WeeChat/relay.""" | ||||
| 
 | ||||
|     def __init__(self): | ||||
|         self._obj_cb = {'chr': self._obj_char, | ||||
|                         'int': self._obj_int, | ||||
|                         'lon': self._obj_long, | ||||
|                         'str': self._obj_str, | ||||
|                         'buf': self._obj_buffer, | ||||
|                         'ptr': self._obj_ptr, | ||||
|                         'tim': self._obj_time, | ||||
|                         'htb': self._obj_hashtable, | ||||
|                         'hda': self._obj_hdata, | ||||
|                         'inf': self._obj_info, | ||||
|                         'inl': self._obj_infolist, | ||||
|                         'arr': self._obj_array, | ||||
|                         } | ||||
| 
 | ||||
|     def _obj_type(self): | ||||
|         """Read type in data (3 chars).""" | ||||
|         if len(self.data) < 3: | ||||
|             self.data = '' | ||||
|             return '' | ||||
|         objtype = str(self.data[0:3]) | ||||
|         self.data = self.data[3:] | ||||
|         return objtype | ||||
| 
 | ||||
|     def _obj_len_data(self, length_size): | ||||
|         """Read length (1 or 4 bytes), then value with this length.""" | ||||
|         if len(self.data) < length_size: | ||||
|             self.data = '' | ||||
|             return None | ||||
|         if length_size == 1: | ||||
|             length = struct.unpack('B', self.data[0:1])[0] | ||||
|             self.data = self.data[1:] | ||||
|         else: | ||||
|             length = self._obj_int() | ||||
|         if length < 0: | ||||
|             return None | ||||
|         if length > 0: | ||||
|             value = self.data[0:length] | ||||
|             self.data = self.data[length:] | ||||
|         else: | ||||
|             value = '' | ||||
|         return value | ||||
| 
 | ||||
|     def _obj_char(self): | ||||
|         """Read a char in data.""" | ||||
|         if len(self.data) < 1: | ||||
|             return 0 | ||||
|         value = struct.unpack('b', self.data[0:1])[0] | ||||
|         self.data = self.data[1:] | ||||
|         return value | ||||
| 
 | ||||
|     def _obj_int(self): | ||||
|         """Read an integer in data (4 bytes).""" | ||||
|         if len(self.data) < 4: | ||||
|             self.data = '' | ||||
|             return 0 | ||||
|         value = struct.unpack('>i', self.data[0:4])[0] | ||||
|         self.data = self.data[4:] | ||||
|         return value | ||||
| 
 | ||||
|     def _obj_long(self): | ||||
|         """Read a long integer 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)) | ||||
| 
 | ||||
|     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) | ||||
| 
 | ||||
|     def _obj_buffer(self): | ||||
|         """Read a buffer in data (length on 4 bytes + data).""" | ||||
|         return self._obj_len_data(4) | ||||
| 
 | ||||
|     def _obj_ptr(self): | ||||
|         """Read a pointer in data (length on 1 byte + value as string).""" | ||||
|         value = self._obj_len_data(1) | ||||
|         if value is None: | ||||
|             return None | ||||
|         return '0x%s' % str(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)) | ||||
| 
 | ||||
|     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() | ||||
|         count = self._obj_int() | ||||
|         hashtable = WeechatDict() | ||||
|         for i in range(0, count): | ||||
|             key = self._obj_cb[type_keys]() | ||||
|             value = self._obj_cb[type_values]() | ||||
|             hashtable[key] = value | ||||
|         return hashtable | ||||
| 
 | ||||
|     def _obj_hdata(self): | ||||
|         """Read a hdata in data.""" | ||||
|         path = self._obj_str() | ||||
|         keys = self._obj_str() | ||||
|         count = self._obj_int() | ||||
|         list_path = path.split('/') | ||||
|         list_keys = keys.split(',') | ||||
|         keys_types = [] | ||||
|         dict_keys = WeechatDict() | ||||
|         for key in list_keys: | ||||
|             items = key.split(':') | ||||
|             keys_types.append(items) | ||||
|             dict_keys[items[0]] = items[1] | ||||
|         items = [] | ||||
|         for i in range(0, count): | ||||
|             item = WeechatDict() | ||||
|             item['__path'] = [] | ||||
|             pointers = [] | ||||
|             for p in range(0, len(list_path)): | ||||
|                 pointers.append(self._obj_ptr()) | ||||
|             for key, objtype in keys_types: | ||||
|                 item[key] = self._obj_cb[objtype]() | ||||
|             item['__path'] = pointers | ||||
|             items.append(item) | ||||
|         return {'path': list_path, | ||||
|                 'keys': dict_keys, | ||||
|                 'count': count, | ||||
|                 'items': items, | ||||
|                 } | ||||
| 
 | ||||
|     def _obj_info(self): | ||||
|         """Read an info in data.""" | ||||
|         name = self._obj_str() | ||||
|         value = self._obj_str() | ||||
|         return (name, value) | ||||
| 
 | ||||
|     def _obj_infolist(self): | ||||
|         """Read an infolist in data.""" | ||||
|         name = self._obj_str() | ||||
|         count_items = self._obj_int() | ||||
|         items = [] | ||||
|         for i in range(0, count_items): | ||||
|             count_vars = self._obj_int() | ||||
|             variables = WeechatDict() | ||||
|             for v in range(0, count_vars): | ||||
|                 var_name = self._obj_str() | ||||
|                 var_type = self._obj_type() | ||||
|                 var_value = self._obj_cb[var_type]() | ||||
|                 variables[var_name] = var_value | ||||
|             items.append(variables) | ||||
|         return {'name': name, 'items': items} | ||||
| 
 | ||||
|     def _obj_array(self): | ||||
|         """Read an array of values in data.""" | ||||
|         type_values = self._obj_type() | ||||
|         count_values = self._obj_int() | ||||
|         values = [] | ||||
|         for i in range(0, count_values): | ||||
|             values.append(self._obj_cb[type_values]()) | ||||
|         return values | ||||
| 
 | ||||
|     def decode(self, data, separator='\n'): | ||||
|         """Decode binary data and return list of objects.""" | ||||
|         self.data = data | ||||
|         size = len(self.data) | ||||
|         size_uncompressed = size | ||||
|         uncompressed = None | ||||
|         # uncompress data (if it is compressed) | ||||
|         compression = struct.unpack('b', self.data[4:5])[0] | ||||
|         if compression: | ||||
|             uncompressed = zlib.decompress(self.data[5:]) | ||||
|             size_uncompressed = len(uncompressed) + 5 | ||||
|             uncompressed = '%s%s%s' % (struct.pack('>i', size_uncompressed), struct.pack('b', 0), uncompressed) | ||||
|             self.data = uncompressed | ||||
|         else: | ||||
|             uncompressed = self.data[:] | ||||
|         # skip length and compression flag | ||||
|         self.data = self.data[5:] | ||||
|         # read id | ||||
|         msgid = self._obj_str() | ||||
|         if msgid is None: | ||||
|             msgid = '' | ||||
|         # read objects | ||||
|         objects = WeechatObjects(separator=separator) | ||||
|         while len(self.data) > 0: | ||||
|             objtype = self._obj_type() | ||||
|             value = self._obj_cb[objtype]() | ||||
|             objects.append(WeechatObject(objtype, value, separator=separator)) | ||||
|         return WeechatMessage(size, size_uncompressed, compression, uncompressed, msgid, objects) | ||||
| 
 | ||||
| 
 | ||||
| def hex_and_ascii(data, bytes_per_line=10): | ||||
|     """Convert a QByteArray to hex + ascii output.""" | ||||
|     num_lines = ((len(data) - 1) // bytes_per_line) + 1 | ||||
|     if num_lines == 0: | ||||
|         return '' | ||||
|     lines = [] | ||||
|     for i in range(0, num_lines): | ||||
|         str_hex = [] | ||||
|         str_ascii = [] | ||||
|         for char in data[i*bytes_per_line:(i*bytes_per_line)+bytes_per_line]: | ||||
|             byte = struct.unpack('B', char)[0] | ||||
|             str_hex.append('%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) | ||||
|  | @ -1,239 +0,0 @@ | |||
| #!/usr/bin/env python | ||||
| # -*- coding: utf-8 -*- | ||||
| # | ||||
| # testproto.py - command-line program for testing protocol WeeChat/relay | ||||
| # | ||||
| # Copyright (C) 2013-2014 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/>. | ||||
| # | ||||
| 
 | ||||
| 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 | ||||
| 
 | ||||
| 
 | ||||
| class TestProto: | ||||
| 
 | ||||
|     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: | ||||
|             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: | ||||
|             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.verbose > 0 | ||||
|                                 else ', ') | ||||
|             print('') | ||||
|             if self.args.verbose >= 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: | ||||
|             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, outr, exceptr = select.select([sys.stdin], [], [], 0) | ||||
|         if inr: | ||||
|             data = os.read(sys.stdin.fileno(), 4096) | ||||
|             if data: | ||||
|                 if not test.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, outr, exceptr = select.select([sys.stdin, self.sock], | ||||
|                                                    [], [], 1) | ||||
|                 for fd in inr: | ||||
|                     if fd == sys.stdin: | ||||
|                         buf = os.read(fd.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 = fd.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: | ||||
|             traceback.print_exc() | ||||
|             self.send('quit') | ||||
|         return 0 | ||||
| 
 | ||||
|     def __del__(self): | ||||
|         print('Closing connection with', self.address) | ||||
|         time.sleep(0.5) | ||||
|         self.sock.close() | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     # parse command line arguments | ||||
|     parser = argparse.ArgumentParser( | ||||
|         formatter_class=argparse.RawDescriptionHelpFormatter, | ||||
|         fromfile_prefix_chars='@', | ||||
|         description='Command-line program for testing protocol WeeChat/relay.', | ||||
|         epilog=''' | ||||
| Environment variable "TESTPROTO_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" | python {0} localhost 5000 | ||||
|   python {0} 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(sys.argv[0])) | ||||
|     parser.add_argument('-6', '--ipv6', action='store_true', | ||||
|                         help='connect using IPv6') | ||||
|     parser.add_argument('-v', '--verbose', action='count', default=0, | ||||
|                         help='verbose mode: long objects view ' | ||||
|                         '(-vv: display raw messages)') | ||||
|     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('TESTPROTO_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) | ||||
|     rc = test.mainloop() | ||||
|     del test | ||||
|     sys.exit(rc) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Sébastien Helleu
						Sébastien Helleu