diff options
| author | Christopher R. Nelson <christopher.nelson@languidnights.com> | 2026-07-21 11:18:56 -0400 |
|---|---|---|
| committer | Christopher R. Nelson <christopher.nelson@languidnights.com> | 2026-07-21 11:18:56 -0400 |
| commit | 7e8472e1e2a0b39746ef86c041d56b92bab5a6b7 (patch) | |
| tree | 4d37a36c945446b4bded6989a780abe4cd3f2f1e /src/er_xdg_pipe_menu_watcher.erl | |
| parent | 46b72a375101b54819f8750750017bd71e7c3798 (diff) | |
Wire up cache, file watcher, and Unix socket daemon
Adds a gen_server cache holding the rendered menu, an fs/inotify-based
watcher that debounces changes and invalidates it, a Unix domain
socket serving the cached binary, and the escript client Openbox
invokes (with an inline render fallback if the daemon isn't running).
Also documents local deployment (systemd user unit, Openbox menu.xml
wiring) in the README.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'src/er_xdg_pipe_menu_watcher.erl')
| -rw-r--r-- | src/er_xdg_pipe_menu_watcher.erl | 85 |
1 files changed, 85 insertions, 0 deletions
diff --git a/src/er_xdg_pipe_menu_watcher.erl b/src/er_xdg_pipe_menu_watcher.erl new file mode 100644 index 0000000..d8c407d --- /dev/null +++ b/src/er_xdg_pipe_menu_watcher.erl @@ -0,0 +1,85 @@ +%%%------------------------------------------------------------------- +%% @doc Watches the XDG application directories for changes. +%% +%% Starts one `fs' (native inotify) watch per existing directory +%% returned by `er_xdg_pipe_menu_scanner:data_dirs/0' and subscribes +%% to its file events. On any event, a debounce timer is (re)started; +%% once it fires with no further events in the window, the menu cache +%% is invalidated so the next request gets a freshly rebuilt menu. +%% +%% `fs' watches recursively (it drives `inotifywait -r' on Linux), so +%% changes in vendor subdirectories (e.g. `applications/kde4/*') are +%% picked up without any extra handling here. +%% @end +%%%------------------------------------------------------------------- + +-module(er_xdg_pipe_menu_watcher). + +-behaviour(gen_server). + +-export([start_link/0]). +-export([init/1, handle_call/3, handle_cast/2, handle_info/2]). + +-define(DEFAULT_DEBOUNCE_MS, 500). + +-spec start_link() -> {ok, pid()}. +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +init([]) -> + warn_if_inotifywait_missing(), + Dirs = [D || D <- er_xdg_pipe_menu_scanner:data_dirs(), filelib:is_dir(D)], + lists:foreach(fun watch/1, lists:zip(lists:seq(1, length(Dirs)), Dirs)), + {ok, #{timer => undefined}}. + +watch({Index, Dir}) -> + Name = list_to_atom("er_xdg_pipe_menu_fs_" ++ integer_to_list(Index)), + case fs:start_link(Name, Dir) of + {ok, _Pid} -> fs:subscribe(Name); + {error, Reason} -> log_watch_failure(Dir, Reason) + end. + +%% `fs' on Linux shells out to the system `inotifywait' binary; if +%% it's missing, `fs:start_link/2' still returns `{ok, _}' but simply +%% never delivers events, which is otherwise silent. +warn_if_inotifywait_missing() -> + case os:find_executable("inotifywait") of + false -> + error_logger:warning_msg( + "er_xdg_pipe_menu_watcher: 'inotifywait' not found on PATH " + "(install inotify-tools) -- desktop file changes won't be " + "picked up until the daemon is restarted~n", + [] + ); + _Path -> + ok + end. + +log_watch_failure(Dir, Reason) -> + error_logger:warning_msg( + "er_xdg_pipe_menu_watcher: failed to watch ~p: ~p~n", [Dir, Reason] + ). + +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({_Pid, {fs, file_event}, {_Path, _Events}}, State) -> + {noreply, reset_debounce(State)}; +handle_info(debounce_fire, State) -> + er_xdg_pipe_menu_cache:invalidate(), + {noreply, State#{timer => undefined}}; +handle_info(_Info, State) -> + {noreply, State}. + +reset_debounce(#{timer := Timer} = State) -> + case Timer of + undefined -> ok; + Ref -> erlang:cancel_timer(Ref) + end, + State#{timer => erlang:send_after(debounce_ms(), self(), debounce_fire)}. + +debounce_ms() -> + application:get_env(er_xdg_pipe_menu, debounce_ms, ?DEFAULT_DEBOUNCE_MS). |
