diff --git a/docs/changelog.rst b/docs/changelog.rst index 3278359a7..cb90dde30 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -100,6 +100,8 @@ Detailed list of changes - Dispatch any clicks waiting for :opt:`click_interval` on key events (:iss:`7601`) +- ``kitten run-shell``: Automatically add the directory containing the kitten binary to PATH if needed. Controlled via the ``--inject-self-onto-path`` option (`disc`:7668`) + - Wayland: Fix an issue with mouse selections not being stopped when there are multiple OS windows (:iss:`7381`) - Splits layout: Fix the ``move_to_screen_edge`` action breaking when only a single window is present (:iss:`7621`) diff --git a/tools/cmd/run_shell/main.go b/tools/cmd/run_shell/main.go index ad0a40e0f..4c90ef6ed 100644 --- a/tools/cmd/run_shell/main.go +++ b/tools/cmd/run_shell/main.go @@ -6,21 +6,27 @@ import ( "fmt" "kitty" "os" + "path/filepath" "strings" "kitty/tools/cli" "kitty/tools/tty" "kitty/tools/tui" "kitty/tools/tui/shell_integration" + "kitty/tools/utils" + + "golang.org/x/exp/slices" + "golang.org/x/sys/unix" ) var _ = fmt.Print type Options struct { - Shell string - ShellIntegration string - Env []string - Cwd string + Shell string + ShellIntegration string + Env []string + Cwd string + InjectSelfOntoPath string } func main(args []string, opts *Options) (rc int, err error) { @@ -45,6 +51,52 @@ func main(args []string, opts *Options) (rc int, err error) { if os.Getenv("TERM") == "" { os.Setenv("TERM", kitty.DefaultTermName) } + if opts.InjectSelfOntoPath == "always" || (opts.InjectSelfOntoPath == "unless-root" && os.Geteuid() != 0) { + if exe, err := os.Executable(); err == nil { + if exe_dir, err := filepath.Abs(exe); err == nil { + realpath := func(x string) string { + if ans, err := filepath.EvalSymlinks(x); err == nil { + return ans + } + return x + } + exe_dir = realpath(filepath.Dir(exe_dir)) + path_items := strings.Split(os.Getenv("PATH"), string(os.PathListSeparator)) + realpath_items := utils.Map(realpath, path_items) + done := false + changed := false + is_executable_file := func(q string) bool { + if unix.Access(q, unix.X_OK) != nil { + return false + } + if s, err := os.Stat(q); err == nil && !s.IsDir() { + return true + } + return false + } + for i, x := range realpath_items { + q := filepath.Join(x, filepath.Base(exe)) + if is_executable_file(q) { + // some kitten already in path + if utils.Samefile(q, exe) { + done = true + break + } + path_items = slices.Insert(path_items, i, exe_dir) + changed, done = true, true + break + } + } + if !done { + path_items = append(path_items, exe_dir) + changed = true + } + if changed { + os.Setenv("PATH", strings.Join(path_items, string(os.PathListSeparator))) + } + } + } + } if term := os.Getenv("TERM"); term == kitty.DefaultTermName && shell_integration.PathToTerminfoDb(term) == "" { if terminfo_dir, err := shell_integration.EnsureTerminfoFiles(); err == nil { os.Unsetenv("TERMINFO") @@ -105,6 +157,12 @@ func EntryPoint(root *cli.Command) *cli.Command { Name: "--cwd", Help: "The working directory to use when executing the shell.", }) + sc.Add(cli.OptionSpec{ + Name: "--inject-self-onto-path", + Help: "Add the directory containing this kitten binary to PATH. Directory is added only if not already present.", + Default: "always", + Choices: "always,never,unless-root", + }) return sc }