1# Plugin by Sinan Nalkaya <sardok@gmail.com> 2# See LICENSE of Terminator package. 3 4""" logger.py - Terminator Plugin to log 'content' of individual 5terminals """ 6 7import os 8import sys 9from gi.repository import Gtk 10import terminatorlib.plugin as plugin 11from terminatorlib.translation import _ 12 13AVAILABLE = ['Logger'] 14 15class Logger(plugin.MenuItem): 16 """ Add custom command to the terminal menu""" 17 capabilities = ['terminal_menu'] 18 loggers = None 19 dialog_action = Gtk.FileChooserAction.SAVE 20 dialog_buttons = (_("_Cancel"), Gtk.ResponseType.CANCEL, 21 _("_Save"), Gtk.ResponseType.OK) 22 23 def __init__(self): 24 plugin.MenuItem.__init__(self) 25 if not self.loggers: 26 self.loggers = {} 27 28 def callback(self, menuitems, menu, terminal): 29 """ Add save menu item to the menu""" 30 vte_terminal = terminal.get_vte() 31 if vte_terminal not in self.loggers: 32 item = Gtk.MenuItem.new_with_mnemonic(_('Start _Logger')) 33 item.connect("activate", self.start_logger, terminal) 34 else: 35 item = Gtk.MenuItem.new_with_mnemonic(_('Stop _Logger')) 36 item.connect("activate", self.stop_logger, terminal) 37 item.set_has_tooltip(True) 38 item.set_tooltip_text("Saving at '" + self.loggers[vte_terminal]["filepath"] + "'") 39 menuitems.append(item) 40 41 def write_content(self, terminal, row_start, col_start, row_end, col_end): 42 """ Final function to write a file """ 43 content = terminal.get_text_range(row_start, col_start, row_end, col_end, 44 lambda *a: True) 45 content = content[0] 46 fd = self.loggers[terminal]["fd"] 47 # Don't write the last char which is always '\n' 48 fd.write(content[:-1]) 49 self.loggers[terminal]["col"] = col_end 50 self.loggers[terminal]["row"] = row_end 51 52 def save(self, terminal): 53 """ 'contents-changed' callback """ 54 last_saved_col = self.loggers[terminal]["col"] 55 last_saved_row = self.loggers[terminal]["row"] 56 (col, row) = terminal.get_cursor_position() 57 # Save only when buffer is nearly full, 58 # for the sake of efficiency 59 if row - last_saved_row < terminal.get_row_count(): 60 return 61 self.write_content(terminal, last_saved_row, last_saved_col, row, col) 62 63 def start_logger(self, _widget, Terminal): 64 """ Handle menu item callback by saving text to a file""" 65 savedialog = Gtk.FileChooserDialog(title=_("Save Log File As"), 66 action=self.dialog_action, 67 buttons=self.dialog_buttons) 68 savedialog.set_transient_for(_widget.get_toplevel()) 69 savedialog.set_do_overwrite_confirmation(True) 70 savedialog.set_local_only(True) 71 savedialog.show_all() 72 response = savedialog.run() 73 if response == Gtk.ResponseType.OK: 74 try: 75 logfile = os.path.join(savedialog.get_current_folder(), 76 savedialog.get_filename()) 77 fd = open(logfile, 'w+') 78 # Save log file path, 79 # associated file descriptor, signal handler id 80 # and last saved col,row positions respectively. 81 vte_terminal = Terminal.get_vte() 82 (col, row) = vte_terminal.get_cursor_position() 83 84 self.loggers[vte_terminal] = {"filepath":logfile, 85 "handler_id":0, "fd":fd, 86 "col":col, "row":row} 87 # Add contents-changed callback 88 self.loggers[vte_terminal]["handler_id"] = vte_terminal.connect('contents-changed', self.save) 89 except: 90 e = sys.exc_info()[1] 91 error = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, 92 Gtk.ButtonsType.OK, e.strerror) 93 error.set_transient_for(savedialog) 94 error.run() 95 error.destroy() 96 savedialog.destroy() 97 98 def stop_logger(self, _widget, terminal): 99 vte_terminal = terminal.get_vte() 100 last_saved_col = self.loggers[vte_terminal]["col"] 101 last_saved_row = self.loggers[vte_terminal]["row"] 102 (col, row) = vte_terminal.get_cursor_position() 103 if last_saved_col != col or last_saved_row != row: 104 # Save unwritten bufer to the file 105 self.write_content(vte_terminal, last_saved_row, last_saved_col, row, col) 106 fd = self.loggers[vte_terminal]["fd"] 107 fd.close() 108 vte_terminal.disconnect(self.loggers[vte_terminal]["handler_id"]) 109 del(self.loggers[vte_terminal]) 110