2021-04-19 01:33:35 +02:00
|
|
|
-- Playerctl signals
|
2021-02-17 08:59:40 +01:00
|
|
|
--
|
2021-02-09 15:15:20 +01:00
|
|
|
-- Provides:
|
|
|
|
-- bling::playerctl::status
|
|
|
|
-- playing (boolean)
|
2021-04-19 01:33:35 +02:00
|
|
|
-- player_name (string)
|
2021-02-17 02:14:26 +01:00
|
|
|
-- bling::playerctl::title_artist_album
|
2021-02-13 03:37:45 +01:00
|
|
|
-- title (string)
|
2021-04-19 01:33:35 +02:00
|
|
|
-- artist (string)
|
2021-02-17 02:14:26 +01:00
|
|
|
-- album_path (string)
|
2021-04-19 01:33:35 +02:00
|
|
|
-- player_name (string)
|
2021-02-13 03:37:45 +01:00
|
|
|
-- bling::playerctl::position
|
2021-02-09 15:15:20 +01:00
|
|
|
-- interval_sec (number)
|
|
|
|
-- length_sec (number)
|
2021-04-19 01:33:35 +02:00
|
|
|
-- player_name (string)
|
|
|
|
-- bling::playerctl::no_players
|
|
|
|
-- (No parameters)
|
|
|
|
|
|
|
|
local gears = require("gears")
|
2021-02-09 15:15:20 +01:00
|
|
|
local awful = require("awful")
|
|
|
|
local beautiful = require("beautiful")
|
2021-04-24 11:37:06 +02:00
|
|
|
local Playerctl = nil
|
2021-02-09 15:15:20 +01:00
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
local manager = nil
|
|
|
|
local position_timer = nil
|
|
|
|
|
|
|
|
local ignore = {}
|
|
|
|
local priority = {}
|
|
|
|
local update_on_activity = true
|
|
|
|
local interval = 1
|
|
|
|
|
|
|
|
-- Track position callback
|
|
|
|
local last_position = -1
|
|
|
|
local last_length = -1
|
|
|
|
function position_cb()
|
|
|
|
local player = manager.players[1]
|
|
|
|
if player then
|
|
|
|
local position = player:get_position() / 1000000
|
|
|
|
local length = player.metadata.value["mpris:length"] / 1000000
|
|
|
|
if position ~= last_position or length ~= last_length then
|
|
|
|
awesome.emit_signal("bling::playerctl::position",
|
|
|
|
position,
|
|
|
|
length,
|
|
|
|
player.player_name)
|
|
|
|
last_position = position
|
|
|
|
last_length = length
|
|
|
|
end
|
|
|
|
end
|
2021-02-09 15:15:20 +01:00
|
|
|
end
|
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
local function get_album_art(url)
|
|
|
|
return awful.util.shell .. [[ -c '
|
2021-02-09 15:15:20 +01:00
|
|
|
|
2021-02-11 15:12:19 +01:00
|
|
|
tmp_dir="$XDG_CACHE_HOME/awesome/"
|
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
if [ -z "$XDG_CACHE_HOME" ]; then
|
2021-02-11 15:12:19 +01:00
|
|
|
tmp_dir="$HOME/.cache/awesome/"
|
|
|
|
fi
|
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
tmp_cover_path="${tmp_dir}cover.png"
|
2021-02-09 15:15:20 +01:00
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
if [ ! -d "$tmp_dir" ]; then
|
2021-02-09 15:15:20 +01:00
|
|
|
mkdir -p $tmp_dir
|
|
|
|
fi
|
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
curl -s ']] .. url .. [[' --output $tmp_cover_path
|
2021-02-09 15:15:20 +01:00
|
|
|
|
2021-02-13 03:37:45 +01:00
|
|
|
echo "$tmp_cover_path"
|
2021-02-09 15:15:20 +01:00
|
|
|
']]
|
2021-04-19 01:33:35 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
-- Metadata callback for title, artist, and album art
|
|
|
|
local last_player = nil
|
|
|
|
local last_title = ""
|
|
|
|
local last_artist = ""
|
|
|
|
local last_artUrl = ""
|
|
|
|
function metadata_cb(player, metadata)
|
|
|
|
if update_on_activity then
|
|
|
|
manager:move_player_to_top(player)
|
|
|
|
end
|
|
|
|
|
|
|
|
local data = metadata.value
|
2021-02-09 15:15:20 +01:00
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
local title = data["xesam:title"] or ""
|
|
|
|
local artist = data["xesam:artist"][1] or ""
|
|
|
|
for i = 2, #data["xesam:artist"] do
|
|
|
|
artist = artist .. ", " .. data["xesam:artist"][i]
|
|
|
|
end
|
|
|
|
local artUrl = data["mpris:artUrl"] or ""
|
|
|
|
-- Spotify client doesn't report its art URL's correctly...
|
|
|
|
if player.player_name == "spotify" then
|
|
|
|
artUrl = artUrl:gsub("open.spotify.com", "i.scdn.co")
|
|
|
|
end
|
|
|
|
|
|
|
|
if player == manager.players[1] then
|
|
|
|
-- Callback can be called even though values we care about haven't
|
|
|
|
-- changed, so check to see if they have
|
|
|
|
if player ~= last_player or title ~= last_title or
|
|
|
|
artist ~= last_artist or artUrl ~= last_artUrl
|
|
|
|
then
|
2021-04-19 12:14:15 +02:00
|
|
|
awful.spawn.with_line_callback(get_album_art(artUrl), {
|
|
|
|
stdout = function(line)
|
2021-04-19 01:33:35 +02:00
|
|
|
awesome.emit_signal("bling::playerctl::title_artist_album",
|
|
|
|
title,
|
|
|
|
artist,
|
2021-04-19 12:14:15 +02:00
|
|
|
line,
|
2021-04-19 01:33:35 +02:00
|
|
|
player.player_name)
|
2021-02-09 15:15:20 +01:00
|
|
|
end
|
2021-04-19 12:14:15 +02:00
|
|
|
})
|
2021-04-19 01:33:35 +02:00
|
|
|
-- Re-sync with position timer when track changes
|
|
|
|
position_timer:again()
|
|
|
|
last_player = player
|
|
|
|
last_title = title
|
|
|
|
last_artist = artist
|
|
|
|
last_artUrl = artUrl
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Playback status callback
|
|
|
|
-- Reported as PLAYING, PAUSED, or STOPPED
|
|
|
|
function playback_status_cb(player, status)
|
|
|
|
if update_on_activity then
|
|
|
|
manager:move_player_to_top(player)
|
|
|
|
end
|
|
|
|
|
|
|
|
if player == manager.players[1] then
|
|
|
|
if status == "PLAYING" then
|
|
|
|
awesome.emit_signal("bling::playerctl::status", true, player.player_name)
|
|
|
|
else
|
|
|
|
awesome.emit_signal("bling::playerctl::status", false, player.player_name)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Determine if player should be managed
|
|
|
|
function name_is_selected(name)
|
|
|
|
if ignore[name.name] then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
if #priority > 0 then
|
|
|
|
for _, arg in pairs(priority) do
|
|
|
|
if arg == name.name or arg == "%any" then
|
|
|
|
return true
|
2021-02-09 15:15:20 +01:00
|
|
|
end
|
2021-04-19 01:33:35 +02:00
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
2021-02-09 15:15:20 +01:00
|
|
|
end
|
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
-- Create new player and connect it to callbacks
|
|
|
|
local function init_player(name)
|
|
|
|
if name_is_selected(name) then
|
|
|
|
local player = Playerctl.Player.new_from_name(name)
|
|
|
|
manager:manage_player(player)
|
|
|
|
player.on_playback_status = playback_status_cb
|
|
|
|
player.on_metadata = metadata_cb
|
2021-02-09 15:15:20 +01:00
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
-- Start position timer if its not already running
|
|
|
|
if not position_timer.started then
|
|
|
|
position_timer:again()
|
|
|
|
end
|
|
|
|
end
|
2021-02-09 15:15:20 +01:00
|
|
|
end
|
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
-- Determine if a player name comes before or after another according to the
|
|
|
|
-- priority order
|
|
|
|
local function player_compare_name(name_a, name_b)
|
|
|
|
local any_index = math.huge
|
|
|
|
local a_match_index = nil
|
|
|
|
local b_match_index = nil
|
|
|
|
|
|
|
|
if name_a == name_b then
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
|
|
|
|
for index, name in ipairs(priority) do
|
|
|
|
if name == "%any" then
|
|
|
|
any_index = (any_index == math.huge) and index or any_index
|
|
|
|
elseif name == name_a then
|
|
|
|
a_match_index = a_match_index or index
|
|
|
|
elseif name == name_b then
|
|
|
|
b_match_index = b_match_index or index
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if not a_match_index and not b_match_index then
|
|
|
|
return 0
|
|
|
|
elseif not a_match_index then
|
|
|
|
return (b_match_index < any_index) and 1 or -1
|
|
|
|
elseif not b_match_index then
|
|
|
|
return (a_match_index < any_index) and -1 or 1
|
|
|
|
elseif a_match_index == b_match_index then
|
|
|
|
return 0
|
|
|
|
else
|
|
|
|
return (a_match_index < b_match_index) and -1 or 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Sorting function used by manager if a priority order is specified
|
|
|
|
local function player_compare(a, b)
|
|
|
|
local player_a = Playerctl.Player(a)
|
|
|
|
local player_b = Playerctl.Player(b)
|
|
|
|
return player_compare_name(player_a.player_name, player_b.player_name)
|
|
|
|
end
|
|
|
|
|
|
|
|
local function start_manager()
|
|
|
|
manager = Playerctl.PlayerManager()
|
|
|
|
if #priority > 0 then
|
|
|
|
manager:set_sort_func(player_compare)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Timer to update track position at specified interval
|
|
|
|
position_timer = gears.timer {
|
|
|
|
timeout = interval,
|
|
|
|
callback = position_cb
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Manage existing players on startup
|
|
|
|
for _, name in ipairs(manager.player_names) do
|
|
|
|
init_player(name)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Callback to manage new players
|
|
|
|
function manager:on_name_appeared(name)
|
|
|
|
init_player(name)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Callback to check if all players have exited
|
|
|
|
function manager:on_name_vanished(name)
|
|
|
|
if #manager.players == 0 then
|
|
|
|
position_timer:stop()
|
|
|
|
awesome.emit_signal("bling::playerctl::no_players")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Parse arguments
|
|
|
|
local function parse_args(args)
|
|
|
|
if args then
|
|
|
|
update_on_activity = args.update_on_activity or update_on_activity
|
|
|
|
interval = args.interval or interval
|
|
|
|
|
|
|
|
if type(args.ignore) == "string" then
|
|
|
|
ignore[args.ignore] = true
|
|
|
|
elseif type(args.ignore) == "table" then
|
|
|
|
for _, name in pairs(args.ignore) do
|
|
|
|
ignore[name] = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if type(args.player) == "string" then
|
|
|
|
priority[1] = args.player
|
|
|
|
elseif type(args.player) == "table" then
|
|
|
|
priority = args.player
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function playerctl_enable(args)
|
|
|
|
args = args or {}
|
|
|
|
-- Grab settings from beautiful variables if not set explicitly
|
|
|
|
args.ignore = args.ignore or beautiful.playerctl_ignore
|
|
|
|
args.player = args.player or beautiful.playerctl_player
|
|
|
|
args.update_on_activity = args.update_on_activity or
|
|
|
|
beautiful.playerctl_update_on_activity
|
|
|
|
args.interval = args.interval or beautiful.playerctl_position_update_interval
|
|
|
|
parse_args(args)
|
|
|
|
|
2021-04-24 11:37:06 +02:00
|
|
|
-- Grab playerctl library
|
|
|
|
Playerctl = require("lgi").Playerctl
|
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
-- Ensure main event loop has started before starting player manager
|
|
|
|
gears.timer.delayed_call(start_manager)
|
|
|
|
end
|
2021-03-10 21:52:31 +01:00
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
local function playerctl_disable()
|
|
|
|
-- Remove manager and timer
|
|
|
|
manager = nil
|
|
|
|
position_timer:stop()
|
|
|
|
position_timer = nil
|
|
|
|
-- Restore default settings
|
|
|
|
ignore = {}
|
|
|
|
priority = {}
|
|
|
|
update_on_activity = true
|
|
|
|
interval = 1
|
|
|
|
-- Reset default values
|
|
|
|
last_position = -1
|
|
|
|
last_length = -1
|
|
|
|
last_player = nil
|
|
|
|
last_title = ""
|
|
|
|
last_artist = ""
|
|
|
|
last_artUrl = ""
|
2021-03-10 21:52:31 +01:00
|
|
|
end
|
|
|
|
|
2021-04-19 01:33:35 +02:00
|
|
|
return {enable = playerctl_enable, disable = playerctl_disable}
|