--[[ Licensed under GNU General Public License v2 * (c) 2014, Yauheni Kirylau --]] local awful = require("awful") local naughty = require("naughty") local beautiful = require("beautiful") local os = { getenv = os.getenv } local string = { format = string.format } local setmetatable = setmetatable local helpers = require("actionless.helpers") local h_string = require("actionless.string") local common_widget = require("actionless.widgets.common").widget local markup = require("actionless.markup") local async = require("actionless.async") local backend_modules = require("actionless.widgets.music.backends") local tag_parser = require("actionless.widgets.music.tag_parser") -- player infos local player = { id=nil, cmd=nil, player_status = { state=nil, title=nil, artist=nil, album=nil, date=nil, file=nil }, cover="/tmp/awesome_cover.png" } local function worker(args) local args = args or {} local update_interval = args.update_interval or 2 local timeout = args.timeout or 5 local default_art = args.default_art or "" local enabled_backends = args.backends or { 'mpd', 'cmus', 'spotify', 'clementine', 'mocp', } local cover_size = args.cover_size or 100 local font = args.font or beautiful.tasklist_font or beautiful.font local bg = args.bg or beautiful.panel_bg or beautiful.bg local fg = args.fg or beautiful.panel_fg or beautiful.fg local artist_color = "#53f3f3" --beautiful.player_artist or fg or beautiful.fg_normal local title_color = beautiful.player_title or fg or beautiful.fg_normal player.widget = common_widget(args) local backend_id = 0 local cached_backends = {} function player.use_next_backend() --[[ music player backends: backend should have methods: * .toggle () * .next_song () * .prev_song () * .update (parse_status_callback) optional: * .init(args) * .resize_cover(coversize, default_art, show_notification_callback) --]] backend_id = backend_id + 1 if backend_id > #enabled_backends then backend_id = 1 end if backend_id > #cached_backends then cached_backends[backend_id] = backend_modules[enabled_backends[backend_id]] if cached_backends[backend_id].init then cached_backends[backend_id].init() end end player.backend = cached_backends[backend_id] player.cmd = args.player_cmd or player.backend.player_cmd end player.use_next_backend() helpers.set_map("current player track", nil) ------------------------------------------------------------------------------- function player.run_player() awful.util.spawn_with_shell(player.cmd) end ------------------------------------------------------------------------------- function player.hide_notification() if player.id ~= nil then naughty.destroy(player.id) player.id = nil end end ------------------------------------------------------------------------------- function player.show_notification() local text local ps = player.player_status player.hide_notification() if ps.album or ps.date then text = string.format( "%s (%s)\n%s", ps.album, ps.date, ps.artist ) elseif ps.artist then text = string.format( "%s\n%s", ps.artist, ps.file or enabled_backends[backend_id] ) else text = enabled_backends[backend_id] end player.id = naughty.notify({ icon = player.cover, title = ps.title, text = text, timeout = timeout }) end ------------------------------------------------------------------------------- function player.toggle() if player.player_status.state ~= 'pause' and player.player_status.state ~= 'play' then player.run_player() return end player.backend.toggle() player.update() end function player.next_song() player.backend.next_song() player.update() end function player.prev_song() player.backend.prev_song() player.update() end player.widget:connect_signal( "mouse::enter", function () player.show_notification() end) player.widget:connect_signal( "mouse::leave", function () player.hide_notification() end) player.widget:buttons(awful.util.table.join( awful.button({ }, 1, player.toggle), awful.button({ }, 3, function() player.use_next_backend() player.update() end), awful.button({ }, 5, player.next_song), awful.button({ }, 4, player.prev_song) )) ------------------------------------------------------------------------------- function player.update(args) player.backend.update(function(player_status) player.parse_status(player_status, args) end) end ------------------------------------------------------------------------------- function player.parse_status(player_status, args) args = args or {} player_status = tag_parser.predict_missing_tags(player_status) player.player_status = player_status local artist = "" local title = "" if player_status.state == "play" then -- playing artist = player_status.artist or "playing" title = player_status.title or " " player.widget:set_icon('music_play') if #artist + #title > 60 then if #artist > 25 then artist = h_string.max_length(artist, 15) .. "…" end if #player_status.title > 35 then title = h_string.max_length(title, 25) .. "…" end end artist = h_string.escape(artist) title = h_string.escape(title) -- playing new song if player_status.title ~= helpers.get_map("current player track") then helpers.set_map("current player track", player_status.title) player.resize_cover() end elseif player_status.state == "pause" then -- paused artist = enabled_backends[backend_id] title = "paused" --@TODO: can it be safely deleted? : --helpers.set_map("current player track", nil) player.widget:set_icon('music_pause') else -- stop artist = enabled_backends[backend_id] helpers.set_map("current player track", nil) end if player_status.state == "play" or player_status.state == "pause" then artist = markup.fg.color(artist_color, artist) --player.widget:set_bg(bg) --player.widget:set_fg(fg) player.widget:set_markup( markup.font(font, " " .. (beautiful.panel_enbolden_details and markup.bold(artist) or artist) .. " " .. markup.fg.color(title_color, title) .. " ") ) else if beautiful.show_widget_icon then player.widget:set_icon('music_stop') player.widget:set_text('') else player.widget:set_text('(m)') end --player.widget:set_bg(fg) --player.widget:set_fg(bg) end end ------------------------------------------------------------------------------- function player.resize_cover() -- backend supports it: if player.backend.resize_cover then return player.backend.resize_cover( player.player_status, cover_size, player.cover, function() player.show_notification() end ) end -- fallback: local resize = string.format('%sx%s', cover_size, cover_size) if not player.player_status.cover then player.player_status.cover = default_art end async.execute( string.format( [[convert %q -thumbnail %q -gravity center -background "none" -extent %q %q]], player.player_status.cover, resize, resize, player.cover ), function(f) player.show_notification() end ) end ------------------------------------------------------------------------------- helpers.newtimer("player", update_interval, player.update) return setmetatable(player, { __index = player.widget }) end return setmetatable( player, { __call = function(_, ...) return worker(...) end } )