You are here

Portable web browser module for Python apps

9 posts / 0 new
Last post
Chris Morgan
Chris Morgan's picture
Offline
Last seen: 8 years 9 months ago
Joined: 2007-04-15 21:08
Portable web browser module for Python apps

Earlier today, vf2nsr mentioned he was looking at the Python documentation, for googsystray, to see if he could come up with a way to make it use a portable browser when it opens web pages - it uses the Python webbrowser module to open webpages. So I thought to myself that it would be useful to have a module which we could add to Python apps if they open web pages often (normally when it's just an about box or things like that I wouldn't bother unless it can be conveniently arranged with the development team).

In general there will be no need for code like this, but for some apps this may be useful to be able to give to the authors to apply to their code. Variants of this for other languages - at least the detection part, even if not the automatic changing of the browser that will be used if it's not possible or easily possible - could be helpful too.

So here is this code:

portablewebbrowser.py

"""
portablewebbrowser.py
=====================

Version 1.0.1, 2010-06-23

Python provides a ``webbrowser`` module for opening web pages and other URLs in
browsers, but by default this launches the system's default browser. For
portable apps, it's helpful to make this instead launch a portable browser, of
the user's choice if possible, rather than a local browser. This module does
just that by finding and registering a portable browser with ``webbrowser`` if
it can.

Usage::

    import webbrowser
    import portablewebbrowser

    webbrowser.open('http://www.example.com')

If you want to use this without using webbrowser, just for getting the path to
a portable browser so you can use it directly or something like that, use
this::

    import portablewebbrowser

    portablewebbrowser.get_portable_browser_path()

This returns the path to the portable browser, or None if one wasn't found.

At present this module does not support running Thunderbird or another portable
mail client for a mailto: link; this is something which I'm not decided about
yet. At least one thing uses webbrowser for mailto: links (googsystray), but it
is a tad harder requiring extending one of the classes to provide a bit of
parsing. I'll think about it more.

If an app uses webbrowser.register() itself, the ``import portablewebbrowser``
should come after all that code to make sure that it takes highest precedence.

License information
===================

Copyright 2010 Chris Morgan of PortableApps.com
Website: http://PortableApps.com/node/23921

This software is OSI Certified Open Source Software.
OSI Certified is a certification mark of the Open Source Initiative.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

EXCEPTION: This Python module can be used with open source applications
licensed under OSI-approved licenses as well as freeware provided it is
unmodified. It may also be used with commercial software by contacting
PortableApps.com.

Version history
===============

1.0, 2010-06-19: initial release
1.0.1, 2010-06-23: fixed search order bug
"""

import webbrowser, os

_portable_browser = None

def _register_portable_browser(path):
    "Register a portable browser with webbrowser."
    if webbrowser._iscommand(path):
        # Insert the browser to the start of the lookup order
        webbrowser.register('%s-portable' % os.path.basename(path).lower(), None, webbrowser.GenericBrowser(path), -1)
        global _portable_browser
        _portable_browser = path
        return True
    return False

def _find_portable_browser():
    """
    Attempt to find a portable browser.

    First it looks for an environment variable called PortableApps.comBrowser.

    If that's not set, then in the PortableApps directory it looks for portable
    browsers known about at the time of writing. These are Firefox and Google
    Chrome, which are official, and some which are in development, Iron,
    K-Meleon, Arora, QtWeb and Midori.

    If the app isn't in PortableApps.com Format or none of these work out, the
    browser will be left as the system default.
    """

    if 'PortableApps.comBrowser' in os.environ:
        # At the time of writing, this environment variable had not been
        # implemented, nor was it clearly on the roadmap. It had been
        # mentioned, so here's a proposal for it.
        _register_portable_browser(os.environ['PortableApps.comBrowser'])

    if _portable_browser == None:
        # Try looking for it. First of all see if it's in PAF.
        package_dir, _, _ = __file__.rpartition(os.path.sep + 'App' + os.path.sep)

        # If there's not a directory App in the tree somewhere, package_dir
        # will be empty and so portableapps_dir will be empty.
        portableapps_dir = os.path.dirname(package_dir)

        if portableapps_dir != '':
            browser_list = (
                    # Officially released
                    'Firefox',
                    'GoogleChrome',

                    # In development
                    'Iron',
                    'K-Meleon',
                    'Arora',
                    'QtWeb',
                    'Midori',
            )

            for browser in browser_list:
                found_portable_browser = _register_portable_browser(
                        os.path.join(portableapps_dir,
                            '%sPortable' % browser,
                            '%sPortable.exe' % browser
                        ))
                if found_portable_browser: break

    # If we've found a portable browser by now, then all calls to
    # webbrowser.open() will invoke this portable browser rather than the
    # system's default browser.

def get_portable_browser_path():
    """
    Get the path to the user's portable browser if it can be found. If no
    portable browser can be found, this will be None.
    """
    return _portable_browser

_find_portable_browser()

For apps that use webbrowser, using this is as simple as putting in the file and a single import line and it'll all work.

Example usage before:

import webbrowser
webbrowser.open('http://portableapps.com')

Example usage after: (it doesn't matter whether portablewebbrowser is imported before or after webbrowser)

import webbrowser
import portablewebbrowser
webbrowser.open('http://portableapps.com')

At some time we'll want this in the Platform in such a way that the user can specify their preferred portable browser. If we can start doing this sort of a technique across the board, pushing changes upstream, I think it would be a Good Thing for PortableApps.com and portable apps. The value that I've used is an environment variable called PortableApps.comBrowser which contains a path.

Observe also my note about the use of webbrowser.open for mailto: links. Should I extend this so that it can try using Thunderbird directly for mailto: links if it's there?

vf2nsr
vf2nsr's picture
Offline
Last seen: 7 years 5 months ago
Developer
Joined: 2010-02-13 17:10
Thanks Chris

I will try later tonight to set this in motion both at the author level as well aws local trying to recompile it myself into googsystray. Much appreciated

“Be who you are and say what you feel because those who mind don't matter and those who matter don't mind.” Dr. Seuss

vf2nsr
vf2nsr's picture
Offline
Last seen: 7 years 5 months ago
Developer
Joined: 2010-02-13 17:10
Question

In looking at the GMain.py file I came across this

def fixWebBrowser():
	# *sigh* webbrowser is totally broken.  The user's BROWSER
	# variable only works if webbrowser already KNOWS about the
	# browser in question.  What's worse is that the BROWSER 
	# variable only gets checked on import, so register() just
	# sticks the new one at the end, so it'll never get called.
	# So we have to duplicate the BROWSER code as well.
	# This shouldn't matter for folks using Gnome or KDE, but
	# webbrowser won't check for xdg-open for xfce.
	for browser in ("chrome", "chromium",
			"chrome-browser", "chromium-browser",
			"xdg-open"):
		if webbrowser._iscommand(browser):
			webbrowser.register(browser, None, webbrowser.BackgroundBrowser(browser))
	if "BROWSER" in os.environ:
		_userchoices = os.environ["BROWSER"].split(os.pathsep)
		_userchoices.reverse()

		# Treat choices in same way as if passed into get() but do register
		# and prepend to _tryorder
		for cmdline in _userchoices:
			if cmdline != '':
				webbrowser._synthesize(cmdline, -1)
		cmdline = None # to make del work if _userchoices was empty
		del cmdline
		del _userchoices

Will this mess up your script?

“Be who you are and say what you feel because those who mind don't matter and those who matter don't mind.” Dr. Seuss

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 8 years 9 months ago
Joined: 2007-04-15 21:08
Shouldn't do

I thought about that case but decided that as far as the first lot are concerned, they won't go wrong as they'll be appended rather than prepended to the search list (as the fourth parameter of webbrowser.register is not provided); for the BROWSER environment variable, it's probably better for it to override it anyway (not that anyone ever will).

If it was considered important, portablewebbrowser could be imported at the end of that method or after it's invoked and it'll happen last.

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

vf2nsr
vf2nsr's picture
Offline
Last seen: 7 years 5 months ago
Developer
Joined: 2010-02-13 17:10
error log


Traceback (most recent call last):
File "googsystray", line 30, in
File "googsystray\GMain.pyc", line 11, in
File "googsystray\portablewebbrowser.pyc", line 153, in
File "googsystray\portablewebbrowser.pyc", line 132, in _find_portable_browser
AttributeError: 'tuple' object has no attribute 'reverse'

I know the Gmain issue as well as the googsystray issue.

Not sure about the portablewebbrowser one?

“Be who you are and say what you feel because those who mind don't matter and those who matter don't mind.” Dr. Seuss

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 8 years 9 months ago
Joined: 2007-04-15 21:08
Whoops

At one point I changed the way I did it and removed all the previous way except that one line. That was silly. Try 1.0.1.

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

vf2nsr
vf2nsr's picture
Offline
Last seen: 7 years 5 months ago
Developer
Joined: 2010-02-13 17:10
Thanks again Chris

Will give this a go later today. I really do appreciate your help. On another question sort of related? Since I am getting no real support from the author on making the changes and now with the source code I am also looking at tweaking it a bit more so that it will save things portably instead of having the launcher do it do should I keep the name? Do I have to fork it? Do I need to rename it?

“Be who you are and say what you feel because those who mind don't matter and those who matter don't mind.” Dr. Seuss

Soulmech
Offline
Last seen: 11 years 9 months ago
Joined: 2010-03-03 10:52
I'd do the Thunderbird thing

I'd do the Thunderbird thing as a band-aid fix for now and make a "portableemail" akin to "portablewebbrowser" for opening mailto links.

SWAG

vf2nsr
vf2nsr's picture
Offline
Last seen: 7 years 5 months ago
Developer
Joined: 2010-02-13 17:10
New Code 1.01

It seems to do the trick or at least so far in my testing. Not sure whether to release an update? rename it? or finish the tweaks that I want for it first? But on your part GREAT job Chris! Thanks ever so much

“Be who you are and say what you feel because those who mind don't matter and those who matter don't mind.” Dr. Seuss

Log in or register to post comments