%%%------------------------------------------------------------------- %% @doc Serves the cached menu over a Unix domain socket. %% %% Connecting to the socket *is* the request: each accepted connection %% is handed the current `er_xdg_pipe_menu_cache:get_menu/0' binary and %% closed. There is no request payload to parse. %% @end %%%------------------------------------------------------------------- -module(er_xdg_pipe_menu_socket). -export([start_link/0, socket_path/0]). -export([init/1]). -spec start_link() -> {ok, pid()}. start_link() -> proc_lib:start_link(?MODULE, init, [self()]). -spec socket_path() -> file:filename_all(). socket_path() -> case application:get_env(er_xdg_pipe_menu, socket_path) of {ok, Path} -> Path; undefined -> default_socket_path() end. default_socket_path() -> case env("XDG_RUNTIME_DIR") of undefined -> filename:join("/tmp", "er_xdg_pipe_menu-" ++ os:getenv("USER", "unknown") ++ ".sock"); Dir -> filename:join(Dir, "er_xdg_pipe_menu.sock") end. env(Name) -> case os:getenv(Name) of false -> undefined; "" -> undefined; Value -> Value end. init(Parent) -> Path = socket_path(), %% Clean up a stale socket file left behind by a previous run that %% didn't shut down cleanly; gen_tcp:listen fails with eaddrinuse %% otherwise. _ = file:delete(Path), {ok, Listen} = gen_tcp:listen(0, [ {ifaddr, {local, Path}}, binary, {active, false}, {packet, raw}, {reuseaddr, true} ]), proc_lib:init_ack(Parent, {ok, self()}), accept_loop(Listen). accept_loop(Listen) -> case gen_tcp:accept(Listen) of {ok, Socket} -> spawn(fun() -> serve(Socket) end), accept_loop(Listen); {error, Reason} -> exit(Reason) end. serve(Socket) -> gen_tcp:send(Socket, er_xdg_pipe_menu_cache:get_menu()), gen_tcp:close(Socket).