Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1229 lines (1116 sloc) 42.526 kb
---------------------------------------------------------------------------
-- @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["&"] = "&amp;"
-- 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
Jump to Line
Something went wrong with that request. Please try again.