[PATCH] Use VNC4 instead of Xephyr so non-US keyboards work fine #1659 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.

Sascha Silbe sascha at silbe.org
Wed May 19 18:02:12 EDT 2010


Tested by Sascha on:
- Debian Squeeze
- Ubuntu Intrepid
- Ubuntu Jaunty
- Fedora 12

Tested by James on:
- Debian Squeeze (with packages vnc4server and xtightvncviewer)

http://bugs.sugarlabs.org/ticket/1659

Signed-off-by: Sascha Silbe <sascha at silbe.org>
Reviewed-by: James Cameron <quozl at laptop.org>
Tested-by: James Cameron <quozl at laptop.org>
---
 src/jarabe/util/emulator.py |  215 +++++++++++++++++++++++++++++++------------
 1 files changed, 154 insertions(+), 61 deletions(-)

diff --git a/src/jarabe/util/emulator.py b/src/jarabe/util/emulator.py
index 607d840..e118cba 100644
--- a/src/jarabe/util/emulator.py
+++ b/src/jarabe/util/emulator.py
@@ -14,6 +14,7 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
+import errno
 import os
 import signal
 import subprocess
@@ -21,90 +22,162 @@ import time
 from optparse import OptionParser
 
 import gtk
-import gobject
 
 from sugar import env
 
+DEFAULT_DIMENSIONS = (800, 600)
+_DEV_NULL = open(os.devnull, 'w')
 
-default_dimensions = (800, 600)
-def _run_xephyr(display, dpi, dimensions, fullscreen):
-    cmd = [ 'Xephyr' ]
-    cmd.append(':%d' % display)
-    cmd.append('-ac') 
+server = None
 
-    screen_size = (gtk.gdk.screen_width(), gtk.gdk.screen_height())
 
-    if (not dimensions) and (fullscreen is None) and \
-       (screen_size < default_dimensions) :
-        # no forced settings, screen too small => fit screen
-        fullscreen = True
-    elif (not dimensions) :
-        # screen is big enough or user has en/disabled fullscreen manually
-        # => use default size (will get ignored for fullscreen)
-        dimensions = '%dx%d' % default_dimensions
-
-    if not dpi :
-        dpi = gtk.settings_get_default().get_property('gtk-xft-dpi') / 1024
+def _run_pipe(command, stdin=None):
+    """Run a program with optional input and return output.
 
-    if fullscreen :
-        cmd.append('-fullscreen')
+    Will raise CalledProcessError if program exits with non-zero return
+    code.
+    """
+    pipe = subprocess.Popen(command, close_fds=True, stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE)
+    stdout, stderr_ = pipe.communicate(stdin)
+    if pipe.returncode:
+        raise subprocess.CalledProcessError(pipe.returncode, command)
 
-    if dimensions :
-        cmd.append('-screen')
-        cmd.append(dimensions)
+    return stdout
 
-    if dpi :
-        cmd.append('-dpi')
-        cmd.append('%d' % dpi)
 
-    cmd.append('-noreset')
+def _get_distro():
+    """Run lsb_release to get distribution name.
+    Return None if distribution name cannot be determined.
+    """
+    try:
+        distro = ''.join(_run_pipe(['lsb_release', '-is'])).strip()
+    except subprocess.CalledProcessError:
+        return None
 
-    pipe = subprocess.Popen(cmd)
+    return distro or None
 
-    os.environ['DISPLAY'] = ":%d" % (display)
-    os.environ['SUGAR_EMULATOR_PID'] = str(pipe.pid)
-    return pipe
 
+def _run_xauth(display):
+    """Set up Xauthority file for new display.
 
-def _check_server(display):
-    result = subprocess.call(['xdpyinfo', '-display', ':%d' % display],
-                             stdout=open(os.devnull, "w"),
-                             stderr=open(os.devnull, "w"))
-    return result == 0
+    Returns name of Xauthority file."""
+    # pylint: disable-msg=E1103,W0612
+    xauth_file = os.environ.get('XAUTHORITY',
+        os.path.expanduser('~/.Xauthority'))
+    host = _run_pipe(['uname', '-n']).strip()
+    cookie = _run_pipe(['mcookie']).strip()
+    xauth_pipe = subprocess.Popen(['xauth', '-f', xauth_file],
+        stdin=subprocess.PIPE, close_fds=True)
+    xauth_pipe.communicate('add %(host)s:%(display)s . %(cookie)s\n'
+        'add %(host)s/unix:%(display)s . %(cookie)s\n' % locals())
+    return xauth_file
+
+
+def _run_server(display, dpi, dimensions, fullscreen):
+    """Start the X server."""
+    screen_size = (gtk.gdk.screen_width(), gtk.gdk.screen_height())
+
+    if (not dimensions) and (fullscreen is None) and \
+       (screen_size < DEFAULT_DIMENSIONS):
+        dimensions = '%dx%d' % screen_size
+    elif fullscreen:
+        dimensions = '%dx%d' % screen_size
+    elif not dimensions:
+        dimensions = '%dx%d' % DEFAULT_DIMENSIONS
+
+    if not dpi:
+        dpi = gtk.settings_get_default().get_property('gtk-xft-dpi') / 1024
+
+    xauth_file = _run_xauth(display)
+    command = ['Xvnc', '-DisconnectClients', '-NeverShared', '-localhost',
+        '-SecurityTypes', 'None', '-auth', xauth_file]
+    if dimensions:
+        command.append('-geometry')
+        command.append(dimensions)
+    if dpi:
+        command.append('-dpi')
+        command.append('%d' % dpi)
+
+    if _get_distro() == 'Ubuntu':
+        # workaround for Ubuntu bug #110263
+        command += ['-extension', 'XFIXES']
+
+    command.append(':%d' % (display, ))
+    pipe = subprocess.Popen(command, close_fds=True)
+    return pipe
 
 
 def _kill_pipe(pipe):
-    """Terminate and wait for child process."""
+    """Terminate and wait for child process (if any)."""
     try:
         os.kill(pipe.pid, signal.SIGTERM)
-    except OSError:
-        pass
+    except OSError, exception:
+        if exception.errno != errno.ESRCH:
+            raise
 
-    pipe.wait()
+    try:
+        pipe.wait()
+    except OSError, exception:
+        if exception.errno != errno.ECHILD:
+            raise
+
+
+def _wait_pipe(pipe):
+    """Wait for pipe to finish.
 
+    Retries on EINTR to work around <http://bugs.python.org/issue1068268>.
+    """
+    while pipe.returncode is None:
+        try:
+            pipe.wait()
+        except OSError, exception:
+            if exception.errno != errno.EINTR:
+                raise
 
-def _start_xephyr(dpi, dimensions, fullscreen):
+
+def _start_server(dpi, dimensions, fullscreen):
+    """Try running the X server on a free display."""
     for display in range(30, 40):
         if not _check_server(display):
-            pipe = _run_xephyr(display, dpi, dimensions, fullscreen)
+            pipe = _run_server(display, dpi, dimensions, fullscreen)
 
             for i_ in range(10):
                 if _check_server(display):
-                    return pipe
+                    return display, pipe
 
                 time.sleep(0.1)
 
             _kill_pipe(pipe)
 
 
-def _start_window_manager():
-    cmd = ['metacity']
+def _start_viewer(display, fullscreen):
+    """Start the VNC viewer."""
+    command = ['vncviewer']
+    if fullscreen:
+        command.append('-fullscreen')
 
-    cmd.extend(['--no-force-fullscreen'])
+    command.append(':%d' % (display, ))
+    pipe = subprocess.Popen(command, close_fds=True)
+    return pipe
+
+
+def _check_server(display):
+    """Check the X server on the given display is reachable."""
+    result = subprocess.call(['xdpyinfo', '-display', ':%d' % (display, )],
+                             stdout=_DEV_NULL, stderr=_DEV_NULL)
+    return result == 0
 
-    gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
 
-def _setup_env():
+def _start_window_manager():
+    """Start the window manager inside the new X server."""
+    command = ['metacity', '--no-force-fullscreen']
+    pipe_ = subprocess.Popen(command)
+
+
+def _setup_env(display, scaling):
+    """Set up environment variables for running Sugar inside the new X server.
+    """
     os.environ['SUGAR_EMULATOR'] = 'yes'
     os.environ['GABBLE_LOGFILE'] = os.path.join(
             env.get_profile_path(), 'logs', 'telepathy-gabble.log')
@@ -112,11 +185,13 @@ def _setup_env():
             env.get_profile_path(), 'logs', 'telepathy-salut.log')
     os.environ['STREAM_ENGINE_LOGFILE'] = os.path.join(
             env.get_profile_path(), 'logs', 'telepathy-stream-engine.log')
+    os.environ['DISPLAY'] = ':%d' % (display, )
+    if scaling:
+        os.environ['SUGAR_SCALING'] = scaling
 
 
-def main():
-    """Script-level operations"""
-
+def _parse_args():
+    """Parse command line arguments."""
     parser = OptionParser()
     parser.add_option('-d', '--dpi', dest='dpi', type="int",
                       help='Emulator dpi')
@@ -130,14 +205,32 @@ def main():
     parser.add_option('-F', '--no-fullscreen', dest='fullscreen',
                       action='store_false',
                       help='Do not run emulator in fullscreen mode')
-    (options, args) = parser.parse_args()
+    return parser.parse_args()
+
+
+def _sigchld_handler(number_, frame_):
+    """Kill server when any (direct) child exits.
+
+    So closing the viewer will close the server as well."""
+    if server.returncode is not None:
+        return
+
+    signal.signal(signal.SIGCHLD, signal.SIG_DFL)
 
-    _setup_env()
+    print 'sugar-emulator: Child exited, shutting down server'
+    _kill_pipe(server)
+    return
 
-    server = _start_xephyr(options.dpi, options.dimensions, options.fullscreen)
 
-    if options.scaling:
-        os.environ['SUGAR_SCALING'] = options.scaling
+def main():
+    """Script-level operations"""
+    global server
+
+    options, args = _parse_args()
+    display, server = _start_server(options.dpi, options.dimensions,
+        options.fullscreen)
+    viewer = _start_viewer(display, options.fullscreen)
+    _setup_env(display, options.scaling)
 
     command = ['dbus-launch', '--exit-with-session']
 
@@ -145,11 +238,11 @@ def main():
         command.append('sugar')
     else:
         _start_window_manager()
+        command += args
 
-        if args[0].endswith('.py'):
-            command.append('python')
-
-        command.append(args[0])
+    signal.signal(signal.SIGCHLD, _sigchld_handler)
+    session = subprocess.Popen(command, close_fds=True)
+    _wait_pipe(session)
 
-    subprocess.call(command)
+    _kill_pipe(viewer)
     _kill_pipe(server)
-- 
1.7.1

-- 
James Cameron
http://quozl.linux.org.au/


More information about the Sugar-devel mailing list