@@ -11,13 +11,6 @@ local watch = require("awful.widget.watch")
11
11
local wibox = require (" wibox" )
12
12
local gears = require (" gears" )
13
13
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
-
21
14
local PATH_TO_ICONS = " /usr/share/icons/Adwaita"
22
15
local PAUSE_ICON_NAME = PATH_TO_ICONS .. " /symbolic/actions/media-playback-pause-symbolic.svg"
23
16
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
26
19
27
20
local FONT = ' Roboto Condensed 16px'
28
21
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
30
80
31
81
local icon = wibox .widget {
32
82
id = " icon" ,
@@ -84,128 +134,140 @@ local metadata_widget = wibox.widget {
84
134
forced_width = 300 ,
85
135
}
86
136
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 },
101
152
}
102
153
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 )
127
174
128
- table.insert (rows , wibox .widget {
175
+ table.insert (self .rows , wibox .widget {
176
+ {
177
+ {
178
+ checkbox ,
179
+ {
129
180
{
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
146
184
},
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 )
151
206
end
152
207
end
153
- end )
154
208
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 )
156
213
end
157
214
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
170
218
else
171
- cover_art_widget .image = nil
172
- cover_art_widget .forced_height = 0
219
+ self :rebuild ()
173
220
end
174
221
end
175
222
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
185
234
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
187
254
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 ) .. " …"
191
259
end
192
260
end
193
261
194
- if player_status == " Playing" then
262
+ if metadata . status == " Playing" then
195
263
icon .image = PLAY_ICON_NAME
196
264
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
202
267
icon .image = PAUSE_ICON_NAME
203
268
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
209
271
icon .image = STOP_ICON_NAME
210
272
else -- no player is running
211
273
icon .image = LIBRARY_ICON_NAME
@@ -215,48 +277,30 @@ local function worker()
215
277
216
278
mpris_widget :buttons (
217
279
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 )
229
284
)
230
285
)
231
286
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 ,
254
297
}
255
- )
298
+ }
256
299
257
300
mpris_widget :connect_signal (' mouse::enter' ,
258
301
function ()
259
302
mpris_popup .visible = true
303
+ mpris_popup :move_next_to (mouse .current_widget_geometry )
260
304
end )
261
305
mpris_widget :connect_signal (' mouse::leave' ,
262
306
function ()
0 commit comments