The branch, build-59a18b6e3b5d3f1dd8f07f26433d37fe5984a57d has been updated via 66a75dce88b6ea0d3a5e38d2beab3dcb3eddb46e (commit) from 04ed83455e600017764b185eeb5eda65220b9432 (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- ----------------------------------------------------------------------- Summary of changes: SessionProfile.py | 251 ++++++++++++++++++++++++++++ pyhoca-gui.py | 145 +++++++++++++++++ pyx2go_client.py | 313 +++++++++++++++++++++++++++++++++++ x2go | 1 + x2goLogon.py | 471 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1181 insertions(+) create mode 100644 SessionProfile.py create mode 100644 pyhoca-gui.py create mode 100644 pyx2go_client.py create mode 120000 x2go create mode 100644 x2goLogon.py The diff of changes is: diff --git a/SessionProfile.py b/SessionProfile.py new file mode 100644 index 0000000..48e2844 --- /dev/null +++ b/SessionProfile.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- +#----------------------------------------------------------------------------- +# Name: SessionProfile.py +# Purpose: Define the session info coming from an ini file +# +# Author: Dick Kniep +# +# Created: 2010/10/21 +# RCS-ID: $Id: SessionProfile.py,v 1.2 2010/10/21 20:07:56 dick Exp $ +# Copyright: (c) 2010 Lindix +#----------------------------------------------------------------------------- + +import os +import ConfigParser +from types import * + +class processINI: + """ + Base class to process the different ini files used in x2go. + Primarily used to standardize the content of the + ini file. + If entries are omitted in the file, they are filled with + default values, so the resulting objects always contain + the same fields + """ + def __init__(self, fileName): + self.writeconfig = False + self.iniConfig = ConfigParser.SafeConfigParser() + if fileName and os.path.exists(fileName): + self.iniConfig.read(fileName) + + + def fillDefaultsSection(self): + for section, sectionvalue in self.defaultValues.items(): + for key, value in sectionvalue.items(): + if self.iniConfig.has_option(section,key): continue + if not self.iniConfig.has_section(section): + self.iniConfig.add_section(section) + self.storeValueTypes(section, key, value) + + def updValue(self, section, key, value): + if not self.iniConfig.has_section(section): + self.iniConfig.add_section(section) + self.storeValueTypes(section, key, value) + self.writeconfig = True + + def storeValueTypes(self, section, key, value): + if type(value) is StringType: + self.iniConfig.set(section,key,value) + elif type(value) is BooleanType: + if value: + self.iniConfig.set(section,key,'1') + else: + self.iniConfig.set(section,key,'0') + else: + self.iniConfig.set(section,key,str(value)) + + def writeIni(self): + if self.writeconfig: + fd = open(self.fileName, 'wb') + self.iniConfig.write(fd) + fd.close() + self.writeconfig = False + + def getValue(self, section, key, getType=None): + if self.iniConfig.has_option(section, key): + if getType is None: + return self.iniConfig.get(section, key) + elif getType is BooleanType: + return self.iniConfig.getboolean(section, key) + elif getType is IntType: + return self.iniConfig.getint(section, key) + + def bldSessionObj(self): + """ + This routine flattens the items making them simple + object members + + Note, it assumes the option is unique within the config! + """ + for section in self.iniConfig.sections(): + for option in self.iniConfig.options(section): + if section in self.defaultValues and option in self.defaultValues[section]: + setattr(self, option, self.getValue(section, option, type(self.defaultValues[section][option]))) + else: + setattr(self, option, self.getValue(section, option)) + +class Settings(processINI): + """ + Settings object that contains all data that is generally necessary + """ + defaultValues = { 'LDAP':{'useldap':False,'port':389,'server':'localhost','port1':0,'port2':0}, \ + 'General':{'clientport':22,'autoresume':True}, \ + 'Authorization': {'newprofile':True,'suspend':True,'editprofile':True,'resume':True} + } + def __init__(self, fileName=None): + if fileName is None: + fileName = os.path.normpath(os.path.expanduser('~/.x2goclient/settings')) + processINI.__init__(self, fileName) + self.fillDefaultsSection() + self.bldSessionObj() + +class Printing(processINI): + """ + Printing object that contains all data that is necessary for printing + """ + defaultValues = { 'General': {'showdialog':False,'pdfview':False}, \ + 'print': {'startcmd':False,'command':'lpr','stdin':False,'ps':False}, \ + 'view': {'open':True,'command':'xpdf'}, \ + 'CUPS': {'defaultprinter':None, 'options':'@Invalid()'} + } + def __init__(self, fileName=None): + + if fileName is None: + fileName = os.path.normpath(os.path.expanduser('~/.x2goclient/printing')) + processINI.__init__(self, fileName) + self.fillDefaultsSection() + self.bldSessionObj() + + +class SessionProfiles(processINI): + """ + Session object that contains several sessionProfiles that contain all data necessary to open the connection with + an x2go server + """ + defaultValues = \ + {'speed':2,'pack':'16m-jpeg','quality':9,'fstunnel':True,'export':'"/home/dick/Documenten:1;"','fullscreen':False,'width':800,'height':600,'dpi':96, + 'setdpi':False,'usekbd':True,'layout':'us','type':'pc105/us','sound':False,'soundsystem':'pulse','startsoundsystem':True,'soundtunnel':True, + 'defsndport':True,'sndport':4713, 'printing':True,'name':None,'icon':':icons/128x128/x2gosession.png','host':None,'user':None, 'key':None, + 'sshport':22,'rootless':True,'applications':'dummy, WWWBROWSER, MAILCLIENT, OFFICE, TERMINAL','command':'dummy','rdpoptions':None, + 'rdpserver':None,'default':False,'connected':False} + def __init__(self, fileName=None): + if fileName is None: + fileName = os.path.normpath(os.path.expanduser('~/.x2goclient/sessions')) + processINI.__init__(self, fileName) + self.SessionProfiles = self.iniConfig.sections() + for section in self.SessionProfiles: + for key, sectionvalue in self.defaultValues.items(): + if not self.iniConfig.has_option(section,key): + self.storeValueTypes(section, key, sectionvalue) + + def getSection(self, section): + return self.iniConfig.items(section) + + +class SingleProfile: + def __init__(self, prof, profiles): + self.prof = prof + self.profiles = profiles + self.session_uuid = None + self.showConfigScreen = False + self.bldSessionObj() + if self.host is None: + self.showConfigScreen = True + + def bldSessionObj(self): + for option in self.profiles.iniConfig.options(self.prof): + if self.prof in self.profiles.defaultValues and option in self.profiles.defaultValues[self.prof]: + setattr(self, option, self.profiles.getValue(self.prof, option, type(self.profiles.defaultValues[self.prof][option]))) + else: + setattr(self, option, self.profiles.getValue(self.prof, option)) + + def updConfig(self): + for key, retType in self.fieldList: + self.updValue(self.prof, key, self.__dict__[key]) + + def Connect(self, parent, printing): + geometry = str(self.width) + 'x' + str(self.height) + self.c = x2go.X2goClient(logger=parent.liblogger) + self.session_uuid = c.register_session(self.host, port=self.sshport, + username=self.user, + password=self.password, + key_filename=self.key, + add_to_known_hosts=self.add_to_known_hosts, + profile_name = self.name, + session_type=self.session_type, + link=self.link, + geometry=geometry, + pack=self.pack, + cache_type=self.cache_type, + kblayout=self.layout, + kbtype=self.type, + snd_system=self.sound, + printing=self.printing, + print_action=printing.print_action, + print_action_args=printing.print_action_args, + cmd=printing.command) + self.c.session_start(session_uid) + self.profiles.updValue(self.prof, 'connected', True) + self.connected = True + self.profiles.writeIni() + + def Resume(self, parent, printing): + pass + + def DisConnect(self): + self.profiles.updValue(self.prof, 'connected', True) + self.connected = False + self.profiles.writeIni() + + def isAlive(self): + return True + +class x2goProfiles: + def __init__(self): + self.x2goprofs = [] + self.there_is_a_default = 0 + self.profiles = SessionProfiles() + for prof in self.profiles.SessionProfiles: + newSession = SingleProfile(prof, self.profiles) + if newSession.default: + self.x2goprofs.insert(0,newSession) + self.there_is_a_default += 1 + else: + self.x2goprofs.append(newSession) + if len(self.profiles.SessionProfiles): + self.current_profile = self.x2goprofs[0] + + def append(self, name): + self.x2goprofs.append(SingleProfile(name)) + + def writeIni(self): + for s in self.x2goprofs: + s.updConfig() + self.profiles.writeIni() + + def defaultAvailable(self): + return self.there_is_a_default == 1 + + def profileExists(self, name): + for profile in self.x2goprofs: + if profile.prof == name or profile.name == name: + self.current_profile = profile + return True + return False + + def runningSessions(self): + running = [] + for idx, profs in enumerate(self.profiles.iniConfig.sections()): + connected = self.profiles.getValue(profs, 'connected', getType='bool') + if running: + running.append(x2goprofs[idx]) + return running + + def suspendedSessions(self): + running = self.runningSessions() + suspended = [] + for idx, run in enumerate(running): + if running.isAlive(): continue + suspended.appended(run) + return suspended \ No newline at end of file diff --git a/pyhoca-gui.py b/pyhoca-gui.py new file mode 100644 index 0000000..67c1e70 --- /dev/null +++ b/pyhoca-gui.py @@ -0,0 +1,145 @@ +#----------------------------------------------------------------------------- +# Name: pyhoca-gui.py +# Purpose: Main program to start the python x2go gui +# +# Author: Dick Kniep +# +# Created: 2010/10/25 +# RCS-ID: $Id: PyApp1.py $ +# Copyright: (c) 2010 Lindix +#----------------------------------------------------------------------------- +#!/usr/bin/env python +#Boa:PyApp:main + +modules ={} + +try: + import wxversion + wxversion.select('2.8') +except: pass +import wx +import x2go +import x2goLogon +import argparse +import os.path +import os +import sys +import platform +import exceptions + +class notsupportedError(exceptions.StandardError): pass + +# version information +PROG_NAME = os.path.basename(sys.argv[0]) +PROG_PID = os.getpid() +VERSION="0.0.1" +VERSION_TEXT=""" +%s[%s] - an X2go GUI client written in Python +---------------------------------------------------------------------- +developed by Dick Kniep <dick.kniep@lindix.nl> + +VERSION: %s + +""" % (PROG_NAME, PROG_PID, VERSION) + +__author__ = "Dick J. Kniep" +__version__ = "$Id$" + +class pyhocagui(wx.App): + def __init__(self, args, logger, liblogger): + self.args = args + self.logger = logger + self.liblogger = liblogger + wx.App.__init__(self) + + def OnInit(self): + wx.BeginBusyCursor() + self.SetAppName('pyhoca, GUI Client of X2Go') + self.SetVendorName('Lindix BV. Almere (c) 2010') + x2goLogon.startX2Go(self) + wx.EndBusyCursor() + return True + + def OnExit(self): + pass + +# debug options... +debug_options = [ + {'args':['-d','--debug'], 'default': False, 'action': 'store_true', 'help': 'enable application debugging code', }, + {'args':['--libdebug'], 'default': False, 'action': 'store_true', 'help': 'enable debugging code of the underlying Python X2go module', }, + {'args':['--libdebug-sftpxfer'], 'default': False, 'action': 'store_true', 'help': 'enable debugging code of Python X2go\'s sFTP server code (very verbose, and even promiscuous)', }, + {'args':['-V', '--version'], 'default': False, 'action': 'store_true', 'help': 'print version number and exit', }, + ] +x2go_gui_options = [ + {'args':['-m','--minimized'], 'default':False, 'action': 'store_true', 'help': 'start x2go gui minimized on the taskbar',} , + {'args':['-u','--username'], 'default': None, 'help': 'username for the session (default: current user)', }, + {'args':['-p','--password'], 'default': None, 'help': 'user password (not recommended from the command line, default: not set)', }, + {'args':['-s','--profile'], 'default': None, 'help': 'The name of the sessionprofile to be used to make the connection', } + ] + +def parseargs(): + + global DEBUG + global print_action_args + + p = argparse.ArgumentParser(description='X2go Gui client implemented in (wx)Python.',\ + epilog="""Possible values for the --pack NX option are:""", \ + formatter_class=argparse.RawDescriptionHelpFormatter, \ + add_help=True, argument_default=None) + p_debugopts = p.add_argument_group('debug options') + p_guiopts = p.add_argument_group('PyHoca Gui options') + + for (p_group, opts) in ((p_guiopts, x2go_gui_options), (p_debugopts, debug_options)): + required = False + for opt in opts: + + args = opt['args'] + del opt['args'] + p_group.add_argument(*args, **opt) + + a = p.parse_args() + + logger = x2go.X2goLogger(tag='MAIN') + liblogger = x2go.X2goLogger() + + if a.debug: + logger.set_loglevel_debug() + + if a.libdebug: + liblogger.set_loglevel_debug() + + if a.libdebug_sftpxfer: + liblogger.enable_debug_sftpxfer() + + if a.version: + version() + + if a.username is None: + if platform.system() == 'Windows': + import win32api + a.username = win32api.GetUserName() + elif platform.system() == 'Linux': + import getpass + a.username = getpass.getuser() + elif platform.system() == 'Mac': + import getpass + a.username = getpass.getuser() + else: + raise notsupportedError('Platform %s is not supported' % platform.system()) + + return a, logger, liblogger + +# print version text and exit +def version(): + + sys.stderr.write ("%s\n" % VERSION_TEXT) + sys.exit(0) + + +def main(): + args, logger, liblogger = parseargs() + pgui = pyhocagui(args, logger, liblogger) + pgui.MainLoop() + +if __name__ == '__main__': + main() diff --git a/pyx2go_client.py b/pyx2go_client.py new file mode 100644 index 0000000..e4028d1 --- /dev/null +++ b/pyx2go_client.py @@ -0,0 +1,313 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" + Copyright (C) 2010 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> + + 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 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 this program; if not, write to the + Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + Contributors to the code of this programme: + Jörg Sawatzki <joerg.sawatzki@web.de> + Dick Kniep <dick.kniep@lindix.nl> + + + +""" + + +### +### module section +### + +import sys, os +import time +import argparse +import getpass +import x2go +import paramiko +from types import * +import SessionProfile + +# for debugging +import pprint + +# Python X2go provides the current local username (OS independent) +from x2go.utils import CURRENT_LOCAL_USER as current_user + +# version information +PROG_NAME = os.path.basename(sys.argv[0]) +PROG_PID = os.getpid() +VERSION="0.0.14" +VERSION_TEXT=""" +%s[%s] - an X2go client written in Python +---------------------------------------------------------------------- +developed by Mike Gabriel <m.gabriel@das-netzwerkteam.de> +and Dick Kniep <dick.kniep@lindix.nl> + +VERSION: %s + +""" % (PROG_NAME, PROG_PID, VERSION) + +class X2GoConnection: + def __init__(self): + + self.x2goSession = SessionProfile.x2goSession() + + # ,,constants'' needed for debugging + self.logger = x2go.X2goLogger(tag='MAIN') + self.liblogger = x2go.X2goLogger() + x2go_session_hash = '' + + # use current_home as user home dir + current_home = os.path.expanduser("~") + + # define and create known_hosts file (if not there) + ssh_known_hosts_filename = os.path.join(current_home, '.ssh', 'known_hosts') + if not os.path.isfile(ssh_known_hosts_filename): + self._touch_file(ssh_known_hosts_filename) + # define and create ssh_config file (if not there) + ssh_config_filename = os.path.join(current_home, '.ssh', 'config') + if not os.path.isfile(ssh_config_filename): + self._touch_file(ssh_config_filename) + + + ### + ### beginning of code + ### + + def _touch_file(self, filename): + + if not os.path.isfile(filename): + f = open(filename, 'w') + f.close() + + + # print version text and exit + def version(self): + + sys.stderr.write ("%s\n" % VERSION_TEXT) + sys.exit(0) + + + # sometimes we have to fail... + def runtime_error(self, m, parser=None, exitcode=-1): + if parser is not None: + parser.print_usage() + sys.stderr.write ("%s: error: %s\n" % (PROG_NAME, m)) + sys.exit(exitcode) + + + def makeConnection(self): + + self.logger('preparing requested X2go session', x2go.loglevel_NOTICE, ) + + x2goclient = x2go.X2goClient(logger=self.liblogger) + x2go_session_hash = x2goclient.register_session(self.x2goSession.server, port=int(self.x2goSession.remote_ssh_port), + username=self.x2goSession.username, + password=self.x2goSession.password, + key_filename=self.x2goSession.ssh_privkey, + add_to_known_hosts=self.x2goSession.add_to_known_hosts, + profile_name = 'Pyhoca-Client_Session', + session_type=self.x2goSession.session_type, + link=self.x2goSession.link, + geometry=self.x2goSession.geometry, + pack=self.x2goSession.pack, + cache_type='unix-kde', + kblayout=self.x2goSession.kbd_layout, + kbtype=self.x2goSession.kbd_type, + snd_system=self.x2goSession.sound, + printing=self.x2goSession.printing, + cmd=self.x2goSession.command) + + x2goclient.with_session(x2go_session_hash).load_host_keys(ssh_known_hosts_filename) + self.connected = False + force_password_auth = False + x2goclient.connect_session(x2go_session_hash, password=self.x2goSession.password, force_password_auth=force_password_auth) + self.connected = True + + def checkSSHConfig(self) + ### + ### initialize SSH context + ### + # check if SERVER is in .ssh/config file, extract information from there... + ssh_config = paramiko.SSHConfig() + ssh_config_fileobj = open(ssh_config_filename) + ssh_config.parse(ssh_config_fileobj) + ssh_host = ssh_config.lookup(a.server) + if ssh_host: + if 'hostname' in ssh_host.keys(): + a.server = ssh_host['hostname'] + if 'port' in ssh_host.keys(): + a.remote_ssh_port = ssh_host['port'] + ssh_config_fileobj.close() + # check if ssh priv key exists + if a.ssh_privkey and not os.path.isfile(a.ssh_privkey): + runtime_error("SSH private key %s file does not exist." % a.ssh_privkey, parser=p, exitcode=30) + if not a.ssh_privkey and os.path.isfile('%s/.ssh/id_rsa' % current_home): + a.ssh_privkey = '%s/.ssh/id_rsa' % current_home + if not a.ssh_privkey and os.path.isfile('%s/.ssh/id_dsa' % current_home): + a.ssh_privkey = '%s/.ssh/id_dsa' % current_home + + return p, a + + + def list_sessions(self, cli, s_hash): + # retrieve a session list + print + print "Available runing/suspended X2go sessions" + print "========================================" + print "Hostname: [%s]:%s" % cli.get_server(s_hash) + print "Username: %s" % cli.get_username(s_hash) + print + session_infos = cli.list_sessions(s_hash) + for session_info in session_infos.values(): + print "Session Name: %s" % session_info + print "-------------" + print "cookie: %s" % session_info.cookie + print "agent PID: %s" % session_info.agent_pid + print "display: %s" % session_info.display + print "status: %s" % session_info.status + print "graphic port: %s" % session_info.graphics_port + print "snd port: %s" % session_info.snd_port + print "sshfs port: %s" % session_info.sshfs_port + print "username: %s" % session_info.username + print "hostname: %s" % session_info.hostname + # TODO: turn into datetime object + print "create date: %s" % session_info.date_created + # TODO: turn into datetime object + print "suspended since: %s" % session_info.date_suspended + print + + + def clean_sessions(self, cli, s_hash): + # clean all sessions from X2go server + logger('cleaning up all running sessions from X2go server: %s' % self.x2goSession.server, x2go.loglevel_NOTICE, ) + cli.clean_sessions(s_hash) + + + def new_session(self, cli, s_hash): + # start a new session and run a command + logger('starting a new X2go session', x2go.loglevel_INFO, ) + logger('Command for new session is: %s' % self.x2goSession.command, x2go.loglevel_DEBUG, ) + cli.start_session(s_hash) + + + def resume_session(self, cli, s_hash): + # resume a running session + logger('resuming X2go session: %s' % self.x2goSession.resume, x2go.loglevel_INFO, ) + available_sessions = cli.list_sessions(s_hash) + if self.x2goSession.resume in available_sessions.keys(): + cli.resume_session(s_hash, self.x2goSession.resume) + else: + runtime_error('requested session not available on X2go server [%s]:%s.' % (self.x2goSession.server, self.x2goSession.remote_ssh_port), exitcode=20) + + + def suspend_session(self, cli, s_hash): + # send a suspend request to a session + logger('requesting X2go session suspend of session: %s' % self.x2goSession.suspend, x2go.loglevel_INFO, ) + available_sessions = cli.list_sessions(s_hash) + if self.x2goSession.suspend in available_sessions.keys(): + cli.suspend_session(s_hash, self.x2goSession.suspend) + else: + runtime_error('requested session not available on X2go server [%s]:%s.' % (self.x2goSession.server, self.x2goSession.remote_ssh_port), exitcode=21) + + def terminate_session(self, cli, s_hash): + # send a terminate request to a session + logger('requesting X2go session terminate of session: %s' % self.x2goSession.terminate, x2go.loglevel_INFO, ) + available_sessions = cli.list_sessions(s_hash) + if self.x2goSession.terminate in available_sessions.keys(): + cli.terminate_session(s_hash, self.x2goSession.terminate) + else: + runtime_error('requested session not available on X2go server [%s]:%s.' % (self.x2goSession.server, self.x2goSession.remote_ssh_port), exitcode=22) + + +if __name__ == '__main__': + + + if self.x2goSession.clean_sessions: + clean_sessions(x2goclient, x2go_session_hash) + + # go through the possible X2go client modes + if self.x2goSession.list_sessions: + # print a beautified session list for the user + list_sessions(x2goclient, x2go_session_hash) + sys.exit(0) + + if args.resume: + resume_session(x2goclient, x2go_session_hash) + + elif args.suspend: + suspend_session(x2goclient, x2go_session_hash) + + elif args.terminate: + terminate_session(x2goclient, x2go_session_hash) + + elif args.new: + new_session(x2goclient, x2go_session_hash) + + + if args.new or args.resume: + # give the session some time to come up... + # no CTRL-C is allowed during this phase... + i=0 + logger("give the X2go session some time to come up...", x2go.loglevel_NOTICE, ) + while i < args.time_to_wait: + time.sleep(1) + i+=1 + + if x2goclient.session_ok(x2go_session_hash): + + profile_name = x2goclient.get_profile_name(x2go_session_hash) + session_name = x2goclient.get_session_name(x2go_session_hash) + logger("X2go session is now running, the X2go client's profile name is: %s." % profile_name, x2go.loglevel_INFO, ) + logger("X2go session name is: %s." % session_name, x2go.loglevel_INFO, ) + logger("Press CTRL+C to suspend the running session.", x2go.loglevel_NOTICE, ) + try: + + session_duration = 0 + mounted = False + while x2goclient.session_ok(x2go_session_hash): + time.sleep(2) + session_duration +=2 + + if session_duration > 2 and not mounted and args.share_local_folders is not None: + if x2goclient.with_session(x2go_session_hash).get_transport().reverse_tunnels['sshfs'][1] is not None: + for _folder in args.share_local_folders: + x2goclient.share_local_folder(x2go_session_hash, _folder) + mounted = True + + # wait a little longer before telling the user what had happened + time.sleep(2) + + if x2goclient.has_terminated(x2go_session_hash): + logger("X2go session %s has terminated." % session_name, x2go.loglevel_NOTICE, ) + elif x2goclient.is_suspended(x2go_session_hash): + logger("X2go session %s has been suspended." % session_name, x2go.loglevel_NOTICE, ) + elif x2goclient.is_running(x2go_session_hash): + logger("X2go session %s has been moved to a different screen." % session_name, x2go.loglevel_NOTICE, ) + + except KeyboardInterrupt: + logger("Suspending X2go session %s." % session_name, x2go.loglevel_INFO, ) + x2goclient.suspend_session(x2go_session_hash) + # giving nxproxy's SSH tunnel some time to settle + time.sleep(2) + logger("X2go session %s has been suspended." % session_name, x2go.loglevel_NOTICE, ) + + sys.exit(0) + + except (KeyboardInterrupt, SystemExit), e: + x2go.x2go_cleanup(e) + diff --git a/x2go b/x2go new file mode 120000 index 0000000..3e76290 --- /dev/null +++ b/x2go @@ -0,0 +1 @@ +../../python/python-x2go/trunk/x2go \ No newline at end of file diff --git a/x2goLogon.py b/x2goLogon.py new file mode 100644 index 0000000..b5f28a6 --- /dev/null +++ b/x2goLogon.py @@ -0,0 +1,471 @@ +# -*- coding: utf-8 -*- +#----------------------------------------------------------------------------- +# Name: x2goLogon.py +# Purpose: display the Logon screen for x2go +# +# Author: Dick Kniep +# +# Created: 2010/10/21 +# Copyright: (c) Lindix BV 2010 +#----------------------------------------------------------------------------- + + +import wx +import time +import sys +import wx.lib.scrolledpanel as scrolled +import SessionProfile +import x2go +try: + from agw import knobctrl as KC + knobctrlavailable = True +except ImportError: # if it's not there locally, try the wxPython lib. + try: + import wx.lib.agw.knobctrl as KC + knobctrlavailable = True + except ImportError: + knobctrlavailable = False + +import wx.lib.sized_controls as sc + +class menuActions(wx.Menu): + def __init__(self, parent, settingsProfile, SessionProfiles): + OPENNEWMENUTXT = "Open new Session" + RUNNINGMENUTXT = "Running sessions" + SUSPENDMENUTXT = "Suspend session" + RESUMEMENUTXT = "Resume suspended session" + UPDATEPROFMNUTEXT = "Update Profile" + EXITMENUTXT = "E&xit sessions" + MENU_NEWSESSION = wx.NewId() + MENU_LISTSESSIONS = wx.NewId() + MENU_SUSPEND = wx.NewId() + MENU_RESUME = wx.NewId() + MENU_EDITSESSION = wx.NewId() + MENU_EXIT = wx.NewId() + wx.Menu.__init__(self) + parent.logger('settingsProfile.newProfile %s' % dir(settingsProfile), x2go.loglevel_INFO, ) + if settingsProfile.newprofile: + self.Append(MENU_NEWSESSION, OPENNEWMENUTXT) + self.Bind(wx.EVT_MENU, self.OnNewSession, id=MENU_NEWSESSION) + if SessionProfiles.runningSessions(): + self.Append(MENU_LISTSESSIONS, RUNNINGMENUTXT) + self.Bind(wx.EVT_MENU, self.OnListSessions, id=MENU_LISTSESSIONS) + self.Append(MENU_SUSPEND, SUSPENDMENUTXT) + self.Bind(wx.EVT_MENU, self.OnSuspend, id=MENU_SUSPEND) + if SessionProfiles.suspendedSessions() and settingsProfile.resume: + self.Append(MENU_RESUME, RESUMEMENUTXT) + self.Bind(wx.EVT_MENU, self.OnResume, id=MENU_RESUME) + if settingsProfile.editprofile: + self.AppendSeparator() + self.Append(MENU_EDITSESSION, UPDATEPROFMNUTEXT) + self.Bind(wx.EVT_MENU, self.OnUpdateProfile, id=MENU_EDITSESSION) + self.AppendSeparator() + self.Bind(wx.EVT_CLOSE, self.OnClose) + self.Bind(wx.EVT_MENU, self.OnExit, id=MENU_EXIT) + + def OnNewSession(self, evt): + pass + + def OnListSessions(self, evt): + pass + + def OnSuspend(self, evt): + pass + + def OnResume(self, evt): + pass + + def OnUpdateProfile(self, evt): + pass + + def OnExit(self, evt): + self.Close(True) + + def OnClose(self, evt): + self.parent.env.exitAllChildren() + self.Destroy() + +class LogonStatusBar(wx.StatusBar): + def __init__(self, parent): + wx.StatusBar.__init__(self, parent, -1) + font = self.GetFont() + font.SetPointSize(7) + self.SetFont(font) + self.SetFieldsCount(2) + self.SetStatusWidths([-1,200]) + self.parent = parent + + self.timer = wx.PyTimer(self.Notify) + self.timer.Start(1000) + self.Notify() + + def Notify(self): + self.SetStatusText(self.parent.Environment, 0) + self.SetStatusText(self.parent.StatusText, 1) + t = time.localtime(time.time()) + +class X2GoResumeSessions(sc.SizedFrame): + def __init__(self, parent, SessionProfiles, settingsProfile, printProfile, Iconize): + sc.SizedFrame.__init__(self, None, -1, "X2go Password entry", + style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) + +class X2GoPasswordScrn(sc.SizedFrame): + def __init__(self, parent, SessionProfiles, settingsProfile, printProfile, Iconize): + """ + Screen to enter the userid and password for the session + + if the screen is iconized, but an error occurs, the screen is displayed + and the user can enter another userid/password + """ + sc.SizedFrame.__init__(self, None, -1, "X2go Password entry", + style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) + self.CentreOnScreen() + + self.SetSize((350,250)) + + self.settingsProfile = settingsProfile + self.SessionProfiles = SessionProfiles + self.current_profile = SessionProfiles.current_profile + self.parent = parent + parent.logger('Password entry screen started', x2go.loglevel_INFO, ) + pane = self.GetContentsPane() + pane.SetSizerType("form") + pwScrn = self.passwordScrn(pane) + self.Main_MenuBar = wx.MenuBar() + self.SetMenuBar(self.Main_MenuBar) + self.Main_MenuBar.Append(menuActions(parent, settingsProfile, SessionProfiles), '&Connection') + self.tb = X2GoLogonTaskBarIcon(self) + if Iconize: + if self.IsIconized(): + self.Iconize(True) + if parent.args.password and parent.args.username and parent.args.profile and SessionProfiles.profileExists(parent.args.profile): + self.onConnect() + else: + Message(self, 'Not all credentials are available') + self.Iconize(False) + else: + self.Show(True) + + def passwordScrn(self, pnl): + wx.StaticText(pnl, -1, 'User'), + self.username_ctl = wx.TextCtrl(pnl, -1) + self.username_ctl.SetSizerProps(expand=True) + if hasattr(self.current_profile,'username'): + self.username_ctl.SetValue(self.SessionProfile.username) + else: + self.username_ctl.SetValue(self.parent.args.username) + + wx.StaticText(pnl, -1, 'Password'), + self.passwd_ctl = wx.TextCtrl(pnl, -1, style=wx.TE_PASSWORD) + + self.ConnectButton = wx.Button(pnl, -1, "Connect") + self.ConnectButton.Bind(wx.EVT_BUTTON, self.OnOK) + + self.CancelButton = wx.Button(pnl, -1, "Cancel") + self.CancelButton.Bind(wx.EVT_BUTTON, self.OnCancel) + #self.SetButtonSizer(self.CreateStdDialogButtonSizer(self.ConnectButton | self.CancelButton)) + #self.SetButtonSizer(self.CreateStdDialogButtonSizer(wx.OK| wx.CANCEL)) + + def OnOK(self, evt): + username = self.username_ctl.GetValue() + password = self.passwd_ctl.GetValue() + if len(username) == 0: + self.Message(self,'Userid is invalid') + return + if len(password) == 0: + self.Message(self,'Password is required') + return + self.current_profile.updValue('server','username',username) + self.current_profile.password = password + self.onConnect() + + def onConnect(self): + set_iconize = False + try: + connection = x2goConnect.X2GoConnection(self.current_profile) + connection.makeConnection(self.session, self.StatusText) + set_iconize = True + except x2go.AuthenticationException: + self.Message(self,'Userid/Password verification failed') + except x2go.BadHostKeyException: + self.Message(self,'SSH host key verification for remote host [%s]:%s failed' % (self.current_profile.host, self.current_profile.ssh_port )) + except x2go.SSHException, e: + self.Message(self,'Problem with ssh tunnel for host [%s]:%s failed' % (self.current_profile.host, self.current_profile.ssh_port )) + if (set_iconize and not self.IsIconized()) or (self.IsIconized() and not set_iconize): + self.Iconize(set_iconize) + return + + def OnCancel(self, evt): + self.Close + + +class Message: + def __init__(self, parent, message, extraCaption='', msgtype='error'): + if msgtype == 'warning': + msgstyle = wx.ICON_QUESTION|wx.STAY_ON_TOP + caption = 'Warning ' + elif msgtype == 'error': + msgstyle = wx.ICON_QUESTION|wx.STAY_ON_TOP + caption = 'Error ' + else: + msgstyle = wx.ICON_INFORMATION|wx.STAY_ON_TOP + caption = 'Information ' + caption += extraCaption + md = wx.MessageDialog(parent, message, caption=caption, style=msgstyle) + result = md.ShowModal() + self.retValue = False + if result == wx.OK: + self.retValue = True + md.Destroy() + +class X2GoChooseSessionScrn(sc.SizedDialog): + def __init__(self, parent, settingsProfile, printProfile): + parent.logger('Choose Session screen started', x2go.loglevel_INFO, ) + pass + +class X2GoSessionDefScrn(sc.SizedDialog): + SESSIONNOTEBOOK = wx.NewId() + TypeList = ['GNOME','LXDE','Connect to Windows terminal server','Custom desktop','Server security','Single application'] + CommandList = ['Internet Browser','Email client','OpenOffice','Terminal'] + ConnectList = ['Modem','ISDN','ADSL','WAN','LAN'] + CommpressionList = ['nopack','64k','256k','2m','256-rdp','32k-rdp','64k-rdp','16m-rdp','16m-rdp-compressed','64k-tight','2m-tight','4k-jpeg','16m-jpeg','64k-png-jpeg','16m-png-jpeg','64k-png','16m-png','16m-rgb','16m-rle'] + def __init__(self, parent, SessionProfiles, settingsProfile, printProfile): + + parent.logger('Session definition screen started', x2go.loglevel_INFO, ) + self.pnl = wx.Panel(self, -1) + if self.current_profile and self.current_profile.connected: + self.StatusText = self.current_profile.StatusText + self.Environment = self.current_profile.Environment + else: + self.StatusText = 'Not Connected' + self.Environment = '' + self.sb = LogonStatusBar(self) + self.SetStatusBar(self.sb) + + self.Main_MenuBar = wx.MenuBar() + self.SetMenuBar(self.Main_MenuBar) + + if self.current_profile and self.current_profile.showConfigScreen is False: + self.passwordScrn(self.pnl) + else: + self.defineSessionScrn(self.pnl) + + def defineSessionScrn(self, pnl, session): + self.SessionDefinition = wx.Notebook(id = SESSIONNOTEBOOK, name = 'SessionNoteBook', parent = pnl, pos = wx.Point(0, 0), style = 0) + self.SessionDefinition.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged, id=SESSIONNOTEBOOK) + self.HostPanel = wx.Panel(self.SessionDefinition, -1) + self.SessionDefinition.AddPage(self.HostPanel, 'Host') + self.ConnectionPanel = wx.Panel(self.SessionDefinition, -1) + self.SessionDefinition.AddPage(self.ConnectionPanel, 'Connection') + self.SessionPanel = wx.Panel(self.SessionDefinition, -1) + self.SessionDefinition.AddPage(self.SessionPanel, 'Session') + self.SharedFilesPanel = wx.Panel(self.SessionDefinition, -1) + self.SessionDefinition.AddPage(self.SharedFilesPanel, 'Shared Files') + + self.bldHostPanel(self.HostPanel) + self.bldConnPanel(self.ConnectionPanel) + self.bldSessionPanel(self.SessionPanel) + self.bldSharedFilesPanel(self.SharedFilesPanel) + + def bldHostPanel(self, pnl): + self.hostgbs = wx.GridBagSizer(6, 4) + self.hostgbs.Add((0,0), (1,1)) + self.hostgbs.Add( wx.StaticText(pnl, -1, 'Session Name'), + (1,0), flag=wx.ALIGN_LEFT | wx.ALL) + self.ctl_sessionName = wx.TextCtrl(pnl, -1) + self.ctl_sessionName.SetValue(self.current_profile.username) + self.hostgbs.Add(self.ctl_sessionName, (3,0)) + + self.hostgbs.Add( wx.StaticText(pnl, -1, 'Server address'), + (1,1), flag=wx.ALIGN_LEFT | wx.ALL) + self.ctl_serverName = wx.TextCtrl(pnl, -1) + self.ctl_serverName.SetValue(self.current_profile.servername) + self.hostgbs.Add(self.ctl_serverName, (3,1)) + + self.hostgbs.Add( wx.StaticText(pnl, -1, 'Server port'), + (1,2), flag=wx.ALIGN_LEFT | wx.ALL) + self.ctl_remote_ssh_port = wx.TextCtrl(pnl, -1) + self.ctl_remote_ssh_port.SetValue(self.current_profile.remote_ssh_port) + self.hostgbs.Add(self.ctl_remote_ssh_port, (3,2)) + + self.hostgbs.Add( wx.StaticText(pnl, -1, 'Login'), + (1,3), flag=wx.ALIGN_LEFT | wx.ALL) + self.ctl_username = wx.TextCtrl(pnl, -1) + self.ctl_username.SetValue(self.current_profile.username) + self.hostgbs.Add(self.ctl_username, (3,3)) + + self.hostgbs.Add( wx.StaticText(pnl, -1, 'Use RSA/DSA key for connection'), + (1,4), flag=wx.ALIGN_LEFT | wx.ALL) + self.ctl_username = wx.TextCtrl(pnl, -1) + self.ctl_username.SetValue(self.current_profile.username) + self.hostgbs.Add(self.ctl_username, (3,4)) + + self.hostgbs.Add( wx.StaticText(pnl, -1, 'Session type'), + (1,5), flag=wx.ALIGN_LEFT | wx.ALL) + self.cb = wx.ComboBox(self, 500, "KDE", (90, 50), (160, -1), self.TypeList, wx.CB_DROPDOWN|wx.CB_READONLY) + self.hostgbs.Add(self.cb, (3,5)) + + self.hostgbs.Add( wx.StaticText(pnl, -1, 'Command'), + (1,6), flag=wx.ALIGN_LEFT | wx.ALL) + self.cmdcb = wx.ComboBox(self, 500, "Path to executable", (90, 50), (160, -1), self.CommandList, wx.CB_DROPDOWN) + self.Bind(wx.EVT_TEXT_ENTER, self.onCommandEntered, self.cmdcb) + self.hostgbs.Add(self.cmdcb, (3,6)) + + + def bldConnectionPanel(self, pnl): + lefttopsizer_staticbox = wx.StaticBox(self.panel, -1, "Connection Speed") + mainsizer = wx.BoxSizer(wx.VERTICAL) + panelsizer = wx.BoxSizer(wx.HORIZONTAL) + rightsizer = wx.FlexGridSizer(6, 2, 10, 10) + leftsizer = wx.BoxSizer(wx.VERTICAL) + lefttopsizer = wx.StaticBoxSizer(lefttopsizer_staticbox, wx.VERTICAL) + + self.knob1 = KC.KnobCtrl(pnl, -1, size=(100, 100)) + self.knob1.SetTags(range(0, 4, 1)) + self.knob1.SetAngularRange(-45, 225) + knobValue = self.__findConnection(self.current_profile.link) + self.knob1.SetValue(knobValue) + self.knobtracker1 = wx.StaticText(self.panel, -1, self.ConnectList[knobValue]) + lefttopsizer.Add(self.knob1, 1, wx.ALL|wx.EXPAND, 5) + lefttopsizer.Add(self.knobtracker1, 0, wx.ALL) + leftbottomsizer_staticbox = wx.StaticBox(self.panel, -1, "Compression") + + self.conngbs = wx.GridBagSizer(6, 4) + self.conngbs.Add((0,0), (1,1)) + self.conngbs.Add( wx.StaticText(pnl, -1, 'Method'), + (1,0), flag=wx.ALIGN_LEFT | wx.ALL) + self.cbComp = wx.ComboBox(self, 500, "16m-jpeg-9", (90, 50), (160, -1), self.CommpressionList, wx.CB_DROPDOWN|wx.CB_READONLY) + self.conngbs.Add(self.cbComp, (3,4)) + + def bldSessionPanel(self, pnl): + self.sessgbs = wx.GridBagSizer(6, 4) + lefttopsizer_staticbox = wx.StaticBox(self.panel, -1, "Display") + + def bldSharedFilesPanel(self, pnl): + pass + + + def __findConnection(self, value): + for idx, c in enumerate(self.ConnectList): + if c.lower() == value.lower(): break + return idx + + def onCommandEntered(self, evt): + pass + + def getSession(self): + pass + + def OnUpdateProfile(self, evt): + pass + +class X2GoLogonTaskBarIcon(wx.TaskBarIcon): + + def __init__(self, frame=None): + wx.TaskBarIcon.__init__(self) + self.frame = frame + self.frame.parent.logger('Start TaskBarIcon', x2go.loglevel_INFO, ) + img = wx.Image('/usr/share/icons/hicolor/32x32/apps/x2goclient.png') + icon = self.MakeIcon(img) + self.SetIcon(icon, "x2go connect") + self.imgidx = 1 + + def CreatePopupMenu(self): + """ + This method is called by the base class when it needs to popup + the menu for the default EVT_RIGHT_DOWN event. Just create + the menu how you want it and return it from this function, + the base class takes care of the rest. + """ + menu = menuActions(self.frame.parent, self.frame.settingsProfile, self.frame.SessionProfiles) + return menu + + def MakeIcon(self, img): + """ + The various platforms have different requirements for the + icon size... + """ + if "wxMSW" in wx.PlatformInfo: + img = img.Scale(16, 16) + elif "wxGTK" in wx.PlatformInfo: + img = img.Scale(22, 22) + # wxMac can be any size upto 128x128, so leave the source img alone.... + icon = wx.IconFromBitmap(img.ConvertToBitmap() ) + return icon + + def OnTaskBarEditSession(self, evt): + if self.frame: + if self.frame.IsIconized(): + self.frame.Iconize(False) + if not self.frame.IsShown(): + self.frame.Show(True) + self.frame.Raise() + + + def OnTaskBarExitSessions(self, evt): + if self.frame: + wx.CallAfter(self.frame.Close) + + + def OnTaskBarResumeSession(self, evt): + names = [ "WXPdemo", "Mondrian", "Pencil", "Carrot" ] + name = names[self.imgidx] + + eImg = getattr(images, name) + self.imgidx += 1 + if self.imgidx >= len(names): + self.imgidx = 0 + + icon = self.MakeIcon(eImg.Image) + self.SetIcon(icon, "This is a new icon: " + name) + + + def OnTaskBarNewSession(self, evt): + self.RemoveIcon() + + +def checkArgs(parent, args, SessionProfiles): + if args.profile and not SessionProfiles.profileExists(args.profile): + Message(parent, 'Profile is entered, but is not known') + exit(0) + + +def startX2Go(parent): + """ + This routine starts processing + + If there is only one profile available, or if there is one (1) single + profile that has the default switch, the logon screen + can be shown immediately + """ + parent.logger('starting a new X2go GUI session', x2go.loglevel_INFO, ) + + printProfile = SessionProfile.Printing() + settingsProfile = SessionProfile.Settings() + SessionProfiles = SessionProfile.x2goProfiles() + noSessionsDefined = len(SessionProfiles.x2goprofs) == 0 + moreSessionsDefined = len(SessionProfiles.x2goprofs) > 1 + + checkArgs(parent, parent.args, SessionProfiles) + sessionsSuspended = SessionProfiles.suspendedSessions() + if len(sessionsSuspended) and settingsProfile.autoresume: + parent.logger('autoresume sessionsSuspended %s' % sessionsSuspended, x2go.loglevel_INFO, ) + for suspended in sessionsSuspended: + suspended.Resume() + elif len(sessionsSuspended): + parent.logger('Choose SuspendedSessions %s' % sessionsSuspended, x2go.loglevel_INFO, ) + X2GoResumeSessions(parent, sessionsSuspended, settingsProfile, printProfile) + else: + if parent.args.minimized: + parent.logger('Start minimized', x2go.loglevel_INFO, ) + pwScrn = X2GoPasswordScrn(parent, SessionProfiles, settingsProfile, printProfile, Iconize=True) + else: + if not noSessionsDefined and (not moreSessionsDefined or SessionProfiles.defaultAvailable()): + parent.logger('Start password entry normally', x2go.loglevel_INFO, ) + pwScrn = X2GoPasswordScrn(parent, SessionProfiles, settingsProfile, printProfile) + elif noSessionsDefined: + parent.logger('Start Profile Definition', x2go.loglevel_INFO, ) + defScrn = X2GoSessionDefScrn(parent, SessionProfiles, settingsProfile, printProfile) + else: + parent.logger('Start Profile choice', x2go.loglevel_INFO, ) + choiceScrn = X2GoChooseSessionScrn(parent, settingsProfile, printProfile) hooks/post-receive -- pyhoca-gui.git (Python X2Go Client (wxPython GUI)) This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "pyhoca-gui.git" (Python X2Go Client (wxPython GUI)).