c66b3381f0a316593f2e8bd02eab33590e555e93
[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 reset_mode(self):
148         if self._mode==self.AUTOMATIC:
149             self._send_mce_request(self._ENABLE_ACCEL)
150         else:
151             self._send_mce_request(self._DISABLE_ACCEL)
152
153     def _send_mce_request(self, request):
154         rpc = osso.Rpc(self._osso_context)
155         rpc.rpc_run(self._MCE_SERVICE, \
156                     self._MCE_REQUEST_PATH, \
157                     self._MCE_REQUEST_IF, \
158                     request, \
159                     use_system_bus=True)
160
161     def _on_topmost_changed(self, program, property_spec):
162         # XXX: This seems to never get called on Fremantle(?)
163         if self._mode == self.AUTOMATIC:
164             if program.get_is_topmost():
165                 self._send_mce_request(self._ENABLE_ACCEL)
166             else:
167                 self._send_mce_request(self._DISABLE_ACCEL)
168
169     def _get_main_window(self):
170         if self._main_window:
171             # If we have gotten the main window as parameter, return it and
172             # don't try "harder" to find another window using the stack
173             return self._main_window
174         else:
175             # The main window is at the "bottom" of the window stack, and as
176             # the list we get with get_windows() is sorted "topmost first", we
177             # simply take the last item of the list to get our main window
178             windows = self._stack.get_windows()
179             if windows:
180                 return windows[-1]
181             else:
182                 return None
183
184     def _orientation_changed(self, orientation):
185         if self._orientation == orientation:
186             # Ignore repeated requests
187             return
188
189         flags = 0
190
191         if orientation != self._LANDSCAPE:
192             flags |= hildon.PORTRAIT_MODE_SUPPORT
193
194         if orientation == self._PORTRAIT:
195             flags |= hildon.PORTRAIT_MODE_REQUEST
196
197         window = self._get_main_window()
198         if window is not None and self.dontrotate==False:
199             hildon.hildon_gtk_window_set_portrait_flags(window, flags)
200
201         self._orientation = orientation
202
203         self.on_orientation_changed(orientation)
204
205     def on_orientation_changed(self, orientation):
206         pass
207
208     def _get_keyboard_state(self):
209         # For sbox, if the device does not exist assume that it's closed
210         try:
211             return open(self.KBD_SLIDER).read().strip()
212         except IOError:
213             return self._KBD_CLOSED
214
215     def _keyboard_state_changed(self):
216         state = self._get_keyboard_state()
217
218         if state == self._KBD_OPEN:
219             self._orientation_changed(self._LANDSCAPE)
220         elif state == self._KBD_CLOSED:
221             if self._mode == self.AUTOMATIC:
222                 self._orientation_changed(self._last_dbus_orientation)
223             elif self._mode == self.ALWAYS:
224                 self._orientation_changed(self._PORTRAIT)
225
226         self._keyboard_state = state
227
228     def _on_keyboard_signal(self, condition, button_name):
229         if condition == 'ButtonPressed' and button_name == 'cover':
230             self._keyboard_state_changed()
231
232     def _on_orientation_signal(self, orientation, stand, face, x, y, z):
233         if orientation in (self._PORTRAIT, self._LANDSCAPE):
234             if self._mode == self.AUTOMATIC and \
235                     self._keyboard_state != self._KBD_OPEN:
236                 # Automatically set the rotation based on hardware orientation
237                 self._orientation_changed(orientation)
238
239             # Save the current orientation for "automatic" mode later on
240             self._last_dbus_orientation = orientation
241