[Mud-builder-commits] r280 - in trunk/packages: . horizon.pkg

jaffa at garage.maemo.org jaffa at garage.maemo.org
Wed Jul 15 01:37:38 EEST 2009


Author: jaffa
Date: 2009-07-15 01:37:34 +0300 (Wed, 15 Jul 2009)
New Revision: 280

Added:
   trunk/packages/horizon.pkg/
   trunk/packages/horizon.pkg/Makefile
   trunk/packages/horizon.pkg/framework.py
   trunk/packages/horizon.pkg/horizon
   trunk/packages/horizon.pkg/horizon-128.png
   trunk/packages/horizon.pkg/horizon-26.png
   trunk/packages/horizon.pkg/horizon-40.png
   trunk/packages/horizon.pkg/horizon-48.png
   trunk/packages/horizon.pkg/horizon-64.png
   trunk/packages/horizon.pkg/horizon.desktop
   trunk/packages/horizon.pkg/horizon.service
   trunk/packages/horizon.pkg/main.py
   trunk/packages/horizon.pkg/mud.xml
   trunk/packages/horizon.pkg/providers.py
Log:
Add Horizon source package, which tests accelerometers

Added: trunk/packages/horizon.pkg/Makefile
===================================================================
--- trunk/packages/horizon.pkg/Makefile	                        (rev 0)
+++ trunk/packages/horizon.pkg/Makefile	2009-07-14 22:37:34 UTC (rev 280)
@@ -0,0 +1,9 @@
+compile:
+	echo No compilation step necessary
+
+install:
+	dh_install main.py providers.py framework.py usr/lib/python2.5/site-packages/horizon
+	dh_install horizon usr/bin
+
+clean:
+	echo No clean step necesary

Added: trunk/packages/horizon.pkg/framework.py
===================================================================
--- trunk/packages/horizon.pkg/framework.py	                        (rev 0)
+++ trunk/packages/horizon.pkg/framework.py	2009-07-14 22:37:34 UTC (rev 280)
@@ -0,0 +1,198 @@
+#! /usr/bin/env python
+#
+# Cairo Gtk/Hildon/OSSO framework                        (c) Andrew Flegg 2009
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~          Released under the Artistic Licence
+# Based on the version at http://www.tortall.net/mu/wiki/PyGTKCairoTutorial.
+#                                                  
+import pygtk
+pygtk.require('2.0')
+import gtk, gobject, cairo
+
+global has_hildon, has_osso, osso_context, debug
+debug = False # Change this line to enable debug logging
+try:
+  import hildon
+  has_hildon = True
+except ImportError:
+  has_hildon = False
+  
+try:
+  import osso
+  has_osso = True
+  try:
+    f = open('/etc/maemo_version', 'r')
+    if (f.readline().startswith('5.0')):
+      print "*** Disabling OSSO functionality due to Maemo 5 bug #4782"
+      has_osso = False
+  except:
+    pass
+except ImportError:
+  has_osso = False
+
+# Create a GTK+ widget on which we will draw using Cairo
+class Screen(gtk.DrawingArea):
+    """This class provides a mechanism for doing graphical programs in a
+       power-efficient and simple way. Subclasses should override {{draw}},
+       and potentially {{set_visible}}."""
+    
+    # Tracks whether we should do screen updates
+    _visible  = None
+    paused    = False
+
+    # Draw in response to an expose-event, this isn't done in run() for
+    # reasons which aren't clear
+    __gsignals__ = { "expose-event": "override" }
+
+
+    # -----------------------------------------------------------------------
+    @property    
+    def version(self):
+        """Returns the version number which should be given to DBUS, displayed
+           in about boxes etc. This can be overridden in subclasses."""
+        return "0.01"
+
+
+    # -----------------------------------------------------------------------
+    @property
+    def display_name(self):
+        """The name of the application. This is displayed in the titlebar, about
+           boxes etc. This can be overridden in subclasses, but defaults
+           to the class' name."""
+        return self.__class__.__name__
+
+
+    # -----------------------------------------------------------------------
+    @property
+    def dbus_name(self):
+        """The DBUS name of the application. This defaults to the class name
+           in lowercase prefixed with {{org.maemo.}}"""
+        return 'org.maemo.' + self.__class__.__name__.lower()
+
+
+    # -----------------------------------------------------------------------
+    @property
+    def visible(self):
+        """Whether the application is visible. This should be used to indicate
+           if timers should continue to fire, etc."""
+        return self._visible
+
+
+    # -----------------------------------------------------------------------
+    def set_visible(self, value):
+        """Update the _visible_ property."""
+        self._visible = value
+
+
+    # -----------------------------------------------------------------------
+    def do_window_state_event(self, window, event):
+        """Handle normal GTK+ window state events. If the window is withdrawn
+           or minimised, the application is no longer visible."""
+        self.set_visible(not event.new_window_state &
+                                  (gtk.gdk.WINDOW_STATE_ICONIFIED
+                                  |gtk.gdk.WINDOW_STATE_WITHDRAWN))
+
+
+    # -----------------------------------------------------------------------
+    def do_focus_in_event(self, widget, event):
+        """If the application has focus given to it, assume that it is now
+           visible. This is necessary as Maemo 4 does not always provide the
+           correct event handling."""
+        self.set_visible(True)
+
+
+    # -----------------------------------------------------------------------
+    def do_general_event(self, widget, event):
+        """A general event handler which will, on Hildon, check whether the
+           application is visible."""
+        if (debug):
+          print "%s, state = %d, paused = %d" % (event.type, self.window.get_state(), self.paused)
+        if (has_hildon):
+          topmost = self.app_window.get_property('is-topmost')
+          if (debug):
+            print "    topmost = %d" % (topmost)
+          self.set_visible(not self.paused and topmost)
+          
+          
+    # -----------------------------------------------------------------------
+    def do_property_event(self, widget, event):
+        """Track property change events. Ideally, this would be used on Hildon
+           to check the 'is-topmost' property; however this event does not
+           get fired correctly on Maemo 4."""
+        self.do_general_event(widget, event)
+        if (debug):
+          print "    property = %s" % (event.atom)
+
+
+    # -----------------------------------------------------------------------
+    def do_expose_event(self, event):
+        """Handle the 'expose' event, which signals a portion of the viewport
+           needs redrawing. This sets up a Cairo context and delegates to the
+           {{draw}} method."""
+        self.do_general_event(event.window, event)
+        cr = self.window.cairo_create()
+        cr.rectangle(event.area.x, event.area.y,
+                     event.area.width, event.area.height)
+        cr.clip()
+
+        self.draw(cr, *self.window.get_size())
+
+
+    # -----------------------------------------------------------------------
+    def draw(self, cr, width, height):
+        """Draw the Cairo display. This should be overridden in subclasses."""
+
+        # Fill the background with gray
+        cr.set_source_rgb(0.5, 0.5, 0.5)
+        cr.rectangle(0, 0, width, height)
+        cr.fill()
+
+
+# ---------------------------------------------------------------------------
+# Create and set-up the application, window, event handlers etc.
+def run(widget):
+    if (has_hildon):
+      print "+++ Hildon, yay!\n"
+      widget.app = hildon.Program()
+      window = hildon.Window()
+      gtk.set_application_name(widget.display_name)
+    else:
+      print "--- No Hildon, sorry\n"
+      window = gtk.Window()
+      window.set_title(widget.display_name)
+      
+    widget.app_window = window
+    window.resize(800, 480)
+    window.add(widget)
+
+    window.connect("delete-event", gtk.main_quit)
+    window.connect("window-state-event", widget.do_window_state_event)
+    window.connect("focus-in-event", widget.do_focus_in_event)
+    window.connect("property-notify-event", widget.do_property_event)
+    
+    if (has_osso):
+      print "+++ Have osso, yay!\n"
+      try:
+        osso_context = osso.Context(widget.dbus_name, widget.version, False)
+        device = osso.DeviceState(osso_context)
+        device.set_device_state_callback(state_change, system_inactivity=True, user_data=widget)
+      except:
+        print "*** Failed to initialise OSSO context. Power management disabled..."
+        has_osoo = False
+
+    window.present()
+    widget.show()
+    gtk.main()
+    
+# ---------------------------------------------------------------------------
+def state_change(shutdown, save_unsaved_data, memory_low, system_inactivity, message, widget):
+    """Handle OSSO-specific DBUS callbacks, in this case whether the system has
+       been paused."""
+    if (debug):
+      print "State change (%s): shutdown = %d, save = %d, low mem = %d, paused = %d" % (
+                  message, shutdown, save_unsaved_data, memory_low, system_inactivity)
+    widget.set_visible(not system_inactivity)
+    widget.paused = system_inactivity
+    
+    
+if __name__ == "__main__":
+    run(Screen)

Added: trunk/packages/horizon.pkg/horizon
===================================================================
--- trunk/packages/horizon.pkg/horizon	                        (rev 0)
+++ trunk/packages/horizon.pkg/horizon	2009-07-14 22:37:34 UTC (rev 280)
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+PYTHON=`which python2.5` || PYTHON=`which python`
+exec $PYTHON /usr/lib/python2.5/site-packages/horizon/main.py "$@"
\ No newline at end of file

Added: trunk/packages/horizon.pkg/horizon-128.png
===================================================================
(Binary files differ)


Property changes on: trunk/packages/horizon.pkg/horizon-128.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/packages/horizon.pkg/horizon-26.png
===================================================================
(Binary files differ)


Property changes on: trunk/packages/horizon.pkg/horizon-26.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/packages/horizon.pkg/horizon-40.png
===================================================================
(Binary files differ)


Property changes on: trunk/packages/horizon.pkg/horizon-40.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/packages/horizon.pkg/horizon-48.png
===================================================================
(Binary files differ)


Property changes on: trunk/packages/horizon.pkg/horizon-48.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/packages/horizon.pkg/horizon-64.png
===================================================================
(Binary files differ)


Property changes on: trunk/packages/horizon.pkg/horizon-64.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: trunk/packages/horizon.pkg/horizon.desktop
===================================================================
--- trunk/packages/horizon.pkg/horizon.desktop	                        (rev 0)
+++ trunk/packages/horizon.pkg/horizon.desktop	2009-07-14 22:37:34 UTC (rev 280)
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Version=1.0
+Encoding=UTF-8
+Name=Horizon
+Icon=horizon
+Exec=/usr/bin/horizon
+X-Osso-Service=org.maemo.horizon
+Type=Application

Added: trunk/packages/horizon.pkg/horizon.service
===================================================================
--- trunk/packages/horizon.pkg/horizon.service	                        (rev 0)
+++ trunk/packages/horizon.pkg/horizon.service	2009-07-14 22:37:34 UTC (rev 280)
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.maemo.horizon
+Exec=/usr/bin/horizon

Added: trunk/packages/horizon.pkg/main.py
===================================================================
--- trunk/packages/horizon.pkg/main.py	                        (rev 0)
+++ trunk/packages/horizon.pkg/main.py	2009-07-14 22:37:34 UTC (rev 280)
@@ -0,0 +1,165 @@
+#!/usr/bin/env python
+#
+# Main class for `Horizon' - a false horizon display using accelerometer
+# information.                              (c) Andrew Flegg 2009
+#                                           Released under the Artistic Licence
+
+import providers
+import framework
+from framework import Screen
+
+import gobject
+import cairo
+import gtk
+from math import pi, sin, atan2, degrees
+
+class Horizon(Screen):
+    """Create and manage a Cairo display using an external information
+    system providing accelerometer information.
+    
+    This uses the `Screen' framework as described in the Cairo tutorials
+    at http://www.tortall.net/mu/wiki/PyGTKCairoTutorial.
+    """
+    
+    provider = None
+    
+    rotation = None
+    boundary = None
+    freq     = 10
+
+    ground = None
+    sky    = None
+
+    # -----------------------------------------------------------------------
+    def __init__(self, provider):
+        """Initialise the information provider. This is responsible for
+        having a {{position}} method which returns an x, y, z tuple."""
+        Screen.__init__(self)
+        self.provider = provider
+
+    # -----------------------------------------------------------------------
+    def set_visible(self, visible):
+        """Override visibility setter to re-enable timeout."""
+        if (visible == self.visible):
+          return
+          
+        super(Horizon, self).set_visible(visible)
+        print "Visibility change: %d" % (visible)
+        if (visible):
+          self.update_display()
+          gobject.timeout_add(self.freq, Horizon.update_display, self)
+        
+    # -----------------------------------------------------------------------
+    def show(self):
+        """Override initial display to create update schedule and reused
+        gradient fills."""
+        Screen.show(self)
+     
+        self.ground = cairo.LinearGradient(0, -0.5, 0, 0.5)
+        self.ground.add_color_stop_rgb(0, 87/255.0, 153/255.0, 10/255.0)
+        self.ground.add_color_stop_rgb(1, 0, 123/255.0, 0)
+        
+        self.sky = cairo.LinearGradient(0, -0.5, 0, 0.5)
+        self.sky.add_color_stop_rgb(0, 0, 107/255.0, 214/255.0)
+        self.sky.add_color_stop_rgb(1, 30/255.0, 148/255.0, 1)
+        
+        self.set_visible(True)
+
+    # -----------------------------------------------------------------------
+    def update_display(self):
+        """Call the configured position provider's {{position}} method which
+        returns x, y, z tuple. The values should be in the range -1000 - 1000."""
+        
+        (x, y, z) = self.provider.position()
+        boundary = max(min(z / 1000.0, 1), -1)
+        rotation = atan2(x, y) - pi
+        if (rotation < -pi):
+          rotation += 2*pi
+        
+        if (self.rotation <> rotation or self.boundary <> boundary):
+          self.rotation = rotation
+          self.boundary = boundary
+          self.window.invalidate_region(
+                  gtk.gdk.region_rectangle((0, 0,
+                                            self.window.get_size()[0],
+                                            self.window.get_size()[1])),
+                  True)
+
+        return self.visible
+
+    # -----------------------------------------------------------------------
+    def draw(self, cr, width, height):
+        """Responsible for the actual drawing of: the ground, sky, false
+        horizon, height bar and roll display."""
+        
+        cr.save()
+        cr.scale(height, height)
+        cr.translate(0.5 * width / height, 0.5)
+        cr.rotate(self.rotation)
+
+        # -- Draw the ground...
+        #
+        cr.set_source(self.ground)
+        cr.rectangle(-2, self.boundary, 4, 2)
+        cr.fill()
+        
+        # -- Draw the sky...
+        #
+        cr.rectangle(-2, self.boundary - 2, 4, 2)
+        cr.set_source(self.sky)
+        cr.fill()
+
+        # -- Draw the false horizon...
+        #
+        cr.set_source_rgba(1, 1, 1, 0.8)
+        cr.set_line_width(0.004)
+
+        cr.move_to(-0.14, 0)
+        cr.line_to(-0.03, 0)
+        cr.move_to(0.03, 0)
+        cr.line_to(0.14, 0)
+        cr.stroke()
+
+        # -- Draw the height bars...
+        #
+        bar_height = self.boundary / 4.0
+        cr.move_to(-0.04, bar_height - 0.25)
+        cr.line_to(0, bar_height - 0.25)
+        cr.line_to(0, bar_height + 0.25)
+        cr.line_to(0.04, bar_height + 0.25)
+        for i in range(1, 10):
+          y = (bar_height - 0.25) + (i * 0.05)
+          cr.move_to(-0.018, y)
+          cr.line_to(0.018, y)
+        cr.stroke()
+
+        # -- Draw the down arrow...
+        #
+        cr.set_line_width(0.003)
+        cr.move_to(-0.02, 0.45)
+        cr.line_to(0.02, 0.45)
+        cr.line_to(0, 0.5)
+        cr.close_path()
+        cr.stroke()
+        
+        # -- Show the telemetry...
+        #
+        cr.restore()
+        cr.set_source_rgba(1, 1, 1, 0.8)
+        cr.select_font_face("sans-serif")
+        cr.set_font_size(32)
+        angle = unicode(round(degrees(self.rotation), 1)) + u'\u00b0'
+        (x, y, w, h, xa, ya) = cr.text_extents(angle)
+        cr.move_to(width - 40 - w, 40)
+        cr.show_text(angle)
+
+
+if __name__ == "__main__":
+    provider = None
+    if (providers.NokiaAccelerometer.available()):
+      provider = providers.NokiaAccelerometer()
+    else:
+      provider = providers.Demo()
+      
+    framework.run(Horizon(provider))
+

Added: trunk/packages/horizon.pkg/mud.xml
===================================================================
--- trunk/packages/horizon.pkg/mud.xml	                        (rev 0)
+++ trunk/packages/horizon.pkg/mud.xml	2009-07-14 22:37:34 UTC (rev 280)
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<package>
+  <fetch type="command">
+    <command>
+      PKGDIR=horizon-0.0.3
+      mkdir "${PKGDIR}"
+      cp "${MUD_PACKAGES_DIR}/horizon.pkg/"*.py "${PKGDIR}/"
+      cp "${MUD_PACKAGES_DIR}/horizon.pkg/"{Makefile,horizon} "${PKGDIR}/"
+    </command>
+  </fetch>
+  <deb>
+    <depends>python-runtime | python2.5-runtime</depends>
+    <section>user/graphics</section>
+    <maintainer>Andrew Flegg &lt;andrew at bleb.org&gt;</maintainer>
+    <description>An artificial horizon and spirit level.
+      This application shows the pitch and roll of an accelerometer
+      enabled device. In the absence of accelerometers, it shows a
+      twisty turny path through the sky.
+      .
+      The application can also be used as a spirit level, showing the
+      pitch angle in degrees.
+    </description>
+    <display-name>Horizon</display-name>
+  </deb>
+</package>
+

Added: trunk/packages/horizon.pkg/providers.py
===================================================================
--- trunk/packages/horizon.pkg/providers.py	                        (rev 0)
+++ trunk/packages/horizon.pkg/providers.py	2009-07-14 22:37:34 UTC (rev 280)
@@ -0,0 +1,45 @@
+#
+# Provider information sources for `Horizon' - a false horizon display using
+# accelerometer information.                (c) Andrew Flegg 2009
+#                                           Released under the Artistic Licence
+
+import os.path
+from math import sin, cos, pi
+
+class Dummy:
+    """One of the simplest providers: returns dead-on, flat."""
+    def position(self):
+        return (0, -1000, 0)
+
+
+class Demo:
+    """A demonstration provider which will take the user on a tour through
+       the air."""
+    x = 0
+    y = -1000
+    z = 0
+    
+    def position(self):
+        self.x -= 2
+        self.y += 1
+        self.z += 2
+        return (sin(self.x / 150.0 * pi) * 150,
+                cos(self.y / 150.0 * pi) * 200,
+                sin(self.z / 150.0 * pi) * 300)
+
+
+class NokiaAccelerometer:
+    """An accelerometer provider which actually reads an RX-51's
+       accelerometers, based on http://wiki.maemo.org/Accelerometers"""
+       
+    global ACCELEROMETER_PATH
+    ACCELEROMETER_PATH = '/sys/class/i2c-adapter/i2c-3/3-001d/coord'
+    
+    def position(self):
+        f = open(ACCELEROMETER_PATH, 'r')
+        return [int(w) for w in f.readline().split()]
+
+    @classmethod
+    def available(cls):
+        return os.path.isfile(ACCELEROMETER_PATH)
+



More information about the Mud-builder-commits mailing list