Skip to content

Commit f944270

Browse files
committed
refactoring + fix player selection
1 parent d8376f7 commit f944270

File tree

1 file changed

+185
-141
lines changed

1 file changed

+185
-141
lines changed

mpris-widget/init.lua

Lines changed: 185 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,6 @@ local watch = require("awful.widget.watch")
1111
local wibox = require("wibox")
1212
local gears = require("gears")
1313

14-
local GET_MPD_CMD = "playerctl -f '{{status}};{{xesam:artist}};{{xesam:title}};{{mpris:artUrl}};{{position}};{{mpris:length}}' metadata"
15-
16-
local TOGGLE_MPD_CMD = "playerctl play-pause"
17-
local NEXT_MPD_CMD = "playerctl next"
18-
local PREV_MPD_CMD = "playerctl previous"
19-
local LIST_PLAYERS_CMD = "playerctl -l"
20-
2114
local PATH_TO_ICONS = "/usr/share/icons/Adwaita"
2215
local PAUSE_ICON_NAME = PATH_TO_ICONS .. "/symbolic/actions/media-playback-pause-symbolic.svg"
2316
local PLAY_ICON_NAME = PATH_TO_ICONS .. "/symbolic/actions/media-playback-start-symbolic.svg"
@@ -26,7 +19,64 @@ local LIBRARY_ICON_NAME = PATH_TO_ICONS .. "/symbolic/places/folder-music-symbol
2619

2720
local FONT = 'Roboto Condensed 16px'
2821

29-
local default_player = 'mpv'
22+
local playerctl = {
23+
player_name = 'mpv',
24+
}
25+
26+
function playerctl:set_player(name)
27+
self.player_name = name
28+
29+
if self.timer ~= nil then
30+
self.timer:stop()
31+
playerctl:watch(self.watch_params.timeout, self.watch_params.callback, self.watch_params.widget)
32+
end
33+
end
34+
35+
function playerctl:cmd(cmd)
36+
return "playerctl -p '" .. self.player_name .. "' " .. cmd
37+
end
38+
39+
function playerctl:watch(timeout, callback, widget)
40+
local cmd = self:cmd("-f '{{status}};{{xesam:artist}};{{xesam:title}};{{mpris:artUrl}};{{position}};{{mpris:length}};{{album}}' metadata")
41+
42+
self.watch_params = {timeout = timeout, callback = callback, widget = widget}
43+
44+
local cb = function(widget, stdout, _, _, _)
45+
local words = gears.string.split(stdout, ';')
46+
47+
local progress
48+
if words[5] ~= nil and words[6] ~= nil then
49+
progress = tonumber(words[5]) / tonumber(words[6])
50+
end
51+
52+
local metadata = {
53+
status = words[1],
54+
artist = words[2],
55+
current_song = words[3],
56+
art_url = words[4],
57+
position = words[5],
58+
length = words[6],
59+
album = words[7],
60+
progress = progress,
61+
}
62+
63+
callback(widget, metadata)
64+
end
65+
66+
_, self.timer = awful.widget.watch(cmd, timeout, cb, widget)
67+
end
68+
69+
function playerctl:toggle()
70+
awful.spawn(self:cmd("play-pause"), false)
71+
end
72+
73+
function playerctl:next()
74+
awful.spawn(self:cmd("next"), false)
75+
end
76+
77+
function playerctl:prev()
78+
awful.spawn(self:cmd("previous"), false)
79+
end
3080

3181
local icon = wibox.widget {
3282
id = "icon",
@@ -84,128 +134,140 @@ local metadata_widget = wibox.widget {
84134
forced_width = 300,
85135
}
86136

87-
88-
local rows = { layout = wibox.layout.fixed.vertical }
89-
90-
local popup = awful.popup {
91-
bg = beautiful.bg_normal,
92-
fg = beautiful.fg_normal,
93-
ontop = true,
94-
visible = false,
95-
shape = gears.shape.rounded_rect,
96-
border_width = 1,
97-
border_color = beautiful.bg_focus,
98-
maximum_width = 400,
99-
offset = { y = 5 },
100-
widget = {}
137+
local player_selector_popup = {
138+
popup = awful.popup {
139+
bg = beautiful.bg_normal,
140+
fg = beautiful.fg_normal,
141+
ontop = true,
142+
visible = false,
143+
shape = gears.shape.rounded_rect,
144+
border_width = 1,
145+
border_color = beautiful.bg_focus,
146+
maximum_width = 400,
147+
offset = { y = 5 },
148+
widget = {}
149+
},
150+
151+
rows = { layout = wibox.layout.fixed.vertical },
101152
}
102153

103-
local function rebuild_popup()
104-
awful.spawn.easy_async(LIST_PLAYERS_CMD, function(stdout, _, _, _)
105-
for i = 0, #rows do rows[i] = nil end
106-
for player_name in stdout:gmatch("[^\r\n]+") do
107-
if player_name ~= '' and player_name ~= nil then
108-
local checkbox = wibox.widget {
109-
{
110-
checked = player_name == default_player,
111-
color = beautiful.bg_normal,
112-
paddings = 2,
113-
shape = gears.shape.circle,
114-
forced_width = 20,
115-
forced_height = 20,
116-
check_color = beautiful.fg_normal,
117-
widget = wibox.widget.checkbox
118-
},
119-
valign = 'center',
120-
layout = wibox.container.place,
121-
}
122-
123-
checkbox:connect_signal("button::press", function()
124-
default_player = player_name
125-
rebuild_popup()
126-
end)
154+
function player_selector_popup:add_radio_button(player_name)
155+
local checkbox = wibox.widget {
156+
{
157+
checked = player_name == playerctl.player_name,
158+
color = beautiful.bg_normal,
159+
paddings = 2,
160+
shape = gears.shape.circle,
161+
forced_width = 20,
162+
forced_height = 20,
163+
check_color = beautiful.fg_normal,
164+
widget = wibox.widget.checkbox
165+
},
166+
valign = 'center',
167+
layout = wibox.container.place,
168+
}
169+
170+
checkbox:connect_signal("button::press", function()
171+
playerctl:set_player(player_name)
172+
self:toggle()
173+
end)
127174

128-
table.insert(rows, wibox.widget {
175+
table.insert(self.rows, wibox.widget {
176+
{
177+
{
178+
checkbox,
179+
{
129180
{
130-
{
131-
checkbox,
132-
{
133-
{
134-
text = player_name,
135-
align = 'left',
136-
widget = wibox.widget.textbox
137-
},
138-
left = 10,
139-
layout = wibox.container.margin
140-
},
141-
spacing = 8,
142-
layout = wibox.layout.align.horizontal
143-
},
144-
margins = 4,
145-
layout = wibox.container.margin
181+
text = player_name,
182+
align = 'left',
183+
widget = wibox.widget.textbox
146184
},
147-
bg = beautiful.bg_normal,
148-
fg = beautiful.fg_normal,
149-
widget = wibox.container.background
150-
})
185+
left = 10,
186+
layout = wibox.container.margin
187+
},
188+
spacing = 8,
189+
layout = wibox.layout.align.horizontal
190+
},
191+
margins = 4,
192+
layout = wibox.container.margin
193+
},
194+
bg = beautiful.bg_normal,
195+
fg = beautiful.fg_normal,
196+
widget = wibox.container.background
197+
})
198+
end
199+
200+
function player_selector_popup:rebuild()
201+
self.rows = { layout = wibox.layout.fixed.vertical }
202+
awful.spawn.easy_async("playerctl -l", function(stdout, _, _, _)
203+
for name in stdout:gmatch("[^\r\n]+") do
204+
if name ~= '' and name ~= nil then
205+
self:add_radio_button(name)
151206
end
152207
end
153-
end)
154208

155-
popup:setup(rows)
209+
self.popup:setup(self.rows)
210+
self.popup.visible = true
211+
self.popup:move_next_to(mouse.current_widget_geometry)
212+
end)
156213
end
157214

158-
local function update_metadata(artist, current_song, progress, art_url)
159-
artist_widget:set_text(artist)
160-
title_widget:set_text(current_song)
161-
progress_widget.value = progress
162-
163-
-- poor man's urldecode
164-
art_url = art_url:gsub("file://", "/")
165-
art_url = art_url:gsub("%%(%x%x)", function(x) return string.char(tonumber(x, 16)) end)
166-
167-
if art_url ~= nil and art_url ~= "" then
168-
cover_art_widget.image = art_url
169-
cover_art_widget.forced_height = 300
215+
function player_selector_popup:toggle()
216+
if self.popup.visible then
217+
self.popup.visible = false
170218
else
171-
cover_art_widget.image = nil
172-
cover_art_widget.forced_height = 0
219+
self:rebuild()
173220
end
174221
end
175222

176-
local function worker()
177-
-- retrieve song info
178-
local current_song, artist, player_status, art_url, progress
179-
180-
local update_graphic = function(widget, stdout, _, _, _)
181-
local words = gears.string.split(stdout, ';')
182-
player_status = words[1]
183-
artist = words[2]
184-
current_song = words[3]
223+
local function duration(microseconds)
224+
local seconds = microseconds / 1000000
225+
local minutes = seconds / 60
226+
seconds = seconds % 60
227+
local hours = minutes / 60
228+
minutes = minutes % 60
229+
if hours >= 1 then
230+
return string.format("%.f:%02.f:%02.f", hours, minutes, seconds)
231+
end
232+
return string.format("%.f:%02.f", minutes, seconds)
233+
end
185234

186-
art_url = words[4]
235+
local function worker()
236+
local update_metadata = function(meta)
237+
artist_widget:set_text(meta.artist)
238+
title_widget:set_text(meta.current_song)
239+
metadata_widget:set_text(string.format('%s - %s (%s/%s)', meta.album, meta.current_song, duration(meta.position), duration(meta.length)))
240+
progress_widget.value = meta.progress
241+
242+
-- poor man's urldecode
243+
local art_url = meta.art_url:gsub("file://", "/")
244+
art_url = art_url:gsub("%%(%x%x)", function(x) return string.char(tonumber(x, 16)) end)
245+
246+
if art_url ~= nil and art_url ~= "" then
247+
cover_art_widget.image = art_url
248+
cover_art_widget.forced_height = 300
249+
else
250+
cover_art_widget.image = nil
251+
cover_art_widget.forced_height = 0
252+
end
253+
end
187254

188-
if current_song ~= nil then
189-
if string.len(current_song) > 40 then
190-
current_song = string.sub(current_song, 0, 38) .. ""
255+
local update_graphic = function(widget, metadata)
256+
if metadata.current_song ~= nil then
257+
if string.len(metadata.current_song) > 40 then
258+
metadata.current_song = string.sub(metadata.current_song, 0, 38) .. ""
191259
end
192260
end
193261

194-
if player_status == "Playing" then
262+
if metadata.status == "Playing" then
195263
icon.image = PLAY_ICON_NAME
196264
widget.colors = { beautiful.widget_main_color }
197-
if words[5] ~= nil and words[6] ~= nil then
198-
progress = tonumber(words[5]) / tonumber(words[6])
199-
end
200-
update_metadata(artist, current_song, progress, art_url)
201-
elseif player_status == "Paused" then
265+
update_metadata(metadata)
266+
elseif metadata.status == "Paused" then
202267
icon.image = PAUSE_ICON_NAME
203268
widget.colors = { beautiful.widget_main_color }
204-
if words[5] ~= nil and words[6] ~= nil then
205-
progress = tonumber(words[5]) / tonumber(words[6])
206-
end
207-
update_metadata(artist, current_song, progress, art_url)
208-
elseif player_status == "Stopped" then
269+
update_metadata(metadata)
270+
elseif metadata.status == "Stopped" then
209271
icon.image = STOP_ICON_NAME
210272
else -- no player is running
211273
icon.image = LIBRARY_ICON_NAME
@@ -215,48 +277,30 @@ local function worker()
215277

216278
mpris_widget:buttons(
217279
awful.util.table.join(
218-
awful.button({}, 3, function()
219-
if popup.visible then
220-
popup.visible = not popup.visible
221-
else
222-
rebuild_popup()
223-
popup:move_next_to(mouse.current_widget_geometry)
224-
end
225-
end),
226-
awful.button({}, 4, function() awful.spawn(NEXT_MPD_CMD, false) end),
227-
awful.button({}, 5, function() awful.spawn(PREV_MPD_CMD, false) end),
228-
awful.button({}, 1, function() awful.spawn(TOGGLE_MPD_CMD, false) end)
280+
awful.button({}, 3, function() player_selector_popup:toggle() end),
281+
awful.button({}, 4, function() playerctl:next() end),
282+
awful.button({}, 5, function() playerctl:prev() end),
283+
awful.button({}, 1, function() playerctl:toggle() end)
229284
)
230285
)
231286

232-
watch(GET_MPD_CMD, 1, update_graphic, mpris_widget)
233-
234-
local mpris_popup = awful.widget.watch(
235-
"playerctl metadata --format '{{ status }}: {{ artist }} - {{ title }}\n"
236-
.. "Duration: {{ duration(position) }}/{{ duration(mpris:length) }}'",
237-
1,
238-
function(callback_popup, stdout)
239-
local metadata = stdout
240-
if callback_popup.visible then
241-
metadata_widget:set_text(metadata)
242-
callback_popup:move_next_to(mouse.current_widget_geometry)
243-
end
244-
end,
245-
awful.popup {
246-
border_color = beautiful.border_color,
247-
ontop = true,
248-
visible = false,
249-
widget = wibox.widget {
250-
cover_art_widget,
251-
metadata_widget,
252-
layout = wibox.layout.fixed.vertical,
253-
}
287+
playerctl:watch(1, update_graphic, mpris_widget)
288+
289+
local mpris_popup = awful.popup {
290+
border_color = beautiful.border_color,
291+
ontop = true,
292+
visible = false,
293+
widget = wibox.widget {
294+
cover_art_widget,
295+
metadata_widget,
296+
layout = wibox.layout.fixed.vertical,
254297
}
255-
)
298+
}
256299

257300
mpris_widget:connect_signal('mouse::enter',
258301
function()
259302
mpris_popup.visible = true
303+
mpris_popup:move_next_to(mouse.current_widget_geometry)
260304
end)
261305
mpris_widget:connect_signal('mouse::leave',
262306
function()

0 commit comments

Comments
 (0)