racket/collects/net/sendurl.ss
Paul Graunke 04a3da9994 fixed error reporting
original commit: 7ab9526c21855ab623ee427bfdb94cd6c90e68e6
2002-07-09 20:25:37 +00:00

183 lines
8.0 KiB
Scheme

(module sendurl mzscheme
(require (lib "process.ss")
(lib "file.ss")
(lib "etc.ss")
(lib "sendevent.ss"))
(provide send-url)
(define separate-by-default?
(get-preference 'new-browser-for-urls (lambda () #t)))
; send-url : str -> void
(define send-url
(opt-lambda (str [separate-window? separate-by-default?])
; The with-handler reverts to the old error port before printing raised error messages.
(with-handlers ([void (lambda (exn) (raise exn))])
(parameterize ([current-input-port null-input]
[current-error-port null-output]
[current-output-port null-output])
(cond
[(eq? (system-type) 'macos)
;; actually, I think GURL means something slightly different...
(send-event "MACS" "GURL" "GURL" str)]
[(or (eq? (system-type) 'macosx)
(equal? "ppc-macosxonx" (system-library-subpath)))
(system (format "osascript -e 'open location \"~a\"'" str))]
[(eq? (system-type) 'windows)
(shell-execute #f str "" (current-directory) 'SW_SHOWNORMAL)]
[(eq? (system-type) 'unix)
(let ([preferred (get-preference 'external-browser (lambda () #f))])
(cond
[(use-browser 'opera preferred)
=>
(lambda (browser-path)
;; opera may not return -- always open asyncronously
;; opera starts a new browser automatically, if it can't find one
(process*/close-ports browser-path "-remote"
(format "openURL(~a)"
(if separate-window?
(format "~a,new-window" str)
str))))]
[(use-browser 'galeon preferred)
=>
(lambda (browser-path)
(process*/close-ports browser-path
(if separate-window? "-w" "-x")
str))]
[(or (use-browser 'netscape preferred)
(use-browser 'mozilla preferred))
=>
(lambda (browser-path)
;; netscape's -remote returns with an error code, if no
;; netscape is around. start a new netscape in that case.
(or (system* browser-path "-remote"
(format "openURL(~a)"
(if separate-window?
(format "~a,new-window" str)
str)))
(process*/close-ports browser-path str)))]
[(use-browser 'dillo preferred)
=>
(lambda (browser-path)
(process*/close-ports browser-path str))]
[else
(error 'open-url "Couldn't find Opera, Galeon, Mozilla, Netscape, or Dillo to open URL: ~e" str)]))]
[else (error 'send-url "don't know how to open URL on platform: ~s" (system-type))])))))
; : sym sym -> (U #f str)
; to find the path for the named browser, unless another browser is preferred
(define (use-browser browser-name preferred)
(and (or (not preferred)
(eq? preferred browser-name))
(find-executable-path (symbol->string browser-name) #f)))
; null-input : iport
(define null-input
(make-custom-input-port #f
(lambda (s) eof)
#f
void))
; null-output : oport
(define null-output
(make-custom-output-port #f (lambda (s start end flush?) (- end start)) void void))
(define dev-null "/dev/null") ; unix specific, so slashes are okay
; process*/close-ports : [arg]* -> void
; This is a funny hack. Closing unused scheme pipe ports right away closes
; the ports the browser is using, resulting in errors if they are read from or written
; to. However, closing file ports (i.e. to and from /dev/null) after the subprocess is
; spawned does _not_ close the browser's ports since they are copied when the subprocess
; loads. Blech.
; All this is necessary because system administrators replace netscape with a funny
; perl/zsh/whatever script that prints out extra crud or does weird, system dependent
; setup stuff before launching the original browser executable.
(define (process*/close-ports . args)
(if (and (memq (system-type) '(unix macosx))
; we can't check for _the_ dev-null, so check what we can
(file-exists? dev-null)
(let ([perms (file-or-directory-permissions dev-null)])
(and (memq 'read perms)
(memq 'write perms)))
(zero? (file-size dev-null)))
(let ([out (open-output-file dev-null 'append)]
[in (open-input-file dev-null)]
[err (open-output-file dev-null 'append)])
(let-values ([(false-out false-in id false-err status)
(apply values (apply process*/ports out in err args))])
(close-output-port out)
(close-input-port in)
(close-output-port err)))
(let-values ([(out in id err status)
(apply values (apply process*/ports #f #f #f args))])
(close-input-port out)
(close-output-port in)
(close-input-port err))))
; parse-command : iport -> (listof str)
(define (parse-command.com in)
(let parse ()
(cond
[(eof-object? (skip-space-tab in)) null]
[else (cons (list->string (parse-one #t in)) (parse))])))
; parse-one : bool iport -> (listof char)
(define (parse-one unquoted in)
(let parse-1 ([unquoted unquoted])
(let ([c (read-char in)])
(cond
[(eof-object? c) null]
[(eq? c #\\) (parse-backslashes 1 unquoted in)]
[(eq? c #\") (parse-1 (not unquoted))]
[(and unquoted (or (eq? #\space c) (eq? #\tab c))) null]
[else (cons c (parse-1 unquoted))]))))
; parse-backslashes : nat bool iport -> (listof char)
(define (parse-backslashes n unquoted in)
(let more ([n n])
(let ([c (read-char in)])
(cond
[(eq? c #\\) (more (add1 n))]
[(eq? c #\")
(if (even? n)
(cons-n-backslashes (/ n 2) (parse-one (not unquoted) in))
(cons-n-backslashes (/ (sub1 n) 2) (cons #\" (parse-one unquoted in))))]
[(and unquoted (or (eq? #\space c) (eq? #\tab c))) null]
[else (cons-n-backslashes n (cons c (parse-one unquoted in)))]))))
; cons-n-backslashes : nat (listof char) -> (listof char)
(define (cons-n-backslashes n l)
(cond
[(zero? n) l]
[else (cons-n-backslashes (sub1 n) (cons #\\ l))]))
; skip-space-tab : iport -> (U char eof)
; to skip spaces and tabs
(define (skip-space-tab in)
(let loop ()
(let ([c (peek-char in)])
(cond
[(or (eq? #\space c) (eq? #\tab c))
(read-char in)
(loop)]
[else c]))))
; test-parse-command.com : -> bool
; all but one of these tests is taken from MS's documentation
; http://www.cygwin.com/ml/cygwin/1999-08/msg00701.html
(define (test-parse-command.com)
(and (equal? (parse-command.com (open-input-string "\"a b c\" d e"))
'("a b c" "d" "e"))
(equal? (parse-command.com (open-input-string "\"ab\\\"c\" \"\\\\\" d"))
'("ab\"c" "\\" "d"))
(equal? (parse-command.com (open-input-string "a\\\\\\b d\"e f\"g h"))
'("a\\\\\\b" "de fg" "h"))
(equal? (parse-command.com (open-input-string "a\\\"b c d"))
'("a\"b" "c" "d"))
(equal? (parse-command.com (open-input-string "a\\\\\\\"b c d"))
'("a\\\"b" "c" "d"))
(equal? (parse-command.com (open-input-string "a\\\\\\\\\"b c\" d e"))
'("a\\\\b c" "d" "e")))))