12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229 |
- ---------------------------------------------------------------------------
- -- @author Alexander Yakushev <yakushev.alex@gmail.com>
- -- @copyright 2010-2013 Alexander Yakushev
- -- @release v1.2.4
- ---------------------------------------------------------------------------
- local wibox = require("wibox")
- local awful = require('awful')
- local beautiful = require('beautiful')
- local naughty = require('naughty')
- local format = string.format
- local module_path = (...):match ("(.+/)[^/]+$") or ""
- local 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") .. '/'..
- module_path .. module .. ".lua") then
- return require(module_path .. module)
- else
- return require(module)
- end
- end
- local utf8 = awesompd.try_require("utf8")
- asyncshell = awesompd.try_require("asyncshell")
- local jamendo = awesompd.try_require("jamendo")
- -- 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, #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
- -- Returns the given string if it is not nil or non-empty, otherwise
- -- returns nil.
- local function non_empty(s)
- if s and s ~= "" then
- return s
- end
- 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 = wibox.layout.fixed.horizontal()
- 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
- instance.state_volume = "NaN"
- instance.state_repeat = "NaN"
- instance.state_random = "NaN"
- instance.state_single = "NaN"
- instance.state_consume = "NaN"
- -- Default user options
- instance.servers = { { server = "localhost", port = 6600 } }
- instance.font = "Monospace"
- instance.font_color = beautiful.fg_normal
- instance.background = beautiful.bg_normal
- 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:connect_signal("mouse::enter", function(c)
- instance:notify_track()
- end)
- instance.widget:connect_signal("mouse::leave", function(c)
- instance:hide_notification()
- end)
- return instance
- end
- -- Registers timers for the widget
- function awesompd:run()
- 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.text_widget = wibox.widget.textbox()
- if self.widget_icon then
- self.icon_widget = wibox.widget.imagebox()
- self.icon_widget:set_image(self.widget_icon)
- self.widget:add(self.icon_widget)
- end
- self.widget:add(self.text_widget)
- self:update_track()
- self:check_playlists()
- if scheduler then
- scheduler.register_recurring("awesompd_scroll", 1,
- function() self:update_widget() end)
- scheduler.register_recurring("awesompd_update", self.update_interval,
- function() self:update_track() end)
- else
- self.update_widget_timer = timer({ timeout = 1 })
- self.update_widget_timer:connect_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:connect_signal("timeout", function()
- self:update_track()
- end)
- self.update_track_timer:start()
- end
- end
- -- Function that registers buttons on the widget.
- function awesompd:register_buttons(buttons)
- widget_buttons = {}
- self.global_bindings = {}
- for b=1, #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, #self.global_bindings do
- keytable = awful.util.table.join(keytable, self.global_bindings[i])
- end
- return keytable
- else
- for i = 1, #self.global_bindings do
- globalkeys = awful.util.table.join(globalkeys, self.global_bindings[i])
- end
- end
- end
- -- /// Group of mpc command functions ///
- -- Returns a mpc command with all necessary parameters. Boolean
- -- human_readable argument configures if the command special
- -- formatting of the output (to be later used in parsing) should not
- -- be used.
- function awesompd:mpcquery(human_readable)
- local result =
- "mpc -h " .. self.servers[self.current_server].server ..
- " -p " .. self.servers[self.current_server].port .. " "
- if human_readable then
- return result
- else
- return result ..' -f "%file%-<>-%name%-<>-%title%-<>-%artist%-<>-%album%" '
- end
- end
- -- 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:hide_notification()
- 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, theme = { 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 ~= #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 = #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 #self.playlists_array > 0 then
- for i = 1, #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, #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:show_notification("Can't connect to Jamendo server", "Please check your network connection")
- else
- self:add_jamendo_tracks(track_table)
- self:show_notification("Jamendo Top 100 by " ..
- jamendo.current_request_table.params.order.short_display,
- format("Added %s tracks to the playlist",
- #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 = #result.tracks
- self:add_jamendo_tracks(result.tracks)
- self:show_notification(format("%s \"%s\" was found",
- what.display,
- result.search_res.name),
- format("Added %s tracks to the playlist",
- track_count))
- else
- self:show_notification("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(true) .. "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(true) .. "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:hide_notification()
- 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, #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:show_notification(hint_title, hint_text, hint_image)
- self:hide_notification()
- 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:hide_notification()
- 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:show_notification(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, #state_array do
- full_state = full_state .. "\n" .. state_array[i]
- end
- self:show_notification(state_header, full_state)
- end
- function awesompd:wrap_output(text)
- return format('<span font="%s" color="%s" background="%s">%s%s%s</span>',
- self.font, self.font_color, self.background,
- (text == "" and "" or self.ldecorator), awesompd.protect_string(text),
- (text == "" and "" or self.rdecorator))
- end
- -- This function actually sets the text on the widget.
- function awesompd:set_text(text)
- self.text_widget:set_markup(self:wrap_output(text))
- end
- function awesompd.find_pattern(text, pattern, start)
- return utf8.sub(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 < utf8.len(text) then
- text = text .. " - "
- if self.scroll_pos + self.output_size - 1 > utf8.len(text) then
- result = utf8.sub(text, self.scroll_pos)
- result = result .. utf8.sub(text, 1, self.scroll_pos + self.output_size - 1 - utf8.len(text))
- self.scroll_pos = self.scroll_pos + 1
- if self.scroll_pos > utf8.len(text) then
- self.scroll_pos = 1
- end
- else
- result = utf8.sub(text, self.scroll_pos, self.scroll_pos + self.output_size - 1)
- self.scroll_pos = self.scroll_pos + 1
- end
- 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:show_notification("Connected", "Connection established to " .. self.servers[self.current_server].server ..
- " on port " .. self.servers[self.current_server].port)
- end
- function awesompd:notify_disconnect()
- self:show_notification("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, station, title, artist, album =
- string.find(track_line, "(.*)%-<>%-(.*)%-<>%-(.*)%-<>%-(.*)%-<>%-(.*)")
- local display_name, force_update = artist .. " - " .. title, false
- -- The following code checks if the current track is an
- -- Internet link. Internet radios change tracks, but the
- -- current file stays the same, so we should manually compare
- -- its title.
- if string.match(new_file, "http://") and
- -- The following line is awful. This needs to be replaced ASAP.
- not string.match(new_file, "http://storage%-new%.newjamendo%.com") then
- album = non_empty(station) or ""
- display_name = non_empty(title) or new_file
- if display_name ~= self.current_track.display_name then
- force_update = true
- end
- end
- if new_file ~= self.current_track.unique_name or force_update then
- self.current_track = jamendo.get_track_by_link(new_file)
- if not self.current_track then
- self.current_track = { display_name = display_name,
- album_name = 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 ~= #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 utf8.replace(str, awesompd.ESCAPE_MENU_SYMBOL_MAPPING)
- else
- return utf8.replace(str, awesompd.ESCAPE_SYMBOL_MAPPING)
- end
- end
- -- Initialize the inputbox.
- function awesompd:init_inputbox()
- local width = 200
- local height = 30
- local border_color = beautiful.bg_focus or '#535d6c'
- local margin = 4
- local wbox = wibox({ name = "awmpd_ibox", height = height , width = width,
- border_color = border_color, border_width = 1 })
- local ws = screen[mouse.screen].workarea
- wbox.screen = mouse.screen
- wbox.ontop = true
- local wprompt = awful.widget.prompt()
- local wtbox = wibox.widget.textbox()
- local wtmarginbox = wibox.layout.margin(wtbox, margin)
- local tw, th = wtbox:fit(-1, -1)
- wbox:geometry({ x = ws.width - width - 5, y = 25,
- width = 200, height = th * 2 + margin})
- local layout = wibox.layout.flex.vertical()
- layout:add(wtmarginbox)
- layout:add(wprompt)
- wbox:set_widget(layout)
- self.inputbox = { wibox = wbox,
- title = wtbox,
- prompt = wprompt }
- 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 not self.inputbox then
- self:init_inputbox()
- end
- if self.inputbox.wibox.visible then -- Inputbox already exists, replace it
- keygrabber.stop()
- end
- local exe_callback = function(s)
- hook(s)
- self.inputbox.wibox.visible = false
- end
- local done_callback = function()
- self.inputbox.wibox.visible = false
- end
- self.inputbox.title:set_markup("<b>" .. title_text .. "</b>")
- awful.prompt.run( { prompt = " " .. prompt_text .. ": ", bg_cursor = "#222222" },
- self.inputbox.prompt.widget,
- exe_callback, nil, nil, nil, done_callback, nil, nil)
- self.inputbox.wibox.visible = true
- 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)
- local radio_cover = nil
- if self.radio_covers then
- for station, cover in pairs(self.radio_covers) do
- if track:match(station) then
- radio_cover = cover
- break
- end
- end
- end
- return radio_cover or jamendo.try_get_cover(track) or
- self:try_get_local_cover(track) 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(current_file)
- 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_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 -P -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
- return awesompd
|