| 6 | | from umitCore.I18N import _ |
| 7 | | |
| 8 | | from PMGui.Core.App import PMApp |
| 9 | | from Manager.PreferenceManager import Prefs |
| 10 | | from higwidgets.higanimates import HIGAnimatedBar |
| 11 | | |
| 12 | | class SniffRenderer(gtk.CellRendererText): |
| 13 | | __gtype_name__ = "SniffRenderer" |
| 14 | | |
| 15 | | def do_render(self, window, widget, back, cell, expose, flags): |
| 16 | | cr = window.cairo_create() |
| 17 | | cr.save() |
| 18 | | |
| 19 | | cr.set_line_width(0.5) |
| 20 | | cr.set_dash([1, 1], 1) |
| 21 | | cr.move_to(back.x, back.y + back.height) |
| 22 | | cr.line_to(back.x + back.width, back.y + back.height) |
| 23 | | cr.stroke() |
| 24 | | |
| 25 | | cr.restore() |
| 26 | | |
| 27 | | return gtk.CellRendererText.do_render(self, window, widget, back, cell, expose, flags) |
| 28 | | |
| 29 | | gobject.type_register(SniffRenderer) |
| 30 | | |
| 31 | | class SniffPage(gtk.VBox): |
| 32 | | COL_NO = 0 |
| 33 | | COL_TIME = 1 |
| 34 | | COL_SRC = 2 |
| 35 | | COL_DST = 3 |
| 36 | | COL_PROTO = 4 |
| 37 | | COL_INFO = 5 |
| 38 | | COL_COLOR = 6 |
| 39 | | COL_OBJECT = 7 |
| 40 | | |
| 41 | | def __init__(self, session): |
| 42 | | super(SniffPage, self).__init__(False, 4) |
| 43 | | |
| 44 | | self.session = session |
| 45 | | |
| 46 | | self.set_border_width(2) |
| 47 | | |
| 48 | | self.__create_toolbar() |
| 49 | | self.__create_view() |
| 50 | | |
| 51 | | self.statusbar = HIGAnimatedBar('', gtk.STOCK_INFO) |
| 52 | | self.pack_start(self.statusbar, False, False) |
| 53 | | |
| 54 | | self.show_all() |
| 55 | | |
| 56 | | self.use_colors = True |
| 57 | | |
| 58 | | # TODO: get from preference |
| 59 | | self.colors = ( |
| 60 | | gtk.gdk.color_parse('#FFFA99'), |
| 61 | | gtk.gdk.color_parse('#8DFF7F'), |
| 62 | | gtk.gdk.color_parse('#FFE3E5'), |
| 63 | | gtk.gdk.color_parse('#C797FF'), |
| 64 | | gtk.gdk.color_parse('#A0A0A0'), |
| 65 | | gtk.gdk.color_parse('#D6E8FF'), |
| 66 | | gtk.gdk.color_parse('#C2C2FF'), |
| 67 | | ) |
| 68 | | |
| 69 | | Prefs()['gui.maintab.sniffview.font'].connect(self.__modify_font) |
| 70 | | Prefs()['gui.maintab.sniffview.usecolors'].connect(self.__modify_colors) |
| 71 | | |
| 72 | | self.tree.get_selection().connect('changed', self.__on_selection_changed) |
| 73 | | self.filter.get_entry().connect('activate', self.__on_apply_filter) |
| 74 | | |
| 75 | | self.timeout_id = None |
| 76 | | self.reload() |
| 77 | | |
| 78 | | def __create_toolbar(self): |
| 79 | | self.toolbar = gtk.Toolbar() |
| 80 | | self.toolbar.set_style(gtk.TOOLBAR_ICONS) |
| 81 | | |
| 82 | | stocks = ( |
| 83 | | gtk.STOCK_REFRESH, |
| 84 | | gtk.STOCK_MEDIA_STOP, |
| 85 | | gtk.STOCK_SAVE, |
| 86 | | gtk.STOCK_SAVE_AS |
| 87 | | ) |
| 88 | | |
| 89 | | callbacks = ( |
| 90 | | self.__on_restart, |
| 91 | | self.__on_stop, |
| 92 | | self.__on_save, |
| 93 | | self.__on_save_as |
| 94 | | ) |
| 95 | | |
| 96 | | tooltips = ( |
| 97 | | _('Restart capturing'), |
| 98 | | _('Stop capturing'), |
| 99 | | _('Save packets'), |
| 100 | | _('Save packets as') |
| 101 | | ) |
| 102 | | |
| 103 | | for tooltip, stock, callback in zip(tooltips, stocks, callbacks): |
| 104 | | action = gtk.Action(None, None, tooltip, stock) |
| 105 | | action.connect('activate', callback) |
| 106 | | |
| 107 | | self.toolbar.insert(action.create_tool_item(), -1) |
| 108 | | |
| 109 | | self.filter = SniffFilter() |
| 110 | | |
| 111 | | item = gtk.ToolItem() |
| 112 | | item.add(self.filter) |
| 113 | | item.set_expand(True) |
| 114 | | |
| 115 | | self.toolbar.insert(item, -1) |
| 116 | | |
| 117 | | self.pack_start(self.toolbar, False, False) |
| 118 | | |
| 119 | | def __create_view(self): |
| 120 | | sw = gtk.ScrolledWindow() |
| 121 | | sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) |
| 122 | | sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) |
| 123 | | |
| 124 | | self.store = gtk.ListStore(object) |
| 125 | | self.tree = gtk.TreeView(self.store) |
| 126 | | |
| 127 | | # Create a filter function |
| 128 | | self.model_filter = self.store.filter_new() |
| 129 | | self.model_filter.set_visible_func(self.__filter_func) |
| 130 | | |
| 131 | | self.tree.set_model(self.model_filter) |
| 132 | | |
| 133 | | idx = 0 |
| 134 | | rend = SniffRenderer() |
| 135 | | |
| 136 | | for txt in (_('No.'), _('Time'), _('Source'), \ |
| 137 | | _('Destination'), _('Protocol'), _('Info')): |
| 138 | | |
| 139 | | col = gtk.TreeViewColumn(txt, rend) |
| 140 | | col.set_cell_data_func(rend, self.__cell_data_func, idx) |
| 141 | | self.tree.append_column(col) |
| 142 | | |
| 143 | | idx += 1 |
| 144 | | |
| 145 | | sw.add(self.tree) |
| 146 | | self.pack_start(sw) |
| 147 | | |
| 148 | | def __cell_data_func(self, col, cell, model, iter, idx): |
| 149 | | packet = model.get_value(iter, 0) |
| 150 | | |
| 151 | | if idx == self.COL_NO: |
| 152 | | cell.set_property('text', str(model.get_path(iter)[0] + 1)) |
| 153 | | elif idx == self.COL_TIME: |
| 154 | | cell.set_property('text', packet.get_time()) |
| 155 | | elif idx == self.COL_SRC: |
| 156 | | cell.set_property('text', packet.get_source()) |
| 157 | | elif idx == self.COL_DST: |
| 158 | | cell.set_property('text', packet.get_dest()) |
| 159 | | elif idx == self.COL_PROTO: |
| 160 | | cell.set_property('text', packet.get_protocol_str()) |
| 161 | | elif idx == self.COL_INFO: |
| 162 | | cell.set_property('text', packet.summary()) |
| 163 | | |
| 164 | | cell.set_property('cell-background-gdk', self.__get_color(packet)) |
| 165 | | |
| 166 | | def __modify_font(self, font): |
| 167 | | try: |
| 168 | | desc = pango.FontDescription(font) |
| 169 | | |
| 170 | | for col in self.tree.get_columns(): |
| 171 | | for rend in col.get_cell_renderers(): |
| 172 | | rend.set_property('font-desc', desc) |
| 173 | | |
| 174 | | self.__redraw_rows() |
| 175 | | except: |
| 176 | | # Block change |
| 177 | | |
| 178 | | return True |
| 179 | | |
| 180 | | def __modify_colors(self, value): |
| 181 | | self.use_colors = value |
| 182 | | self.tree.set_rules_hint(not self.use_colors) |
| 183 | | |
| 184 | | self.__redraw_rows() |
| 185 | | |
| 186 | | def __redraw_rows(self): |
| 187 | | def emit_row_changed(model, path, iter): |
| 188 | | model.row_changed(path, iter) |
| 189 | | |
| 190 | | self.store.foreach(emit_row_changed) |
| 191 | | |
| 192 | | def __get_color(self, packet): |
| 193 | | if self.use_colors: |
| 194 | | proto = packet.get_protocol_str() |
| 195 | | return self.colors[hash(proto) % len(self.colors)] |
| 196 | | else: |
| 197 | | return None |
| 198 | | |
| 199 | | def __update_tree(self): |
| 200 | | for packet in self.session.context.get_data(): |
| 201 | | self.store.append([packet]) |
| 202 | | |
| 203 | | # Scroll to end |
| 204 | | if getattr(self.session.context, 'auto_scroll', True): |
| 205 | | self.tree.scroll_to_cell(len(self.model_filter) - 1) |
| 206 | | |
| 207 | | alive = self.session.context.is_alive() |
| 208 | | |
| 209 | | if not alive: |
| 210 | | self.statusbar.label = "<b>%s</b>" % self.session.context.summary |
| 211 | | self.statusbar.image = gtk.STOCK_INFO |
| 212 | | self.statusbar.show() |
| 213 | | |
| 214 | | return alive |
| 215 | | |
| 216 | | # Public functions |
| 217 | | |
| 218 | | def populate(self, pktlist): |
| 219 | | for packet in pktlist: |
| 220 | | self.store.append([packet]) |
| 221 | | |
| 222 | | def clear(self): |
| 223 | | self.store.clear() |
| 224 | | |
| 225 | | def reload(self): |
| 226 | | for packet in self.session.context.get_data(): |
| 227 | | self.store.append([packet]) |
| 228 | | |
| 229 | | self.statusbar.label = "<b>%s</b>" % self.session.context.summary |
| 230 | | |
| 231 | | if self.timeout_id: |
| 232 | | gobject.source_remove(self.timeout_id) |
| 233 | | |
| 234 | | if isinstance(self.session.context, Backend.TimedContext): |
| 235 | | self.timeout_id = gobject.timeout_add(200, self.__update_tree) |
| 236 | | |
| 237 | | def save(self): |
| 238 | | return self.__on_save(None) |
| 239 | | |
| 240 | | def save_as(self): |
| 241 | | return self.__on_save_as(None) |
| 242 | | |
| 243 | | # Signals callbacks |
| 244 | | |
| 245 | | def __on_selection_changed(self, selection): |
| 246 | | model, iter = selection.get_selected() |
| 247 | | |
| 248 | | if not iter: |
| 249 | | return |
| 250 | | |
| 251 | | packet = model.get_value(iter, 0) |
| 252 | | |
| 253 | | if not packet: |
| 254 | | return |
| 255 | | |
| 256 | | nb = PMApp().main_window.get_tab("MainTab").session_notebook |
| 257 | | session = nb.get_current_session() |
| 258 | | |
| 259 | | if session: |
| 260 | | session.set_active_packet(packet) |
| 261 | | |
| 262 | | def __on_apply_filter(self, entry): |
| 263 | | self.model_filter.refilter() |
| 264 | | |
| 265 | | def __filter_func(self, model, iter): |
| 266 | | txt = self.filter.get_text() |
| 267 | | |
| 268 | | if not txt: |
| 269 | | return True |
| 270 | | |
| 271 | | packet = model.get_value(iter, 0) |
| 272 | | |
| 273 | | strs = ( |
| 274 | | str(model.get_path(iter)[0] + 1), |
| 275 | | packet.get_time(), |
| 276 | | packet.get_source(), |
| 277 | | packet.get_dest(), |
| 278 | | packet.get_protocol_str(), |
| 279 | | packet.summary() |
| 280 | | ) |
| 281 | | |
| 282 | | # TODO: implement a search engine like num: summary: ? |
| 283 | | |
| 284 | | for pattern in strs: |
| 285 | | if txt in pattern: |
| 286 | | return True |
| 287 | | |
| 288 | | return False |
| 289 | | |
| 290 | | def __on_stop(self, action): |
| 291 | | self.session.context.stop() |
| 292 | | def __on_restart(self, action): |
| 293 | | self.session.context.restart() |
| 294 | | |
| 295 | | def __on_save(self, action): |
| 296 | | if self.session.context.cap_file: |
| 297 | | return self.__save_packets(self.session.context.cap_file) |
| 298 | | else: |
| 299 | | return self.__on_save_as(None) |
| 300 | | |
| 301 | | def __on_save_as(self, action): |
| 302 | | dialog = gtk.FileChooserDialog(_('Save Pcap file to'), |
| 303 | | self.get_toplevel(), gtk.FILE_CHOOSER_ACTION_SAVE, |
| 304 | | buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, |
| 305 | | gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)) |
| 306 | | |
| 307 | | for name, pattern in ((_('Pcap files'), '*.pcap'), |
| 308 | | (_('Pcap gz files'), '*.pcap.gz'), |
| 309 | | (_('All files'), '*')): |
| 310 | | |
| 311 | | filter = gtk.FileFilter() |
| 312 | | filter.set_name(name) |
| 313 | | filter.add_pattern(pattern) |
| 314 | | dialog.add_filter(filter) |
| 315 | | |
| 316 | | ret = False |
| 317 | | if dialog.run() == gtk.RESPONSE_ACCEPT: |
| 318 | | ret = self.__save_packets(dialog.get_filename()) |
| 319 | | |
| 320 | | dialog.hide() |
| 321 | | dialog.destroy() |
| 322 | | |
| 323 | | return ret |
| 324 | | |
| 325 | | def __save_packets(self, fname): |
| 326 | | self.session.context.cap_file = fname |
| 327 | | ret = self.session.context.save() |
| 328 | | |
| 329 | | if ret: |
| 330 | | self.statusbar.image = gtk.STOCK_HARDDISK |
| 331 | | else: |
| 332 | | self.statusbar.image = gtk.STOCK_DIALOG_ERROR |
| 333 | | |
| 334 | | self.statusbar.label = "<b>%s</b>" % self.session.context.summary |
| 335 | | self.statusbar.start_animation(True) |
| 336 | | |
| 337 | | return ret |
| 338 | | |
| 339 | | class SniffFilter(gtk.HBox): |
| 340 | | __gtype_name__ = "SniffFilter" |
| | 24 | class FilterEntry(gtk.HBox): |
| | 25 | __gtype_name__ = "FilterEntry" |