Added redirect_err() for easier debugging.
[drlaunch] / src / portrait.py
1 # -*- coding: utf-8 -*-
2 #
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
5 #
6 # gPodder is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # gPodder is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 import dbus
21 import dbus.glib
22 from dbus.mainloop.glib import DBusGMainLoop
23
24 import hildon
25 import osso
26
27 # Replace this with your own gettext() functionality
28 _ = str
29
30
31 class FremantleRotation(object):
32     """thp's screen rotation for Maemo 5
33
34     Simply instantiate an object of this class and let it auto-rotate
35     your StackableWindows depending on the device orientation.
36
37     If you need to relayout a window, connect to its "configure-event"
38     signal and measure the ratio of width/height and relayout for that.
39
40     You can set the mode for rotation to AUTOMATIC (default), NEVER or
41     ALWAYS with the set_mode() method.
42     """
43     AUTOMATIC, NEVER, ALWAYS = range(3)
44
45     # Human-readable captions for the above constants
46     MODE_CAPTIONS = (_('Automatic'), _('Landscape'), _('Portrait'))
47
48     # Privately-used constants
49     _PORTRAIT, _LANDSCAPE = ('portrait', 'landscape')
50     _ENABLE_ACCEL = 'req_accelerometer_enable'
51     _DISABLE_ACCEL = 'req_accelerometer_disable'
52
53     # Defined in mce/dbus-names.h
54     _MCE_SERVICE = 'com.nokia.mce'
55     _MCE_REQUEST_PATH = '/com/nokia/mce/request'
56     _MCE_REQUEST_IF = 'com.nokia.mce.request'
57
58     # sysfs device name for the keyboard slider switch
59     KBD_SLIDER = '/sys/devices/platform/gpio-switch/slide/state'
60     _KBD_OPEN = 'open'
61     _KBD_CLOSED = 'closed'
62
63     def __init__(self, app_name, main_window=None, version='1.0', mode=0,
64         dontrotate=False):
65         """Create a new rotation manager
66
67         app_name    ... The name of your application (for osso.Context)
68         main_window ... The root window (optional, hildon.StackableWindow)
69         version     ... The version of your application (optional, string)
70         mode        ... Initial mode for this manager (default: AUTOMATIC)
71         dontrotate  ... Don't rotate the window. (def: False)
72         """
73         self.dontrotate = dontrotate # V13
74         self._orientation = None
75         self._main_window = main_window
76         self._stack = hildon.WindowStack.get_default()
77         self._mode = -1
78         self._last_dbus_orientation = None
79         self._keyboard_state = self._get_keyboard_state()
80         app_id = '-'.join((app_name, self.__class__.__name__))
81         self._osso_context = osso.Context(app_id, version, False)
82         program = hildon.Program.get_instance()
83         program.connect('notify::is-topmost', self._on_topmost_changed)
84
85         # Hack for dbus. See:
86         # https://garage.maemo.org/pipermail/pymaemo-developers/2010-April/001445.html
87         # https://garage.maemo.org/pipermail/pymaemo-developers/2010-April/001454.html
88         # https://garage.maemo.org/pipermail/pymaemo-developers/2010-April/thread.html
89         # https://bugs.maemo.org/show_bug.cgi?id=8611
90         #
91         # If we use dbus.Bus.get_system() or dbus.SystemBus() then the
92         # program fails whenever bluezwitch is installed. This could
93         # also happen whenever another widget is using System Bus (sure?).
94         # 
95         #V13 system_bus = dbus.Bus.get_system()
96         dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
97         busaddress='unix:path=/var/run/dbus/system_bus_socket'
98         system_bus=dbus.bus.BusConnection(busaddress)
99         self.system_bus=system_bus
100
101         system_bus.add_signal_receiver(self._on_orientation_signal, \
102                 signal_name='sig_device_orientation_ind', \
103                 dbus_interface='com.nokia.mce.signal', \
104                 path='/com/nokia/mce/signal')
105         system_bus.add_signal_receiver(self._on_keyboard_signal, \
106                 signal_name='Condition', \
107                 dbus_interface='org.freedesktop.Hal.Device', \
108                 path='/org/freedesktop/Hal/devices/platform_slide')
109         self.set_mode(mode)
110
111     def get_mode(self):
112         """Get the currently-set rotation mode
113
114         This will return one of three values: AUTOMATIC, ALWAYS or NEVER.
115         """
116         return self._mode
117
118     def set_mode(self, new_mode):
119         """Set the rotation mode
120
121         You can set the rotation mode to AUTOMATIC (use hardware rotation
122         info), ALWAYS (force portrait) and NEVER (force landscape).
123         """
124         if new_mode not in (self.AUTOMATIC, self.ALWAYS, self.NEVER):
125             raise ValueError('Unknown rotation mode')
126
127         if self._mode != new_mode:
128             if self._mode == self.AUTOMATIC:
129                 # Remember the current "automatic" orientation for later
130                 self._last_dbus_orientation = self._orientation
131                 # Tell MCE that we don't need the accelerometer anymore
132                 self._send_mce_request(self._DISABLE_ACCEL)
133
134             if new_mode == self.NEVER:
135                 self._orientation_changed(self._LANDSCAPE)
136             elif new_mode == self.ALWAYS and \
137                     self._keyboard_state != self._KBD_OPEN:
138                 self._orientation_changed(self._PORTRAIT)
139             elif new_mode == self.AUTOMATIC:
140                 # Restore the last-known "automatic" orientation
141                 self._orientation_changed(self._last_dbus_orientation)
142                 # Tell MCE that we need the accelerometer again
143                 self._send_mce_request(self._ENABLE_ACCEL)
144
145             self._mode = new_mode
146
147     def _send_mce_request(self, request):
148         rpc = osso.Rpc(self._osso_context)
149         rpc.rpc_run(self._MCE_SERVICE, \
150                     self._MCE_REQUEST_PATH, \
151                     self._MCE_REQUEST_IF, \
152                     request, \
153                     use_system_bus=True)
154
155     def _on_topmost_changed(self, program, property_spec):
156         # XXX: This seems to never get called on Fremantle(?)
157         if self._mode == self.AUTOMATIC:
158             if program.get_is_topmost():
159                 self._send_mce_request(self._ENABLE_ACCEL)
160             else:
161                 self._send_mce_request(self._DISABLE_ACCEL)
162
163     def _get_main_window(self):
164         if self._main_window:
165             # If we have gotten the main window as parameter, return it and
166             # don't try "harder" to find another window using the stack
167             return self._main_window
168         else:
169             # The main window is at the "bottom" of the window stack, and as
170             # the list we get with get_windows() is sorted "topmost first", we
171             # simply take the last item of the list to get our main window
172             windows = self._stack.get_windows()
173             if windows:
174                 return windows[-1]
175             else:
176                 return None
177
178     def _orientation_changed(self, orientation):
179         if self._orientation == orientation:
180             # Ignore repeated requests
181             return
182
183         flags = 0
184
185         if orientation != self._LANDSCAPE:
186             flags |= hildon.PORTRAIT_MODE_SUPPORT
187
188         if orientation == self._PORTRAIT:
189             flags |= hildon.PORTRAIT_MODE_REQUEST
190
191         window = self._get_main_window()
192         if window is not None and self.dontrotate==False:
193             hildon.hildon_gtk_window_set_portrait_flags(window, flags)
194
195         self._orientation = orientation
196
197         self.on_orientation_changed(orientation)
198
199     def on_orientation_changed(self, orientation):
200         pass
201
202     def _get_keyboard_state(self):
203         # For sbox, if the device does not exist assume that it's closed
204         try:
205             return open(self.KBD_SLIDER).read().strip()
206         except IOError:
207             return self._KBD_CLOSED
208
209     def _keyboard_state_changed(self):
210         state = self._get_keyboard_state()
211
212         if state == self._KBD_OPEN:
213             self._orientation_changed(self._LANDSCAPE)
214         elif state == self._KBD_CLOSED:
215             if self._mode == self.AUTOMATIC:
216                 self._orientation_changed(self._last_dbus_orientation)
217             elif self._mode == self.ALWAYS:
218                 self._orientation_changed(self._PORTRAIT)
219
220         self._keyboard_state = state
221
222     def _on_keyboard_signal(self, condition, button_name):
223         if condition == 'ButtonPressed' and button_name == 'cover':
224             self._keyboard_state_changed()
225
226     def _on_orientation_signal(self, orientation, stand, face, x, y, z):
227         if orientation in (self._PORTRAIT, self._LANDSCAPE):
228             if self._mode == self.AUTOMATIC and \
229                     self._keyboard_state != self._KBD_OPEN:
230                 # Automatically set the rotation based on hardware orientation
231                 self._orientation_changed(orientation)
232
233             # Save the current orientation for "automatic" mode later on
234             self._last_dbus_orientation = orientation
235