summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2025-10-09 01:05:26 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2025-10-09 01:07:59 +0900
commit01cb38a5fb11224807452be28122de7066e1a2fa (patch)
tree4f65383763500e10c21cb1dcfde6cd8f37ff9faf
parentc38c6cad794c040e5ef66485922f4d1a9fdab678 (diff)
downloadfzf-01cb38a5fb11224807452be28122de7066e1a2fa.tar.gz
Add Unix domain socket support for --listen
Close #4541
-rw-r--r--CHANGELOG.md18
-rw-r--r--man/man1/fzf.134
-rw-r--r--src/options.go4
-rw-r--r--src/server.go48
-rw-r--r--src/terminal.go7
5 files changed, 86 insertions, 25 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1b3a4aa8..91b0450f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -124,6 +124,24 @@ This version includes a few minor updates to fzf's classic visual style:
- Markers no longer use background colors.
- The `--color base16` theme (alias: `16`) has been updated for better compatibility with both dark and light themes.
+### `--listen` now supports Unix domain sockets
+
+If an argument to `--listen` ends with `.sock`, fzf will listen on a Unix
+domain socket at the specified path.
+
+```sh
+fzf --listen /tmp/fzf.sock --no-tmux
+
+# GET
+curl --unix-socket /tmp/fzf.sock http
+
+# POST
+curl --unix-socket /tmp/fzf.sock http -d up
+```
+
+Note that any existing file at the given path will be removed before creating
+the socket, so avoid using an important file path.
+
### Added options
#### `--gutter CHAR`
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index 95f33fb6..b5135101 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -1133,19 +1133,25 @@ On Windows, the default value is \fBcmd /s/c\fR when \fB$SHELL\fR is not
set.
.TP
-.B "\-\-listen[=[ADDR:]PORT]" "\-\-listen\-unsafe[=[ADDR:]PORT]"
-Start HTTP server and listen on the given address. It allows external processes
-to send actions to perform via POST method.
+.B "\-\-listen[=SOCKET_PATH|[ADDR:]PORT]" "\-\-listen\-unsafe[=[ADDR:]PORT]"
+Start HTTP server and listen on the given address or Unix socket. It allows
+external processes to send actions to perform via POST method and query the
+program state via GET method. For the argument to be recognized as a socket
+path, it must have \fB.sock\fR extension.
- If the port number is omitted or given as 0, fzf will automatically choose
-a port and export it as \fBFZF_PORT\fR environment variable to the child processes
+a port and export it as \fBFZF_PORT\fR environment variable to the child processes.
+
+- If a Unix socket path is given, fzf will create a Unix domain socket at the
+ given path. The existing file will be removed. The path to the socket file
+ is exported as \fBFZF_SOCK\fR environment variable.
- If \fBFZF_API_KEY\fR environment variable is set, the server would require
-sending an API key with the same value in the \fBx\-api\-key\fR HTTP header
+ sending an API key with the same value in the \fBx\-api\-key\fR HTTP header.
-- \fBFZF_API_KEY\fR is required for a non-localhost listen address
+- \fBFZF_API_KEY\fR is required for a non-localhost listen address.
-- To allow remote process execution, use \fB\-\-listen\-unsafe\fR
+- To allow remote process execution, use \fB\-\-listen\-unsafe\fR.
e.g.
\fB# Start HTTP server on port 6266
@@ -1184,6 +1190,18 @@ e.g.
'
\fR
+Here is an example script that uses a Unix socket instead of a TCP port.
+
+ \fB
+ fzf --listen=/tmp/fzf.sock
+
+ # GET
+ curl --unix-socket /tmp/fzf.sock http
+
+ # POST
+ curl --unix-socket /tmp/fzf.sock http -d up
+ \fR
+
.SS DIRECTORY TRAVERSAL
.TP
.B "\-\-walker=[file][,dir][,follow][,hidden]"
@@ -1373,6 +1391,8 @@ fzf exports the following environment variables to its child processes.
.br
.BR FZF_PORT " Port number when \-\-listen option is used"
.br
+.BR FZF_SOCK " Unix socket path when \-\-listen option is used"
+.br
.BR FZF_PREVIEW_TOP " Top position of the preview window"
.br
.BR FZF_PREVIEW_LEFT " Left position of the preview window"
diff --git a/src/options.go b/src/options.go
index a0b6f514..c1f75585 100644
--- a/src/options.go
+++ b/src/options.go
@@ -206,7 +206,9 @@ Usage: fzf [options]
ADVANCED
--with-shell=STR Shell command and flags to start child processes with
- --listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
+ --listen[=SOCKET_PATH] Start HTTP server to receive actions via Unix domain socket
+ (Path should end with .sock)
+ --listen[=[ADDR:]PORT] Start HTTP server to receive actions via TCP
(To allow remote process execution, use --listen-unsafe)
DIRECTORY TRAVERSAL (Only used when $FZF_DEFAULT_COMMAND is not set)
diff --git a/src/server.go b/src/server.go
index 5757e160..9f1200da 100644
--- a/src/server.go
+++ b/src/server.go
@@ -46,15 +46,20 @@ type httpServer struct {
type listenAddress struct {
host string
port int
+ sock string
}
func (addr listenAddress) IsLocal() bool {
- return addr.host == "localhost" || addr.host == "127.0.0.1"
+ return addr.host == "localhost" || addr.host == "127.0.0.1" || len(addr.sock) > 0
}
-var defaultListenAddr = listenAddress{"localhost", 0}
+var defaultListenAddr = listenAddress{"localhost", 0, ""}
func parseListenAddress(address string) (listenAddress, error) {
+ if strings.HasSuffix(address, ".sock") {
+ return listenAddress{"", 0, address}, nil
+ }
+
parts := strings.SplitN(address, ":", 3)
if len(parts) == 1 {
parts = []string{"localhost", parts[0]}
@@ -70,7 +75,7 @@ func parseListenAddress(address string) (listenAddress, error) {
if len(parts[0]) == 0 {
parts[0] = "localhost"
}
- return listenAddress{parts[0], port}, nil
+ return listenAddress{parts[0], port, ""}, nil
}
func startHttpServer(address listenAddress, actionChannel chan []*action, getHandler func(getParams) string) (net.Listener, int, error) {
@@ -80,21 +85,32 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, getHan
if !address.IsLocal() && len(apiKey) == 0 {
return nil, port, errors.New("FZF_API_KEY is required to allow remote access")
}
- addrStr := fmt.Sprintf("%s:%d", host, port)
- listener, err := net.Listen("tcp", addrStr)
- if err != nil {
- return nil, port, fmt.Errorf("failed to listen on %s", addrStr)
- }
- if port == 0 {
- addr := listener.Addr().String()
- parts := strings.Split(addr, ":")
- if len(parts) < 2 {
- return nil, port, fmt.Errorf("cannot extract port: %s", addr)
+
+ var listener net.Listener
+ var err error
+ if len(address.sock) > 0 {
+ os.Remove(address.sock)
+ listener, err = net.Listen("unix", address.sock)
+ if err != nil {
+ return nil, 0, fmt.Errorf("failed to listen on %s", address.sock)
}
- var err error
- port, err = strconv.Atoi(parts[len(parts)-1])
+ } else {
+ addrStr := fmt.Sprintf("%s:%d", host, port)
+ listener, err = net.Listen("tcp", addrStr)
if err != nil {
- return nil, port, err
+ return nil, port, fmt.Errorf("failed to listen on %s", addrStr)
+ }
+ if port == 0 {
+ addr := listener.Addr().String()
+ parts := strings.Split(addr, ":")
+ if len(parts) < 2 {
+ return nil, port, fmt.Errorf("cannot extract port: %s", addr)
+ }
+ var err error
+ port, err = strconv.Atoi(parts[len(parts)-1])
+ if err != nil {
+ return nil, port, err
+ }
}
}
diff --git a/src/terminal.go b/src/terminal.go
index 9a014d14..cf95623c 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -1268,7 +1268,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
return nil, err
}
t.listener = listener
- t.listenPort = &port
+ if port > 0 {
+ t.listenPort = &port
+ }
}
if t.hasStartActions {
@@ -1292,6 +1294,9 @@ func (t *Terminal) environForPreview() []string {
func (t *Terminal) environImpl(forPreview bool) []string {
env := os.Environ()
+ if t.listenAddr != nil && len(t.listenAddr.sock) > 0 {
+ env = append(env, "FZF_SOCK="+t.listenAddr.sock)
+ }
if t.listenPort != nil {
env = append(env, fmt.Sprintf("FZF_PORT=%d", *t.listenPort))
}