diff --git a/kittens/choose_files/main.go b/kittens/choose_files/main.go index 91b023647..b716a10d5 100644 --- a/kittens/choose_files/main.go +++ b/kittens/choose_files/main.go @@ -1,11 +1,13 @@ package choose_files import ( + "bytes" "encoding/json" "fmt" "os" "path/filepath" "slices" + "strconv" "strings" "github.com/kovidgoyal/kitty/tools/cli" @@ -474,7 +476,14 @@ func main(_ *cli.Command, opts *Options, args []string) (rc int, err error) { if default_cwd, err = filepath.Abs(default_cwd); err != nil { return } - lp.OnInitialize = handler.OnInitialize + lp.OnInitialize = func() (string, error) { + if opts.WritePidTo != "" { + if err := utils.AtomicWriteFile(opts.WritePidTo, bytes.NewReader([]byte(strconv.Itoa(os.Getpid()))), 0600); err != nil { + return "", err + } + } + return handler.OnInitialize() + } lp.OnResize = func(old, new_size loop.ScreenSize) (err error) { handler.init_sizes(new_size) return handler.draw_screen() diff --git a/kittens/choose_files/main.py b/kittens/choose_files/main.py index fbe9e2248..7aac2f682 100644 --- a/kittens/choose_files/main.py +++ b/kittens/choose_files/main.py @@ -60,6 +60,10 @@ def main(args: list[str]) -> None: choices=text,json default=text The format in which to write the output. + + +--write-pid-to +Path to a file to which to write the process ID (PID) of this process to. '''.format(config_help=CONFIG_HELP.format(conf_name='choose-files', appname=appname)).format diff --git a/kittens/desktop_ui/portal.go b/kittens/desktop_ui/portal.go index 3983f50cc..8a252b63d 100644 --- a/kittens/desktop_ui/portal.go +++ b/kittens/desktop_ui/portal.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "time" "github.com/kovidgoyal/dbus" @@ -36,6 +37,7 @@ const FILE_CHOOSER_INTERFACE = "org.freedesktop.impl.portal.FileChooser" const KITTY_OBJECT_PATH = "/net/kovidgoyal/kitty/portal" const CHANGE_SETTINGS_INTERFACE = "net.kovidgoyal.kitty.settings" const DESKTOP_PORTAL_NAME = "org.freedesktop.portal.Desktop" +const REQUEST_INTERFACE = "org.freedesktop.impl.portal.Request" // Special portal setting used to check if we are being called by xdg-desktop-portal const SETTINGS_CANARY_NAMESPACE = "net.kovidgoyal.kitty" @@ -631,6 +633,7 @@ type ChooseFilesData struct { Mode string Cwd string SuggestedSaveFileName, SuggestedSaveFilePath string + Handle dbus.ObjectPath } func var_to_bool_or_false(v dbus.Variant) bool { @@ -666,15 +669,34 @@ type ChooserResponse struct { Interrupted bool `json:"interrupted"` } -func (self *Portal) run_file_chooser(cfd ChooseFilesData) (response uint32, uris []string) { +func (self *Portal) run_file_chooser(cfd ChooseFilesData) (response uint32, result_dict vmap) { response = RESPONSE_ENDED tdir, err := os.MkdirTemp("", "kitty-cfd") if err != nil { fmt.Fprintf(os.Stderr, "cannot run file chooser as failed to create a temporary directory with error: %s\n", err) return } - defer os.RemoveAll(tdir) + pid_path := filepath.Join(tdir, "pid") + var close_requested, child_killed atomic.Bool + Close := func() *dbus.Error { + close_requested.Store(true) + if !child_killed.Load() { + if raw, err := os.ReadFile(pid_path); err == nil { + if pid, err := strconv.Atoi(string(raw)); err == nil { + child_killed.Store(true) + unix.Kill(pid, unix.SIGTERM) + } + } + } + return nil + } + self.bus.ExportMethodTable(map[string]any{"Close": Close}, cfd.Handle, REQUEST_INTERFACE) + defer func() { + self.bus.ExportMethodTable(nil, cfd.Handle, REQUEST_INTERFACE) + _ = os.RemoveAll(tdir) + }() output_path := filepath.Join(tdir, "output.json") + cmd := func() *exec.Cmd { self.lock.Lock() defer self.lock.Unlock() @@ -726,24 +748,31 @@ func (self *Portal) run_file_chooser(cfd ChooseFilesData) (response uint32, uris if cfd.SuggestedSaveFilePath != "" { args = append(args, `--suggested-save-file-path`, cfd.SuggestedSaveFilePath) } + args = append(args, "--write-pid-to", pid_path) cmd := exec.Command(utils.KittyExe(), args...) cmd.Dir = cfd.Cwd cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd }() - if cmd == nil { + if cmd == nil || close_requested.Load() { return } if err := cmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "running file chooser failed with error: %s\n", err) return } + if close_requested.Load() { + return + } raw, err := os.ReadFile(output_path) if err != nil { fmt.Fprintf(os.Stderr, "running file chooser failed, could not read from output file with error: %s\n", err) return } + if close_requested.Load() { + return + } var result ChooserResponse if err = json.Unmarshal(raw, &result); err != nil { fmt.Fprintf(os.Stderr, "running file chooser failed, invalid JSON response with error: %s\n", err) @@ -760,11 +789,12 @@ func (self *Portal) run_file_chooser(cfd ChooseFilesData) (response uint32, uris } response = RESPONSE_SUCCESS prefix := "file://" + utils.IfElse(runtime.GOOS == "windows", "/", "") - uris = utils.Map(func(path string) string { + uris := utils.Map(func(path string) string { path = filepath.ToSlash(path) u := url.URL{Path: path} return prefix + u.EscapedPath() }, result.Paths) + result_dict = vmap{"uris": dbus.MakeVariant(uris)} return } @@ -778,7 +808,7 @@ func (options vmap) get_bytearray(name string) string { } func (self *Portal) OpenFile(handle dbus.ObjectPath, app_id string, parent_window string, title string, options vmap) (uint32, vmap, *dbus.Error) { - cfd := ChooseFilesData{Title: title, Cwd: options.get_bytearray("current_folder")} + cfd := ChooseFilesData{Title: title, Cwd: options.get_bytearray("current_folder"), Handle: handle} dir_only := false if v, found := options["directory"]; found && var_to_bool_or_false(v) { dir_only = true @@ -792,13 +822,13 @@ func (self *Portal) OpenFile(handle dbus.ObjectPath, app_id string, parent_windo } else { cfd.Mode = utils.IfElse(multiple, "files", "file") } - - return RESPONSE_CANCELED, nil, nil + response, result := self.run_file_chooser(cfd) + return response, result, nil } func (self *Portal) SaveFile(handle dbus.ObjectPath, app_id string, parent_window string, title string, options vmap) (uint32, vmap, *dbus.Error) { cfd := ChooseFilesData{ - Title: title, Cwd: options.get_bytearray("current_folder"), + Title: title, Cwd: options.get_bytearray("current_folder"), Handle: handle, SuggestedSaveFileName: options.get_bytearray("current_name"), SuggestedSaveFilePath: options.get_bytearray("current_file")} multiple := false @@ -807,5 +837,6 @@ func (self *Portal) SaveFile(handle dbus.ObjectPath, app_id string, parent_windo } cfd.Mode = utils.IfElse(multiple, "save-files", "save-file") - return RESPONSE_CANCELED, nil, nil + response, result := self.run_file_chooser(cfd) + return response, result, nil }