--------------------------------------------------------------------------- -- @author Alexander Yakushev -- @copyright 2010-2011 Alexander Yakushev -- @release v1.1.5 --------------------------------------------------------------------------- awesompd = {} -- Function for checking icons and modules. Checks if a file exists, -- and if it does, returns the path to file, nil otherwise. function awesompd.try_load(file) if awful.util.file_readable(file) then return file end end -- Function for loading modules. function awesompd.try_require(module) if awesompd.try_load(awful.util.getdir("config") .. "/awesompd/" .. module .. ".lua") then return require('awesompd/' .. module) else return require(module) end end awesompd.try_require("utf8") awesompd.try_require("asyncshell") awesompd.try_require("jamendo") local beautiful = require('beautiful') local naughty = naughty local awful = awful local format = string.format local keygrabber = keygrabber -- Debug stuff local enable_dbg = true local function dbg (...) if enable_dbg then print(...) end end local function tbl_pr(tbl,shift) if enable_dbg then local shift = shift or "" for k, v in pairs(tbl) do print(shift .. k .. ": " .. tostring(v)) if type(v) == "table" then tbl_pr(v, shift .. " ") end end end end -- Constants awesompd.PLAYING = "Playing" awesompd.PAUSED = "Paused" awesompd.STOPPED = "MPD stopped" awesompd.DISCONNECTED = "Disconnected" awesompd.MOUSE_LEFT = 1 awesompd.MOUSE_MIDDLE = 2 awesompd.MOUSE_RIGHT = 3 awesompd.MOUSE_SCROLL_UP = 4 awesompd.MOUSE_SCROLL_DOWN = 5 awesompd.NOTIFY_VOLUME = 1 awesompd.NOTIFY_REPEAT = 2 awesompd.NOTIFY_RANDOM = 3 awesompd.NOTIFY_SINGLE = 4 awesompd.NOTIFY_CONSUME = 5 awesompd.FORMAT_MP3 = jamendo.FORMAT_MP3 awesompd.FORMAT_OGG = jamendo.FORMAT_OGG awesompd.ESCAPE_SYMBOL_MAPPING = {} awesompd.ESCAPE_SYMBOL_MAPPING["&"] = "&" -- Menus do not handle symbol escaping correctly, so they need their -- own mapping. awesompd.ESCAPE_MENU_SYMBOL_MAPPING = {} awesompd.ESCAPE_MENU_SYMBOL_MAPPING["&"] = "'n'" -- /// Current track variables and functions /// -- Returns a string for the given track to be displayed in the widget -- and notification. function awesompd.get_display_name(track) if track.display_name then return track.display_name elseif track.artist_name and track.track_name then return track.artist_name .. " - " .. track.name end end -- Returns a track display name, album name (if exists) and album -- release year (if exists). function awesompd.get_extended_info(track) local result = awesompd.get_display_name(track) if track.album_name then result = result .. "\n" .. track.album_name end if track.year then result = result .. "\n" .. track.year end return result end -- Returns true if the current status is either PLAYING or PAUSED function awesompd:playing_or_paused() return self.status == awesompd.PLAYING or self.status == awesompd.PAUSED end -- /// Helper functions /// -- Just like awful.util.pread, but takes an argument how to read like -- "*line" or "*all". function awesompd.pread(com, mode) local f = io.popen(com, 'r') local result = nil if f then result = f:read(mode) f:close() end return result end -- Slightly modified function awful.util.table.join. function awesompd.ajoin(buttons) local result = {} for i = 1, table.getn(buttons) do if buttons[i] then for k, v in pairs(buttons[i]) do if type(k) == "number" then table.insert(result, v) else result[k] = v end end end end return result end -- Splits a given string with linebreaks into an array. function awesompd.split(s) local l = { n = 0 } if s == "" then return l end s = s .. "\n" local f = function (s) l.n = l.n + 1 l[l.n] = s end local p = "%s*(.-)%s*\n%s*" s = string.gsub(s,p,f) return l end -- Icons function awesompd.load_icons(path) awesompd.ICONS = {} awesompd.ICONS.PLAY = awesompd.try_load(path .. "/play_icon.png") awesompd.ICONS.PAUSE = awesompd.try_load(path .. "/pause_icon.png") awesompd.ICONS.PLAY_PAUSE = awesompd.try_load(path .. "/play_pause_icon.png") awesompd.ICONS.STOP = awesompd.try_load(path .. "/stop_icon.png") awesompd.ICONS.NEXT = awesompd.try_load(path .. "/next_icon.png") awesompd.ICONS.PREV = awesompd.try_load(path .. "/prev_icon.png") awesompd.ICONS.CHECK = awesompd.try_load(path .. "/check_icon.png") awesompd.ICONS.RADIO = awesompd.try_load(path .. "/radio_icon.png") awesompd.ICONS.DEFAULT_ALBUM_COVER = awesompd.try_load(path .. "/default_album_cover.png") end -- Function that returns a new awesompd object. function awesompd:create() -- Initialization local instance = {} setmetatable(instance,self) self.__index = self instance.current_server = 1 instance.widget = widget({ type = "textbox" }) instance.notification = nil instance.scroll_pos = 1 instance.text = "" instance.to_notify = false instance.album_cover = nil instance.current_track = { } instance.recreate_menu = true instance.recreate_playback = true instance.recreate_list = true instance.recreate_servers = true instance.recreate_options = true instance.recreate_jamendo_formats = true instance.recreate_jamendo_order = true instance.recreate_jamendo_browse = true instance.current_number = 0 instance.menu_shown = false -- Default user options instance.servers = { { server = "localhost", port = 6600 } } instance.font = "Monospace" instance.scrolling = true instance.output_size = 30 instance.update_interval = 10 instance.path_to_icons = "" instance.ldecorator = " " instance.rdecorator = " " instance.jamendo_format = awesompd.FORMAT_MP3 instance.show_album_cover = true instance.album_cover_size = 50 instance.browser = "firefox" -- Widget configuration instance.widget:add_signal("mouse::enter", function(c) instance:notify_track() end) instance.widget:add_signal("mouse::leave", function(c) instance:remove_hint() end) return instance end -- Registers timers for the widget function awesompd:run() enable_dbg = self.debug_mode self.load_icons(self.path_to_icons) jamendo.set_current_format(self.jamendo_format) if self.album_cover_size > 100 then self.album_cover_size = 100 end self:update_track() self:check_playlists() self.update_widget_timer = timer({ timeout = 1 }) self.update_widget_timer:add_signal("timeout", function() self:update_widget() end) self.update_widget_timer:start() self.update_track_timer = timer({ timeout = self.update_interval }) self.update_track_timer:add_signal("timeout", function() self:update_track() end) self.update_track_timer:start() end -- Function that registers buttons on the widget. function awesompd:register_buttons(buttons) widget_buttons = {} self.global_bindings = {} for b=1,table.getn(buttons) do if type(buttons[b][1]) == "string" then mods = { buttons[b][1] } else mods = buttons[b][1] end if type(buttons[b][2]) == "number" then -- This is a mousebinding, bind it to the widget table.insert(widget_buttons, awful.button(mods, buttons[b][2], buttons[b][3])) else -- This is a global keybinding, remember it for later usage in append_global_keys table.insert(self.global_bindings, awful.key(mods, buttons[b][2], buttons[b][3])) end end self.widget:buttons(self.ajoin(widget_buttons)) end -- Takes the current table with keybindings and adds widget's own -- global keybindings that were specified in register_buttons. -- If keytable is not specified, then adds bindings to default -- globalkeys table. If specified, then adds bindings to keytable and -- returns it. function awesompd:append_global_keys(keytable) if keytable then for i = 1, table.getn(self.global_bindings) do keytable = awful.util.table.join(keytable, self.global_bindings[i]) end return keytable else for i = 1, table.getn(self.global_bindings) do globalkeys = awful.util.table.join(globalkeys, self.global_bindings[i]) end end end -- /// Group of mpc command functions /// -- Takes a command to mpc and a hook that is provided with awesompd -- instance and the result of command execution. function awesompd:command(com,hook) local file = io.popen(self:mpcquery() .. com) if hook then hook(self,file) end file:close() end -- Takes a command to mpc and read mode and returns the result. function awesompd:command_read(com, mode) mode = mode or "*line" self:command(com, function(_, f) result = f:read(mode) end) return result end function awesompd:command_playpause() return function() self:command("toggle",self.update_track) end end function awesompd:command_next_track() return function() self:command("next",self.update_track) end end function awesompd:command_prev_track() return function() self:command("seek 0") self:command("prev",self.update_track) end end function awesompd:command_stop() return function() self:command("stop",self.update_track) end end function awesompd:command_play_specific(n) return function() self:command("play " .. n,self.update_track) end end function awesompd:command_volume_up() return function() self:command("volume +5") self:update_track() -- Nasty! I should replace it with proper callback later. self:notify_state(self.NOTIFY_VOLUME) end end function awesompd:command_volume_down() return function() self:command("volume -5") self:update_track() self:notify_state(self.NOTIFY_VOLUME) end end function awesompd:command_load_playlist(name) return function() self:command("load " .. name, function() self.recreate_menu = true end) end end function awesompd:command_replace_playlist(name) return function() self:command("clear") self:command("load " .. name) self:command("play 1", self.update_track) end end function awesompd:command_clear_playlist() return function() self:command("clear", self.update_track) self.recreate_list = true self.recreate_menu = true end end function awesompd:command_open_in_browser(link) return function() if self.browser then awful.util.spawn(self.browser .. " '" .. link .. "'") end end end --- Change to the previous server. function awesompd:command_previous_server() return function() servers = table.getn(self.servers) if servers == 1 or servers == nil then return else if self.current_server > 1 then self:change_server(self.current_server - 1) else self:change_server(servers) end end end end --- Change to the previous server. function awesompd:command_next_server() return function() servers = table.getn(self.servers) if servers == 1 or servers == nil then return else if self.current_server < servers then self:change_server(self.current_server + 1) else self:change_server(1) end end end end -- /// End of mpc command functions /// -- /// Menu generation functions /// function awesompd:command_show_menu() return function() self:remove_hint() if self.recreate_menu then local new_menu = {} if self.main_menu ~= nil then self.main_menu:hide() end if self.status ~= awesompd.DISCONNECTED then self:check_list() self:check_playlists() local jamendo_menu = { { "Search by", { { "Nothing (Top 100)", self:menu_jamendo_top() }, { "Artist", self:menu_jamendo_search_by(jamendo.SEARCH_ARTIST) }, { "Album", self:menu_jamendo_search_by(jamendo.SEARCH_ALBUM) }, { "Tag", self:menu_jamendo_search_by(jamendo.SEARCH_TAG) }}} } local browse_menu = self:menu_jamendo_browse() if browse_menu then table.insert(jamendo_menu, browse_menu) end table.insert(jamendo_menu, self:menu_jamendo_format()) table.insert(jamendo_menu, self:menu_jamendo_order()) new_menu = { { "Playback", self:menu_playback() }, { "Options", self:menu_options() }, { "List", self:menu_list() }, { "Playlists", self:menu_playlists() }, { "Jamendo", jamendo_menu } } end table.insert(new_menu, { "Servers", self:menu_servers() }) self.main_menu = awful.menu({ items = new_menu, width = 300 }) self.recreate_menu = false end self.main_menu:toggle() end end -- Returns an icon for a checkbox menu item if it is checked, nil -- otherwise. function awesompd:menu_item_toggle(checked) return checked and self.ICONS.CHECK or nil end -- Returns an icon for a radiobox menu item if it is selected, nil -- otherwise. function awesompd:menu_item_radio(selected) return selected and self.ICONS.RADIO or nil end -- Returns the playback menu. Menu contains of: -- Play\Pause - always -- Previous - if the current track is not the first -- in the list and playback is not stopped -- Next - if the current track is not the last -- in the list and playback is not stopped -- Stop - if the playback is not stopped -- Clear playlist - always function awesompd:menu_playback() if self.recreate_playback then local new_menu = {} table.insert(new_menu, { "Play\\Pause", self:command_toggle(), self.ICONS.PLAY_PAUSE }) if self:playing_or_paused() then if self.list_array and self.list_array[self.current_number-1] then table.insert(new_menu, { "Prev: " .. awesompd.protect_string(jamendo.replace_link( self.list_array[self.current_number - 1]), true), self:command_prev_track(), self.ICONS.PREV }) end if self.list_array and self.current_number ~= table.getn(self.list_array) then table.insert(new_menu, { "Next: " .. awesompd.protect_string(jamendo.replace_link( self.list_array[self.current_number + 1]), true), self:command_next_track(), self.ICONS.NEXT }) end table.insert(new_menu, { "Stop", self:command_stop(), self.ICONS.STOP }) table.insert(new_menu, { "", nil }) end table.insert(new_menu, { "Clear playlist", self:command_clear_playlist() }) self.recreate_playback = false playback_menu = new_menu end return playback_menu end -- Returns the current playlist menu. Menu consists of all elements in the playlist. function awesompd:menu_list() if self.recreate_list then local new_menu = {} if self.list_array then local total_count = table.getn(self.list_array) local start_num = (self.current_number - 15 > 0) and self.current_number - 15 or 1 local end_num = (self.current_number + 15 < total_count ) and self.current_number + 15 or total_count for i = start_num, end_num do table.insert(new_menu, { jamendo.replace_link(self.list_array[i]), self:command_play_specific(i), self.current_number == i and (self.status == self.PLAYING and self.ICONS.PLAY or self.ICONS.PAUSE) or nil} ) end end self.recreate_list = false self.list_menu = new_menu end return self.list_menu end -- Returns the playlists menu. Menu consists of all files in the playlist folder. function awesompd:menu_playlists() if self.recreate_playlists then local new_menu = {} if table.getn(self.playlists_array) > 0 then for i = 1, table.getn(self.playlists_array) do local submenu = {} submenu[1] = { "Add to current", self:command_load_playlist(self.playlists_array[i]) } submenu[2] = { "Replace current", self:command_replace_playlist(self.playlists_array[i]) } new_menu[i] = { self.playlists_array[i], submenu } end table.insert(new_menu, {"", ""}) -- This is a separator end table.insert(new_menu, { "Refresh", function() self:check_playlists() end }) self.recreate_playlists = false self.playlists_menu = new_menu end return self.playlists_menu end -- Returns the server menu. Menu consists of all servers specified by user during initialization. function awesompd:menu_servers() if self.recreate_servers then local new_menu = {} for i = 1, table.getn(self.servers) do table.insert(new_menu, {"Server: " .. self.servers[i].server .. ", port: " .. self.servers[i].port, function() self:change_server(i) end, self:menu_item_radio(i == self.current_server)}) end self.servers_menu = new_menu end return self.servers_menu end -- Returns the options menu. Menu works like checkboxes for it's elements. function awesompd:menu_options() if self.recreate_options then local new_menu = { { "Repeat", self:menu_toggle_repeat(), self:menu_item_toggle(self.state_repeat == "on")}, { "Random", self:menu_toggle_random(), self:menu_item_toggle(self.state_random == "on")}, { "Single", self:menu_toggle_single(), self:menu_item_toggle(self.state_single == "on")}, { "Consume", self:menu_toggle_consume(), self:menu_item_toggle(self.state_consume == "on")} } self.options_menu = new_menu self.recreate_options = false end return self.options_menu end function awesompd:menu_toggle_random() return function() self:command("random",self.update_track) self:notify_state(self.NOTIFY_RANDOM) end end function awesompd:menu_toggle_repeat() return function() self:command("repeat",self.update_track) self:notify_state(self.NOTIFY_REPEAT) end end function awesompd:menu_toggle_single() return function() self:command("single",self.update_track) self:notify_state(self.NOTIFY_SINGLE) end end function awesompd:menu_toggle_consume() return function() self:command("consume",self.update_track) self:notify_state(self.NOTIFY_CONSUME) end end function awesompd:menu_jamendo_top() return function () local track_table = jamendo.return_track_table() if not track_table then self:add_hint("Can't connect to Jamendo server", "Please check your network connection") else self:add_jamendo_tracks(track_table) self:add_hint("Jamendo Top 100 by " .. jamendo.current_request_table.params.order.short_display, format("Added %s tracks to the playlist", table.getn(track_table))) end end end function awesompd:menu_jamendo_format() if self.recreate_jamendo_formats then local setformat = function(format) return function() jamendo.set_current_format(format) self.recreate_menu = true self.recreate_jamendo_formats = true end end local iscurr = function(f) return jamendo.current_request_table.params.streamencoding.value == f.value end local new_menu = {} for _, format in pairs(jamendo.ALL_FORMATS) do table.insert(new_menu, { format.display, setformat(format), self:menu_item_radio(iscurr(format))}) end self.recreate_jamendo_formats = false self.jamendo_formats_menu = { "Format: " .. jamendo.current_request_table.params.streamencoding.short_display, new_menu } end return self.jamendo_formats_menu end function awesompd:menu_jamendo_browse() if self.recreate_jamendo_browse and self.browser and self.current_track.unique_name then local track = jamendo.get_track_by_link(self.current_track.unique_name) local new_menu if track then local artist_link = "http://www.jamendo.com/artist/" .. track.artist_link_name local album_link = "http://www.jamendo.com/album/" .. track.album_id new_menu = { { "Artist's page" , self:command_open_in_browser(artist_link) }, { "Album's page" , self:command_open_in_browser(album_link) } } self.jamendo_browse_menu = { "Browse on Jamendo", new_menu } else self.jamendo_browse_menu = nil end end return self.jamendo_browse_menu end function awesompd:menu_jamendo_order() if self.recreate_jamendo_order then local setorder = function(order) return function() jamendo.set_current_order(order) self.recreate_menu = true self.recreate_jamendo_order = true end end local iscurr = function(o) return jamendo.current_request_table.params.order.value == o.value end local new_menu = {} for _, order in pairs(jamendo.ALL_ORDERS) do table.insert(new_menu, { order.display, setorder(order), self:menu_item_radio(iscurr(order))}) end self.recreate_jamendo_order = false self.jamendo_order_menu = { "Order: " .. jamendo.current_request_table.params.order.short_display, new_menu } end return self.jamendo_order_menu end function awesompd:menu_jamendo_search_by(what) return function() local callback = function(s) local result = jamendo.search_by(what, s) if result then local track_count = table.getn(result.tracks) self:add_jamendo_tracks(result.tracks) self:add_hint(format("%s \"%s\" was found", what.display, result.search_res.name), format("Added %s tracks to the playlist", track_count)) else self:add_hint("Search failed", format("%s \"%s\" was not found", what.display, s)) end end self:display_inputbox("Search music on Jamendo", what.display, callback) end end -- Checks if the current playlist has changed after the last check. function awesompd:check_list() local bus = io.popen(self:mpcquery() .. "playlist") local info = bus:read("*all") bus:close() if info ~= self.list_line then self.list_line = info if string.len(info) > 0 then self.list_array = self.split(string.sub(info,1,string.len(info))) else self.list_array = {} end self.recreate_menu = true self.recreate_list = true end end -- Checks if the collection of playlists changed after the last check. function awesompd:check_playlists() local bus = io.popen(self:mpcquery() .. "lsplaylists") local info = bus:read("*all") bus:close() if info ~= self.playlists_line then self.playlists_line = info if string.len(info) > 0 then self.playlists_array = self.split(info) else self.playlists_array = {} end self.recreate_menu = true self.recreate_playlists = true end end -- Changes the current server to the specified one. function awesompd:change_server(server_number) self.current_server = server_number self:remove_hint() self.recreate_menu = true self.recreate_playback = true self.recreate_list = true self.recreate_playlists = true self.recreate_servers = true self:update_track() end function awesompd:add_jamendo_tracks(track_table) for i = 1,table.getn(track_table) do self:command("add '" .. string.gsub(track_table[i].stream, '\\/', '/') .. "'") end self.recreate_menu = true self.recreate_list = true end -- /// End of menu generation functions /// function awesompd:add_hint(hint_title, hint_text, hint_image) self:remove_hint() self.notification = naughty.notify({ title = hint_title , text = awesompd.protect_string(hint_text) , timeout = 5 , position = "top_right" , icon = hint_image , icon_size = self.album_cover_size }) end function awesompd:remove_hint() if self.notification ~= nil then naughty.destroy(self.notification) self.notification = nil end end function awesompd:notify_track() if self:playing_or_paused() then local caption = self.status_text local nf_text = self.get_display_name(self.current_track) local al_cover = nil if self.show_album_cover then nf_text = self.get_extended_info(self.current_track) al_cover = self.current_track.album_cover end self:add_hint(caption, nf_text, al_cover) end end function awesompd:notify_state(state_changed) state_array = { "Volume: " .. self.state_volume , "Repeat: " .. self.state_repeat , "Random: " .. self.state_random , "Single: " .. self.state_single , "Consume: " .. self.state_consume } state_header = state_array[state_changed] table.remove(state_array,state_changed) full_state = state_array[1] for i = 2, table.getn(state_array) do full_state = full_state .. "\n" .. state_array[i] end self:add_hint(state_header, full_state) end function awesompd:wrap_output(text) return format('%s%s%s', self.font, self.ldecorator, awesompd.protect_string(text), self.rdecorator) end function awesompd:mpcquery() return "mpc -h " .. self.servers[self.current_server].server .. " -p " .. self.servers[self.current_server].port .. " " end -- This function actually sets the text on the widget. function awesompd:set_text(text) self.widget.text = self:wrap_output(text) end function awesompd.find_pattern(text, pattern, start) return utf8sub(text, string.find(text, pattern, start)) end -- Scroll the given text by the current number of symbols. function awesompd:scroll_text(text) local result = text if self.scrolling then if self.output_size < utf8len(text) then text = text .. " - " if self.scroll_pos + self.output_size - 1 > utf8len(text) then result = utf8sub(text, self.scroll_pos) result = result .. utf8sub(text, 1, self.scroll_pos + self.output_size - 1 - utf8len(text)) self.scroll_pos = self.scroll_pos + 1 if self.scroll_pos > utf8len(text) then self.scroll_pos = 1 end else result = utf8sub(text, self.scroll_pos, self.scroll_pos + self.output_size - 1) self.scroll_pos = self.scroll_pos + 1 end end else if self.output_size < utf8len(text) then result = utf8sub(text, 1, self.output_size) end end return result end -- This function is called every second. function awesompd:update_widget() self:set_text(self:scroll_text(self.text)) self:check_notify() end -- This function is called by update_track each time content of -- the widget must be changed. function awesompd:update_widget_text() if self:playing_or_paused() then self.text = self.get_display_name(self.current_track) else self.text = self.status end end -- Checks if notification should be shown and shows if positive. function awesompd:check_notify() if self.to_notify then self:notify_track() self.to_notify = false end end function awesompd:notify_connect() self:add_hint("Connected", "Connection established to " .. self.servers[self.current_server].server .. " on port " .. self.servers[self.current_server].port) end function awesompd:notify_disconnect() self:add_hint("Disconnected", "Cannot connect to " .. self.servers[self.current_server].server .. " on port " .. self.servers[self.current_server].port) end function awesompd:update_track(file) local file_exists = (file ~= nil) if not file_exists then file = io.popen(self:mpcquery()) end local track_line = file:read("*line") local status_line = file:read("*line") local options_line = file:read("*line") if not file_exists then file:close() end if not track_line or string.len(track_line) == 0 then if self.status ~= awesompd.DISCONNECTED then self:notify_disconnect() self.recreate_menu = true self.status = awesompd.DISCONNECTED self.current_track = { } self:update_widget_text() end else if self.status == awesompd.DISCONNECTED then self:notify_connect() self.recreate_menu = true self:update_widget_text() end if string.find(track_line,"volume:") or string.find(track_line,"Updating DB") then if self.status ~= awesompd.STOPPED then self.status = awesompd.STOPPED self.current_number = 0 self.recreate_menu = true self.recreate_playback = true self.recreate_list = true self.album_cover = nil self.current_track = { } self:update_widget_text() end self:update_state(track_line) else self:update_state(options_line) local _, _, new_file, new_album = string.find(self:command_read('current -f "%file%-<>-%album%"', "*line"), "(.+)%-<>%-(.*)") if new_file ~= self.current_track.unique_name then self.current_track = jamendo.get_track_by_link(new_file) if not self.current_track then self.current_track = { display_name = track_line, album_name = new_album } end self.current_track.unique_name = new_file if self.show_album_cover then self.current_track.album_cover = self:get_cover(new_file) end self.to_notify = true self.recreate_menu = true self.recreate_playback = true self.recreate_list = true self.current_number = tonumber(self.find_pattern(status_line,"%d+")) self:update_widget_text() -- If the track is not the last, asynchronously download -- the cover for the next track. if self.list_array and self.current_number ~= table.getn(self.list_array) then -- Get the link (in case it is Jamendo stream) to the next track local next_track = self:command_read('playlist -f "%file%" | head -' .. self.current_number + 1 .. ' | tail -1', "*line") jamendo.try_get_cover_async(next_track) end end local tmp_pst = string.find(status_line,"%d+%:%d+%/") local progress = self.find_pattern(status_line,"%#%d+/%d+") .. " " .. string.sub(status_line,tmp_pst) local new_status = awesompd.PLAYING if string.find(status_line,"paused") then new_status = awesompd.PAUSED end if new_status ~= self.status then self.to_notify = true self.recreate_list = true self.status = new_status self:update_widget_text() end self.status_text = self.status .. " " .. progress end end end function awesompd:update_state(state_string) self.state_volume = self.find_pattern(state_string,"%d+%% ") if string.find(state_string,"repeat: on") then self.state_repeat = self:check_set_state(self.state_repeat, "on") else self.state_repeat = self:check_set_state(self.state_repeat, "off") end if string.find(state_string,"random: on") then self.state_random = self:check_set_state(self.state_random, "on") else self.state_random = self:check_set_state(self.state_random, "off") end if string.find(state_string,"single: on") then self.state_single = self:check_set_state(self.state_single, "on") else self.state_single = self:check_set_state(self.state_single, "off") end if string.find(state_string,"consume: on") then self.state_consume = self:check_set_state(self.state_consume, "on") else self.state_consume = self:check_set_state(self.state_consume, "off") end end function awesompd:check_set_state(statevar, val) if statevar ~= val then self.recreate_menu = true self.recreate_options = true end return val end function awesompd:run_prompt(welcome,hook) awful.prompt.run({ prompt = welcome }, self.promptbox[mouse.screen].widget, hook) end -- Replaces control characters with escaped ones. -- for_menu - defines if the special escable table for menus should be -- used. function awesompd.protect_string(str, for_menu) if for_menu then return utf8replace(str, awesompd.ESCAPE_MENU_SYMBOL_MAPPING) else return utf8replace(str, awesompd.ESCAPE_SYMBOL_MAPPING) end end -- Displays an inputbox on the screen (looks like naughty with prompt). -- title_text - bold text on the first line -- prompt_text - preceding text on the second line -- hook - function that will be called with input data -- Use it like this: -- self:display_inputbox("Search music on Jamendo", "Artist", print) function awesompd:display_inputbox(title_text, prompt_text, hook) if self.inputbox then -- Inputbox already exists, replace it keygrabber.stop() self.inputbox.screen = nil self.inputbox = nil end local width = 200 local height = 30 local border_color = beautiful.bg_focus or '#535d6c' local margin = 5 local wbox = wibox({ name = "awmpd_ibox", height = height , width = width, border_color = border_color, border_width = 1 }) self.inputbox = wbox local ws = screen[mouse.screen].workarea wbox:geometry({ x = ws.width - width - 5, y = 25 }) wbox.screen = mouse.screen wbox.ontop = true local exe_callback = function(s) hook(s) wbox.screen = nil self.inputbox = nil end local done_callback = function() wbox.screen = nil self.inputbox = nil end local wprompt = awful.widget.prompt({ layout = awful.widget.layout.horizontal.leftright }) local wtbox = widget({ type = "textbox" }) wtbox:margin({ right = margin, left = margin, bottom = margin, top = margin }) wtbox.text = "" .. title_text .. "" wbox.widgets = { wtbox, wprompt, layout = awful.widget.layout.vertical.flex } awful.prompt.run( { prompt = " " .. prompt_text .. ": " }, wprompt.widget, exe_callback, nil, nil, nil, done_callback) end -- Gets the cover for the given track. First looks in the Jamendo -- cache. If the track is not a Jamendo stream, looks in local -- folders. If there is no cover art either returns the default album -- cover. function awesompd:get_cover(track) return jamendo.try_get_cover(track) or self:try_get_local_cover() or self.ICONS.DEFAULT_ALBUM_COVER end -- Tries to find an album cover for the track that is currently -- playing. function awesompd:try_get_local_cover() if self.mpd_config then local result -- First find the music directory in MPD configuration file local _, _, music_folder = string.find( self.pread('cat ' .. self.mpd_config .. ' | grep -v "#" | grep music_directory', "*line"), 'music_directory%s+"(.+)"') music_folder = music_folder .. "/" -- If the music_folder is specified with ~ at the beginning, -- replace it with user home directory if string.sub(music_folder, 1, 1) == "~" then local user_folder = self.pread("echo ~", "*line") music_folder = user_folder .. string.sub(music_folder, 2) end -- Get the path to the file currently playing. local current_file = self:command_read('current -f "%file%"') local _, _, current_file_folder = string.find(current_file, '(.+%/).*') -- Check if the current file is not some kind of http stream or -- Spotify track (like spotify:track:5r65GeuIoebfJB5sLcuPoC) if not current_file_folder or string.match(current_file, "%w+://") then return -- Let the default image to be the cover end local folder = music_folder .. current_file_folder -- Get all images in the folder. Also escape occasional single -- quotes in folder name. local request = format("ls '%s' | grep -P '\.jpg\|\.png\|\.gif|\.jpeg'", string.gsub(folder, "'", "'\\''")) local covers = self.pread(request, "*all") local covers_table = self.split(covers) if covers_table.n > 0 then result = folder .. covers_table[1] if covers_table.n > 1 then -- Searching for front cover with grep because Lua regular -- expressions suck:[ local front_cover = self.pread('echo "' .. covers .. '" | grep -i "cover\|front\|folder\|albumart" | head -n 1', "*line") if front_cover then result = folder .. front_cover end end end return result end end -- /// Deprecated, left for some backward compatibility in -- configuration /// function awesompd:command_toggle() return self:command_playpause() end