simpler sockets

There are a handful of keys that I never touch—Scroll Lock, SysRq, F1 through F12, and that strange button with an unidentifiable glyph which doesn't seem to do anything.

Pause is another one of those keys. Wouldn't it be nice if pause lived up to its name and could actually pause a video or music, regardless of which window is in focus?

Well, it can. But that's not the interesting part. This post showcases some curious functionality of zsh that ended up being surprisingly useful: the zsocket command.

the setup

My media player of choice, mpv, can be controlled by an external program through the use of a JSON IPC protocol. Let's get started by enabling the necessary option in mpv.conf—it just takes a single line:

input-ipc-server=/tmp/mpv-socket

Whenever we run mpv, it will start listening for commands on a Unix domain socket named /tmp/mpv-socket (creating the file if it doesn't already exist).

The man page for mpv describes its protocol in more detail, but the message for toggling the player's pause state is fairly straightforward:

{"command": ["cycle", "pause"]}

the solution

Now that the socket is open, we need to send our command. Despite the fact that it looks like a normal file, we can't simply echo a string to the socket:

> echo '{"command": ["cycle", "pause"]}' > /tmp/mpv-socket
zsh: no such device or address: /tmp/mpv-socket

mpv's man page recommends using socat to talk to the socket. That may be the easiest solution, but I'd rather not install an extra package on my system for a command that I only use in one place.

We could write a short program to do the job, but the POSIX socket API is a bit unwieldy for such a simple task.

Considering that this post is supposed to be about zsh, you already know where this is going. Fine, let's see what zsh can do:

zmodload zsh/net/socket
zsocket /tmp/mpv-socket
print -u "${REPLY}" '{"command": ["cycle", "pause"]}'

Three lines, not too shabby! It works like this:

  • zmodload loads zsh's socket module, which provides the zsocket function.
  • zsocket opens the socket and stores its file descriptor in the variable REPLY.
  • print writes our command to the socket. The -u option allows us to specify a file descriptor.

All that's left is to wire up the pause key to run our script. That process varies by desktop environment, so I'll leave the rest up to you.

One last thing...

If you think zsocket is neat, be sure to check out ztcp in the zshmodules man page. Just as zsocket provides a simple way to communicate via Unix sockets, ztcp can be used to read and write Internet sockets.

Happy scripting!