Invoking browsers

Difference between version 107 and 108 - Previous - Next
This page describes how to '''call''' upon an '''external [browser] to display a page''', given the URL. - DL
----
** Multi-platform library **
----
**MThe https://github.com/hpcloud/stiackato-Pcli/tree/master/lib/browse%|%`browse` package%|% developed by [aku] as part of stackato-cli provides a command Stolu open a URL in the browser on Windows, Mac, an**d Unix systems. [Tclssg] uses `browse`.
Usage: `::browse::url https://example.com`

** Multi-platform solution **

[CL], greatly amused by the extraordinary variety of approaches, offers the one he's using
as of mid-2004 (cleaned up a bit by st3ve,hao in 2014):
======tcl
package require Tcl 8.5
proc launchBrowser url {
    global tcl_platform

    if {$tcl_platform(platform) eq "windows"} {
        # first argument to "start" is "window title", which is not used here
        set command [list {*}[auto_execok start] {}]
        # (older) Windows shell would start a new command after &, so shell escape it with ^
        #set url [string map {& ^&} $url]
        # but 7+ don't seem to (?) so this nonsense is gone
        if {[file isdirectory $url]} {
            # if there is an executable named eg ${url}.exe, avoid opening that instead:
            set url [file nativename [file join $url .]]
        }
    } elseif {$tcl_platform(os) eq "Darwin"} {
        # It *is* generally a mistake to use $tcl_platform(os) to select functionality,
        # particularly in comparison to $tcl_platform(platform).  For now, let's just
        # regard it as a stylistic variation subject to debate.
        set command [list open]
    } else {
        set command [list xdg-open]
    }
    exec {*}$command $url &
}

proc _launchBrowser {url} {
    if [catch {launchBrowser $url} err] {
        tk_messageBox -icon error -message "error '$err' with '$command'"
    }
}
======
[aspect] 2015-11-19:  commented the [string map] command above as it doesn't seem to be neccessary on Windows 7+.  There seems to be a lot of confusion below about correct quoting for Windows shell, but at least on 7+ with tcl8.6, none of it seems to be needed.

Note that the above proc is also sufficient to open most "well known" file types, and browse folders, with the system's default handler, on all platforms.

[HaO] 2021-04-27: Androwish may be added, where the command is (without exec):

======
borg activity android.intent.action.VIEW http://wiki.tcl.tk text/html
======

[Lorry] 2015-11-22: To avoid having to use $tcl_platform(os) to select functionality:
======tcl
proc invokeBrowser {url} {
  # open is the OS X equivalent to xdg-open on Linux, start is used on Windows
  set commands {xdg-open open start}
  foreach browser $commands {
    if {$browser eq "start"} {
      set command [list {*}[auto_execok start] {}]
    } else {
      set command [auto_execok $browser]
    }
    if {[string length $command]} {
      break
    }
  }

  if {[string length $command] == 0} {
    return -code error "couldn't find browser"
  }
  if {[catch {exec {*}$command $url &} error]} {
    return -code error "couldn't execute '$command': $error"
  }
}
======
----
<<discussion>>
**Windows**
I use this on [Windows] 95 and 98.

  proc url x {
    set x [regsub -all -nocase {htm} $x {ht%6D}]
    exec rundll32 url.dll,FileProtocolHandler $x &
  }

Internet Explorer (actually rundll32 according to the links here) doesn't like .htm
[http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&safe=off&th=3b125e7bc35a509a&rnum=5]
[http://www.jsifaq.com/SUBI/tip4100/rh4162.htm]
that's why I work around it.

''--[ro]''  

Thanks [Vince] for suggesting the [rundll] method ;)  I found the htm problem the hard way ;(

[TR] The htm problem is not there on Win XP.

----

Here's an example from my Tcl/Tk Programmer's Reference [http://www.purl.org/net/TclTkProgRef] which uses the registry on Win32. This example can browse to
URLs with anchors (foo.html#bar). It can be adapted to open any file based on its association by replacing ".html" with the
desired extention in the first registry get command. 

(perSub does percent substitutions like those in event bindings. It's available in the regexp example at the site noted above.) 

Chris Nelson 

  package require registry
  proc showHtml { htmlFile } {
      # Look for the application under
      # HKEY_CLASSES_ROOT
      set root HKEY_CLASSES_ROOT

      # Get the application key for HTML files
      set appKey [registry get $root\\.html ""]

      # Get the command for opening HTML files
      set appCmd [registry get \
           $root\\$appKey\\shell\\open\\command ""]

      # Substitute the HTML filename into the
      # command for %1
      set appCmd [perSub $appCmd %1 $htmlFile]

      # Double up the backslashes for eval (below)
      regsub -all {\\} $appCmd  {\\\\} appCmd

      # Invoke the command
      eval exec $appCmd &
  }
  showHtml C:/foobar.html

----
Surely the line 

        set result [lindex $progs]

in browser::findExecutable should be 

        set result [list $progs]

BHT 
----

We verified this to work on a variety of Windows platforms:

        # On Win95, even if there is a web browser installed, it cannot
        # open an internet address, only a local .html file.

        # Win98 doesn't like "/"
        if {[lindex [array get ::tcl_platform] 1]=="4.10"} {
            regsub -all "/" $file "\\\\" file
        }
        }
        # Substitute & with ^&
        # (Example: http://www.ideogramic.com?a=1&b=1 => http://www.ideogramic.com?a=1^&b=1
        #  Otherwise, it will only open http://www.ideogramic.com?a=1)
        regsub -all "&" $file "^&" file
        regsub -all "&" $file "^&" file
        # Open file
        eval exec [auto_execok start] [list $file] &

Trust me :-)

Klaus Marius Hansen

[Brian Theado] 13May03 - How does the ^& trick work?  Is it something within Tcl that is treating the ^ as an escape?

If I try 
 eval exec [auto_execok start] [list http://ats.nist.gov/cgi-bin/cgi.tcl/echo.cgi?hello=1&hi=2]
I get 
 'hi' is not recognized as an internal or external command, operable program or batch file.
and ''hi=2'' doesn't make it through to the browser.  If I try:
 eval exec [auto_execok start] [list http://ats.nist.gov/cgi-bin/cgi.tcl/echo.cgi?hello=1^&hi=2]
Both ''hello=1'' and ''hi=2'' makes it through to the browser.  This happens for me on both WinXP and NT 4.0.

[BR] 15May03 - On NT, START is an internal command to CMD.EXE.  ^ is an escape in CMD.EXE command syntax.
You can do line continuation with ^, and you can do several commands in one line with &.  You use ^^ and ^&
if you really want to pass ^ or & to a command.  You can also quote using double quotation marks as in

  eval exec [auto_execok start] {"http://ats.nist.gov/cgi-bin/cgi.tcl/echo.cgi?hello=1&hi=2"}

All this is probably different on W9x/Me where START.EXE is an external command and the command
interpreter is different (COMMAND.COM).  

[MG] April 1st 2004 - On Win 98SE, at least, the '^' isn't needed; including it causes 'hello' to equal '1^' rather than just '1' on the webpage.

[Gordon Scott] September 4th 2007. In addition to the above ampersand substitution, I found I also had to substitute the space character with percent 20 so my application could open its own help files in C:/Program Files/MyApp/html/

    regsub -all " " $uri "%20" uri


----
**Unix and Netscape**

For UNIX users that want to use ONE Netscape window to display various content, here's what I do: 

Create a default html file that establishes an initial title for the Netscape window that starts. 

    <title>
    foobar.20001005232400     <-- This is a date time string
    </title>

Then start Netscape with this default file: 

    exec netscape -geometry 600x800 default.html 2> /dev/null &

Then get the X-windows id for the Netscape window just started: 

    set windowID [exec xlswins | grep foobar.20001005232400]

        xlswins is a UNIX command used to query the X-windows which
        are open.

Now that we have a window id, that particular Netscape window can be used for dedicated content display for your
application: 

    exec netscape -id $windowID -remote openFile(somefile.html)

Of course I've presented the bare bones approach that isn't very practical or 'clean' by itself, but it has the basics. Just wish I
could do this in Windows also. Maybe Microsoft's Explorer has similar capability? 

Marty Backe
----
The rough windows equivalent of the above is:
 if {[catch {dde request $browser WWW_OpenURL $url}]} {
     regsub %1 $appCmd $url appCmd
     regsub -all {\\} $appCmd {\\\\} appCmd
     eval exec $appCmd $url &
 }

$browser is "NETSCAPE" or "IExplore" (as determined by the appCmd extracted from the registry), $appCmd is the application invocation command extracted from the registry as in showHtml above

------
here a more dynamic version of the above, simply try starting the browser with a remote command and check for errors, if no browser instance is running, you get something like "... not running .." and you have to start the browser. This works on unix and with mozilla, netscape would need some modifications.

 set rcmdbrowser /usr/local/mozilla/mozilla
 set browser /usr/local/mozilla/run-mozilla.sh
 # use new-tab or new-window - whatever you prefer
 catch {exec $rcmdbrowser -remote "OpenURL($url,new-tab)"} resp
 if {[string match "*running*" $resp]} {
   exec $browser /usr/local/mozilla/mozilla-bin $url &
 }

Gerhard Hintermayer
------
**MacOS and determining the browser's current page**

Here's a procedure I use to ask the browser what page it is currently viewing.  It currently needs 'browserSig' to point to the browser, and on [MacOS] relies on the [TclAE] apple-events extension to Tcl.

    proc url::browserWindow {} {
        global tcl_platform browserSig browserSigs
        switch -- $tcl_platform(platform) {
            "macintosh" {
                # If several different browsers are running, we should
                # really pick the frontmost, somehow.
                if {![app::isRunning $browserSigs name sig]} {
                    error "No browser running."
                }
                if {![regexp {\[([0-9]+)} [AEBuild -r '$sig' WWW! LSTW] "" winnum]} {
                    error "No browser window."
                }
                # returns window info
                regexp {\[([^ ]+)} [AEBuild -r '$sig' WWW! WNFO ---- $winnum] "" winurl
                set winurl [string trim $winurl ","]
                if {$winurl == "'TEXT'()"} {
                    error "Empty browser window."
                }
                return $winurl
            }
            "windows" {
                if {[info exists browserSig]} {
                    set root [string tolower [file rootname [file tail $browserSig]]]
                } else {
                    set root iexplore
                }
                set root [string trim $root ".0123456789"]
                # If multiple iexplore instances are running, this seems
                # to pick the first?  This should work for 'iexplore' and
                # 'netscape' names.
                set info [dde request $root WWW_GetWindowInfo 1]
                set url [lindex [split $info \"] 1]
                return $url
            }
            "unix" {
                if {$tcl_platform(os) == "Darwin"} {
                    if {![app::isRunning $browserSigs name sig]} {
                        error "No browser running."
                    }
                    if {![regexp {\[([0-9]+)} [AEBuild -r '$sig' WWW! LSTW] "" winnum]} {
                        error "No browser window."
                    }
                    # returns window info
                    regexp {\[([^ ]+)} [AEBuild -r '$sig' WWW! WNFO ---- $winnum] "" winurl
                    set winurl [string trim $winurl "??,"]
                    if {$winurl == "'TEXT'()"} {
                        error "Empty browser window."
                    }
                    return $winurl
                } else {
                    error "Sorry, this is unimplemented.  Please contribute\
                      a suitable implementation!"
                }
            }
        }
    }

----
This doesn't need to be so complicated on windows, IMO.

Get my [winutils] extension @ http://sourceforge.net/project/showfiles.php?group_id=1616&release_id=51105

Inside it is a command [[winutils::shell]].  [exec] was intended to 
launch stdio apps and communicate over pipes.  Windows does not
have this concept for GUI programs.  [[winutils::shell]] is a fire and
forget approach and like `cmd /c start` will use associations.

 % package require winutils
 0.2
 % winutils::shell http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/tcl/tcl/generic/tcl.h?rev=1.103.2.1&content-type=text/vnd.viewcvs-markup&only_with_tag=macosx-8-4-branch

[DG]
----
Also on Windows you can use the Internet Explorer ActiveX component to display a web page in a Tk frame.  See [optcl] for more information and examples of embeddeding ActiveX components into a Tk frame. 

[MPJ]
----
See also "[Advanced browser management]", "[Using Tcl to write WWW client side applications]",
"[Tcl/Tk Tclet Plugin]", "[BrowseX]", "[sh8]",
....

----

[MHo] After testing this and that, I'm using the following construct, which seems to work reliable and looks pretty easy... at least it is handy in programs which already use [tcom]:

      set wsh [::tcom::ref createobject "WScript.Shell"]
      $wsh Run xyz.html 3

----

Almost all you say here is about MS Windows. However, on Unix (and sometimes even Windows),
Tcl can interact with browsers in a "text-mode" way, too.  [w3m] provides
one example.


----
**Linux**

The following works nicely for most Linux distributions out there (and probably other Unix)
 
    proc invokeBrowser {url} {
        foreach browser {htmlview mozilla konqueror netscape} {
            set binary [lindex [auto_execok $browser] 0]
            if {[string length $binary]} {
                catch {exec $binary $url &}
                break
            }
        }
    }

[andrewsh]: I'd also try x-www-browser or sensible-browser (as in Debian) first. I'd even try sensible-browser before anything else.

[Aud] 2011/05/30: xdg-open is a good first choice, too.

[JM] 5/30/2015: I had to use "firefox" instead of "mozilla" above with my ubuntu 1204.

----
**MacOS X**

[Bryan Oakley] 09-Nov-2003 I don't think [applescript] needs to be involved. This seems
to work just fine on my box:

    exec open http://wiki.tcl.tk/557


----
[[ [CL] has examples of [COM] management of IE and Navigator
he'll include here sometime.]]


----
More ideas appear in this [http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/302324] page, which happens to be expressed in [Python].


----
[RA] I added "Epiphany", the default gnome browser before galeon and put firefox in front of mozilla because of number of firefox users.
----
[Duoas] 2008-10-07
I've been messing with this again recently and it still bothers me that on Win32 using '''wish''' and '''start foo.html''' takes at least 30 seconds to work. In the meantime the application freezes. In the past I've just put up a little box that says something like
  Starting your web browser...
  (Please be patient)
but this is decidedly unprofessional (and tolerable only because a locked program is worse). The problem does ''not'' manifest with '''tclsh''', or from the command prompt, or with any other file type I have tested, and I have narrowed it down to only the start + html combination --other Tcl commands and list processing etc are not involved. I would prefer to avoid having to link-in Windows-specific packages (like TWAPI or FFidl) just to pop up HTML documentation quickly.

My XP '''start''' command documentation does say:
    When executing an application that is a 32-bit GUI application, CMD.EXE
    does not wait for the application to terminate before returning to
    the command prompt.  This new behavior does NOT occur if executing
    within a command script.
but I haven't combed through Tcl sources since 8.1.

Has anyone else suffered this? (I've tried it on Windows 98, NT 2000, and XP.) Does anyone know how to fix it?

[[Add reference to discussion (was it on comp.lang.tcl?) of this issue.]]

[Duoas] Yes, apparently there was such a discussion, but all the links I can find to it are broken. According to the synopses I have found, the OP's question was never resolved. I'm currently using Ffidl to use ShellExecute() in shell32.dll --which makes it work lickity-split like it ought, but that does mean a 66.6k platform-specific addition just to start the browser...  The confounding part (for me, anyway) is that the slow behavior is specific to wish, not tclsh, and only for HTML files (regardless of file association).

[Lars H]: This made me think about [http://groups.google.com/group/comp.lang.tcl/browse_thread/thread/29ea98d58bd807aa#], although it turns out that was about [csv] data rather than [html]. Still, the symptoms are similar, so it's quite probably the same issue. Some quotes from thread:
   '''Mark Janssen''':   I am not sure about the exact reason, but adding a & at the end makes starting programs with start in wish much faster. If you don't need the output, you could try with `[[exec .../excel.exe file.csv &]]`. 
   '''MB''':   calling excel directly make the slow startup problem disappear. I was just hoping to allow the system defined program associated with .csv files to be used. 
   '''Alexandre Ferrieux''':   Yes, I see the same thing with wordview.exe. After a bit of tracing it turns out that the sequence is wish->cmd.exe->wordview.exe, (cmd.exe running the "start" command), and that the stall occurs in the middle of cmd.exe and is exactly 30 seconds long, down to the millisecond. So it must be some kind of timeout in failing interprocess communication or something like that. What's funny is that although it happens long ''before'' wordview is launched, it is still dependent on the nature of the child (doesn't occur with Acrobat nor Emacs).

[Duoas] Wow! That hit the mark exactly! I've edited [CL]'s code above to have that ampersand. I modified the '''exec''' command for all platforms. If that is not appropriate (for the platforms listed it should be fine), it can be moved to the Windows-specific case instead:
          set command "[auto_execok start] {} [list $url] &"
(I still wonder, though, what difference starting as a background or foreground process could possibly make on the cmd shell to cause this delay?)

<<categories>> Internet | Tutorial