Ticket #1659: 1659.patch

File 1659.patch, 9.8 KB (added by sascha_silbe, 14 years ago)

sugar-emulator: use vnc instead of Xephyr

  • src/jarabe/util/emulator.py

    From: Sascha Silbe <sascha@silbe.org>
    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 |  216 ++++++++++++++++++++++++++++++------------
     1 files changed, 154 insertions(+), 62 deletions(-)
    
    diff --git a/src/jarabe/util/emulator.py b/src/jarabe/util/emulator.py
    index c00f1db..e118cba 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
    18 import random
    1919import signal
    2020import subprocess
    2121import time
    2222from optparse import OptionParser
    2323
    2424import gtk
    25 import gobject
    2625
    2726from sugar import env
    2827
     28DEFAULT_DIMENSIONS = (800, 600)
     29_DEV_NULL = open(os.devnull, 'w')
    2930
    30 default_dimensions = (800, 600)
    31 def _run_xephyr(display, dpi, dimensions, fullscreen):
    32     cmd = [ 'Xephyr' ]
    33     cmd.append(':%d' % display)
    34     cmd.append('-ac')
     31server = None
    3532
    36     screen_size = (gtk.gdk.screen_width(), gtk.gdk.screen_height())
    3733
    38     if (not dimensions) and (fullscreen is None) and \
    39        (screen_size < default_dimensions) :
    40         # no forced settings, screen too small => fit screen
    41         fullscreen = True
    42     elif (not dimensions) :
    43         # screen is big enough or user has en/disabled fullscreen manually
    44         # => use default size (will get ignored for fullscreen)
    45         dimensions = '%dx%d' % default_dimensions
    46 
    47     if not dpi :
    48         dpi = gtk.settings_get_default().get_property('gtk-xft-dpi') / 1024
     34def _run_pipe(command, stdin=None):
     35    """Run a program with optional input and return output.
    4936
    50     if fullscreen :
    51         cmd.append('-fullscreen')
     37    Will raise CalledProcessError if program exits with non-zero return
     38    code.
     39    """
     40    pipe = subprocess.Popen(command, close_fds=True, stdin=subprocess.PIPE,
     41        stdout=subprocess.PIPE)
     42    stdout, stderr_ = pipe.communicate(stdin)
     43    if pipe.returncode:
     44        raise subprocess.CalledProcessError(pipe.returncode, command)
    5245
    53     if dimensions :
    54         cmd.append('-screen')
    55         cmd.append(dimensions)
     46    return stdout
    5647
    57     if dpi :
    58         cmd.append('-dpi')
    59         cmd.append('%d' % dpi)
    6048
    61     cmd.append('-noreset')
     49def _get_distro():
     50    """Run lsb_release to get distribution name.
     51    Return None if distribution name cannot be determined.
     52    """
     53    try:
     54        distro = ''.join(_run_pipe(['lsb_release', '-is'])).strip()
     55    except subprocess.CalledProcessError:
     56        return None
    6257
    63     pipe = subprocess.Popen(cmd)
     58    return distro or None
    6459
    65     os.environ['DISPLAY'] = ":%d" % (display)
    66     os.environ['SUGAR_EMULATOR_PID'] = str(pipe.pid)
    67     return pipe
    6860
     61def _run_xauth(display):
     62    """Set up Xauthority file for new display.
    6963
    70 def _check_server(display):
    71     result = subprocess.call(['xdpyinfo', '-display', ':%d' % display],
    72                              stdout=open(os.devnull, "w"),
    73                              stderr=open(os.devnull, "w"))
    74     return result == 0
     64    Returns name of Xauthority file."""
     65    # pylint: disable-msg=E1103,W0612
     66    xauth_file = os.environ.get('XAUTHORITY',
     67        os.path.expanduser('~/.Xauthority'))
     68    host = _run_pipe(['uname', '-n']).strip()
     69    cookie = _run_pipe(['mcookie']).strip()
     70    xauth_pipe = subprocess.Popen(['xauth', '-f', xauth_file],
     71        stdin=subprocess.PIPE, close_fds=True)
     72    xauth_pipe.communicate('add %(host)s:%(display)s . %(cookie)s\n'
     73        'add %(host)s/unix:%(display)s . %(cookie)s\n' % locals())
     74    return xauth_file
     75
     76
     77def _run_server(display, dpi, dimensions, fullscreen):
     78    """Start the X server."""
     79    screen_size = (gtk.gdk.screen_width(), gtk.gdk.screen_height())
     80
     81    if (not dimensions) and (fullscreen is None) and \
     82       (screen_size < DEFAULT_DIMENSIONS):
     83        dimensions = '%dx%d' % screen_size
     84    elif fullscreen:
     85        dimensions = '%dx%d' % screen_size
     86    elif not dimensions:
     87        dimensions = '%dx%d' % DEFAULT_DIMENSIONS
     88
     89    if not dpi:
     90        dpi = gtk.settings_get_default().get_property('gtk-xft-dpi') / 1024
     91
     92    xauth_file = _run_xauth(display)
     93    command = ['Xvnc', '-DisconnectClients', '-NeverShared', '-localhost',
     94        '-SecurityTypes', 'None', '-auth', xauth_file]
     95    if dimensions:
     96        command.append('-geometry')
     97        command.append(dimensions)
     98    if dpi:
     99        command.append('-dpi')
     100        command.append('%d' % dpi)
     101
     102    if _get_distro() == 'Ubuntu':
     103        # workaround for Ubuntu bug #110263
     104        command += ['-extension', 'XFIXES']
     105
     106    command.append(':%d' % (display, ))
     107    pipe = subprocess.Popen(command, close_fds=True)
     108    return pipe
    75109
    76110
    77111def _kill_pipe(pipe):
    78     """Terminate and wait for child process."""
     112    """Terminate and wait for child process (if any)."""
    79113    try:
    80114        os.kill(pipe.pid, signal.SIGTERM)
    81     except OSError:
    82         pass
     115    except OSError, exception:
     116        if exception.errno != errno.ESRCH:
     117            raise
    83118
    84     pipe.wait()
     119    try:
     120        pipe.wait()
     121    except OSError, exception:
     122        if exception.errno != errno.ECHILD:
     123            raise
     124
     125
     126def _wait_pipe(pipe):
     127    """Wait for pipe to finish.
    85128
     129    Retries on EINTR to work around <http://bugs.python.org/issue1068268>.
     130    """
     131    while pipe.returncode is None:
     132        try:
     133            pipe.wait()
     134        except OSError, exception:
     135            if exception.errno != errno.EINTR:
     136                raise
    86137
    87 def _start_xephyr(dpi, dimensions, fullscreen):
     138
     139def _start_server(dpi, dimensions, fullscreen):
     140    """Try running the X server on a free display."""
    88141    for display in range(30, 40):
    89142        if not _check_server(display):
    90             pipe = _run_xephyr(display, dpi, dimensions, fullscreen)
     143            pipe = _run_server(display, dpi, dimensions, fullscreen)
    91144
    92145            for i_ in range(10):
    93146                if _check_server(display):
    94                     return pipe
     147                    return display, pipe
    95148
    96149                time.sleep(0.1)
    97150
    98151            _kill_pipe(pipe)
    99152
    100153
    101 def _start_window_manager():
    102     cmd = ['metacity']
     154def _start_viewer(display, fullscreen):
     155    """Start the VNC viewer."""
     156    command = ['vncviewer']
     157    if fullscreen:
     158        command.append('-fullscreen')
    103159
    104     cmd.extend(['--no-force-fullscreen'])
     160    command.append(':%d' % (display, ))
     161    pipe = subprocess.Popen(command, close_fds=True)
     162    return pipe
     163
     164
     165def _check_server(display):
     166    """Check the X server on the given display is reachable."""
     167    result = subprocess.call(['xdpyinfo', '-display', ':%d' % (display, )],
     168                             stdout=_DEV_NULL, stderr=_DEV_NULL)
     169    return result == 0
    105170
    106     gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
    107171
    108 def _setup_env():
     172def _start_window_manager():
     173    """Start the window manager inside the new X server."""
     174    command = ['metacity', '--no-force-fullscreen']
     175    pipe_ = subprocess.Popen(command)
     176
     177
     178def _setup_env(display, scaling):
     179    """Set up environment variables for running Sugar inside the new X server.
     180    """
    109181    os.environ['SUGAR_EMULATOR'] = 'yes'
    110182    os.environ['GABBLE_LOGFILE'] = os.path.join(
    111183            env.get_profile_path(), 'logs', 'telepathy-gabble.log')
    def _setup_env(): 
    113185            env.get_profile_path(), 'logs', 'telepathy-salut.log')
    114186    os.environ['STREAM_ENGINE_LOGFILE'] = os.path.join(
    115187            env.get_profile_path(), 'logs', 'telepathy-stream-engine.log')
     188    os.environ['DISPLAY'] = ':%d' % (display, )
     189    if scaling:
     190        os.environ['SUGAR_SCALING'] = scaling
    116191
    117192
    118 def main():
    119     """Script-level operations"""
    120 
     193def _parse_args():
     194    """Parse command line arguments."""
    121195    parser = OptionParser()
    122196    parser.add_option('-d', '--dpi', dest='dpi', type="int",
    123197                      help='Emulator dpi')
    def main(): 
    131205    parser.add_option('-F', '--no-fullscreen', dest='fullscreen',
    132206                      action='store_false',
    133207                      help='Do not run emulator in fullscreen mode')
    134     (options, args) = parser.parse_args()
     208    return parser.parse_args()
     209
     210
     211def _sigchld_handler(number_, frame_):
     212    """Kill server when any (direct) child exits.
     213
     214    So closing the viewer will close the server as well."""
     215    if server.returncode is not None:
     216        return
     217
     218    signal.signal(signal.SIGCHLD, signal.SIG_DFL)
    135219
    136     _setup_env()
     220    print 'sugar-emulator: Child exited, shutting down server'
     221    _kill_pipe(server)
     222    return
    137223
    138     server = _start_xephyr(options.dpi, options.dimensions, options.fullscreen)
    139224
    140     if options.scaling:
    141         os.environ['SUGAR_SCALING'] = options.scaling
     225def main():
     226    """Script-level operations"""
     227    global server
     228
     229    options, args = _parse_args()
     230    display, server = _start_server(options.dpi, options.dimensions,
     231        options.fullscreen)
     232    viewer = _start_viewer(display, options.fullscreen)
     233    _setup_env(display, options.scaling)
    142234
    143235    command = ['dbus-launch', '--exit-with-session']
    144236
    def main(): 
    146238        command.append('sugar')
    147239    else:
    148240        _start_window_manager()
     241        command += args
    149242
    150         if args[0].endswith('.py'):
    151             command.append('python')
    152 
    153         command.append(args[0])
     243    signal.signal(signal.SIGCHLD, _sigchld_handler)
     244    session = subprocess.Popen(command, close_fds=True)
     245    _wait_pipe(session)
    154246
    155     subprocess.call(command)
     247    _kill_pipe(viewer)
    156248    _kill_pipe(server)