Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.topdeps1
-rw-r--r--.topmsg16
-rw-r--r--src/jarabe/util/emulator.py210
3 files changed, 164 insertions, 63 deletions
diff --git a/.topdeps b/.topdeps
index 9c9ac90..8ff3a11 100644
--- a/.topdeps
+++ b/.topdeps
@@ -1 +1,2 @@
upstream/master
+t/keymap-wait
diff --git a/.topmsg b/.topmsg
index 1a60dc0..93235d6 100644
--- a/.topmsg
+++ b/.topmsg
@@ -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)