diff options
-rw-r--r-- | .topdeps | 1 | ||||
-rw-r--r-- | .topmsg | 16 | ||||
-rw-r--r-- | src/jarabe/util/emulator.py | 210 |
3 files changed, 164 insertions, 63 deletions
@@ -1 +1,2 @@ upstream/master +t/keymap-wait @@ -1,11 +1,15 @@ From: Sascha Silbe <sascha@silbe.org> -Subject: [PATCH] add workaround for TigerVNC empty keymap bug +Subject: [PATCH] sugar-emulator: use vnc instead of Xephyr (#1659) -TigerVNC returns an empty keymap until a key has been pressed, preventing our -global hotkeys ("accelerators") from working because we can't determine the -corresponding keycodes at initialization time. +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. -This patch adds a script called "keymap-wait" that checks for this bug and -waits for the user to press a key on startup (with translatable instructions). +Tested on: +- Debian squeeze +- Ubuntu Intrepid +- Ubuntu Jaunty +- Fedora 12 Signed-off-by: Sascha Silbe <sascha@silbe.org> diff --git a/src/jarabe/util/emulator.py b/src/jarabe/util/emulator.py index fda1b59..dfdbfa8 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 @@ -23,86 +24,140 @@ from optparse import OptionParser from gettext import gettext as _ import gtk -import gobject from sugar import env - +DEFAULT_DIMENSIONS = (800, 600) ERROR_NO_DISPLAY = 30 ERROR_NO_SERVER = 31 -default_dimensions = (800, 600) +_DEV_NULL = open(os.devnull, 'w') + +server = None + + +def _run_pipe(command, stdin=None): + """Run a program with optional input and return output. + + 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) + + return stdout + + +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 + + return distro or None -def _run_xephyr(display, dpi, dimensions, fullscreen): - cmd = ['Xephyr'] - cmd.append(':%d' % display) - cmd.append('-ac') - cmd += ['-title', _('Sugar in a window')] +def _run_xauth(display): + """Set up Xauthority file for new display. + 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): - # no forced settings, screen too small => fit screen - fullscreen = True + (screen_size <= DEFAULT_DIMENSIONS): + dimensions = '%dx%d' % screen_size + elif fullscreen: + dimensions = '%dx%d' % screen_size 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 + dimensions = '%dx%d' % DEFAULT_DIMENSIONS if not dpi: dpi = gtk.settings_get_default().get_property('gtk-xft-dpi') / 1024 - if fullscreen: - cmd.append('-fullscreen') - + xauth_file = _run_xauth(display) + command = ['Xvnc', '-DisconnectClients', '-NeverShared', '-localhost', + '-SecurityTypes', 'None', + '-auth', xauth_file, + '-desktop', _('Sugar in a window')] if dimensions: - cmd.append('-screen') - cmd.append(dimensions) - + command.append('-geometry') + command.append(dimensions) if dpi: - cmd.append('-dpi') - cmd.append('%d' % dpi) + command.append('-dpi') + command.append('%d' % dpi) - cmd.append('-noreset') + if _get_distro() == 'Ubuntu': + # workaround for Ubuntu bug #110263 + command += ['-extension', 'XFIXES'] + command.append(':%d' % (display, )) + pipe = subprocess.Popen(command, close_fds=True) try: pipe = subprocess.Popen(cmd) except OSError, exc: sys.stderr.write('Error executing server: %s\n' % (exc, )) - return None + sys.exit(ERROR_NO_SERVER) return pipe -def _check_server(display): - result = subprocess.call(['xdpyinfo', '-display', ':%d' % display], - stdout=open(os.devnull, 'w'), - stderr=open(os.devnull, 'w')) - return result == 0 - - 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) - if pipe is None: - return None, None + pipe = _run_server(display, dpi, dimensions, fullscreen) for i_ in range(10): if _check_server(display): - return pipe, display + return display, pipe time.sleep(0.1) @@ -111,15 +166,34 @@ def _start_xephyr(dpi, dimensions, fullscreen): return None, None -def _start_window_manager(): - cmd = ['metacity'] +def _start_viewer(display, fullscreen): + """Start the VNC viewer.""" + command = ['vncviewer'] + if fullscreen: + command.append('-fullscreen') + + command.append(':%d' % (display, )) + pipe = subprocess.Popen(command, close_fds=True) + return pipe - cmd.extend(['--no-force-fullscreen']) - gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH) +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 + + +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, emulator_pid): + """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') @@ -129,7 +203,9 @@ def _setup_env(display, scaling, emulator_pid): env.get_profile_path(), 'logs', 'mission-control.log') os.environ['STREAM_ENGINE_LOGFILE'] = os.path.join( env.get_profile_path(), 'logs', 'telepathy-stream-engine.log') - os.environ['DISPLAY'] = ':%d' % (display) + os.environ['DISPLAY'] = ':%d' % (display, ) + if scaling: + os.environ['SUGAR_SCALING'] = scaling os.environ['SUGAR_EMULATOR_PID'] = emulator_pid os.environ['MC_ACCOUNT_DIR'] = os.path.join( env.get_profile_path(), 'accounts') @@ -137,10 +213,8 @@ def _setup_env(display, scaling, emulator_pid): 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') @@ -154,19 +228,41 @@ 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) + + print 'sugar-emulator: Child exited, shutting down server' + _kill_pipe(server) + return + + +def main(): + """Script-level operations""" + global server if not os.environ.get('DISPLAY'): sys.stderr.write('DISPLAY not set, cannot connect to host X server.\n') return ERROR_NO_DISPLAY - server, display = _start_xephyr(options.dpi, options.dimensions, - options.fullscreen) + options, args = _parse_args() + display, server = _start_server(options.dpi, options.dimensions, + options.fullscreen) + if server is None: sys.stderr.write('Failed to start server. Please check output above' ' for any error message.\n') return ERROR_NO_SERVER + viewer = _start_viewer(display, options.fullscreen) _setup_env(display, options.scaling, str(server.pid)) command = ['dbus-launch', '--exit-with-session'] @@ -175,11 +271,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) |