[submodule "awesome/awesome-freedesktop"]
path = awesome/awesome-freedesktop
url = https://github.com/terceiro/awesome-freedesktop.git
+[submodule "awesome/widgets/awesompd"]
+ path = awesome/widgets/awesompd
+ url = git@github.com:Ram-Z/awesompd.git
--- /dev/null
+Subproject commit 20156ff10e67c67965aeaf6ada1d5e02a581d600
+++ /dev/null
-## Description ##
-
-This is an advanced MPD widget\client for AwesomeWM.
-
-For the detailed installation guide please see
-http://awesome.naquadah.org/wiki/Awesompd_widget .
-
-Also you can find an example of the widget configuration in the file
-rcsample.lua.
-
-## Version explanation ##
-
-Use this version with Awesome v3.5.x. If you are using Awesome
-v.3.4.x, please consider using
-[this](https://github.com/alexander-yakushev/awesompd/tree/awesome-3.4)
-version of the widget.
-
-### Changes in 1.1.0 ###
-
-* Album covers are now also shown for the local tracks (images are taken from the current track's folder)
-* When the Jamendo track is playing you can visit artist's or album's page from the Jamendo menu
-* Notification now shows the album name for the current track (for both local and Jamendo tracks)
-* A few minor modifications and bugfixes
-
+++ /dev/null
--- Asynchronous io.popen for Awesome WM.
--- How to use...
--- ...asynchronously:
--- asyncshell.request('wscript -Kiev', function(f) wwidget.text = f:read("*l") end)
--- ...synchronously
--- wwidget.text = asyncshell.demand('wscript -Kiev', 5):read("*l") or "Error"
-
-local awful = require('awful')
-
-asyncshell = {}
-asyncshell.request_table = {}
-asyncshell.id_counter = 0
-asyncshell.folder = "/tmp/asyncshell"
-asyncshell.file_template = asyncshell.folder .. '/req'
-
--- Create a directory for asynchell response files
-os.execute("mkdir -p " .. asyncshell.folder)
-
--- Returns next tag - unique identifier of the request
-local function next_id()
- asyncshell.id_counter = (asyncshell.id_counter + 1) % 100000
- return asyncshell.id_counter
-end
-
--- Sends an asynchronous request for an output of the shell command.
--- @param command Command to be executed and taken output from
--- @param callback Function to be called when the command finishes
--- @return Request ID
-function asyncshell.request(command, callback)
- local id = next_id()
- local tmpfname = asyncshell.file_template .. id
- asyncshell.request_table[id] = {callback = callback}
- local req =
- string.format("bash -c '%s > %s; " ..
- 'echo "asyncshell.deliver(%s)" | ' ..
- "awesome-client' 2> /dev/null",
- string.gsub(command, "'", "'\\''"), tmpfname, id, tmpfname)
- awful.util.spawn(req, false)
- return id
-end
-
--- Calls the remembered callback function on the output of the shell
--- command.
--- @param id Request ID
--- @param output The output file of the shell command to be delievered
-function asyncshell.deliver(id)
- if asyncshell.request_table[id] and
- asyncshell.request_table[id].callback then
- local output = io.open(asyncshell.file_template .. id, 'r')
- asyncshell.request_table[id].callback(output)
- end
-end
-
--- Sends a synchronous request for an output of the command. Waits for
--- the output, but if the given timeout expires returns nil.
--- @param command Command to be executed and taken output from
--- @param timeout Maximum amount of time to wait for the result
--- @return File handler on success, nil otherwise
-function asyncshell.demand(command, timeout)
- local id = next_id()
- local tmpfname = asyncshell.file_template .. id
- local f = io.popen(string.format("(%s > %s; echo asyncshell_done) & " ..
- "(sleep %s; echo asynchell_timeout)",
- command, tmpfname, timeout))
- local result = f:read("*line")
- if result == "asyncshell_done" then
- return io.open(tmpfname)
- end
-end
-
-return asyncshell
+++ /dev/null
----------------------------------------------------------------------------
--- @author Alexander Yakushev <yakushev.alex@gmail.com>
--- @copyright 2010-2013 Alexander Yakushev
--- @release v1.2.4
----------------------------------------------------------------------------
-
-local wibox = require("wibox")
-local awful = require('awful')
-local beautiful = require('beautiful')
-local naughty = require('naughty')
-local format = string.format
-
-local module_path = (...):match ("(.+/)[^/]+$") or ""
-
-local awesompd = {}
-
--- Function for checking icons and modules. Checks if a file exists,
--- and if it does, returns the path to file, nil otherwise.
-function awesompd.try_load(file)
- if awful.util.file_readable(file) then
- return file
- end
-end
-
--- Function for loading modules.
-function awesompd.try_require(module)
- if awesompd.try_load(awful.util.getdir("config") .. '/'..
- module_path .. module .. ".lua") then
- return require(module_path .. module)
- else
- return require(module)
- end
-end
-
-local utf8 = awesompd.try_require("utf8")
-asyncshell = awesompd.try_require("asyncshell")
-local jamendo = awesompd.try_require("jamendo")
-
--- Constants
-awesompd.PLAYING = "Playing"
-awesompd.PAUSED = "Paused"
-awesompd.STOPPED = "MPD stopped"
-awesompd.DISCONNECTED = "Disconnected"
-
-awesompd.MOUSE_LEFT = 1
-awesompd.MOUSE_MIDDLE = 2
-awesompd.MOUSE_RIGHT = 3
-awesompd.MOUSE_SCROLL_UP = 4
-awesompd.MOUSE_SCROLL_DOWN = 5
-
-awesompd.NOTIFY_VOLUME = 1
-awesompd.NOTIFY_REPEAT = 2
-awesompd.NOTIFY_RANDOM = 3
-awesompd.NOTIFY_SINGLE = 4
-awesompd.NOTIFY_CONSUME = 5
-awesompd.FORMAT_MP3 = jamendo.FORMAT_MP3
-awesompd.FORMAT_OGG = jamendo.FORMAT_OGG
-awesompd.ESCAPE_SYMBOL_MAPPING = {}
-awesompd.ESCAPE_SYMBOL_MAPPING["&"] = "&"
--- Menus do not handle symbol escaping correctly, so they need their
--- own mapping.
-awesompd.ESCAPE_MENU_SYMBOL_MAPPING = {}
-awesompd.ESCAPE_MENU_SYMBOL_MAPPING["&"] = "'n'"
-
--- /// Current track variables and functions ///
-
--- Returns a string for the given track to be displayed in the widget
--- and notification.
-function awesompd.get_display_name(track)
- if track.display_name then
- return track.display_name
- elseif track.artist_name and track.track_name then
- return track.artist_name .. " - " .. track.name
- end
-end
-
--- Returns a track display name, album name (if exists) and album
--- release year (if exists).
-function awesompd.get_extended_info(track)
- local result = awesompd.get_display_name(track)
- if track.album_name then
- result = result .. "\n" .. track.album_name
- end
- if track.year then
- result = result .. "\n" .. track.year
- end
- return result
-end
-
--- Returns true if the current status is either PLAYING or PAUSED
-function awesompd:playing_or_paused()
- return self.status == awesompd.PLAYING
- or self.status == awesompd.PAUSED
-end
-
--- /// Helper functions ///
-
--- Just like awful.util.pread, but takes an argument how to read like
--- "*line" or "*all".
-function awesompd.pread(com, mode)
- local f = io.popen(com, 'r')
- local result = nil
- if f then
- result = f:read(mode)
- f:close()
- end
- return result
-end
-
--- Slightly modified function awful.util.table.join.
-function awesompd.ajoin(buttons)
- local result = {}
- for i = 1, #buttons do
- if buttons[i] then
- for k, v in pairs(buttons[i]) do
- if type(k) == "number" then
- table.insert(result, v)
- else
- result[k] = v
- end
- end
- end
- end
- return result
- end
-
--- Splits a given string with linebreaks into an array.
-function awesompd.split(s)
- local l = { n = 0 }
- if s == "" then
- return l
- end
- s = s .. "\n"
- local f = function (s)
- l.n = l.n + 1
- l[l.n] = s
- end
- local p = "%s*(.-)%s*\n%s*"
- s = string.gsub(s,p,f)
- return l
-end
-
--- Returns the given string if it is not nil or non-empty, otherwise
--- returns nil.
-local function non_empty(s)
- if s and s ~= "" then
- return s
- end
-end
-
--- Icons
-
-function awesompd.load_icons(path)
- awesompd.ICONS = {}
- awesompd.ICONS.PLAY = awesompd.try_load(path .. "/play_icon.png")
- awesompd.ICONS.PAUSE = awesompd.try_load(path .. "/pause_icon.png")
- awesompd.ICONS.PLAY_PAUSE = awesompd.try_load(path .. "/play_pause_icon.png")
- awesompd.ICONS.STOP = awesompd.try_load(path .. "/stop_icon.png")
- awesompd.ICONS.NEXT = awesompd.try_load(path .. "/next_icon.png")
- awesompd.ICONS.PREV = awesompd.try_load(path .. "/prev_icon.png")
- awesompd.ICONS.CHECK = awesompd.try_load(path .. "/check_icon.png")
- awesompd.ICONS.RADIO = awesompd.try_load(path .. "/radio_icon.png")
- awesompd.ICONS.DEFAULT_ALBUM_COVER =
- awesompd.try_load(path .. "/default_album_cover.png")
-end
-
--- Function that returns a new awesompd object.
-function awesompd:create()
--- Initialization
- local instance = {}
- setmetatable(instance,self)
- self.__index = self
- instance.current_server = 1
- instance.widget = wibox.layout.fixed.horizontal()
- instance.notification = nil
- instance.scroll_pos = 1
- instance.text = ""
- instance.to_notify = false
- instance.album_cover = nil
- instance.current_track = { }
- instance.recreate_menu = true
- instance.recreate_playback = true
- instance.recreate_list = true
- instance.recreate_servers = true
- instance.recreate_options = true
- instance.recreate_jamendo_formats = true
- instance.recreate_jamendo_order = true
- instance.recreate_jamendo_browse = true
- instance.current_number = 0
- instance.menu_shown = false
- instance.state_volume = "NaN"
- instance.state_repeat = "NaN"
- instance.state_random = "NaN"
- instance.state_single = "NaN"
- instance.state_consume = "NaN"
-
--- Default user options
- instance.servers = { { server = "localhost", port = 6600 } }
- instance.font = "Monospace"
- instance.font_color = beautiful.fg_normal
- instance.background = beautiful.bg_normal
- instance.scrolling = true
- instance.output_size = 30
- instance.update_interval = 10
- instance.path_to_icons = ""
- instance.ldecorator = " "
- instance.rdecorator = " "
- instance.jamendo_format = awesompd.FORMAT_MP3
- instance.show_album_cover = true
- instance.album_cover_size = 50
- instance.browser = "firefox"
-
--- Widget configuration
- instance.widget:connect_signal("mouse::enter", function(c)
- instance:notify_track()
- end)
- instance.widget:connect_signal("mouse::leave", function(c)
- instance:hide_notification()
- end)
- return instance
-end
-
--- Registers timers for the widget
-function awesompd:run()
- self.load_icons(self.path_to_icons)
- jamendo.set_current_format(self.jamendo_format)
- if self.album_cover_size > 100 then
- self.album_cover_size = 100
- end
-
- self.text_widget = wibox.widget.textbox()
- if self.widget_icon then
- self.icon_widget = wibox.widget.imagebox()
- self.icon_widget:set_image(self.widget_icon)
- self.widget:add(self.icon_widget)
- end
- self.widget:add(self.text_widget)
-
- self:update_track()
- self:check_playlists()
-
- if scheduler then
- scheduler.register_recurring("awesompd_scroll", 1,
- function() self:update_widget() end)
- scheduler.register_recurring("awesompd_update", self.update_interval,
- function() self:update_track() end)
- else
- self.update_widget_timer = timer({ timeout = 1 })
- self.update_widget_timer:connect_signal("timeout", function()
- self:update_widget()
- end)
- self.update_widget_timer:start()
- self.update_track_timer = timer({ timeout = self.update_interval })
- self.update_track_timer:connect_signal("timeout", function()
- self:update_track()
- end)
- self.update_track_timer:start()
- end
-end
-
--- Function that registers buttons on the widget.
-function awesompd:register_buttons(buttons)
- widget_buttons = {}
- self.global_bindings = {}
- for b=1, #buttons do
- if type(buttons[b][1]) == "string" then
- mods = { buttons[b][1] }
- else
- mods = buttons[b][1]
- end
- if type(buttons[b][2]) == "number" then
- -- This is a mousebinding, bind it to the widget
- table.insert(widget_buttons,
- awful.button(mods, buttons[b][2], buttons[b][3]))
- else
- -- This is a global keybinding, remember it for later usage in append_global_keys
- table.insert(self.global_bindings, awful.key(mods, buttons[b][2], buttons[b][3]))
- end
- end
- self.widget:buttons(self.ajoin(widget_buttons))
-end
-
--- Takes the current table with keybindings and adds widget's own
--- global keybindings that were specified in register_buttons.
--- If keytable is not specified, then adds bindings to default
--- globalkeys table. If specified, then adds bindings to keytable and
--- returns it.
-function awesompd:append_global_keys(keytable)
- if keytable then
- for i = 1, #self.global_bindings do
- keytable = awful.util.table.join(keytable, self.global_bindings[i])
- end
- return keytable
- else
- for i = 1, #self.global_bindings do
- globalkeys = awful.util.table.join(globalkeys, self.global_bindings[i])
- end
- end
-end
-
--- /// Group of mpc command functions ///
-
--- Returns a mpc command with all necessary parameters. Boolean
--- human_readable argument configures if the command special
--- formatting of the output (to be later used in parsing) should not
--- be used.
-function awesompd:mpcquery(human_readable)
- local result =
- "mpc -h " .. self.servers[self.current_server].server ..
- " -p " .. self.servers[self.current_server].port .. " "
- if human_readable then
- return result
- else
- return result ..' -f "%file%-<>-%name%-<>-%title%-<>-%artist%-<>-%album%" '
- end
-end
-
--- Takes a command to mpc and a hook that is provided with awesompd
--- instance and the result of command execution.
-function awesompd:command(com,hook)
- local file = io.popen(self:mpcquery() .. com)
- if hook then
- hook(self,file)
- end
- file:close()
-end
-
--- Takes a command to mpc and read mode and returns the result.
-function awesompd:command_read(com, mode)
- mode = mode or "*line"
- self:command(com, function(_, f)
- result = f:read(mode)
- end)
- return result
-end
-
-function awesompd:command_playpause()
- return function()
- self:command("toggle",self.update_track)
- end
-end
-
-function awesompd:command_next_track()
- return function()
- self:command("next",self.update_track)
- end
-end
-
-function awesompd:command_prev_track()
- return function()
- self:command("seek 0")
- self:command("prev",self.update_track)
- end
-end
-
-function awesompd:command_stop()
- return function()
- self:command("stop",self.update_track)
- end
-end
-
-function awesompd:command_play_specific(n)
- return function()
- self:command("play " .. n,self.update_track)
- end
-end
-
-function awesompd:command_volume_up()
- return function()
- self:command("volume +5")
- self:update_track() -- Nasty! I should replace it with proper callback later.
- self:notify_state(self.NOTIFY_VOLUME)
- end
-end
-
-function awesompd:command_volume_down()
- return function()
- self:command("volume -5")
- self:update_track()
- self:notify_state(self.NOTIFY_VOLUME)
- end
-end
-
-function awesompd:command_load_playlist(name)
- return function()
- self:command("load \"" .. name .. "\"", function()
- self.recreate_menu = true
- end)
- end
-end
-
-function awesompd:command_replace_playlist(name)
- return function()
- self:command("clear")
- self:command("load \"" .. name .. "\"")
- self:command("play 1", self.update_track)
- end
-end
-
-function awesompd:command_clear_playlist()
- return function()
- self:command("clear", self.update_track)
- self.recreate_list = true
- self.recreate_menu = true
- end
-end
-
-function awesompd:command_open_in_browser(link)
- return function()
- if self.browser then
- awful.util.spawn(self.browser .. " '" .. link .. "'")
- end
- end
-end
-
---- Change to the previous server.
-function awesompd:command_previous_server()
- return function()
- servers = table.getn(self.servers)
- if servers == 1 or servers == nil then
- return
- else
- if self.current_server > 1 then
- self:change_server(self.current_server - 1)
- else
- self:change_server(servers)
- end
- end
- end
-end
-
---- Change to the previous server.
-function awesompd:command_next_server()
- return function()
- servers = table.getn(self.servers)
- if servers == 1 or servers == nil then
- return
- else
- if self.current_server < servers then
- self:change_server(self.current_server + 1)
- else
- self:change_server(1)
- end
- end
- end
-end
-
--- /// End of mpc command functions ///
-
--- /// Menu generation functions ///
-
-function awesompd:command_show_menu()
- return
- function()
- self:hide_notification()
- if self.recreate_menu then
- local new_menu = {}
- if self.main_menu ~= nil then
- self.main_menu:hide()
- end
- if self.status ~= awesompd.DISCONNECTED
- then
- self:check_list()
- self:check_playlists()
- local jamendo_menu = { { "Search by",
- { { "Nothing (Top 100)", self:menu_jamendo_top() },
- { "Artist", self:menu_jamendo_search_by(jamendo.SEARCH_ARTIST) },
- { "Album", self:menu_jamendo_search_by(jamendo.SEARCH_ALBUM) },
- { "Tag", self:menu_jamendo_search_by(jamendo.SEARCH_TAG) }}} }
- local browse_menu = self:menu_jamendo_browse()
- if browse_menu then
- table.insert(jamendo_menu, browse_menu)
- end
- table.insert(jamendo_menu, self:menu_jamendo_format())
- table.insert(jamendo_menu, self:menu_jamendo_order())
-
- new_menu = { { "Playback", self:menu_playback() },
- { "Options", self:menu_options() },
- { "List", self:menu_list() },
- { "Playlists", self:menu_playlists() },
- { "Jamendo", jamendo_menu } }
- end
- table.insert(new_menu, { "Servers", self:menu_servers() })
- self.main_menu = awful.menu({ items = new_menu, theme = { width = 300 } })
- self.recreate_menu = false
- end
- self.main_menu:toggle()
- end
-end
-
--- Returns an icon for a checkbox menu item if it is checked, nil
--- otherwise.
-function awesompd:menu_item_toggle(checked)
- return checked and self.ICONS.CHECK or nil
-end
-
--- Returns an icon for a radiobox menu item if it is selected, nil
--- otherwise.
-function awesompd:menu_item_radio(selected)
- return selected and self.ICONS.RADIO or nil
-end
-
--- Returns the playback menu. Menu contains of:
--- Play\Pause - always
--- Previous - if the current track is not the first
--- in the list and playback is not stopped
--- Next - if the current track is not the last
--- in the list and playback is not stopped
--- Stop - if the playback is not stopped
--- Clear playlist - always
-function awesompd:menu_playback()
- if self.recreate_playback then
- local new_menu = {}
- table.insert(new_menu, { "Play\\Pause",
- self:command_toggle(),
- self.ICONS.PLAY_PAUSE })
- if self:playing_or_paused() then
- if self.list_array and self.list_array[self.current_number-1] then
- table.insert(new_menu,
- { "Prev: " ..
- awesompd.protect_string(jamendo.replace_link(
- self.list_array[self.current_number - 1]),
- true),
- self:command_prev_track(), self.ICONS.PREV })
- end
- if self.list_array and self.current_number ~= #self.list_array then
- table.insert(new_menu,
- { "Next: " ..
- awesompd.protect_string(jamendo.replace_link(
- self.list_array[self.current_number + 1]),
- true),
- self:command_next_track(), self.ICONS.NEXT })
- end
- table.insert(new_menu, { "Stop", self:command_stop(), self.ICONS.STOP })
- table.insert(new_menu, { "", nil })
- end
- table.insert(new_menu, { "Clear playlist", self:command_clear_playlist() })
- self.recreate_playback = false
- playback_menu = new_menu
- end
- return playback_menu
-end
-
--- Returns the current playlist menu. Menu consists of all elements in the playlist.
-function awesompd:menu_list()
- if self.recreate_list then
- local new_menu = {}
- if self.list_array then
- local total_count = #self.list_array
- local start_num = (self.current_number - 15 > 0) and self.current_number - 15 or 1
- local end_num = (self.current_number + 15 < total_count ) and self.current_number + 15 or total_count
- for i = start_num, end_num do
- table.insert(new_menu, { jamendo.replace_link(self.list_array[i]),
- self:command_play_specific(i),
- self.current_number == i and
- (self.status == self.PLAYING and self.ICONS.PLAY or self.ICONS.PAUSE)
- or nil} )
- end
- end
- self.recreate_list = false
- self.list_menu = new_menu
- end
- return self.list_menu
-end
-
--- Returns the playlists menu. Menu consists of all files in the playlist folder.
-function awesompd:menu_playlists()
- if self.recreate_playlists then
- local new_menu = {}
- if #self.playlists_array > 0 then
- for i = 1, #self.playlists_array do
- local submenu = {}
- submenu[1] = { "Add to current", self:command_load_playlist(self.playlists_array[i]) }
- submenu[2] = { "Replace current", self:command_replace_playlist(self.playlists_array[i]) }
- new_menu[i] = { self.playlists_array[i], submenu }
- end
- table.insert(new_menu, {"", ""}) -- This is a separator
- end
- table.insert(new_menu, { "Refresh", function() self:check_playlists() end })
- self.recreate_playlists = false
- self.playlists_menu = new_menu
- end
- return self.playlists_menu
-end
-
--- Returns the server menu. Menu consists of all servers specified by user during initialization.
-function awesompd:menu_servers()
- if self.recreate_servers then
- local new_menu = {}
- for i = 1, #self.servers do
- table.insert(new_menu, {"Server: " .. self.servers[i].server ..
- ", port: " .. self.servers[i].port,
- function() self:change_server(i) end,
- self:menu_item_radio(i == self.current_server)})
- end
- self.servers_menu = new_menu
- end
- return self.servers_menu
-end
-
--- Returns the options menu. Menu works like checkboxes for it's elements.
-function awesompd:menu_options()
- if self.recreate_options then
- local new_menu = { { "Repeat", self:menu_toggle_repeat(),
- self:menu_item_toggle(self.state_repeat == "on")},
- { "Random", self:menu_toggle_random(),
- self:menu_item_toggle(self.state_random == "on")},
- { "Single", self:menu_toggle_single(),
- self:menu_item_toggle(self.state_single == "on")},
- { "Consume", self:menu_toggle_consume(),
- self:menu_item_toggle(self.state_consume == "on")} }
- self.options_menu = new_menu
- self.recreate_options = false
- end
- return self.options_menu
-end
-
-function awesompd:menu_toggle_random()
- return function()
- self:command("random",self.update_track)
- self:notify_state(self.NOTIFY_RANDOM)
- end
-end
-
-function awesompd:menu_toggle_repeat()
- return function()
- self:command("repeat",self.update_track)
- self:notify_state(self.NOTIFY_REPEAT)
- end
-end
-
-function awesompd:menu_toggle_single()
- return function()
- self:command("single",self.update_track)
- self:notify_state(self.NOTIFY_SINGLE)
- end
-end
-
-function awesompd:menu_toggle_consume()
- return function()
- self:command("consume",self.update_track)
- self:notify_state(self.NOTIFY_CONSUME)
- end
-end
-
-function awesompd:menu_jamendo_top()
- return
- function ()
- local track_table = jamendo.return_track_table()
- if not track_table then
- self:show_notification("Can't connect to Jamendo server", "Please check your network connection")
- else
- self:add_jamendo_tracks(track_table)
- self:show_notification("Jamendo Top 100 by " ..
- jamendo.current_request_table.params.order.short_display,
- format("Added %s tracks to the playlist",
- #track_table))
- end
- end
-end
-
-function awesompd:menu_jamendo_format()
- if self.recreate_jamendo_formats then
- local setformat =
- function(format)
- return function()
- jamendo.set_current_format(format)
- self.recreate_menu = true
- self.recreate_jamendo_formats = true
- end
- end
-
- local iscurr =
- function(f)
- return jamendo.current_request_table.params.streamencoding.value
- == f.value
- end
-
- local new_menu = {}
- for _, format in pairs(jamendo.ALL_FORMATS) do
- table.insert(new_menu, { format.display, setformat(format),
- self:menu_item_radio(iscurr(format))})
- end
- self.recreate_jamendo_formats = false
- self.jamendo_formats_menu = {
- "Format: " ..
- jamendo.current_request_table.params.streamencoding.short_display,
- new_menu }
- end
- return self.jamendo_formats_menu
-end
-
-function awesompd:menu_jamendo_browse()
- if self.recreate_jamendo_browse and self.browser
- and self.current_track.unique_name then
- local track = jamendo.get_track_by_link(self.current_track.unique_name)
- local new_menu
- if track then
- local artist_link =
- "http://www.jamendo.com/artist/" .. track.artist_link_name
- local album_link =
- "http://www.jamendo.com/album/" .. track.album_id
- new_menu = { { "Artist's page" ,
- self:command_open_in_browser(artist_link) },
- { "Album's page" ,
- self:command_open_in_browser(album_link) } }
- self.jamendo_browse_menu = { "Browse on Jamendo", new_menu }
- else
- self.jamendo_browse_menu = nil
- end
- end
- return self.jamendo_browse_menu
-end
-
-function awesompd:menu_jamendo_order()
- if self.recreate_jamendo_order then
- local setorder =
- function(order)
- return function()
- jamendo.set_current_order(order)
- self.recreate_menu = true
- self.recreate_jamendo_order = true
- end
- end
-
- local iscurr =
- function(o)
- return jamendo.current_request_table.params.order.value
- == o.value
- end
-
- local new_menu = {}
- for _, order in pairs(jamendo.ALL_ORDERS) do
- table.insert(new_menu, { order.display, setorder(order),
- self:menu_item_radio(iscurr(order))})
- end
- self.recreate_jamendo_order = false
- self.jamendo_order_menu = {
- "Order: " ..
- jamendo.current_request_table.params.order.short_display,
- new_menu }
- end
- return self.jamendo_order_menu
-end
-
-function awesompd:menu_jamendo_search_by(what)
- return function()
- local callback =
- function(s)
- local result = jamendo.search_by(what, s)
- if result then
- local track_count = #result.tracks
- self:add_jamendo_tracks(result.tracks)
- self:show_notification(format("%s \"%s\" was found",
- what.display,
- result.search_res.name),
- format("Added %s tracks to the playlist",
- track_count))
- else
- self:show_notification("Search failed",
- format("%s \"%s\" was not found",
- what.display, s))
- end
- end
- self:display_inputbox("Search music on Jamendo",
- what.display, callback)
- end
-end
-
--- Checks if the current playlist has changed after the last check.
-function awesompd:check_list()
- local bus = io.popen(self:mpcquery(true) .. "playlist")
- local info = bus:read("*all")
- bus:close()
- if info ~= self.list_line then
- self.list_line = info
- if string.len(info) > 0 then
- self.list_array = self.split(string.sub(info,1,string.len(info)))
- else
- self.list_array = {}
- end
- self.recreate_menu = true
- self.recreate_list = true
- end
-end
-
--- Checks if the collection of playlists changed after the last check.
-function awesompd:check_playlists()
- local bus = io.popen(self:mpcquery(true) .. "lsplaylists")
- local info = bus:read("*all")
- bus:close()
- if info ~= self.playlists_line then
- self.playlists_line = info
- if string.len(info) > 0 then
- self.playlists_array = self.split(info)
- else
- self.playlists_array = {}
- end
- self.recreate_menu = true
- self.recreate_playlists = true
- end
-end
-
--- Changes the current server to the specified one.
-function awesompd:change_server(server_number)
- self.current_server = server_number
- self:hide_notification()
- self.recreate_menu = true
- self.recreate_playback = true
- self.recreate_list = true
- self.recreate_playlists = true
- self.recreate_servers = true
- self:update_track()
-end
-
-function awesompd:add_jamendo_tracks(track_table)
- for i = 1, #track_table do
- self:command("add '" .. string.gsub(track_table[i].stream, '\\/', '/') .. "'")
- end
- self.recreate_menu = true
- self.recreate_list = true
-end
-
--- /// End of menu generation functions ///
-
-function awesompd:show_notification(hint_title, hint_text, hint_image)
- self:hide_notification()
- self.notification = naughty.notify({ title = hint_title
- , text = awesompd.protect_string(hint_text)
- , timeout = 5
- , position = "top_right"
- , icon = hint_image
- , icon_size = self.album_cover_size
- })
-end
-
-function awesompd:hide_notification()
- if self.notification ~= nil then
- naughty.destroy(self.notification)
- self.notification = nil
- end
-end
-
-function awesompd:notify_track()
- if self:playing_or_paused() then
- local caption = self.status_text
- local nf_text = self.get_display_name(self.current_track)
- local al_cover = nil
- if self.show_album_cover then
- nf_text = self.get_extended_info(self.current_track)
- al_cover = self.current_track.album_cover
- end
- self:show_notification(caption, nf_text, al_cover)
- end
-end
-
-function awesompd:notify_state(state_changed)
- state_array = { "Volume: " .. self.state_volume ,
- "Repeat: " .. self.state_repeat ,
- "Random: " .. self.state_random ,
- "Single: " .. self.state_single ,
- "Consume: " .. self.state_consume }
- state_header = state_array[state_changed]
- table.remove(state_array,state_changed)
- full_state = state_array[1]
- for i = 2, #state_array do
- full_state = full_state .. "\n" .. state_array[i]
- end
- self:show_notification(state_header, full_state)
-end
-
-function awesompd:wrap_output(text)
- return format('<span font="%s" color="%s" background="%s">%s%s%s</span>',
- self.font, self.font_color, self.background,
- (text == "" and "" or self.ldecorator), awesompd.protect_string(text),
- (text == "" and "" or self.rdecorator))
-end
-
--- This function actually sets the text on the widget.
-function awesompd:set_text(text)
- self.text_widget:set_markup(self:wrap_output(text))
-end
-
-function awesompd.find_pattern(text, pattern, start)
- return utf8.sub(text, string.find(text, pattern, start))
-end
-
--- Scroll the given text by the current number of symbols.
-function awesompd:scroll_text(text)
- local result = text
- if self.scrolling then
- if self.output_size < utf8.len(text) then
- text = text .. " - "
- if self.scroll_pos + self.output_size - 1 > utf8.len(text) then
- result = utf8.sub(text, self.scroll_pos)
- result = result .. utf8.sub(text, 1, self.scroll_pos + self.output_size - 1 - utf8.len(text))
- self.scroll_pos = self.scroll_pos + 1
- if self.scroll_pos > utf8.len(text) then
- self.scroll_pos = 1
- end
- else
- result = utf8.sub(text, self.scroll_pos, self.scroll_pos + self.output_size - 1)
- self.scroll_pos = self.scroll_pos + 1
- end
- end
- end
- return result
-end
-
--- This function is called every second.
-function awesompd:update_widget()
- self:set_text(self:scroll_text(self.text))
- self:check_notify()
-end
-
--- This function is called by update_track each time content of
--- the widget must be changed.
-function awesompd:update_widget_text()
- if self:playing_or_paused() then
- self.text = self.get_display_name(self.current_track)
- else
- self.text = self.status
- end
-end
-
--- Checks if notification should be shown and shows if positive.
-function awesompd:check_notify()
- if self.to_notify then
- self:notify_track()
- self.to_notify = false
- end
-end
-
-function awesompd:notify_connect()
- self:show_notification("Connected", "Connection established to " .. self.servers[self.current_server].server ..
- " on port " .. self.servers[self.current_server].port)
-end
-
-function awesompd:notify_disconnect()
- self:show_notification("Disconnected", "Cannot connect to " .. self.servers[self.current_server].server ..
- " on port " .. self.servers[self.current_server].port)
-end
-
-function awesompd:update_track(file)
- local file_exists = (file ~= nil)
- if not file_exists then
- file = io.popen(self:mpcquery())
- end
- local track_line = file:read("*line")
- local status_line = file:read("*line")
- local options_line = file:read("*line")
- if not file_exists then
- file:close()
- end
-
- if not track_line or string.len(track_line) == 0 then
- if self.status ~= awesompd.DISCONNECTED then
- self:notify_disconnect()
- self.recreate_menu = true
- self.status = awesompd.DISCONNECTED
- self.current_track = { }
- self:update_widget_text()
- end
- else
- if self.status == awesompd.DISCONNECTED then
- self:notify_connect()
- self.recreate_menu = true
- self:update_widget_text()
- end
- if string.find(track_line,"volume:") or string.find(track_line,"Updating DB") then
- if self.status ~= awesompd.STOPPED then
- self.status = awesompd.STOPPED
- self.current_number = 0
- self.recreate_menu = true
- self.recreate_playback = true
- self.recreate_list = true
- self.album_cover = nil
- self.current_track = { }
- self:update_widget_text()
- end
- self:update_state(track_line)
- else
- self:update_state(options_line)
- local _, _, new_file, station, title, artist, album =
- string.find(track_line, "(.*)%-<>%-(.*)%-<>%-(.*)%-<>%-(.*)%-<>%-(.*)")
- local display_name, force_update = artist .. " - " .. title, false
- -- The following code checks if the current track is an
- -- Internet link. Internet radios change tracks, but the
- -- current file stays the same, so we should manually compare
- -- its title.
- if string.match(new_file, "http://") and
- -- The following line is awful. This needs to be replaced ASAP.
- not string.match(new_file, "http://storage%-new%.newjamendo%.com") then
- album = non_empty(station) or ""
- display_name = non_empty(title) or new_file
- if display_name ~= self.current_track.display_name then
- force_update = true
- end
- end
- if new_file ~= self.current_track.unique_name or force_update then
- self.current_track = jamendo.get_track_by_link(new_file)
- if not self.current_track then
- self.current_track = { display_name = display_name,
- album_name = album }
- end
- self.current_track.unique_name = new_file
- if self.show_album_cover then
- self.current_track.album_cover = self:get_cover(new_file)
- end
- self.to_notify = true
- self.recreate_menu = true
- self.recreate_playback = true
- self.recreate_list = true
- self.current_number = tonumber(self.find_pattern(status_line,"%d+"))
- self:update_widget_text()
-
- -- If the track is not the last, asynchronously download
- -- the cover for the next track.
- if self.list_array and self.current_number ~= #self.list_array then
- -- Get the link (in case it is Jamendo stream) to the next track
- local next_track =
- self:command_read('playlist -f "%file%" | head -' ..
- self.current_number + 1 .. ' | tail -1', "*line")
- jamendo.try_get_cover_async(next_track)
- end
- end
- local tmp_pst = string.find(status_line,"%d+%:%d+%/")
- local progress = self.find_pattern(status_line,"%#%d+/%d+") .. " " .. string.sub(status_line,tmp_pst)
- local new_status = awesompd.PLAYING
- if string.find(status_line,"paused") then
- new_status = awesompd.PAUSED
- end
- if new_status ~= self.status then
- self.to_notify = true
- self.recreate_list = true
- self.status = new_status
- self:update_widget_text()
- end
- self.status_text = self.status .. " " .. progress
- end
- end
-end
-
-function awesompd:update_state(state_string)
- self.state_volume = self.find_pattern(state_string,"%d+%% ")
- if string.find(state_string,"repeat: on") then
- self.state_repeat = self:check_set_state(self.state_repeat, "on")
- else
- self.state_repeat = self:check_set_state(self.state_repeat, "off")
- end
- if string.find(state_string,"random: on") then
- self.state_random = self:check_set_state(self.state_random, "on")
- else
- self.state_random = self:check_set_state(self.state_random, "off")
- end
- if string.find(state_string,"single: on") then
- self.state_single = self:check_set_state(self.state_single, "on")
- else
- self.state_single = self:check_set_state(self.state_single, "off")
- end
- if string.find(state_string,"consume: on") then
- self.state_consume = self:check_set_state(self.state_consume, "on")
- else
- self.state_consume = self:check_set_state(self.state_consume, "off")
- end
-end
-
-function awesompd:check_set_state(statevar, val)
- if statevar ~= val then
- self.recreate_menu = true
- self.recreate_options = true
- end
- return val
-end
-
-function awesompd:run_prompt(welcome,hook)
- awful.prompt.run({ prompt = welcome },
- self.promptbox[mouse.screen].widget,
- hook)
-end
-
--- Replaces control characters with escaped ones.
--- for_menu - defines if the special escable table for menus should be
--- used.
-function awesompd.protect_string(str, for_menu)
- if for_menu then
- return utf8.replace(str, awesompd.ESCAPE_MENU_SYMBOL_MAPPING)
- else
- return utf8.replace(str, awesompd.ESCAPE_SYMBOL_MAPPING)
- end
-end
-
--- Initialize the inputbox.
-function awesompd:init_inputbox()
- local width = 200
- local height = 30
- local border_color = beautiful.bg_focus or '#535d6c'
- local margin = 4
- local wbox = wibox({ name = "awmpd_ibox", height = height , width = width,
- border_color = border_color, border_width = 1 })
- local ws = screen[mouse.screen].workarea
-
- wbox.screen = mouse.screen
- wbox.ontop = true
-
- local wprompt = awful.widget.prompt()
- local wtbox = wibox.widget.textbox()
- local wtmarginbox = wibox.layout.margin(wtbox, margin)
- local tw, th = wtbox:fit(-1, -1)
- wbox:geometry({ x = ws.width - width - 5, y = 25,
- width = 200, height = th * 2 + margin})
- local layout = wibox.layout.flex.vertical()
- layout:add(wtmarginbox)
- layout:add(wprompt)
- wbox:set_widget(layout)
- self.inputbox = { wibox = wbox,
- title = wtbox,
- prompt = wprompt }
-end
-
--- Displays an inputbox on the screen (looks like naughty with prompt).
--- title_text - bold text on the first line
--- prompt_text - preceding text on the second line
--- hook - function that will be called with input data
--- Use it like this:
--- self:display_inputbox("Search music on Jamendo", "Artist", print)
-function awesompd:display_inputbox(title_text, prompt_text, hook)
- if not self.inputbox then
- self:init_inputbox()
- end
- if self.inputbox.wibox.visible then -- Inputbox already exists, replace it
- keygrabber.stop()
- end
-
- local exe_callback = function(s)
- hook(s)
- self.inputbox.wibox.visible = false
- end
- local done_callback = function()
- self.inputbox.wibox.visible = false
- end
- self.inputbox.title:set_markup("<b>" .. title_text .. "</b>")
- awful.prompt.run( { prompt = " " .. prompt_text .. ": ", bg_cursor = "#222222" },
- self.inputbox.prompt.widget,
- exe_callback, nil, nil, nil, done_callback, nil, nil)
- self.inputbox.wibox.visible = true
-end
-
--- Gets the cover for the given track. First looks in the Jamendo
--- cache. If the track is not a Jamendo stream, looks in local
--- folders. If there is no cover art either returns the default album
--- cover.
-function awesompd:get_cover(track)
- return jamendo.try_get_cover(track) or
- self:try_get_local_cover(track) or self.ICONS.DEFAULT_ALBUM_COVER
-end
-
--- Tries to find an album cover for the track that is currently
--- playing.
-function awesompd:try_get_local_cover(current_file)
- if self.mpd_config then
- local result
- -- First find the music directory in MPD configuration file
- local _, _, music_folder = string.find(
- self.pread('cat ' .. self.mpd_config .. ' | grep -v "#" | grep music_directory', "*line"),
- 'music_directory%s+"(.+)"')
- music_folder = music_folder .. "/"
-
- -- If the music_folder is specified with ~ at the beginning,
- -- replace it with user home directory
- if string.sub(music_folder, 1, 1) == "~" then
- local user_folder = self.pread("echo ~", "*line")
- music_folder = user_folder .. string.sub(music_folder, 2)
- end
-
- -- Get the path to the file currently playing.
- local _, _, current_file_folder = string.find(current_file, '(.+%/).*')
-
- -- Check if the current file is not some kind of http stream or
- -- Spotify track (like spotify:track:5r65GeuIoebfJB5sLcuPoC)
- if not current_file_folder or string.match(current_file, "%w+://") then
- return -- Let the default image to be the cover
- end
-
- local folder = music_folder .. current_file_folder
-
- -- Get all images in the folder. Also escape occasional single
- -- quotes in folder name.
- local request = format("ls '%s' | grep -P '\\.jpg|\\.png|\\.gif|\\.jpeg'",
- string.gsub(folder, "'", "'\\''"))
-
- local covers = self.pread(request, "*all")
- local covers_table = self.split(covers)
-
- if covers_table.n > 0 then
- result = folder .. covers_table[1]
- if covers_table.n > 1 then
- -- Searching for front cover with grep because Lua regular
- -- expressions suck:[
- local front_cover =
- self.pread('echo "' .. covers ..
- '" | grep -P -i "cover|front|folder|albumart" | head -n 1', "*line")
- if front_cover then
- result = folder .. front_cover
- end
- end
- end
- return result
- end
-end
-
--- /// Deprecated, left for some backward compatibility in
--- configuration ///
-
-function awesompd:command_toggle()
- return self:command_playpause()
-end
-
-return awesompd
+++ /dev/null
----------------------------------------------------------------------------
--- @author Alexander Yakushev <yakushev.alex@gmail.com>
--- @copyright 2011-2013 Alexander Yakushev
--- @release v1.2.4
----------------------------------------------------------------------------
-
--- Grab environment
-local awful = require('awful')
-
-local jamendo = {}
-
--- UTILITY STUFF
--- Checks whether file specified by filename exists.
-local function file_exists(filename, mode)
- mode = mode or 'r'
- f = io.open(filename, mode)
- if f then
- f:close()
- return true
- else
- return false
- end
-end
-
-local function str_interpose(coll, sep)
- if #coll == 0 then
- return ""
- end
- local result = coll[1]
- for i = 2, #coll do
- result = result .. sep .. coll[i]
- end
- print(result)
- return result
-end
-
--- Global variables
-jamendo.FORMAT_MP3 = { display = "MP3 (128k)",
- short_display = "MP3",
- value = "mp31" }
-jamendo.FORMAT_OGG = { display = "Ogg Vorbis (q4)",
- short_display = "Ogg",
- value = "ogg2" }
-jamendo.ORDER_RATINGDAILY = { display = "Daily rating",
- short_display = "daily rating",
- value = "ratingday_desc" }
-jamendo.ORDER_RATINGWEEKLY = { display = "Weekly rating",
- short_display = "weekly rating",
- value = "ratingweek_desc" }
-jamendo.ORDER_RATINGTOTAL = { display = "All time rating",
- short_display = "all time rating",
- value = "ratingtotal_desc" }
-jamendo.ORDER_RANDOM = { display = "Random",
- short_display = "random",
- value = "random_desc" }
-jamendo.ORDER_RELEVANCE = { display = "None (consecutive)",
- short_display = "none",
- value = "searchweight_desc" }
-jamendo.SEARCH_ARTIST = { display = "Artist",
- unit = "artist",
- value = "artist_id" }
-jamendo.SEARCH_ALBUM = { display = "Album",
- unit = "album",
- value = "album_id" }
-jamendo.SEARCH_TAG = { display = "Tag",
- unit = "tag",
- value = "tag_id" }
-jamendo.ALL_FORMATS = { jamendo.FORMAT_MP3, jamendo.FORMAT_OGG }
-jamendo.ALL_ORDERS = { ORDER_RELEVANCE, ORDER_RANDOM, ORDER_RATINGDAILY,
- ORDER_RATINGWEEKLY, ORDER_RATINGTOTAL }
-
-jamendo.current_request_table = { unit = "track",
- fields = {"id", "artist_url", "artist_name", "name",
- "stream", "album_image", "album_name" },
- joins = { "track_album", "album_artist" },
- params = { streamencoding = jamendo.FORMAT_MP3,
- order = jamendo.ORDER_RATINGWEEKLY,
- n = 100 }}
-
--- Local variables
-local jamendo_list = {}
-local cache_file = awful.util.getdir ("cache").."/jamendo_cache"
-local cache_header = "[version=1.1.0]"
-local album_covers_folder = awful.util.getdir("cache") .. "/jamendo_covers/"
-local default_mp3_stream = nil
-local search_template = { fields = { "id", "name" },
- joins = {},
- params = { order = ORDER_RELEVANCE,
- n = 1}}
-
--- DEPRECATED. Will be removed in the next major release.
--- Returns default stream number for MP3 format. Requests API for it
--- not more often than every hour.
-local function get_default_mp3_stream()
- if not default_mp3_stream or
- (os.time() - default_mp3_stream.last_checked) > 3600 then
- local trygetlink =
- jamendo.perform_request("echo $(curl -w %{redirect_url} " ..
- "'http://api.jamendo.com/get2/stream/track/redirect/" ..
- "?streamencoding="..jamendo.FORMAT_MP3.value.."&id=729304')")
- local _, _, prefix = string.find(trygetlink, "stream(%d+)%.jamendo%.com")
- default_mp3_stream = { id = prefix, last_checked = os.time() }
- end
- return default_mp3_stream.id
-end
-
--- Returns the track ID from the given link to Jamendo stream. If the
--- given text is not the Jamendo stream returns nil.
-function jamendo.get_id_from_link(link)
- local _, _, id = string.find(link,"storage%-new.newjamendo.com/%?trackid=(%d+)")
- return id
-end
-
--- Returns link to music stream for the given track ID. Uses MP3
--- format and the default stream for it.
-local function get_link_by_id(id)
- -- This function is subject to change in the future.
- return string.format("http://storage-new.newjamendo.com?trackid=%s&format=mp31&u=0", id)
-end
-
--- -- Returns the album id for given music stream.
--- function get_album_id_by_link(link)
--- local id = get_id_from_link(link, true)
--- if id and jamendo_list[id] then
--- return jamendo_list[id].album_id
--- end
--- end
-
--- Returns the track table for the given music stream.
-function jamendo.get_track_by_link(link)
- local id = jamendo.get_id_from_link(link, true)
- if id and jamendo_list[id] then
- return jamendo_list[id]
- end
-end
-
--- If a track is actually a Jamendo stream, replace it with normal
--- track name.
-function jamendo.replace_link(track_name)
- local track = jamendo.get_track_by_link(track_name)
- if track then
- return track.display_name
- else
- return track_name
- end
-end
-
--- Returns table of track IDs, names and other things based on the
--- request table.
-function jamendo.return_track_table(request_table)
- local req_string = jamendo.form_request(request_table)
- local response = jamendo.perform_request(req_string)
- if not response then
- return nil -- Bad internet connection
- end
- local parse_table = jamendo.parse_json(response)
- for i = 1, #parse_table do
- if parse_table[i].stream == "" then
- -- Some songs don't have Ogg stream, use MP3 instead
- parse_table[i].stream = get_link_by_id(parse_table[i].id)
- end
- _, _, parse_table[i].artist_link_name =
- string.find(parse_table[i].artist_url, "\\/artist\\/(.+)")
- -- Remove Jamendo escape slashes
- parse_table[i].artist_name =
- string.gsub(parse_table[i].artist_name, "\\/", "/")
- parse_table[i].name = string.gsub(parse_table[i].name, "\\/", "/")
-
- parse_table[i].display_name =
- parse_table[i].artist_name .. " - " .. parse_table[i].name
- -- Do Jamendo a favor, extract album_id for the track yourself
- -- from album_image link :)
- local _, _, album_id =
- string.find(parse_table[i].album_image, "\\/(%d+)\\/covers")
- parse_table[i].album_id = album_id or 0
- -- Save fetched tracks for further caching
- jamendo_list[parse_table[i].id] = parse_table[i]
- end
- jamendo.save_cache()
- return parse_table
-end
-
--- Generates the request to Jamendo API based on provided request
--- table. If request_table is nil, uses current_request_table instead.
--- For all values that do not exist in request_table use ones from
--- current_request_table.
--- return - HTTP-request
-function jamendo.form_request(request_table)
- local curl_str = "curl -A 'Mozilla/4.0' -fsm 5 \"%s\""
- local url = "http://api.jamendo.com/get2/%s/%s/json/%s/?%s"
- request_table = request_table or jamendo.current_request_table
-
- local fields = request_table.fields or jamendo.current_request_table.fields
- local joins = request_table.joins or jamendo.current_request_table.joins
- local unit = request_table.unit or jamendo.current_request_table.unit
-
- -- Form fields string (like field1+field2+fieldN)
- local f_string = str_interpose(fields, "+")
- -- Form joins string
- local j_string = str_interpose(joins, "+")
-
- local params = {}
- -- If parameters where supplied in request_table, add them to the
- -- parameters in current_request_table.
- if request_table.params and
- request_table.params ~= jamendo.current_request_table.params then
- -- First fill params with current_request_table parameters
- for k, v in pairs(jamendo.current_request_table.params) do
- params[k] = v
- end
- -- Then add and overwrite them with request_table parameters
- for k, v in pairs(request_table.params) do
- params[k] = v
- end
- else -- Or just use current_request_table.params
- params = jamendo.current_request_table.params
- end
- -- Form parameter string (like param1=value1¶m2=value2)
- local param_string = ""
- for k, v in pairs(params) do
- if type(v) == "table" then
- v = v.value
- end
- v = string.gsub(v, " ", "+")
- param_string = param_string .. "&" .. k .. "=" .. v
- end
-
- return string.format(curl_str, string.format(url, f_string, unit, j_string, param_string))
-end
-
--- Primitive function for parsing Jamendo API JSON response. Does not
--- support arrays. Supports only strings and numbers as values.
--- Provides basic safety (correctly handles special symbols like comma
--- and curly brackets inside strings)
--- text - JSON text
-function jamendo.parse_json(text)
- local parse_table = {}
- local block = {}
- local i = 0
- local inblock = false
- local instring = false
- local curr_key = nil
- local curr_val = nil
- while i and i < string.len(text) do
- if not inblock then -- We are not inside the block, find next {
- i = string.find(text, "{", i+1)
- inblock = true
- block = {}
- else
- if not curr_key then -- We haven't found key yet
- if not instring then -- We are not in string, check for more tags
- local j = string.find(text, '"', i+1)
- local k = string.find(text, '}', i+1)
- if j and j < k then -- There are more tags in this block
- i = j
- instring = true
- else -- Block is over, we found its ending
- i = k
- inblock = false
- table.insert(parse_table, block)
- end
- else -- We are in string, find its ending
- _, i, curr_key = string.find(text,'(.-[^%\\])"', i+1)
- instring = false
- end
- else -- We have the key, let's find the value
- if not curr_val then -- Value is not found yet
- if not instring then -- Not in string, check if value is string
- local j = string.find(text, '"', i+1)
- local k = string.find(text, '[,}]', i+1)
- if j and j < k then -- Value is string
- i = j
- instring = true
- else -- Value is int
- _, i, curr_val = string.find(text,'(%d+)', i+1)
- end
- else -- We are in string, find its ending
- local j = string.find(text, '"', i+1)
- if j == i+1 then -- String is empty
- i = j
- curr_val = ""
- else
- _, i, curr_val = string.find(text,'(.-[^%\\])"', i+1)
- curr_val = jamendo.utf8_codes_to_symbols(curr_val)
- end
- instring = false
- end
- else -- We have both key and value, add it to table
- block[curr_key] = curr_val
- curr_key = nil
- curr_val = nil
- end
- end
- end
- end
- return parse_table
-end
-
--- Jamendo returns Unicode symbols as \uXXXX. Lua does not transform
--- them into symbols so we need to do it ourselves.
-function jamendo.utf8_codes_to_symbols (s)
- local hexnums = "[%dabcdefABCDEF]"
- local pattern = string.format("\\u(%s%s%s%s?)",
- hexnums, hexnums, hexnums, hexnums)
- local decode = function(code)
- code = tonumber(code, 16)
- if code < 128 then -- one-byte symbol
- return string.char(code)
- elseif code < 2048 then -- two-byte symbol
- -- Grab high and low bytes
- local hi = math.floor(code / 64)
- local lo = math.fmod(code, 64)
- -- Return symbol as \hi\lo
- return string.char(hi + 192, lo + 128)
- elseif code < 65536 then
- -- Grab high, middle and low bytes
- local hi = math.floor(code / 4096)
- local leftover = code - hi * 4096
- local mi = math.floor(leftover / 64)
- leftover = leftover - mi * 64
- local lo = math.fmod(leftover, 64)
- -- Return symbol as \hi\mi\lo
- return string.char(hi + 224, mi + 160, lo + 128)
- elseif code < 1114112 then
- -- Grab high, highmiddle, lowmiddle and low bytes
- local hi = math.floor(code / 262144)
- local leftover = code - hi * 262144
- local hm = math.floor(leftover / 4096)
- leftover = leftover - hm * 4096
- local lm = math.floor(leftover / 64)
- local lo = math.fmod(leftover, 64)
- -- Return symbol as \hi\hm\lm\lo
- return string.char(hi + 240, hm + 128, lm + 128, lo + 128)
- else -- It is not Unicode symbol at all
- return tostring(code)
- end
- end
- return string.gsub(s, pattern, decode)
-end
-
--- Retrieves mapping of track IDs to track names and album IDs to
--- avoid redundant queries when Awesome gets restarted.
-local function retrieve_cache()
- local bus = io.open(cache_file)
- local track = {}
- if bus then
- local header = bus:read("*line")
- if header == cache_header then
- for l in bus:lines() do
- local _, _, id, artist_link_name, album_name, album_id, track_name =
- string.find(l,"(%d+)-([^-]+)-([^-]+)-(%d+)-(.+)")
- track = {}
- track.id = id
- track.artist_link_name = string.gsub(artist_link_name, '\\_', '-')
- track.album_name = string.gsub(album_name, '\\_', '-')
- track.album_id = album_id
- track.display_name = track_name
- jamendo_list[id] = track
- end
- else
- -- We encountered an outdated version of the cache
- -- file. Let's just remove it.
- awful.util.spawn("rm -f " .. cache_file)
- end
- end
-end
-
--- Saves track IDs to track names and album IDs mapping into the cache
--- file.
-function jamendo.save_cache()
- local bus = io.open(cache_file, "w")
- bus:write(cache_header .. "\n")
- for id,track in pairs(jamendo_list) do
- bus:write(string.format("%s-%s-%s-%s-%s\n", id,
- string.gsub(track.artist_link_name, '-', '\\_'),
- string.gsub(track.album_name, '-', '\\_'),
- track.album_id, track.display_name))
- end
- bus:flush()
- bus:close()
-end
-
--- Retrieve cache on initialization
-retrieve_cache()
-
--- Returns a filename of the album cover and formed wget request that
--- downloads the album cover for the given track name. If the album
--- cover already exists returns nil as the second argument.
-function jamendo.fetch_album_cover_request(track_id)
- local track = jamendo_list[track_id]
- local album_id = track.album_id
-
- if album_id == 0 then -- No cover for tracks without album!
- return nil
- end
- local file_path = album_covers_folder .. album_id .. ".jpg"
-
- if not file_exists(file_path) then -- We need to download it
- -- First check if cache directory exists
- f = io.popen('test -d ' .. album_covers_folder .. ' && echo t')
- if f:read("*line") ~= 't' then
- awful.util.spawn("mkdir " .. album_covers_folder)
- end
- f:close()
-
- if not track.album_image then -- Wow! We have album_id, but
- local a_id = tostring(album_id) --don't have album_image. Well,
- local prefix = --it happens.
- string.sub(a_id, 1, #a_id - 3)
- track.album_image =
- string.format("http://imgjam.com/albums/s%s/%s/covers/1.100.jpg",
- prefix == "" and 0 or prefix, a_id)
- end
-
- return file_path, string.format("wget %s -O %s 2> /dev/null",
- track.album_image, file_path)
- else -- Cover already downloaded, return its filename and nil
- return file_path, nil
- end
-end
-
--- Returns a file containing an album cover for given track id. First
--- searches in the cache folder. If file is not there, fetches it from
--- the Internet and saves into the cache folder.
-function jamendo.get_album_cover(track_id)
- local file_path, fetch_req = jamendo.fetch_album_cover_request(track_id)
- if fetch_req then
- local f = io.popen(fetch_req)
- f:close()
-
- -- Let's check if file is finally there, just in case
- if not file_exists(file_path) then
- return nil
- end
- end
- return file_path
-end
-
--- Same as get_album_cover, but downloads (if necessary) the cover
--- asynchronously.
-function jamendo.get_album_cover_async(track_id)
- local file_path, fetch_req = jamendo.fetch_album_cover_request(track_id)
- if fetch_req then
- asyncshell.request(fetch_req)
- end
-end
-
--- Checks if track_name is actually a link to Jamendo stream. If true
--- returns the file with album cover for the track.
-function jamendo.try_get_cover(track_name)
- local id = jamendo.get_id_from_link(track_name)
- if id then
- return jamendo.get_album_cover(id)
- end
-end
-
--- Same as try_get_cover, but calls get_album_cover_async inside.
-function jamendo.try_get_cover_async(track_name)
- local id = jamendo.get_id_from_link(track_name)
- if id then
- return jamendo.get_album_cover_async(id)
- end
-end
-
--- Returns the track table for given query and search method.
--- what - search method - SEARCH_ARTIST, ALBUM or TAG
--- s - string to search
-function jamendo.search_by(what, s)
- -- Get a default request and set unit and query
- local req = search_template
- req.unit = what.unit
- req.params.searchquery = s
- local resp = jamendo.perform_request(jamendo.form_request(req))
- if resp then
- local search_res = jamendo.parse_json(resp)[1]
-
- if search_res then
- -- Now when we got the search result, find tracks filtered by
- -- this result.
- local params = {}
- params[what.value] = search_res.id
- req = { params = params }
- local track_table = jamendo.return_track_table(req)
- return { search_res = search_res, tracks = track_table }
- end
- end
-end
-
--- Executes request_string with io.popen and returns the response.
-function jamendo.perform_request(request_string)
- local bus = assert(io.popen(request_string,'r'))
- local response = bus:read("*all")
- bus:close()
- -- Curl with popen can sometimes fail to fetch data when the
- -- connection is slow. Let's try again if it fails.
- if #response == 0 then
- bus = assert(io.popen(request_string,'r'))
- response = bus:read("*all")
- bus:close()
- -- If it still can't read anything, return nil
- if #response ~= 0 then
- return nil
- end
- end
- return response
-end
-
--- Sets default streamencoding in current_request_table.
-function jamendo.set_current_format(format)
- jamendo.current_request_table.params.streamencoding = format
-end
-
--- Sets default order in current_request_table.
-function jamendo.set_current_order(order)
- jamendo.current_request_table.params.order = order
-end
-
-return jamendo
+++ /dev/null
--- Standard awesome library
-local gears = require("gears")
-local awful = require("awful")
-awful.rules = require("awful.rules")
-require("awful.autofocus")
--- Widget and layout library
-local wibox = require("wibox")
--- Theme handling library
-local beautiful = require("beautiful")
--- Notification library
-local naughty = require("naughty")
-local menubar = require("menubar")
-
--- {{{ Error handling
--- Check if awesome encountered an error during startup and fell back to
--- another config (This code will only ever execute for the fallback config)
-if awesome.startup_errors then
- naughty.notify({ preset = naughty.config.presets.critical,
- title = "Oops, there were errors during startup!",
- text = awesome.startup_errors })
-end
-
--- Handle runtime errors after startup
-do
- local in_error = false
- awesome.connect_signal("debug::error", function (err)
- -- Make sure we don't go into an endless error loop
- if in_error then return end
- in_error = true
-
- naughty.notify({ preset = naughty.config.presets.critical,
- title = "Oops, an error happened!",
- text = err })
- in_error = false
- end)
-end
--- }}}
-
--- {{{ Variable definitions
--- Themes define colours, icons, and wallpapers
-beautiful.init("/usr/share/awesome/themes/default/theme.lua")
-
--- This is used later as the default terminal and editor to run.
-terminal = "xterm"
-editor = os.getenv("EDITOR") or "nano"
-editor_cmd = terminal .. " -e " .. editor
-
--- Default modkey.
--- Usually, Mod4 is the key with a logo between Control and Alt.
--- If you do not like this or do not have such a key,
--- I suggest you to remap Mod4 to another key using xmodmap or other tools.
--- However, you can use another modifier like Mod1, but it may interact with others.
-modkey = "Mod4"
-
--- Table of layouts to cover with awful.layout.inc, order matters.
-local layouts =
- {
- awful.layout.suit.floating,
- awful.layout.suit.tile,
- awful.layout.suit.tile.left,
- awful.layout.suit.tile.bottom,
- awful.layout.suit.tile.top,
- awful.layout.suit.fair,
- awful.layout.suit.fair.horizontal,
- awful.layout.suit.spiral,
- awful.layout.suit.spiral.dwindle,
- awful.layout.suit.max,
- awful.layout.suit.max.fullscreen,
- awful.layout.suit.magnifier
- }
- -- }}}
-
- -- {{{ Wallpaper
- if beautiful.wallpaper then
- for s = 1, screen.count() do
- gears.wallpaper.maximized(beautiful.wallpaper, s, true)
- end
- end
- -- }}}
-
- -- {{{ Tags
- -- Define a tag table which hold all screen tags.
- tags = {}
- for s = 1, screen.count() do
- -- Each screen has its own tag table.
- tags[s] = awful.tag({ 1, 2, 3, 4, 5, 6, 7, 8, 9 }, s, layouts[1])
- end
- -- }}}
-
- -- {{{ Menu
- -- Create a laucher widget and a main menu
- myawesomemenu = {
- { "manual", terminal .. " -e man awesome" },
- { "edit config", editor_cmd .. " " .. awesome.conffile },
- { "restart", awesome.restart },
- { "quit", awesome.quit }
- }
-
- mymainmenu = awful.menu({ items = { { "awesome", myawesomemenu, beautiful.awesome_icon },
- { "open terminal", terminal }
- }
- })
-
- mylauncher = awful.widget.launcher({ image = beautiful.awesome_icon,
- menu = mymainmenu })
-
- -- Menubar configuration
- menubar.utils.terminal = terminal -- Set the terminal for applications that require it
- -- }}}
-
- -- {{{ Wibox
- -- Create a textclock widget
- mytextclock = awful.widget.textclock()
-
- -- BEGIN OF AWESOMPD WIDGET DECLARATION
-
- local awesompd = require('awesompd/awesompd')
-
- musicwidget = awesompd:create() -- Create awesompd widget
- musicwidget.font = "Liberation Mono" -- Set widget font
- -- musicwidget.font_color = "#FFFFFF" --Set widget font color
- -- musicwidget.background = "#000000" --Set widget background color
- musicwidget.scrolling = true -- If true, the text in the widget will be scrolled
- musicwidget.output_size = 30 -- Set the size of widget in symbols
- musicwidget.update_interval = 10 -- Set the update interval in seconds
-
- -- Set the folder where icons are located (change username to your login name)
- musicwidget.path_to_icons = "/home/unlogic/.config/awesome/icons"
-
- -- Set the path to the icon to be displayed on the widget itself
- -- musicwidget.widget_icon = "/path/to/icon"
-
- -- Set the default music format for Jamendo streams. You can change
- -- this option on the fly in awesompd itself.
- -- possible formats: awesompd.FORMAT_MP3, awesompd.FORMAT_OGG
- musicwidget.jamendo_format = awesompd.FORMAT_MP3
-
- -- Specify the browser you use so awesompd can open links from
- -- Jamendo in it.
- musicwidget.browser = "firefox"
-
- -- If true, song notifications for Jamendo tracks and local tracks
- -- will also contain album cover image.
- musicwidget.show_album_cover = true
-
- -- Specify how big in pixels should an album cover be. Maximum value
- -- is 100.
- musicwidget.album_cover_size = 50
-
- -- This option is necessary if you want the album covers to be shown
- -- for your local tracks.
- musicwidget.mpd_config = "/home/unlogic/.mpdconf"
-
- -- Specify decorators on the left and the right side of the
- -- widget. Or just leave empty strings if you decorate the widget
- -- from outside.
- musicwidget.ldecorator = " "
- musicwidget.rdecorator = " "
-
- -- Set all the servers to work with (here can be any servers you use)
- musicwidget.servers = {
- { server = "localhost",
- port = 6600 },
- { server = "192.168.0.72",
- port = 6600 }
- }
-
- -- Set the buttons of the widget. Keyboard keys are working in the
- -- entire Awesome environment. Also look at the line 352.
- musicwidget:register_buttons(
- { { "", awesompd.MOUSE_LEFT, musicwidget:command_playpause() },
- { "Control", awesompd.MOUSE_SCROLL_UP, musicwidget:command_prev_track() },
- { "Control", awesompd.MOUSE_SCROLL_DOWN, musicwidget:command_next_track() },
- { "", awesompd.MOUSE_SCROLL_UP, musicwidget:command_volume_up() },
- { "", awesompd.MOUSE_SCROLL_DOWN, musicwidget:command_volume_down() },
- { "", awesompd.MOUSE_RIGHT, musicwidget:command_show_menu() },
- { "", "XF86AudioLowerVolume", musicwidget:command_volume_down() },
- { "", "XF86AudioRaiseVolume", musicwidget:command_volume_up() },
- { modkey, "Pause", musicwidget:command_playpause() } })
-
- musicwidget:run() -- After all configuration is done, run the widget
-
- -- END OF AWESOMPD WIDGET DECLARATION
- -- Don't forget to add the widget to the wibox. It is done on the line 244.
-
- -- Create a wibox for each screen and add it
- mywibox = {}
- mypromptbox = {}
- mylayoutbox = {}
- mytaglist = {}
- mytaglist.buttons = awful.util.table.join(
- awful.button({ }, 1, awful.tag.viewonly),
- awful.button({ modkey }, 1, awful.client.movetotag),
- awful.button({ }, 3, awful.tag.viewtoggle),
- awful.button({ modkey }, 3, awful.client.toggletag),
- awful.button({ }, 4, function(t) awful.tag.viewnext(awful.tag.getscreen(t)) end),
- awful.button({ }, 5, function(t) awful.tag.viewprev(awful.tag.getscreen(t)) end)
- )
- mytasklist = {}
- mytasklist.buttons = awful.util.table.join(
- awful.button({ }, 1, function (c)
- if c == client.focus then
- c.minimized = true
- else
- -- Without this, the following
- -- :isvisible() makes no sense
- c.minimized = false
- if not c:isvisible() then
- awful.tag.viewonly(c:tags()[1])
- end
- -- This will also un-minimize
- -- the client, if needed
- client.focus = c
- c:raise()
- end
- end),
- awful.button({ }, 3, function ()
- if instance then
- instance:hide()
- instance = nil
- else
- instance = awful.menu.clients({ width=250 })
- end
- end),
- awful.button({ }, 4, function ()
- awful.client.focus.byidx(1)
- if client.focus then client.focus:raise() end
- end),
- awful.button({ }, 5, function ()
- awful.client.focus.byidx(-1)
- if client.focus then client.focus:raise() end
- end))
-
- for s = 1, screen.count() do
- -- Create a promptbox for each screen
- mypromptbox[s] = awful.widget.prompt()
- -- Create an imagebox widget which will contains an icon indicating which layout we're using.
- -- We need one layoutbox per screen.
- mylayoutbox[s] = awful.widget.layoutbox(s)
- mylayoutbox[s]:buttons(awful.util.table.join(
- awful.button({ }, 1, function () awful.layout.inc(layouts, 1) end),
- awful.button({ }, 3, function () awful.layout.inc(layouts, -1) end),
- awful.button({ }, 4, function () awful.layout.inc(layouts, 1) end),
- awful.button({ }, 5, function () awful.layout.inc(layouts, -1) end)))
- -- Create a taglist widget
- mytaglist[s] = awful.widget.taglist(s, awful.widget.taglist.filter.all, mytaglist.buttons)
-
- -- Create a tasklist widget
- mytasklist[s] = awful.widget.tasklist(s, awful.widget.tasklist.filter.currenttags, mytasklist.buttons)
-
- -- Create the wibox
- mywibox[s] = awful.wibox({ position = "top", screen = s })
-
- -- Widgets that are aligned to the left
- local left_layout = wibox.layout.fixed.horizontal()
- left_layout:add(mylauncher)
- left_layout:add(mytaglist[s])
- left_layout:add(mypromptbox[s])
-
- -- Widgets that are aligned to the right
- local right_layout = wibox.layout.fixed.horizontal()
- if s == 1 then right_layout:add(wibox.widget.systray()) end
- right_layout:add(musicwidget.widget) -- Widget is added here.
- right_layout:add(mytextclock)
- right_layout:add(mylayoutbox[s])
-
- -- Now bring it all together (with the tasklist in the middle)
- local layout = wibox.layout.align.horizontal()
- layout:set_left(left_layout)
- layout:set_middle(mytasklist[s])
- layout:set_right(right_layout)
-
- mywibox[s]:set_widget(layout)
- end
- -- }}}
-
- -- {{{ Mouse bindings
- root.buttons(awful.util.table.join(
- awful.button({ }, 3, function () mymainmenu:toggle() end),
- awful.button({ }, 4, awful.tag.viewnext),
- awful.button({ }, 5, awful.tag.viewprev)
- ))
- -- }}}
-
- -- {{{ Key bindings
- globalkeys = awful.util.table.join(
- awful.key({ modkey, }, "Left", awful.tag.viewprev ),
- awful.key({ modkey, }, "Right", awful.tag.viewnext ),
- awful.key({ modkey, }, "Escape", awful.tag.history.restore),
-
- awful.key({ modkey, }, "j",
- function ()
- awful.client.focus.byidx( 1)
- if client.focus then client.focus:raise() end
- end),
- awful.key({ modkey, }, "k",
- function ()
- awful.client.focus.byidx(-1)
- if client.focus then client.focus:raise() end
- end),
- awful.key({ modkey, }, "w", function () mymainmenu:show() end),
-
- -- Layout manipulation
- awful.key({ modkey, "Shift" }, "j", function () awful.client.swap.byidx( 1) end),
- awful.key({ modkey, "Shift" }, "k", function () awful.client.swap.byidx( -1) end),
- awful.key({ modkey, "Control" }, "j", function () awful.screen.focus_relative( 1) end),
- awful.key({ modkey, "Control" }, "k", function () awful.screen.focus_relative(-1) end),
- awful.key({ modkey, }, "u", awful.client.urgent.jumpto),
- awful.key({ modkey, }, "Tab",
- function ()
- awful.client.focus.history.previous()
- if client.focus then
- client.focus:raise()
- end
- end),
-
- -- Standard program
- awful.key({ modkey, }, "Return", function () awful.util.spawn(terminal) end),
- awful.key({ modkey, "Control" }, "r", awesome.restart),
- awful.key({ modkey, "Shift" }, "q", awesome.quit),
-
- awful.key({ modkey, }, "l", function () awful.tag.incmwfact( 0.05) end),
- awful.key({ modkey, }, "h", function () awful.tag.incmwfact(-0.05) end),
- awful.key({ modkey, "Shift" }, "h", function () awful.tag.incnmaster( 1) end),
- awful.key({ modkey, "Shift" }, "l", function () awful.tag.incnmaster(-1) end),
- awful.key({ modkey, "Control" }, "h", function () awful.tag.incncol( 1) end),
- awful.key({ modkey, "Control" }, "l", function () awful.tag.incncol(-1) end),
- awful.key({ modkey, }, "space", function () awful.layout.inc(layouts, 1) end),
- awful.key({ modkey, "Shift" }, "space", function () awful.layout.inc(layouts, -1) end),
-
- awful.key({ modkey, "Control" }, "n", awful.client.restore),
-
- -- Prompt
- awful.key({ modkey }, "r", function () mypromptbox[mouse.screen]:run() end),
-
- awful.key({ modkey }, "x",
- function ()
- awful.prompt.run({ prompt = "Run Lua code: " },
- mypromptbox[mouse.screen].widget,
- awful.util.eval, nil,
- awful.util.getdir("cache") .. "/history_eval")
- end),
- -- Menubar
- awful.key({ modkey }, "p", function() menubar.show() end)
- )
-
- clientkeys = awful.util.table.join(
- awful.key({ modkey, }, "f", function (c) c.fullscreen = not c.fullscreen end),
- awful.key({ modkey, "Shift" }, "c", function (c) c:kill() end),
- awful.key({ modkey, "Control" }, "space", awful.client.floating.toggle ),
- awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end),
- awful.key({ modkey, }, "o", awful.client.movetoscreen ),
- awful.key({ modkey, }, "t", function (c) c.ontop = not c.ontop end),
- awful.key({ modkey, }, "n",
- function (c)
- -- The client currently has the input focus, so it cannot be
- -- minimized, since minimized clients can't have the focus.
- c.minimized = true
- end),
- awful.key({ modkey, }, "m",
- function (c)
- c.maximized_horizontal = not c.maximized_horizontal
- c.maximized_vertical = not c.maximized_vertical
- end)
- )
-
- -- Bind all key numbers to tags.
- -- Be careful: we use keycodes to make it works on any keyboard layout.
- -- This should map on the top row of your keyboard, usually 1 to 9.
- for i = 1, 9 do
- globalkeys = awful.util.table.join(globalkeys,
- awful.key({ modkey }, "#" .. i + 9,
- function ()
- local screen = mouse.screen
- local tag = awful.tag.gettags(screen)[i]
- if tag then
- awful.tag.viewonly(tag)
- end
- end),
- awful.key({ modkey, "Control" }, "#" .. i + 9,
- function ()
- local screen = mouse.screen
- local tag = awful.tag.gettags(screen)[i]
- if tag then
- awful.tag.viewtoggle(tag)
- end
- end),
- awful.key({ modkey, "Shift" }, "#" .. i + 9,
- function ()
- local tag = awful.tag.gettags(client.focus.screen)[i]
- if client.focus and tag then
- awful.client.movetotag(tag)
- end
- end),
- awful.key({ modkey, "Control", "Shift" }, "#" .. i + 9,
- function ()
- local tag = awful.tag.gettags(client.focus.screen)[i]
- if client.focus and tag then
- awful.client.toggletag(tag)
- end
- end))
- end
-
- clientbuttons = awful.util.table.join(
- awful.button({ }, 1, function (c) client.focus = c; c:raise() end),
- awful.button({ modkey }, 1, awful.mouse.client.move),
- awful.button({ modkey }, 3, awful.mouse.client.resize))
-
- -- Set keys
- -- Add this line before root.keys(globalkeys).
- musicwidget:append_global_keys()
-
- root.keys(globalkeys)
- -- }}}
-
- -- {{{ Rules
- awful.rules.rules = {
- -- All clients will match this rule.
- { rule = { },
- properties = { border_width = beautiful.border_width,
- border_color = beautiful.border_normal,
- focus = awful.client.focus.filter,
- keys = clientkeys,
- buttons = clientbuttons } },
- { rule = { class = "MPlayer" },
- properties = { floating = true } },
- { rule = { class = "pinentry" },
- properties = { floating = true } },
- { rule = { class = "gimp" },
- properties = { floating = true } },
- -- Set Firefox to always map on tags number 2 of screen 1.
- -- { rule = { class = "Firefox" },
- -- properties = { tag = tags[1][2] } },
- }
- -- }}}
-
- -- {{{ Signals
- -- Signal function to execute when a new client appears.
- client.connect_signal("manage", function (c, startup)
- -- Enable sloppy focus
- c:connect_signal("mouse::enter", function(c)
- if awful.layout.get(c.screen) ~= awful.layout.suit.magnifier
- and awful.client.focus.filter(c) then
- client.focus = c
- end
- end)
-
- if not startup then
- -- Set the windows at the slave,
- -- i.e. put it at the end of others instead of setting it master.
- -- awful.client.setslave(c)
-
- -- Put windows in a smart way, only if they does not set an initial position.
- if not c.size_hints.user_position and not c.size_hints.program_position then
- awful.placement.no_overlap(c)
- awful.placement.no_offscreen(c)
- end
- end
-
- local titlebars_enabled = false
- if titlebars_enabled and (c.type == "normal" or c.type == "dialog") then
- -- buttons for the titlebar
- local buttons = awful.util.table.join(
- awful.button({ }, 1, function()
- client.focus = c
- c:raise()
- awful.mouse.client.move(c)
- end),
- awful.button({ }, 3, function()
- client.focus = c
- c:raise()
- awful.mouse.client.resize(c)
- end)
- )
-
- -- Widgets that are aligned to the left
- local left_layout = wibox.layout.fixed.horizontal()
- left_layout:add(awful.titlebar.widget.iconwidget(c))
- left_layout:buttons(buttons)
-
- -- Widgets that are aligned to the right
- local right_layout = wibox.layout.fixed.horizontal()
- right_layout:add(awful.titlebar.widget.floatingbutton(c))
- right_layout:add(awful.titlebar.widget.maximizedbutton(c))
- right_layout:add(awful.titlebar.widget.stickybutton(c))
- right_layout:add(awful.titlebar.widget.ontopbutton(c))
- right_layout:add(awful.titlebar.widget.closebutton(c))
-
- -- The title goes in the middle
- local middle_layout = wibox.layout.flex.horizontal()
- local title = awful.titlebar.widget.titlewidget(c)
- title:set_align("center")
- middle_layout:add(title)
- middle_layout:buttons(buttons)
-
- -- Now bring it all together
- local layout = wibox.layout.align.horizontal()
- layout:set_left(left_layout)
- layout:set_right(right_layout)
- layout:set_middle(middle_layout)
-
- awful.titlebar(c):set_widget(layout)
- end
- end)
-
- client.connect_signal("focus", function(c) c.border_color = beautiful.border_focus end)
- client.connect_signal("unfocus", function(c) c.border_color = beautiful.border_normal end)
- -- }}}
+++ /dev/null
--- Provides UTF-8 aware string functions implemented in pure lua:
--- * string.utf8len(s)
--- * string.utf8sub(s, i, j)
---
--- All functions behave as their non UTF-8 aware counterparts with the exception
--- that UTF-8 characters are used instead of bytes for all units.
---
--- Note: all validations had been removed due to awesome usage specifics.
---[[
-Copyright (c) 2006-2007, Kyle Smith
-Modified by Alexander Yakushev, 2010-2013.
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice,
-this list of conditions and the following disclaimer.
-* Redistributions in binary form must reproduce the above copyright
-notice, this list of conditions and the following disclaimer in the
-documentation and/or other materials provided with the distribution.
-* Neither the name of the author nor the names of its contributors may be
-used to endorse or promote products derived from this software without
-specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---]]
-
--- ABNF from RFC 3629
---
--- UTF8-octets = *( UTF8-char )
--- UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
--- UTF8-1 = %x00-7F
--- UTF8-2 = %xC2-DF UTF8-tail
--- UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
--- %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
--- UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
--- %xF4 %x80-8F 2( UTF8-tail )
--- UTF8-tail = %x80-BF
---
-
--- returns the number of bytes used by the UTF-8 character at byte i in s
--- also doubles as a UTF-8 character validator
-
-local utf8 = {}
-
-function utf8.charbytes (s, i)
- -- argument defaults
- i = i or 1
- local c = string.byte(s, i)
-
- -- determine bytes needed for character, based on RFC 3629
- if c > 0 and c <= 127 then
- -- UTF8-1
- return 1
- elseif c >= 194 and c <= 223 then
- -- UTF8-2
- local c2 = string.byte(s, i + 1)
- return 2
- elseif c >= 224 and c <= 239 then
- -- UTF8-3
- local c2 = s:byte(i + 1)
- local c3 = s:byte(i + 2)
- return 3
- elseif c >= 240 and c <= 244 then
- -- UTF8-4
- local c2 = s:byte(i + 1)
- local c3 = s:byte(i + 2)
- local c4 = s:byte(i + 3)
- return 4
- end
-end
-
--- returns the number of characters in a UTF-8 string
-function utf8.len (s)
- local pos = 1
- local bytes = string.len(s)
- local len = 0
-
- while pos <= bytes and len ~= chars do
- local c = string.byte(s,pos)
- len = len + 1
-
- pos = pos + utf8.charbytes(s, pos)
- end
-
- if chars ~= nil then
- return pos - 1
- end
-
- return len
-end
-
--- functions identically to string.sub except that i and j are UTF-8 characters
--- instead of bytes
-function utf8.sub (s, i, j)
- j = j or -1
-
- if i == nil then
- return ""
- end
-
- local pos = 1
- local bytes = string.len(s)
- local len = 0
-
- -- only set l if i or j is negative
- local l = (i >= 0 and j >= 0) or utf8.len(s)
- local startChar = (i >= 0) and i or l + i + 1
- local endChar = (j >= 0) and j or l + j + 1
-
- -- can't have start before end!
- if startChar > endChar then
- return ""
- end
-
- -- byte offsets to pass to string.sub
- local startByte, endByte = 1, bytes
-
- while pos <= bytes do
- len = len + 1
-
- if len == startChar then
- startByte = pos
- end
-
- pos = pos + utf8.charbytes(s, pos)
-
- if len == endChar then
- endByte = pos - 1
- break
- end
- end
-
- return string.sub(s, startByte, endByte)
-end
-
--- replace UTF-8 characters based on a mapping table
-function utf8.replace (s, mapping)
- local pos = 1
- local bytes = string.len(s)
- local charbytes
- local newstr = ""
-
- while pos <= bytes do
- charbytes = utf8.charbytes(s, pos)
- local c = string.sub(s, pos, pos + charbytes - 1)
- newstr = newstr .. (mapping[c] or c)
- pos = pos + charbytes
- end
-
- return newstr
-end
-
-return utf8