initial commit
This commit is contained in:
102
opener.go
Normal file
102
opener.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// hostOpen launches the platform's "open" handler for target (a URL, file path,
|
||||
// or opaque string) without blocking. Errors after launch are logged, not
|
||||
// returned, since the GUI handler is fire-and-forget.
|
||||
//
|
||||
// It resolves the opener via realOpenerPath so a host that also shadows
|
||||
// `xdg-open`/`open` with this binary doesn't recurse back into client mode.
|
||||
func hostOpen(target string) error {
|
||||
name, args := openCommand(target)
|
||||
bin, err := realOpenerPath(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command(bin, args...)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("start %s: %w", bin, err)
|
||||
}
|
||||
go func() {
|
||||
if err := cmd.Wait(); err != nil {
|
||||
log.Printf("open %q exited: %v", target, err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func openCommand(target string) (string, []string) {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return "open", []string{target}
|
||||
case "windows":
|
||||
return "rundll32", []string{"url.dll,FileProtocolHandler", target}
|
||||
default:
|
||||
return "xdg-open", []string{target}
|
||||
}
|
||||
}
|
||||
|
||||
// defaultOpenerName is the platform's native opener, used as the fallback target
|
||||
// when we were invoked as `open-proxy open` rather than under a shadowed name.
|
||||
func defaultOpenerName() string {
|
||||
if runtime.GOOS == "darwin" {
|
||||
return "open"
|
||||
}
|
||||
return "xdg-open"
|
||||
}
|
||||
|
||||
// localOpen runs the real opener (the next `name` on $PATH that isn't us) for
|
||||
// arg. Used as the client-side fallback when the host is unreachable.
|
||||
func localOpen(name, arg string) error {
|
||||
bin, err := realOpenerPath(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command(bin, arg)
|
||||
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// realOpenerPath finds an executable named `name` on $PATH that is not this
|
||||
// binary (so a symlink shadowing `xdg-open`/`open` doesn't recurse into us).
|
||||
func realOpenerPath(name string) (string, error) {
|
||||
self := selfFileInfo()
|
||||
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
||||
if dir == "" {
|
||||
continue
|
||||
}
|
||||
cand := filepath.Join(dir, name)
|
||||
info, err := os.Stat(cand) // follows symlinks
|
||||
if err != nil || info.IsDir() || info.Mode()&0o111 == 0 {
|
||||
continue
|
||||
}
|
||||
// Compare by file identity (device+inode), not path string: a symlink or
|
||||
// hardlink pointing back at this binary is recognized as us regardless of
|
||||
// how its path is spelled or whether $PATH entries are relative.
|
||||
if self != nil && os.SameFile(self, info) {
|
||||
continue
|
||||
}
|
||||
return cand, nil
|
||||
}
|
||||
return "", fmt.Errorf("no real %q found on PATH", name)
|
||||
}
|
||||
|
||||
func selfFileInfo() os.FileInfo {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
info, err := os.Stat(exe)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return info
|
||||
}
|
||||
Reference in New Issue
Block a user