#!/usr/bin/python #-*- coding: utf-8 -*- ########################################################################### # Remote assistance # # Copyright (C) 2012 Fotis Tsamis # # This program 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. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FINESS 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 this program. If not, see . # # On Debian GNU/Linux systems, the complete text of the GNU General # Public License can be found in `/usr/share/common-licenses/GPL". ########################################################################### def split_hostname(hostname): """Returns a 2-tuple containing the IP address and the port number extracted from hostname. If there is no port specified, 5500 will be returned. """ if ':' in hostname: hostname, port = hostname.split(':', 1) port = int(port) else: port = 5500 return (hostname, port) import sys import os if __name__ == '__main__' and len(sys.argv) > 1: os.execl("/bin/sh", "sh", "-c", """socat tcp:%s:%d SYSTEM:"sleep 1; exec screen -xRR ra$$",pty,stderr & exec screen -S ra$$""" % split_hostname(sys.argv[1])) import signal import subprocess import gtk import gobject import gettext gettext.install('epoptes', unicode=True) import locale locale.textdomain('epoptes') pid = os.getpid() #os.chdir('/usr/share/epoptes-client') class Support: def __init__(self): self.wTree = gtk.Builder() self.wTree.add_from_file('remote_assistance.ui') self.get = self.wTree.get_object signal.signal(signal.SIGUSR1, self.connected) self.wTree.connect_signals(self) self.status_icon = self.get('status_icon') self.status_label = self.get('status_label') self.method_combo = self.get('method_combobox') self.action_button = self.get('connect_button') self.status_box = self.get('status_hbox') self.action_signal_handler = self.action_button.connect('clicked', self.connect) self.spinner = gtk.Spinner() self.spinner.set_size_request(16, 16) self.status_box.pack_start(self.spinner, False) self.status_box.reorder_child(self.spinner, 0) self.retry_timeout_id = None self.retry = False self.retry_interval = 10 self.manually_stopped = False self.proc = None self.host = '' self.connected = False self.get('support_dialog').show() def connect(self, widget=None): """Gets called when the user presses Connect button""" self.host = self.get('host_entry').get_text().strip() ip, port = split_hostname(self.host) vnc = self.method_combo.get_active() == 0 if vnc: cmd = ['x11vnc', '-q', '-nopw', '-connect_or_exit', '%s:%d' % (ip, port), '-afteraccept', 'kill -USR1 %d' % pid] else: cmd = ['socat', 'tcp:%s:%d' % (ip, port), 'SYSTEM:"{ kill -USR1 %d; sleep 1; xterm -e screen -xRR ra$$; } & exec screen -S ra$$",pty,stderr' % pid] self.proc = subprocess.Popen(cmd) # Set the status as "Connecting" if self.retry_timeout_id: gobject.source_remove(self.retry_timeout_id) self.retry_timeout_id = None self.set_state('connecting') # Start polling the process every 1 second to see if it's still alive gobject.timeout_add(1000, self.poll_process) def disconnect(self, widget=None): self.manually_stopped = True if self.retry_timeout_id is not None: self.set_state('disconnected') gobject.source_remove(self.retry_timeout_id) self.retry_timeout_id = None if self.proc: self.proc.kill() self.set_action_button('connect') def poll_process(self): # if process has not terminated yet return True to continue the timeout if self.proc.poll() is None: return True else: # Check if it's a disconnect or a failure and call the correct signal if self.connected: self.disconnected() self.connected = False else: self.failed() self.proc = None return False #=== Process signal handlers start ===# def connected(self, signum, frame): """Gets called when the program receives SIGUSR1 (succesful connection)""" self.set_state('connected') def disconnected(self): """Gets called when the process is terminated but a successful connection has taken place""" self.set_state('disconnected') if self.retry and not self.manually_stopped: self.update_and_retry(_('Not connected'), self.retry_interval) self.set_action_button('disconnect') self.manually_stopped = True else: self.set_action_button('connect') def failed(self): """Gets called when the process is terminated but there wasn't any successful connection""" self.set_state('failed') #=== Process signal handlers end ===# def on_close_button_clicked(self, widget): """Gets called when the user clicks the Close button""" if self.proc: self.disconnect() exit() def on_dialog_delete_event(self, widget, event): self.on_close_button_clicked(None) def host_changed(self, widget): txt = widget.get_text().strip() if self.action_button.get_label() == gtk.STOCK_CONNECT: self.action_button.set_property('sensitive', len(txt) > 0) def set_state(self, state): self.stop_spinner() if state == 'connecting': self.start_spinner() self.status_label.set_text(_('Connecting to %s...') %self.host) self.set_action_button('disconnect') elif state == 'connected': self.connected = True if self.method_combo.get_active() == 0: mode = 'VNC' else: mode = 'console' self.status_icon.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_BUTTON) self.status_label.set_text(_('Connected to %s') % self.host) self.set_action_button('disconnect') elif state == 'disconnected': msg = _('Not connected') self.status_icon.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_BUTTON) self.status_label.set_text(msg) elif state == 'failed': msg = _('Failed to connect to %s') %self.host self.status_label.set_text(msg) self.status_icon.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_BUTTON) if self.retry: self.update_and_retry(msg, self.retry_interval) self.set_action_button('disconnect') else: self.set_action_button('connect') def update_and_retry(self, msg, interval): if interval == 0: self.connect() else: self.status_label.set_text(msg+' '+_('Retrying in %d...') % interval) self.retry_timeout_id = gobject.timeout_add(1000, self.update_and_retry, msg, interval-1) return False def set_action_button(self, mode): """Change the Connect button to Disconnect and vice versa, according to mode and set the correct signal.""" callback = None btn = self.action_button if mode == 'connect': btn.set_label(gtk.STOCK_CONNECT) callback = self.connect elif mode == 'disconnect': btn.set_label(gtk.STOCK_DISCONNECT) callback = self.disconnect if callback: btn.disconnect(self.action_signal_handler) self.action_signal_handler = btn.connect('clicked', callback) def start_spinner(self): """Replace the status icon with a running spinner""" self.status_icon.hide() self.spinner.show() self.spinner.start() def stop_spinner(self): """Replace the spinner with the status icon""" self.spinner.stop() self.spinner.hide() self.status_icon.show() def toggle_retry(self, widg): self.retry = not self.retry if __name__ == '__main__': Support() gtk.main()