4 Freedesktop.org compliant desktop entries and menu
8 Licensed under GNU General Public License v2
9 * (c) 2016, Luke Bonham
10 * (c) 2009-2015, Antonio Terceiro
14 local awful = require("awful")
15 local theme = require("beautiful")
16 local utils = require("menubar.utils")
17 local wibox = require("wibox")
28 -- freedesktop.desktop
30 -- Default desktop basic icons
35 onclick = "computer://"
40 onclick = os.getenv("HOME")
49 iconsize = { width = 48, height = 48 },
50 labelsize = { width = 140, height = 20 },
51 margin = { x = 20, y = 20 },
58 local desktop_current_pos = {}
60 -- @return iterator on input pipe
61 local function pipelines(...)
62 local f = assert(io.popen(...))
65 if data == nil then f:close() end
70 -- Adds an icon to desktop
71 -- @param args settings from desktop.add_icons
72 -- @param label icon string label
73 -- @param icon icon string file path
74 -- @param onclick function to execute on click
75 function desktop.add_single_icon(args, label, icon, onclick)
78 -- define icon dimensions and position
79 if not desktop_current_pos[s] then
80 desktop_current_pos[s] = { x = (capi.screen[s].geometry.x + args.iconsize.width + args.margin.x), y = 40 }
83 local totheight = (icon and args.iconsize.height or 0) + (label and args.labelsize.height or 0)
84 if totheight == 0 then return end
86 if desktop_current_pos[s].y + totheight > capi.screen[s].geometry.height - 40 then
87 desktop_current_pos[s].x = desktop_current_pos[s].x + args.labelsize.width + args.iconsize.width + args.margin.x
88 desktop_current_pos[s].y = 40
91 local common = { screen = s, bg = "#00000000", visible = true, type = "desktop" }
93 -- create icon container
95 common.width = args.iconsize.width
96 common.height = args.iconsize.height
97 common.x = desktop_current_pos[s].x
98 common.y = desktop_current_pos[s].y
100 icon = wibox.widget {
103 widget = wibox.widget.imagebox
106 icon:buttons(awful.button({ }, 1, nil, onclick))
108 icon_container = wibox(common)
109 icon_container:set_widget(icon)
111 desktop_current_pos[s].y = desktop_current_pos[s].y + args.iconsize.height + 5
114 -- create label container
116 common.width = args.labelsize.width
117 common.height = args.labelsize.height
118 common.x = desktop_current_pos[s].x - (args.labelsize.width/2) + args.iconsize.width/2
119 common.y = desktop_current_pos[s].y
121 caption = wibox.widget {
124 forced_width = common.width,
125 forced_height = common.height,
126 ellipsize = "middle",
127 widget = wibox.widget.textbox
130 caption:buttons(awful.button({ }, 1, onclick))
131 caption_container = wibox(common)
132 caption_container:set_widget(caption)
135 desktop_current_pos[s].y = desktop_current_pos[s].y + args.labelsize.height + args.margin.y
138 -- Adds base icons (This PC, Trash, etc) to desktop
139 -- @param args settings from desktop.add_icons
140 function desktop.add_base_icons(args)
141 for _,base in ipairs(args.baseicons) do
142 desktop.add_single_icon(args, base.label, utils.lookup_icon(base.icon), function()
143 awful.spawn(string.format("%s '%s'", args.open_with, base.onclick))
148 -- Looks up a suitable icon for filename
149 -- @param filename string file name
150 -- @return icon file path (string)
151 function desktop.lookup_file_icon(filename)
152 -- load system MIME types
153 if #mime_types == 0 then
154 for line in io.lines("/etc/mime.types") do
155 if not line:find("^#") then
157 for w in line:gmatch("[^%s]+") do
158 table.insert(parsed, w)
161 for i = 2, #parsed do
162 mime_types[parsed[i]] = parsed[1]:gsub("/", "-")
169 -- try to search a possible icon among standards
170 local extension = filename:match("%a+$")
171 local mime = mime_types[extension] or ""
172 local mime_family = mime:match("^%a+") or ""
174 local possible_filenames = {
175 mime, "gnome-mime-" .. mime,
176 mime_family, "gnome-mime-" .. mime_family,
180 for i, filename in ipairs(possible_filenames) do
181 local icon = utils.lookup_icon(filename)
182 if icon then return icon end
185 -- if we don"t find ad icon, then pretend is a plain text file
186 return utils.lookup_icon("text-x-generic")
189 -- Parse subdirectories and files list from input directory
190 -- @input dir directory to parse (string)
191 -- @return files table with found entries
192 function desktop.parse_dirs_and_files(dir)
194 local paths = pipelines('find '..dir..' -maxdepth 1 -type d | tail -1')
196 if path:match("[^/]+$") then
198 file.filename = path:match("[^/]+$")
201 file.icon = utils.lookup_icon("folder")
202 table.insert(files, file)
205 local paths = pipelines('find '..dir..' -maxdepth 1 -type f')
207 if not path:find("%.desktop$") then
209 file.filename = path:match("[^/]+$")
212 file.icon = desktop.lookup_file_icon(file.filename)
213 table.insert(files, file)
219 -- Adds subdirectories and files icons from args.dir
220 -- @param args settings from desktop.add_icons
221 function desktop.add_dirs_and_files_icons(args)
222 for _, file in ipairs(desktop.parse_dirs_and_files(args.dir)) do
224 local label = args.showlabels and file.filename or nil
225 local onclick = function () awful.spawn(string.format("%s '%s'", args.open_with, file.path)) end
226 desktop.add_single_icon(args, label, file.icon, onclick)
231 -- Main function, adds base, directory and files icons
232 -- @param args user defined settings, with fallback on defaults
233 function desktop.add_icons(args)
235 args.screen = args.screen or mouse.screen
236 args.dir = args.dir or os.getenv("HOME") .. "/Desktop"
237 args.showlabels = args.showlabel or true
238 args.open_with = args.open_with or "xdg_open"
239 args.baseicons = args.baseicons or desktop.baseicons
240 args.iconsize = args.iconsize or desktop.iconsize
241 args.labelsize = args.labelsize or desktop.labelsize
242 args.margin = args.margin or desktop.margin
244 -- trying to fallback on Adwaita if theme.icon_theme is not defined
245 -- if Adwaita is missing too, no icons will be shown
246 if not theme.icon_theme then
247 theme.icon_theme = args.icon_theme or "Adwaita"
250 desktop.add_base_icons(args)
251 desktop.add_dirs_and_files_icons(args)