1 ---------------------------------------------------------------------------
2 -- @author Alexander Yakushev <yakushev.alex@gmail.com>
3 -- @copyright 2010-2013 Alexander Yakushev
5 ---------------------------------------------------------------------------
7 local wibox = require("wibox")
8 local awful = require('awful')
9 local beautiful = require('beautiful')
10 local naughty = require('naughty')
11 local format = string.format
13 local module_path = (...):match ("(.+/)[^/]+$") or ""
17 -- Function for checking icons and modules. Checks if a file exists,
18 -- and if it does, returns the path to file, nil otherwise.
19 function awesompd.try_load(file)
20 if awful.util.file_readable(file) then
25 -- Function for loading modules.
26 function awesompd.try_require(module)
27 if awesompd.try_load(awful.util.getdir("config") .. '/'..
28 module_path .. module .. ".lua") then
29 return require(module_path .. module)
31 return require(module)
35 local utf8 = awesompd.try_require("utf8")
36 asyncshell = awesompd.try_require("asyncshell")
37 local jamendo = awesompd.try_require("jamendo")
40 awesompd.PLAYING = "Playing"
41 awesompd.PAUSED = "Paused"
42 awesompd.STOPPED = "MPD stopped"
43 awesompd.DISCONNECTED = "Disconnected"
45 awesompd.MOUSE_LEFT = 1
46 awesompd.MOUSE_MIDDLE = 2
47 awesompd.MOUSE_RIGHT = 3
48 awesompd.MOUSE_SCROLL_UP = 4
49 awesompd.MOUSE_SCROLL_DOWN = 5
51 awesompd.NOTIFY_VOLUME = 1
52 awesompd.NOTIFY_REPEAT = 2
53 awesompd.NOTIFY_RANDOM = 3
54 awesompd.NOTIFY_SINGLE = 4
55 awesompd.NOTIFY_CONSUME = 5
56 awesompd.FORMAT_MP3 = jamendo.FORMAT_MP3
57 awesompd.FORMAT_OGG = jamendo.FORMAT_OGG
58 awesompd.ESCAPE_SYMBOL_MAPPING = {}
59 awesompd.ESCAPE_SYMBOL_MAPPING["&"] = "&"
60 -- Menus do not handle symbol escaping correctly, so they need their
62 awesompd.ESCAPE_MENU_SYMBOL_MAPPING = {}
63 awesompd.ESCAPE_MENU_SYMBOL_MAPPING["&"] = "'n'"
65 -- /// Current track variables and functions ///
67 -- Returns a string for the given track to be displayed in the widget
69 function awesompd.get_display_name(track)
70 if track.display_name then
71 return track.display_name
72 elseif track.artist_name and track.track_name then
73 return track.artist_name .. " - " .. track.name
77 -- Returns a track display name, album name (if exists) and album
78 -- release year (if exists).
79 function awesompd.get_extended_info(track)
80 local result = awesompd.get_display_name(track)
81 if track.album_name then
82 result = result .. "\n" .. track.album_name
85 result = result .. "\n" .. track.year
90 -- Returns true if the current status is either PLAYING or PAUSED
91 function awesompd:playing_or_paused()
92 return self.status == awesompd.PLAYING
93 or self.status == awesompd.PAUSED
96 -- /// Helper functions ///
98 -- Just like awful.util.pread, but takes an argument how to read like
100 function awesompd.pread(com, mode)
101 local f = io.popen(com, 'r')
104 result = f:read(mode)
110 -- Slightly modified function awful.util.table.join.
111 function awesompd.ajoin(buttons)
113 for i = 1, #buttons do
115 for k, v in pairs(buttons[i]) do
116 if type(k) == "number" then
117 table.insert(result, v)
127 -- Splits a given string with linebreaks into an array.
128 function awesompd.split(s)
134 local f = function (s)
138 local p = "%s*(.-)%s*\n%s*"
139 s = string.gsub(s,p,f)
143 -- Returns the given string if it is not nil or non-empty, otherwise
145 local function non_empty(s)
146 if s and s ~= "" then
153 function awesompd.load_icons(path)
155 awesompd.ICONS.PLAY = awesompd.try_load(path .. "/play_icon.png")
156 awesompd.ICONS.PAUSE = awesompd.try_load(path .. "/pause_icon.png")
157 awesompd.ICONS.PLAY_PAUSE = awesompd.try_load(path .. "/play_pause_icon.png")
158 awesompd.ICONS.STOP = awesompd.try_load(path .. "/stop_icon.png")
159 awesompd.ICONS.NEXT = awesompd.try_load(path .. "/next_icon.png")
160 awesompd.ICONS.PREV = awesompd.try_load(path .. "/prev_icon.png")
161 awesompd.ICONS.CHECK = awesompd.try_load(path .. "/check_icon.png")
162 awesompd.ICONS.RADIO = awesompd.try_load(path .. "/radio_icon.png")
163 awesompd.ICONS.DEFAULT_ALBUM_COVER =
164 awesompd.try_load(path .. "/default_album_cover.png")
167 -- Function that returns a new awesompd object.
168 function awesompd:create()
171 setmetatable(instance,self)
173 instance.current_server = 1
174 instance.widget = wibox.layout.fixed.horizontal()
175 instance.notification = nil
176 instance.scroll_pos = 1
178 instance.to_notify = false
179 instance.album_cover = nil
180 instance.current_track = { }
181 instance.recreate_menu = true
182 instance.recreate_playback = true
183 instance.recreate_list = true
184 instance.recreate_servers = true
185 instance.recreate_options = true
186 instance.recreate_jamendo_formats = true
187 instance.recreate_jamendo_order = true
188 instance.recreate_jamendo_browse = true
189 instance.current_number = 0
190 instance.menu_shown = false
191 instance.state_volume = "NaN"
192 instance.state_repeat = "NaN"
193 instance.state_random = "NaN"
194 instance.state_single = "NaN"
195 instance.state_consume = "NaN"
197 -- Default user options
198 instance.servers = { { server = "localhost", port = 6600 } }
199 instance.font = "Monospace"
200 instance.font_color = beautiful.fg_normal
201 instance.background = beautiful.bg_normal
202 instance.scrolling = true
203 instance.output_size = 30
204 instance.update_interval = 10
205 instance.path_to_icons = ""
206 instance.ldecorator = " "
207 instance.rdecorator = " "
208 instance.jamendo_format = awesompd.FORMAT_MP3
209 instance.show_album_cover = true
210 instance.album_cover_size = 50
211 instance.browser = "firefox"
213 -- Widget configuration
214 instance.widget:connect_signal("mouse::enter", function(c)
215 instance:notify_track()
217 instance.widget:connect_signal("mouse::leave", function(c)
218 instance:hide_notification()
223 -- Registers timers for the widget
224 function awesompd:run()
225 self.load_icons(self.path_to_icons)
226 jamendo.set_current_format(self.jamendo_format)
227 if self.album_cover_size > 100 then
228 self.album_cover_size = 100
231 self.text_widget = wibox.widget.textbox()
232 if self.widget_icon then
233 self.icon_widget = wibox.widget.imagebox()
234 self.icon_widget:set_image(self.widget_icon)
235 self.widget:add(self.icon_widget)
237 self.widget:add(self.text_widget)
240 self:check_playlists()
243 scheduler.register_recurring("awesompd_scroll", 1,
244 function() self:update_widget() end)
245 scheduler.register_recurring("awesompd_update", self.update_interval,
246 function() self:update_track() end)
248 self.update_widget_timer = timer({ timeout = 1 })
249 self.update_widget_timer:connect_signal("timeout", function()
252 self.update_widget_timer:start()
253 self.update_track_timer = timer({ timeout = self.update_interval })
254 self.update_track_timer:connect_signal("timeout", function()
257 self.update_track_timer:start()
261 -- Function that registers buttons on the widget.
262 function awesompd:register_buttons(buttons)
264 self.global_bindings = {}
266 if type(buttons[b][1]) == "string" then
267 mods = { buttons[b][1] }
271 if type(buttons[b][2]) == "number" then
272 -- This is a mousebinding, bind it to the widget
273 table.insert(widget_buttons,
274 awful.button(mods, buttons[b][2], buttons[b][3]))
276 -- This is a global keybinding, remember it for later usage in append_global_keys
277 table.insert(self.global_bindings, awful.key(mods, buttons[b][2], buttons[b][3]))
280 self.widget:buttons(self.ajoin(widget_buttons))
283 -- Takes the current table with keybindings and adds widget's own
284 -- global keybindings that were specified in register_buttons.
285 -- If keytable is not specified, then adds bindings to default
286 -- globalkeys table. If specified, then adds bindings to keytable and
288 function awesompd:append_global_keys(keytable)
290 for i = 1, #self.global_bindings do
291 keytable = awful.util.table.join(keytable, self.global_bindings[i])
295 for i = 1, #self.global_bindings do
296 globalkeys = awful.util.table.join(globalkeys, self.global_bindings[i])
301 -- /// Group of mpc command functions ///
303 -- Returns a mpc command with all necessary parameters. Boolean
304 -- human_readable argument configures if the command special
305 -- formatting of the output (to be later used in parsing) should not
307 function awesompd:mpcquery(human_readable)
309 "mpc -h " .. self.servers[self.current_server].server ..
310 " -p " .. self.servers[self.current_server].port .. " "
311 if human_readable then
314 return result ..' -f "%file%-<>-%name%-<>-%title%-<>-%artist%-<>-%album%" '
318 -- Takes a command to mpc and a hook that is provided with awesompd
319 -- instance and the result of command execution.
320 function awesompd:command(com,hook)
321 local file = io.popen(self:mpcquery() .. com)
328 -- Takes a command to mpc and read mode and returns the result.
329 function awesompd:command_read(com, mode)
330 mode = mode or "*line"
331 self:command(com, function(_, f)
332 result = f:read(mode)
337 function awesompd:command_playpause()
339 self:command("toggle",self.update_track)
343 function awesompd:command_next_track()
345 self:command("next",self.update_track)
349 function awesompd:command_prev_track()
351 self:command("seek 0")
352 self:command("prev",self.update_track)
356 function awesompd:command_stop()
358 self:command("stop",self.update_track)
362 function awesompd:command_play_specific(n)
364 self:command("play " .. n,self.update_track)
368 function awesompd:command_volume_up()
370 self:command("volume +5")
371 self:update_track() -- Nasty! I should replace it with proper callback later.
372 self:notify_state(self.NOTIFY_VOLUME)
376 function awesompd:command_volume_down()
378 self:command("volume -5")
380 self:notify_state(self.NOTIFY_VOLUME)
384 function awesompd:command_load_playlist(name)
386 self:command("load \"" .. name .. "\"", function()
387 self.recreate_menu = true
392 function awesompd:command_replace_playlist(name)
394 self:command("clear")
395 self:command("load \"" .. name .. "\"")
396 self:command("play 1", self.update_track)
400 function awesompd:command_clear_playlist()
402 self:command("clear", self.update_track)
403 self.recreate_list = true
404 self.recreate_menu = true
408 function awesompd:command_open_in_browser(link)
411 awful.util.spawn(self.browser .. " '" .. link .. "'")
416 --- Change to the previous server.
417 function awesompd:command_previous_server()
419 servers = table.getn(self.servers)
420 if servers == 1 or servers == nil then
423 if self.current_server > 1 then
424 self:change_server(self.current_server - 1)
426 self:change_server(servers)
432 --- Change to the previous server.
433 function awesompd:command_next_server()
435 servers = table.getn(self.servers)
436 if servers == 1 or servers == nil then
439 if self.current_server < servers then
440 self:change_server(self.current_server + 1)
442 self:change_server(1)
448 -- /// End of mpc command functions ///
450 -- /// Menu generation functions ///
452 function awesompd:command_show_menu()
455 self:hide_notification()
456 if self.recreate_menu then
458 if self.main_menu ~= nil then
459 self.main_menu:hide()
461 if self.status ~= awesompd.DISCONNECTED
464 self:check_playlists()
465 local jamendo_menu = { { "Search by",
466 { { "Nothing (Top 100)", self:menu_jamendo_top() },
467 { "Artist", self:menu_jamendo_search_by(jamendo.SEARCH_ARTIST) },
468 { "Album", self:menu_jamendo_search_by(jamendo.SEARCH_ALBUM) },
469 { "Tag", self:menu_jamendo_search_by(jamendo.SEARCH_TAG) }}} }
470 local browse_menu = self:menu_jamendo_browse()
472 table.insert(jamendo_menu, browse_menu)
474 table.insert(jamendo_menu, self:menu_jamendo_format())
475 table.insert(jamendo_menu, self:menu_jamendo_order())
477 new_menu = { { "Playback", self:menu_playback() },
478 { "Options", self:menu_options() },
479 { "List", self:menu_list() },
480 { "Playlists", self:menu_playlists() },
481 { "Jamendo", jamendo_menu } }
483 table.insert(new_menu, { "Servers", self:menu_servers() })
484 self.main_menu = awful.menu({ items = new_menu, theme = { width = 300 } })
485 self.recreate_menu = false
487 self.main_menu:toggle()
491 -- Returns an icon for a checkbox menu item if it is checked, nil
493 function awesompd:menu_item_toggle(checked)
494 return checked and self.ICONS.CHECK or nil
497 -- Returns an icon for a radiobox menu item if it is selected, nil
499 function awesompd:menu_item_radio(selected)
500 return selected and self.ICONS.RADIO or nil
503 -- Returns the playback menu. Menu contains of:
504 -- Play\Pause - always
505 -- Previous - if the current track is not the first
506 -- in the list and playback is not stopped
507 -- Next - if the current track is not the last
508 -- in the list and playback is not stopped
509 -- Stop - if the playback is not stopped
510 -- Clear playlist - always
511 function awesompd:menu_playback()
512 if self.recreate_playback then
514 table.insert(new_menu, { "Play\\Pause",
515 self:command_toggle(),
516 self.ICONS.PLAY_PAUSE })
517 if self:playing_or_paused() then
518 if self.list_array and self.list_array[self.current_number-1] then
519 table.insert(new_menu,
521 awesompd.protect_string(jamendo.replace_link(
522 self.list_array[self.current_number - 1]),
524 self:command_prev_track(), self.ICONS.PREV })
526 if self.list_array and self.current_number ~= #self.list_array then
527 table.insert(new_menu,
529 awesompd.protect_string(jamendo.replace_link(
530 self.list_array[self.current_number + 1]),
532 self:command_next_track(), self.ICONS.NEXT })
534 table.insert(new_menu, { "Stop", self:command_stop(), self.ICONS.STOP })
535 table.insert(new_menu, { "", nil })
537 table.insert(new_menu, { "Clear playlist", self:command_clear_playlist() })
538 self.recreate_playback = false
539 playback_menu = new_menu
544 -- Returns the current playlist menu. Menu consists of all elements in the playlist.
545 function awesompd:menu_list()
546 if self.recreate_list then
548 if self.list_array then
549 local total_count = #self.list_array
550 local start_num = (self.current_number - 15 > 0) and self.current_number - 15 or 1
551 local end_num = (self.current_number + 15 < total_count ) and self.current_number + 15 or total_count
552 for i = start_num, end_num do
553 table.insert(new_menu, { jamendo.replace_link(self.list_array[i]),
554 self:command_play_specific(i),
555 self.current_number == i and
556 (self.status == self.PLAYING and self.ICONS.PLAY or self.ICONS.PAUSE)
560 self.recreate_list = false
561 self.list_menu = new_menu
563 return self.list_menu
566 -- Returns the playlists menu. Menu consists of all files in the playlist folder.
567 function awesompd:menu_playlists()
568 if self.recreate_playlists then
570 if #self.playlists_array > 0 then
571 for i = 1, #self.playlists_array do
573 submenu[1] = { "Add to current", self:command_load_playlist(self.playlists_array[i]) }
574 submenu[2] = { "Replace current", self:command_replace_playlist(self.playlists_array[i]) }
575 new_menu[i] = { self.playlists_array[i], submenu }
577 table.insert(new_menu, {"", ""}) -- This is a separator
579 table.insert(new_menu, { "Refresh", function() self:check_playlists() end })
580 self.recreate_playlists = false
581 self.playlists_menu = new_menu
583 return self.playlists_menu
586 -- Returns the server menu. Menu consists of all servers specified by user during initialization.
587 function awesompd:menu_servers()
588 if self.recreate_servers then
590 for i = 1, #self.servers do
591 table.insert(new_menu, {"Server: " .. self.servers[i].server ..
592 ", port: " .. self.servers[i].port,
593 function() self:change_server(i) end,
594 self:menu_item_radio(i == self.current_server)})
596 self.servers_menu = new_menu
598 return self.servers_menu
601 -- Returns the options menu. Menu works like checkboxes for it's elements.
602 function awesompd:menu_options()
603 if self.recreate_options then
604 local new_menu = { { "Repeat", self:menu_toggle_repeat(),
605 self:menu_item_toggle(self.state_repeat == "on")},
606 { "Random", self:menu_toggle_random(),
607 self:menu_item_toggle(self.state_random == "on")},
608 { "Single", self:menu_toggle_single(),
609 self:menu_item_toggle(self.state_single == "on")},
610 { "Consume", self:menu_toggle_consume(),
611 self:menu_item_toggle(self.state_consume == "on")} }
612 self.options_menu = new_menu
613 self.recreate_options = false
615 return self.options_menu
618 function awesompd:menu_toggle_random()
620 self:command("random",self.update_track)
621 self:notify_state(self.NOTIFY_RANDOM)
625 function awesompd:menu_toggle_repeat()
627 self:command("repeat",self.update_track)
628 self:notify_state(self.NOTIFY_REPEAT)
632 function awesompd:menu_toggle_single()
634 self:command("single",self.update_track)
635 self:notify_state(self.NOTIFY_SINGLE)
639 function awesompd:menu_toggle_consume()
641 self:command("consume",self.update_track)
642 self:notify_state(self.NOTIFY_CONSUME)
646 function awesompd:menu_jamendo_top()
649 local track_table = jamendo.return_track_table()
650 if not track_table then
651 self:show_notification("Can't connect to Jamendo server", "Please check your network connection")
653 self:add_jamendo_tracks(track_table)
654 self:show_notification("Jamendo Top 100 by " ..
655 jamendo.current_request_table.params.order.short_display,
656 format("Added %s tracks to the playlist",
662 function awesompd:menu_jamendo_format()
663 if self.recreate_jamendo_formats then
667 jamendo.set_current_format(format)
668 self.recreate_menu = true
669 self.recreate_jamendo_formats = true
675 return jamendo.current_request_table.params.streamencoding.value
680 for _, format in pairs(jamendo.ALL_FORMATS) do
681 table.insert(new_menu, { format.display, setformat(format),
682 self:menu_item_radio(iscurr(format))})
684 self.recreate_jamendo_formats = false
685 self.jamendo_formats_menu = {
687 jamendo.current_request_table.params.streamencoding.short_display,
690 return self.jamendo_formats_menu
693 function awesompd:menu_jamendo_browse()
694 if self.recreate_jamendo_browse and self.browser
695 and self.current_track.unique_name then
696 local track = jamendo.get_track_by_link(self.current_track.unique_name)
700 "http://www.jamendo.com/artist/" .. track.artist_link_name
702 "http://www.jamendo.com/album/" .. track.album_id
703 new_menu = { { "Artist's page" ,
704 self:command_open_in_browser(artist_link) },
706 self:command_open_in_browser(album_link) } }
707 self.jamendo_browse_menu = { "Browse on Jamendo", new_menu }
709 self.jamendo_browse_menu = nil
712 return self.jamendo_browse_menu
715 function awesompd:menu_jamendo_order()
716 if self.recreate_jamendo_order then
720 jamendo.set_current_order(order)
721 self.recreate_menu = true
722 self.recreate_jamendo_order = true
728 return jamendo.current_request_table.params.order.value
733 for _, order in pairs(jamendo.ALL_ORDERS) do
734 table.insert(new_menu, { order.display, setorder(order),
735 self:menu_item_radio(iscurr(order))})
737 self.recreate_jamendo_order = false
738 self.jamendo_order_menu = {
740 jamendo.current_request_table.params.order.short_display,
743 return self.jamendo_order_menu
746 function awesompd:menu_jamendo_search_by(what)
750 local result = jamendo.search_by(what, s)
752 local track_count = #result.tracks
753 self:add_jamendo_tracks(result.tracks)
754 self:show_notification(format("%s \"%s\" was found",
756 result.search_res.name),
757 format("Added %s tracks to the playlist",
760 self:show_notification("Search failed",
761 format("%s \"%s\" was not found",
765 self:display_inputbox("Search music on Jamendo",
766 what.display, callback)
770 -- Checks if the current playlist has changed after the last check.
771 function awesompd:check_list()
772 local bus = io.popen(self:mpcquery(true) .. "playlist")
773 local info = bus:read("*all")
775 if info ~= self.list_line then
776 self.list_line = info
777 if string.len(info) > 0 then
778 self.list_array = self.split(string.sub(info,1,string.len(info)))
782 self.recreate_menu = true
783 self.recreate_list = true
787 -- Checks if the collection of playlists changed after the last check.
788 function awesompd:check_playlists()
789 local bus = io.popen(self:mpcquery(true) .. "lsplaylists")
790 local info = bus:read("*all")
792 if info ~= self.playlists_line then
793 self.playlists_line = info
794 if string.len(info) > 0 then
795 self.playlists_array = self.split(info)
797 self.playlists_array = {}
799 self.recreate_menu = true
800 self.recreate_playlists = true
804 -- Changes the current server to the specified one.
805 function awesompd:change_server(server_number)
806 self.current_server = server_number
807 self:hide_notification()
808 self.recreate_menu = true
809 self.recreate_playback = true
810 self.recreate_list = true
811 self.recreate_playlists = true
812 self.recreate_servers = true
816 function awesompd:add_jamendo_tracks(track_table)
817 for i = 1, #track_table do
818 self:command("add '" .. string.gsub(track_table[i].stream, '\\/', '/') .. "'")
820 self.recreate_menu = true
821 self.recreate_list = true
824 -- /// End of menu generation functions ///
826 function awesompd:show_notification(hint_title, hint_text, hint_image)
827 self:hide_notification()
828 self.notification = naughty.notify({ title = hint_title
829 , text = awesompd.protect_string(hint_text)
831 , position = "top_right"
833 , icon_size = self.album_cover_size
837 function awesompd:hide_notification()
838 if self.notification ~= nil then
839 naughty.destroy(self.notification)
840 self.notification = nil
844 function awesompd:notify_track()
845 if self:playing_or_paused() then
846 local caption = self.status_text
847 local nf_text = self.get_display_name(self.current_track)
849 if self.show_album_cover then
850 nf_text = self.get_extended_info(self.current_track)
851 al_cover = self.current_track.album_cover
853 self:show_notification(caption, nf_text, al_cover)
857 function awesompd:notify_state(state_changed)
858 state_array = { "Volume: " .. self.state_volume ,
859 "Repeat: " .. self.state_repeat ,
860 "Random: " .. self.state_random ,
861 "Single: " .. self.state_single ,
862 "Consume: " .. self.state_consume }
863 state_header = state_array[state_changed]
864 table.remove(state_array,state_changed)
865 full_state = state_array[1]
866 for i = 2, #state_array do
867 full_state = full_state .. "\n" .. state_array[i]
869 self:show_notification(state_header, full_state)
872 function awesompd:wrap_output(text)
873 return format('<span font="%s" color="%s" background="%s">%s%s%s</span>',
874 self.font, self.font_color, self.background,
875 (text == "" and "" or self.ldecorator), awesompd.protect_string(text),
876 (text == "" and "" or self.rdecorator))
879 -- This function actually sets the text on the widget.
880 function awesompd:set_text(text)
881 self.text_widget:set_markup(self:wrap_output(text))
884 function awesompd.find_pattern(text, pattern, start)
885 return utf8.sub(text, string.find(text, pattern, start))
888 -- Scroll the given text by the current number of symbols.
889 function awesompd:scroll_text(text)
891 if self.scrolling then
892 if self.output_size < utf8.len(text) then
894 if self.scroll_pos + self.output_size - 1 > utf8.len(text) then
895 result = utf8.sub(text, self.scroll_pos)
896 result = result .. utf8.sub(text, 1, self.scroll_pos + self.output_size - 1 - utf8.len(text))
897 self.scroll_pos = self.scroll_pos + 1
898 if self.scroll_pos > utf8.len(text) then
902 result = utf8.sub(text, self.scroll_pos, self.scroll_pos + self.output_size - 1)
903 self.scroll_pos = self.scroll_pos + 1
910 -- This function is called every second.
911 function awesompd:update_widget()
912 self:set_text(self:scroll_text(self.text))
916 -- This function is called by update_track each time content of
917 -- the widget must be changed.
918 function awesompd:update_widget_text()
919 if self:playing_or_paused() then
920 self.text = self.get_display_name(self.current_track)
922 self.text = self.status
926 -- Checks if notification should be shown and shows if positive.
927 function awesompd:check_notify()
928 if self.to_notify then
930 self.to_notify = false
934 function awesompd:notify_connect()
935 self:show_notification("Connected", "Connection established to " .. self.servers[self.current_server].server ..
936 " on port " .. self.servers[self.current_server].port)
939 function awesompd:notify_disconnect()
940 self:show_notification("Disconnected", "Cannot connect to " .. self.servers[self.current_server].server ..
941 " on port " .. self.servers[self.current_server].port)
944 function awesompd:update_track(file)
945 local file_exists = (file ~= nil)
946 if not file_exists then
947 file = io.popen(self:mpcquery())
949 local track_line = file:read("*line")
950 local status_line = file:read("*line")
951 local options_line = file:read("*line")
952 if not file_exists then
956 if not track_line or string.len(track_line) == 0 then
957 if self.status ~= awesompd.DISCONNECTED then
958 self:notify_disconnect()
959 self.recreate_menu = true
960 self.status = awesompd.DISCONNECTED
961 self.current_track = { }
962 self:update_widget_text()
965 if self.status == awesompd.DISCONNECTED then
966 self:notify_connect()
967 self.recreate_menu = true
968 self:update_widget_text()
970 if string.find(track_line,"volume:") or string.find(track_line,"Updating DB") then
971 if self.status ~= awesompd.STOPPED then
972 self.status = awesompd.STOPPED
973 self.current_number = 0
974 self.recreate_menu = true
975 self.recreate_playback = true
976 self.recreate_list = true
977 self.album_cover = nil
978 self.current_track = { }
979 self:update_widget_text()
981 self:update_state(track_line)
983 self:update_state(options_line)
984 local _, _, new_file, station, title, artist, album =
985 string.find(track_line, "(.*)%-<>%-(.*)%-<>%-(.*)%-<>%-(.*)%-<>%-(.*)")
986 local display_name, force_update = artist .. " - " .. title, false
987 -- The following code checks if the current track is an
988 -- Internet link. Internet radios change tracks, but the
989 -- current file stays the same, so we should manually compare
991 if string.match(new_file, "http://") and
992 -- The following line is awful. This needs to be replaced ASAP.
993 not string.match(new_file, "http://storage%-new%.newjamendo%.com") then
994 album = non_empty(station) or ""
995 display_name = non_empty(title) or new_file
996 if display_name ~= self.current_track.display_name then
1000 if new_file ~= self.current_track.unique_name or force_update then
1001 self.current_track = jamendo.get_track_by_link(new_file)
1002 if not self.current_track then
1003 self.current_track = { display_name = display_name,
1004 album_name = album }
1006 self.current_track.unique_name = new_file
1007 if self.show_album_cover then
1008 self.current_track.album_cover = self:get_cover(new_file)
1010 self.to_notify = true
1011 self.recreate_menu = true
1012 self.recreate_playback = true
1013 self.recreate_list = true
1014 self.current_number = tonumber(self.find_pattern(status_line,"%d+"))
1015 self:update_widget_text()
1017 -- If the track is not the last, asynchronously download
1018 -- the cover for the next track.
1019 if self.list_array and self.current_number ~= #self.list_array then
1020 -- Get the link (in case it is Jamendo stream) to the next track
1022 self:command_read('playlist -f "%file%" | head -' ..
1023 self.current_number + 1 .. ' | tail -1', "*line")
1024 jamendo.try_get_cover_async(next_track)
1027 local tmp_pst = string.find(status_line,"%d+%:%d+%/")
1028 local progress = self.find_pattern(status_line,"%#%d+/%d+") .. " " .. string.sub(status_line,tmp_pst)
1029 local new_status = awesompd.PLAYING
1030 if string.find(status_line,"paused") then
1031 new_status = awesompd.PAUSED
1033 if new_status ~= self.status then
1034 self.to_notify = true
1035 self.recreate_list = true
1036 self.status = new_status
1037 self:update_widget_text()
1039 self.status_text = self.status .. " " .. progress
1044 function awesompd:update_state(state_string)
1045 self.state_volume = self.find_pattern(state_string,"%d+%% ")
1046 if string.find(state_string,"repeat: on") then
1047 self.state_repeat = self:check_set_state(self.state_repeat, "on")
1049 self.state_repeat = self:check_set_state(self.state_repeat, "off")
1051 if string.find(state_string,"random: on") then
1052 self.state_random = self:check_set_state(self.state_random, "on")
1054 self.state_random = self:check_set_state(self.state_random, "off")
1056 if string.find(state_string,"single: on") then
1057 self.state_single = self:check_set_state(self.state_single, "on")
1059 self.state_single = self:check_set_state(self.state_single, "off")
1061 if string.find(state_string,"consume: on") then
1062 self.state_consume = self:check_set_state(self.state_consume, "on")
1064 self.state_consume = self:check_set_state(self.state_consume, "off")
1068 function awesompd:check_set_state(statevar, val)
1069 if statevar ~= val then
1070 self.recreate_menu = true
1071 self.recreate_options = true
1076 function awesompd:run_prompt(welcome,hook)
1077 awful.prompt.run({ prompt = welcome },
1078 self.promptbox[mouse.screen].widget,
1082 -- Replaces control characters with escaped ones.
1083 -- for_menu - defines if the special escable table for menus should be
1085 function awesompd.protect_string(str, for_menu)
1087 return utf8.replace(str, awesompd.ESCAPE_MENU_SYMBOL_MAPPING)
1089 return utf8.replace(str, awesompd.ESCAPE_SYMBOL_MAPPING)
1093 -- Initialize the inputbox.
1094 function awesompd:init_inputbox()
1097 local border_color = beautiful.bg_focus or '#535d6c'
1099 local wbox = wibox({ name = "awmpd_ibox", height = height , width = width,
1100 border_color = border_color, border_width = 1 })
1101 local ws = screen[mouse.screen].workarea
1103 wbox.screen = mouse.screen
1106 local wprompt = awful.widget.prompt()
1107 local wtbox = wibox.widget.textbox()
1108 local wtmarginbox = wibox.layout.margin(wtbox, margin)
1109 local tw, th = wtbox:fit(-1, -1)
1110 wbox:geometry({ x = ws.width - width - 5, y = 25,
1111 width = 200, height = th * 2 + margin})
1112 local layout = wibox.layout.flex.vertical()
1113 layout:add(wtmarginbox)
1115 wbox:set_widget(layout)
1116 self.inputbox = { wibox = wbox,
1121 -- Displays an inputbox on the screen (looks like naughty with prompt).
1122 -- title_text - bold text on the first line
1123 -- prompt_text - preceding text on the second line
1124 -- hook - function that will be called with input data
1125 -- Use it like this:
1126 -- self:display_inputbox("Search music on Jamendo", "Artist", print)
1127 function awesompd:display_inputbox(title_text, prompt_text, hook)
1128 if not self.inputbox then
1129 self:init_inputbox()
1131 if self.inputbox.wibox.visible then -- Inputbox already exists, replace it
1135 local exe_callback = function(s)
1137 self.inputbox.wibox.visible = false
1139 local done_callback = function()
1140 self.inputbox.wibox.visible = false
1142 self.inputbox.title:set_markup("<b>" .. title_text .. "</b>")
1143 awful.prompt.run( { prompt = " " .. prompt_text .. ": ", bg_cursor = "#222222" },
1144 self.inputbox.prompt.widget,
1145 exe_callback, nil, nil, nil, done_callback, nil, nil)
1146 self.inputbox.wibox.visible = true
1149 -- Gets the cover for the given track. First looks in the Jamendo
1150 -- cache. If the track is not a Jamendo stream, looks in local
1151 -- folders. If there is no cover art either returns the default album
1153 function awesompd:get_cover(track)
1154 return jamendo.try_get_cover(track) or
1155 self:try_get_local_cover(track) or self.ICONS.DEFAULT_ALBUM_COVER
1158 -- Tries to find an album cover for the track that is currently
1160 function awesompd:try_get_local_cover(current_file)
1161 if self.mpd_config then
1163 -- First find the music directory in MPD configuration file
1164 local _, _, music_folder = string.find(
1165 self.pread('cat ' .. self.mpd_config .. ' | grep -v "#" | grep music_directory', "*line"),
1166 'music_directory%s+"(.+)"')
1167 music_folder = music_folder .. "/"
1169 -- If the music_folder is specified with ~ at the beginning,
1170 -- replace it with user home directory
1171 if string.sub(music_folder, 1, 1) == "~" then
1172 local user_folder = self.pread("echo ~", "*line")
1173 music_folder = user_folder .. string.sub(music_folder, 2)
1176 -- Get the path to the file currently playing.
1177 local _, _, current_file_folder = string.find(current_file, '(.+%/).*')
1179 -- Check if the current file is not some kind of http stream or
1180 -- Spotify track (like spotify:track:5r65GeuIoebfJB5sLcuPoC)
1181 if not current_file_folder or string.match(current_file, "%w+://") then
1182 return -- Let the default image to be the cover
1185 local folder = music_folder .. current_file_folder
1187 -- Get all images in the folder. Also escape occasional single
1188 -- quotes in folder name.
1189 local request = format("ls '%s' | grep -P '\\.jpg|\\.png|\\.gif|\\.jpeg'",
1190 string.gsub(folder, "'", "'\\''"))
1192 local covers = self.pread(request, "*all")
1193 local covers_table = self.split(covers)
1195 if covers_table.n > 0 then
1196 result = folder .. covers_table[1]
1197 if covers_table.n > 1 then
1198 -- Searching for front cover with grep because Lua regular
1199 -- expressions suck:[
1201 self.pread('echo "' .. covers ..
1202 '" | grep -P -i "cover|front|folder|albumart" | head -n 1', "*line")
1204 result = folder .. front_cover
1212 -- /// Deprecated, left for some backward compatibility in
1213 -- configuration ///
1215 function awesompd:command_toggle()
1216 return self:command_playpause()