aboutsummaryrefslogtreecommitdiff
path: root/src/er_xdg_pipe_menu_renderer.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/er_xdg_pipe_menu_renderer.erl')
-rw-r--r--src/er_xdg_pipe_menu_renderer.erl58
1 files changed, 58 insertions, 0 deletions
diff --git a/src/er_xdg_pipe_menu_renderer.erl b/src/er_xdg_pipe_menu_renderer.erl
new file mode 100644
index 0000000..fafed45
--- /dev/null
+++ b/src/er_xdg_pipe_menu_renderer.erl
@@ -0,0 +1,58 @@
+%%%-------------------------------------------------------------------
+%% @doc Renders parsed desktop entries as an Openbox pipe menu.
+%%
+%% Given a list of `#desktop_entry{}' records, produces the XML that
+%% Openbox expects on stdout from a pipe menu: one `<item>' per
+%% visible entry (entries with `hidden = true' are dropped), sorted
+%% case-insensitively by name, each wrapping an `Execute' action.
+%% @end
+%%%-------------------------------------------------------------------
+
+-module(er_xdg_pipe_menu_renderer).
+
+-include("er_xdg_pipe_menu.hrl").
+
+-export([render/1]).
+
+-spec render([#desktop_entry{}]) -> binary().
+render(Entries) ->
+ Visible = [E || E <- Entries, not E#desktop_entry.hidden],
+ Sorted = lists:sort(fun by_name/2, Visible),
+ iolist_to_binary([
+ <<"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n">>,
+ <<"<openbox_pipe_menu>\n">>,
+ [render_item(E) || E <- Sorted],
+ <<"</openbox_pipe_menu>\n">>
+ ]).
+
+by_name(#desktop_entry{name = A}, #desktop_entry{name = B}) ->
+ string:lowercase(A) =< string:lowercase(B).
+
+render_item(#desktop_entry{name = Name, exec = Exec, icon = Icon}) ->
+ [
+ <<" <item label=\"">>,
+ escape_attr(Name),
+ <<"\"">>,
+ icon_attr(Icon),
+ <<">\n">>,
+ <<" <action name=\"Execute\">\n">>,
+ <<" <command>">>,
+ escape_text(Exec),
+ <<"</command>\n">>,
+ <<" </action>\n">>,
+ <<" </item>\n">>
+ ].
+
+icon_attr(undefined) -> <<>>;
+icon_attr(Icon) -> [<<" icon=\"">>, escape_attr(Icon), <<"\"">>].
+
+%% Attribute values additionally need '"' escaped; text content doesn't.
+escape_attr(Bin) -> escape(Bin, [{<<"\"">>, <<"&quot;">>}]).
+escape_text(Bin) -> escape(Bin, []).
+
+escape(Bin, Extra) ->
+ lists:foldl(
+ fun({Pat, Repl}, Acc) -> binary:replace(Acc, Pat, Repl, [global]) end,
+ Bin,
+ [{<<"&">>, <<"&amp;">>}, {<<"<">>, <<"&lt;">>}, {<<">">>, <<"&gt;">>} | Extra]
+ ).