Merge branch 'master' of https://git.maemo.org/projects/browser-switch
[browser-switch] / browser-proxy
1 #!/usr/bin/python
2 # browser-proxy.py
3 # version 2.2 by steven676 and xiojason
4
5 # simple python-dbus service that proxies osso_browser events to Tear
6 # based on code from http://paste.lisp.org/display/45824
7
8 # Copyright (C) 2009 Jason Simpson
9 # Copyright (C) 2009 Steven Luo
10
11 # This program is free software; you can redistribute it and/or
12 # modify it under the terms of the GNU General Public License
13 # as published by the Free Software Foundation; either version 2
14 # of the License, or (at your option) any later version.
15
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24
25 # On Maemo systems, a copy of the GPL can be found in
26 # /usr/share/common-licenses/GPL .
27
28
29 import os
30 import gobject
31 import dbus
32 import dbus.service
33 import dbus.glib
34
35 # Set default configuration values
36 # These can be overriden by the config file $HOME/.config/browser-proxy
37 def setconfigdefaults():
38     global continuous_mode, default_browser, other_browser_cmd
39
40     # continuous_mode: 0 -- close after handling a request; 1 -- run
41     #   continuously in the background
42     continuous_mode = 0
43     # default_browser: "tear", "microb", "fennec", "midori", or "other"
44     default_browser = "tear"
45     # If default browser is "other", what program to run (%s will be replaced
46     #   by URI)
47     other_browser_cmd = ""
48
49 class BrowserLauncher:
50     def LaunchTear(self, uri):
51         # We should be able to just call the D-Bus service to open Tear ...
52         # but if Tear's not open, that causes D-Bus to start Tear and then
53         # pass it the OpenAddress call, which results in two browser windows.
54         # Properly fixing this probably requires Tear to provide a D-Bus method
55         # that opens an address in an existing window, but for now work around
56         # by just opening Tear via the command line if it's not running.
57         if os.system("pidof tear > /dev/null") == 0:
58             dbus.SessionBus().get_object('com.nokia.tear', '/com/nokia/tear').OpenAddress(uri, dbus_interface='com.nokia.Tear')
59             if continuous_mode == 0:
60                 quit()
61         else:
62             if continuous_mode:
63                 if os.fork() != 0:
64                     # parent process doesn't need to do anything
65                     return
66                 # child process
67                 os.setsid()
68             os.execl('/usr/bin/tear', '/usr/bin/tear', uri)
69
70     def LaunchMicroB(self, uri):
71         if os.system("pidof /usr/sbin/browserd > /dev/null") != 0:
72             kill_browserd = 1
73             os.system("/usr/sbin/browserd -d")
74         else:
75             kill_browserd = 0
76         dbus.SessionBus().release_name("com.nokia.osso_browser")
77         if uri == "new_window":
78             # exec maemo-invoker directly instead of relying on the
79             # /usr/bin/browser symlink, since /usr/bin/browser may have been
80             # replaced with a shell script calling us via D-Bus
81             os.spawnl(os.P_WAIT, '/usr/bin/maemo-invoker', '/usr/bin/browser')
82         else:
83             os.spawnl(os.P_WAIT, '/usr/bin/maemo-invoker', '/usr/bin/browser', '--url', uri)
84         if kill_browserd:
85             os.system("kill `pidof /usr/sbin/browserd`")
86         if continuous_mode:
87             dbus.SessionBus().request_name("com.nokia.osso_browser")
88         else:
89             quit()
90
91     def LaunchOtherBrowser(self, uri):
92         if uri == "new_window":
93             uri = ""
94         try:
95             # URI must be quoted and quotes in the URI must be URL-escaped
96             # to prevent the shell from interpreting any part of the URI
97             command = other_browser_cmd % "'%s'" % uri.replace("'", "%27")
98         except TypeError:
99             # Couldn't substitute URI in, just launch the browser
100             print "other_browser_cmd should have a %s placeholder for the URI"
101             command = other_browser_cmd
102         import subprocess
103         subprocess.Popen(command, shell=True)
104         if continuous_mode == 0:
105             quit()
106
107     def UpdateDefaultBrowser(self):
108         global other_browser_cmd
109         if default_browser == "tear":
110             self.LaunchBrowser = self.LaunchTear
111         elif default_browser == "microb":
112             self.LaunchBrowser = self.LaunchMicroB
113         elif default_browser == "fennec":
114             # Cheat and reuse LaunchOtherBrowser, since we don't appear to
115             # need to do anything special
116             # TODO: does Fennec have a D-Bus API or signaling mechanism?
117             # Invoking the fennec binary appears to be somewhat expensive
118             other_browser_cmd = "fennec '%s'"
119             self.LaunchBrowser = self.LaunchOtherBrowser
120         elif default_browser == "midori":
121             other_browser_cmd = "midori '%s'"
122             self.LaunchBrowser = self.LaunchOtherBrowser
123         elif default_browser == "other":
124             if other_browser_cmd != "":
125                 self.LaunchBrowser = self.LaunchOtherBrowser
126             else:
127                 print "default_browser is 'other', but no other_browser_cmd set -- using default"
128                 self.LaunchBrowser = self.LaunchTear
129         else:
130             print "Unknown default_browser %s, using default" % default_browser
131             self.LaunchBrowser = self.LaunchTear
132
133     def __init__(self):
134         self.UpdateDefaultBrowser()
135
136 class ProxyBrowserService(dbus.service.Object):
137     def __init__(self):
138         bus_name = dbus.service.BusName('com.nokia.osso_browser', bus=dbus.SessionBus())
139         dbus.service.Object.__init__(self, bus_name, '/com/nokia/osso_browser')
140         dbus.service.Object.__init__(self, bus_name, '/com/nokia/osso_browser/request')
141
142     def OpenAddress(self, uri):
143         print uri
144
145         if uri[0] == '/':
146             print "prefixing apparent local path with file://"
147             uri = "file://" + uri
148
149         launcher.LaunchBrowser(uri)
150
151     @dbus.service.method(dbus_interface='com.nokia.osso_browser', in_signature='s')
152     def load_url(self, uri):
153         print "load_url"
154         self.OpenAddress(uri)
155
156     @dbus.service.method(dbus_interface='com.nokia.osso_browser', in_signature='s')
157     def mime_open(self, uri):
158         print "mime_open"
159         self.OpenAddress(uri)
160
161     @dbus.service.method(dbus_interface='com.nokia.osso_browser', in_signature='s')
162     def open_new_window(self, uri):
163         print "open_new_window"
164         self.OpenAddress(uri)
165
166     @dbus.service.method(dbus_interface='com.nokia.osso_browser')
167     def top_application(self):
168         print "top_application"
169         launcher.LaunchMicroB("new_window")
170
171     # This method is an "undocumented", non-standard extension to the
172     # osso_browser D-Bus API, intended solely to allow a wrapper script
173     # replacing /usr/bin/browser to implement --url
174     @dbus.service.method(dbus_interface='com.nokia.osso_browser')
175     def switchboard_launch_microb(self, uri="new_window"):
176         print "switchboard_launch_microb"
177         launcher.LaunchMicroB(uri)
178
179 def readconfigfile(signalnum=None, frame=None):
180     # reset configuration to defaults
181     setconfigdefaults()
182
183     # read configuration from the config file, if available
184     try:
185         execfile(os.getenv("HOME", "/home/user") + "/.config/browser-proxy", globals())
186         launcher.UpdateDefaultBrowser()
187     except:
188         # No valid config file available
189         pass
190
191 setconfigdefaults()
192
193 launcher = BrowserLauncher()
194 pbrowser = ProxyBrowserService()
195
196 readconfigfile()
197
198 if continuous_mode:
199     import signal
200     def waitforzombies(signalnum, frame):
201         try:
202             while os.waitpid(-1, os.WNOHANG) != (0, 0):
203                 continue
204         except OSError:
205             pass
206     signal.signal(signal.SIGCHLD, waitforzombies)
207     signal.signal(signal.SIGHUP, readconfigfile)
208
209 loop = gobject.MainLoop()
210 loop.run()