Ticket #1659: 0001-sugar-emulator-use-vnc-instead-of-Xephyr-1659.patch

File 0001-sugar-emulator-use-vnc-instead-of-Xephyr-1659.patch, 10.3 KB (added by tomeu, 11 years ago)

Rebased on top of HEAD

  • src/jarabe/util/emulator.py

    From 999584c965f9f4d95f4e44dfd2f4c5d9bbb8669e Mon Sep 17 00:00:00 2001
    From: Sascha Silbe <sascha@silbe.org>
    Date: Tue, 6 Jul 2010 12:09:36 +0200
    Subject: [PATCH] sugar-emulator: use vnc instead of Xephyr (#1659)
    
    Use VNC4 instead of Xephyr so non-US keyboards work fine.
    On Ubuntu it requires lsb_release for detecting Ubuntu so it can use a
    workaround for Ubuntu bug #110263. The workaround breaks other systems so we
    really need it to be conditional.
    
    Tested on:
    - Debian squeeze
    - Ubuntu Intrepid
    - Ubuntu Jaunty
    - Fedora 12
    
    Signed-off-by: Sascha Silbe <sascha@silbe.org>
    ---
     src/jarabe/util/emulator.py |  233 ++++++++++++++++++++++++++++--------------
     1 files changed, 155 insertions(+), 78 deletions(-)
    
    diff --git a/src/jarabe/util/emulator.py b/src/jarabe/util/emulator.py
    index 5a99dbe..31c354c 100644
    a b  
    1414# along with this program; if not, write to the Free Software
    1515# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    1616
     17import errno
    1718import os
    1819import signal
    1920import subprocess
    import time 
    2223from optparse import OptionParser
    2324
    2425import gtk
    25 import gobject
    2626
    2727from sugar import env
    2828
     29DEFAULT_DIMENSIONS = (800, 600)
     30_DEV_NULL = open(os.devnull, 'w')
    2931
    3032ERROR_NO_DISPLAY = 30
    3133ERROR_NO_SERVER = 31
    3234
    33 
    34 default_dimensions = (800, 600)
    35 def _run_xephyr(display, dpi, dimensions, fullscreen):
    36     cmd = [ 'Xephyr' ]
    37     cmd.append(':%d' % display)
    38     cmd.append('-ac')
    39 
     35server = None
     36
     37def _run_pipe(command, stdin=None):
     38    """Run a program with optional input and return output.
     39 
     40    Will raise CalledProcessError if program exits with non-zero return
     41    code.
     42    """
     43    pipe = subprocess.Popen(command, close_fds=True, stdin=subprocess.PIPE,
     44        stdout=subprocess.PIPE)
     45    stdout, stderr_ = pipe.communicate(stdin)
     46    if pipe.returncode:
     47        raise subprocess.CalledProcessError(pipe.returncode, command)
     48 
     49    return stdout
     50
     51
     52def _get_distro():
     53    """Run lsb_release to get distribution name.
     54    Return None if distribution name cannot be determined.
     55    """
     56    try:
     57        distro = ''.join(_run_pipe(['lsb_release', '-is'])).strip()
     58    except subprocess.CalledProcessError:
     59        return None
     60 
     61    return distro or None
     62
     63
     64def _run_xauth(display):
     65    """Set up Xauthority file for new display.
     66 
     67    Returns name of Xauthority file."""
     68    # pylint: disable-msg=E1103,W0612
     69    xauth_file = os.environ.get('XAUTHORITY',
     70        os.path.expanduser('~/.Xauthority'))
     71    host = _run_pipe(['uname', '-n']).strip()
     72    cookie = _run_pipe(['mcookie']).strip()
     73    xauth_pipe = subprocess.Popen(['xauth', '-f', xauth_file],
     74        stdin=subprocess.PIPE, close_fds=True)
     75    xauth_pipe.communicate('add %(host)s:%(display)s . %(cookie)s\n'
     76        'add %(host)s/unix:%(display)s . %(cookie)s\n' % locals())
     77    return xauth_file
     78
     79
     80def _run_server(display, dpi, dimensions, fullscreen):
     81    """Start the X server."""
    4082    screen_size = (gtk.gdk.screen_width(), gtk.gdk.screen_height())
    4183
    4284    if (not dimensions) and (fullscreen is None) and \
    43        (screen_size < default_dimensions) :
    44         # no forced settings, screen too small => fit screen
    45         fullscreen = True
    46     elif (not dimensions) :
    47         # screen is big enough or user has en/disabled fullscreen manually
    48         # => use default size (will get ignored for fullscreen)
    49         dimensions = '%dx%d' % default_dimensions
    50 
    51     if not dpi :
     85       (screen_size < DEFAULT_DIMENSIONS):
     86        dimensions = '%dx%d' % screen_size
     87    elif fullscreen:
     88        dimensions = '%dx%d' % screen_size
     89    elif not dimensions:
     90        dimensions = '%dx%d' % DEFAULT_DIMENSIONS
     91
     92    if not dpi:
    5293        dpi = gtk.settings_get_default().get_property('gtk-xft-dpi') / 1024
    5394
    54     if fullscreen :
    55         cmd.append('-fullscreen')
    56 
    57     if dimensions :
    58         cmd.append('-screen')
    59         cmd.append(dimensions)
    60 
    61     if dpi :
    62         cmd.append('-dpi')
    63         cmd.append('%d' % dpi)
    64 
    65     cmd.append('-noreset')
    66 
    67     try:
    68         pipe = subprocess.Popen(cmd)
    69 
    70     except OSError, exc:
    71         sys.stderr.write('Error executing server: %s\n' % (exc, ))
    72         return None
    73 
     95    xauth_file = _run_xauth(display)
     96    command = ['Xvnc', '-DisconnectClients', '-NeverShared', '-localhost',
     97        '-SecurityTypes', 'None', '-auth', xauth_file]
     98    if dimensions:
     99        command.append('-geometry')
     100        command.append(dimensions)
     101    if dpi:
     102        command.append('-dpi')
     103        command.append('%d' % dpi)
     104
     105    if _get_distro() == 'Ubuntu':
     106        # workaround for Ubuntu bug #110263
     107        command += ['-extension', 'XFIXES']
     108
     109    command.append(':%d' % (display, ))
     110    pipe = subprocess.Popen(command, close_fds=True)
    74111    return pipe
    75112
    76113
    77 def _check_server(display):
    78     result = subprocess.call(['xdpyinfo', '-display', ':%d' % display],
    79                              stdout=open(os.devnull, "w"),
    80                              stderr=open(os.devnull, "w"))
    81     return result == 0
    82 
    83 
    84114def _kill_pipe(pipe):
    85     """Terminate and wait for child process."""
     115    """Terminate and wait for child process (if any)."""
    86116    try:
    87117        os.kill(pipe.pid, signal.SIGTERM)
    88     except OSError:
    89         pass
    90 
    91     pipe.wait()
     118    except OSError, exception:
     119        if exception.errno != errno.ESRCH:
     120            raise
    92121
    93 
    94 def _start_xephyr(dpi, dimensions, fullscreen):
     122    try:
     123        pipe.wait()
     124    except OSError, exception:
     125        if exception.errno != errno.ECHILD:
     126            raise
     127
     128
     129def _wait_pipe(pipe):
     130    """Wait for pipe to finish.
     131 
     132    Retries on EINTR to work around <http://bugs.python.org/issue1068268>.
     133    """
     134    while pipe.returncode is None:
     135        try:
     136            pipe.wait()
     137        except OSError, exception:
     138            if exception.errno != errno.EINTR:
     139                raise
     140
     141
     142def _start_server(dpi, dimensions, fullscreen):
     143    """Try running the X server on a free display."""
    95144    for display in range(30, 40):
    96145        if not _check_server(display):
    97             pipe = _run_xephyr(display, dpi, dimensions, fullscreen)
     146            pipe = _run_server(display, dpi, dimensions, fullscreen)
    98147            if pipe is None:
    99148                return None, None
    100149
    101150            for i_ in range(10):
    102151                if _check_server(display):
    103                     return pipe, display
     152                    return display, pipe
    104153
    105154                time.sleep(0.1)
    106155
    def _start_xephyr(dpi, dimensions, fullscreen): 
    109158    return None, None
    110159
    111160
    112 def _start_window_manager():
    113     cmd = ['metacity']
     161def _start_viewer(display, fullscreen):
     162    """Start the VNC viewer."""
     163    command = ['vncviewer']
     164    if fullscreen:
     165        command.append('-fullscreen')
     166 
     167    command.append(':%d' % (display, ))
     168    pipe = subprocess.Popen(command, close_fds=True)
     169    return pipe
     170
    114171
    115     cmd.extend(['--no-force-fullscreen'])
     172def _check_server(display):
     173    """Check the X server on the given display is reachable."""
     174    result = subprocess.call(['xdpyinfo', '-display', ':%d' % (display, )],
     175                             stdout=_DEV_NULL, stderr=_DEV_NULL)
     176    return result == 0
    116177
    117     gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
    118178
    119 def _setup_env(display, scaling, emulator_pid):
     179def _start_window_manager():
     180    """Start the window manager inside the new X server."""
     181    command = ['metacity', '--no-force-fullscreen']
     182    pipe_ = subprocess.Popen(command)
     183
     184
     185def _setup_env(display, scaling):
     186    """Set up environment variables for running Sugar inside the new X server.
     187    """
    120188    os.environ['SUGAR_EMULATOR'] = 'yes'
    121189    os.environ['GABBLE_LOGFILE'] = os.path.join(
    122190            env.get_profile_path(), 'logs', 'telepathy-gabble.log')
    def _setup_env(display, scaling, emulator_pid): 
    125193    os.environ['STREAM_ENGINE_LOGFILE'] = os.path.join(
    126194            env.get_profile_path(), 'logs', 'telepathy-stream-engine.log')
    127195    os.environ['DISPLAY'] = ":%d" % (display)
    128     os.environ['SUGAR_EMULATOR_PID'] = emulator_pid
    129196
    130197    if scaling:
    131198        os.environ['SUGAR_SCALING'] = scaling
    132199
    133 def main():
    134     """Script-level operations"""
    135 
     200def _parse_args():
     201    """Parse command line arguments."""
    136202    parser = OptionParser()
    137203    parser.add_option('-d', '--dpi', dest='dpi', type="int",
    138204                      help='Emulator dpi')
    def main(): 
    146212    parser.add_option('-F', '--no-fullscreen', dest='fullscreen',
    147213                      action='store_false',
    148214                      help='Do not run emulator in fullscreen mode')
    149     (options, args) = parser.parse_args()
     215    return parser.parse_args()
    150216
    151     if not os.environ.get('DISPLAY'):
    152         sys.stderr.write('DISPLAY not set, cannot connect to host X server.\n')
    153         return ERROR_NO_DISPLAY
    154217
    155     server, display = _start_xephyr(options.dpi, options.dimensions,
    156                                     options.fullscreen)
    157     if server is None:
    158         sys.stderr.write('Failed to start server. Please check output above'
    159             ' for any error message.\n')
    160         return ERROR_NO_SERVER
     218def _sigchld_handler(number_, frame_):
     219    """Kill server when any (direct) child exits.
    161220
    162     _setup_env(display, options.scaling, str(server.pid))
     221    So closing the viewer will close the server as well."""
     222    if server.returncode is not None:
     223        return
     224
     225    signal.signal(signal.SIGCHLD, signal.SIG_DFL)
     226 
     227    print 'sugar-emulator: Child exited, shutting down server'
     228    _kill_pipe(server)
     229    return
     230
     231def main():
     232    """Script-level operations"""
     233    global server
     234
     235    options, args = _parse_args()
     236    display, server = _start_server(options.dpi, options.dimensions,
     237        options.fullscreen)
     238    viewer = _start_viewer(display, options.fullscreen)
     239    _setup_env(display, options.scaling)
    163240
    164241    command = ['dbus-launch', '--exit-with-session']
    165242
    def main(): 
    167244        command.append('sugar')
    168245    else:
    169246        _start_window_manager()
     247        command += args
    170248
    171         if args[0].endswith('.py'):
    172             command.append('python')
    173 
    174         command.append(args[0])
     249    signal.signal(signal.SIGCHLD, _sigchld_handler)
     250    session = subprocess.Popen(command, close_fds=True)
     251    _wait_pipe(session)
    175252
    176     subprocess.call(command)
     253    _kill_pipe(viewer)
    177254    _kill_pipe(server)