aboutsummaryrefslogtreecommitdiff
path: root/src/er_xdg_pipe_menu_renderer.erl
blob: f89d8680724449f62bb0bc0541c9452d893cb623 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
%%%-------------------------------------------------------------------
%% @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 `<menu>' submenu
%% per category, each containing an `<item>' per visible entry in
%% that category (entries with `hidden = true' are dropped). An entry
%% belonging to several categories appears in each of their submenus;
%% an entry with no categories is placed in a catch-all `Other'
%% submenu. Categories are sorted case-insensitively, and entries
%% within a submenu are sorted case-insensitively by name.
%% @end
%%%-------------------------------------------------------------------

-module(er_xdg_pipe_menu_renderer).

-include("er_xdg_pipe_menu.hrl").

-export([render/1]).

-define(OTHER_CATEGORY, <<"Other">>).

-spec render([#desktop_entry{}]) -> binary().
render(Entries) ->
    Visible = [E || E <- Entries, not E#desktop_entry.hidden],
    Grouped = group_by_category(Visible),
    Categories = lists:sort(fun category_lt/2, maps:keys(Grouped)),
    Menu = {openbox_pipe_menu, [], submenus_content(Categories, Grouped)},
    Xml = xmerl:export_simple(
        [Menu],
        xmerl_xml,
        [{prolog, ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"]}]
    ),
    iolist_to_binary([Xml, "\n"]).

group_by_category(Entries) ->
    lists:foldl(fun add_entry_to_categories/2, #{}, Entries).

add_entry_to_categories(#desktop_entry{categories = []} = E, Acc) ->
    add_entry_to_category(?OTHER_CATEGORY, E, Acc);
add_entry_to_categories(#desktop_entry{categories = Cats} = E, Acc) ->
    lists:foldl(fun(Cat, Acc0) -> add_entry_to_category(Cat, E, Acc0) end, Acc, Cats).

add_entry_to_category(Cat, E, Acc) ->
    maps:update_with(Cat, fun(Es) -> [E | Es] end, [E], Acc).

category_lt(A, B) -> string:lowercase(A) =< string:lowercase(B).

by_name(#desktop_entry{name = A}, #desktop_entry{name = B}) ->
    string:lowercase(A) =< string:lowercase(B).

submenus_content(Categories, Grouped) ->
    lists:foldr(
        fun(Cat, Acc) -> ["\n  ", submenu_element(Cat, Grouped) | Acc] end, ["\n"], Categories
    ).

submenu_element(Cat, Grouped) ->
    Entries = lists:sort(fun by_name/2, maps:get(Cat, Grouped)),
    {menu, [{id, Cat}, {label, Cat}], items_content(Entries)}.

items_content(Entries) ->
    lists:foldr(fun(E, Acc) -> ["\n    ", item_element(E) | Acc] end, ["\n  "], Entries).

item_element(#desktop_entry{name = Name, exec = Exec, icon = Icon}) ->
    {item, item_attrs(Name, Icon), [
        "\n      ",
        {action, [{name, <<"Execute">>}], [
            "\n        ",
            {command, [], [text(Exec)]},
            "\n      "
        ]},
        "\n    "
    ]}.

item_attrs(Name, undefined) -> [{label, Name}];
item_attrs(Name, Icon) -> [{label, Name}, {icon, Icon}].

text(Bin) -> unicode:characters_to_list(Bin).