#!/usr/bin/python3 # This file is part of Epoptes, https://epoptes.org # Copyright 2012-2018 the Epoptes team, see AUTHORS. # SPDX-License-Identifier: GPL-3.0-or-later """ Reverse screen sharing using VNC or GNU screen and socat. """ import os import signal import subprocess from _common import gettext as _ from gi.repository import GLib, Gtk class RemoteAssistance: """Reverse screen sharing using VNC or GNU screen and socat.""" def __init__(self, from_main=False): self.builder = Gtk.Builder() self.builder.add_from_file('remote_assistance.ui') self.btn_action = self.builder.get_object('btn_action') self.cmb_method = self.builder.get_object('cmb_method') self.ent_host = self.builder.get_object('ent_host') self.icn_status = self.builder.get_object('icn_status') self.lbl_status = self.builder.get_object('lbl_status') self.spn_status = self.builder.get_object('spn_status') self.wnd_support = self.builder.get_object('wnd_support') self.from_main = from_main 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.builder.connect_signals(self) signal.signal(signal.SIGUSR1, self.on_sigusr1) self.wnd_support.show() def connect(self): """Handle btn_action clicked when it's in the Connect state.""" self.host = self.ent_host.get_text().strip() pid = os.getpid() share_terminal = self.cmb_method.get_active() != 0 if share_terminal: cmd = ['xterm', '-e', os.path.dirname(__file__) + '/share-terminal', self.host] subprocess.Popen(cmd) self.on_btn_close_clicked(None) return cmd = ['x11vnc', '-q', '-nopw', '-connect_or_exit', self.host, '-afteraccept', 'kill -USR1 {}'.format(pid)] self.proc = subprocess.Popen(cmd) # Set the status as "Connecting" if self.retry_timeout_id: GLib.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 GLib.timeout_add(1000, self.poll_process) def disconnect(self): """Handle btn_action clicked when it's in the Disconnect state.""" self.manually_stopped = True if self.retry_timeout_id is not None: self.set_state('disconnected') GLib.source_remove(self.retry_timeout_id) self.retry_timeout_id = None if self.proc: self.proc.kill() self.btn_action.set_label(Gtk.STOCK_CONNECT) def poll_process(self): """Continuously check if the spawned process has terminated.""" # If process has not terminated yet return True to continue the timeout if self.proc.poll() is None: return True else: # Check if it disconnected or failed and call the correct signal if self.connected: self.set_state('disconnected') if self.retry and not self.manually_stopped: self.update_and_retry(_('Not connected'), self.retry_interval) self.btn_action.set_label(Gtk.STOCK_DISCONNECT) self.manually_stopped = True else: self.btn_action.set_label(Gtk.STOCK_CONNECT) self.connected = False else: self.set_state('failed') self.proc = None return False def set_state(self, state): """Update the UI to match the new state.""" # Stop the spinner self.spn_status.stop() self.spn_status.hide() self.icn_status.show() if state == 'connecting': # Start the spinner self.icn_status.hide() self.spn_status.show() self.spn_status.start() self.lbl_status.set_text(_('Connecting to %s...') % self.host) self.btn_action.set_label(Gtk.STOCK_DISCONNECT) elif state == 'connected': self.connected = True self.icn_status.set_from_stock(Gtk.STOCK_YES, Gtk.IconSize.BUTTON) self.lbl_status.set_text(_('Connected to %s') % self.host) self.btn_action.set_label(Gtk.STOCK_DISCONNECT) elif state == 'disconnected': msg = _('Not connected') self.icn_status.set_from_stock(Gtk.STOCK_NO, Gtk.IconSize.BUTTON) self.lbl_status.set_text(msg) elif state == 'failed': msg = _('Failed to connect to %s') % self.host self.lbl_status.set_text(msg) self.icn_status.set_from_stock( Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.BUTTON) if self.retry: self.update_and_retry(msg, self.retry_interval) self.btn_action.set_label(Gtk.STOCK_DISCONNECT) else: self.btn_action.set_label(Gtk.STOCK_CONNECT) def update_and_retry(self, msg, interval): """Show a "Retrying in 10..." label, and then retry.""" if interval == 0: self.on_btn_action_clicked(None) else: self.lbl_status.set_text( msg + ' ' + _('Retrying in %d...') % interval) self.retry_timeout_id = GLib.timeout_add( 1000, self.update_and_retry, msg, interval - 1) return False def on_btn_action_clicked(self, _widget): """Handle btn_action clicked event.""" if self.btn_action.get_label() == Gtk.STOCK_CONNECT: self.connect() else: self.disconnect() def on_btn_close_clicked(self, _widget): """Handle btn_close clicked event.""" if self.proc: self.disconnect() if self.from_main: Gtk.main_quit() def on_chb_reconnect_toggled(self, _widget): """Handle chb_reconnect toggled event.""" self.retry = not self.retry def on_ent_host_changed(self, widget): """Handle ent_host changed event.""" txt = widget.get_text().strip() if self.btn_action.get_label() == Gtk.STOCK_CONNECT: self.btn_action.set_property('sensitive', txt != "") def on_sigusr1(self, _signum, _frame): """Handle SIGUSR1 (successful connection) event.""" self.set_state('connected') def on_wnd_support_delete_event(self, _widget, _event): """Handle wnd_support delete event.""" self.on_btn_close_clicked(None) def main(): """Run the module from the command line.""" RemoteAssistance(True) Gtk.main() if __name__ == '__main__': main()