[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