diff options
Diffstat (limited to 'volume-widget/volume.lua')
| -rw-r--r-- | volume-widget/volume.lua | 329 | 
1 files changed, 182 insertions, 147 deletions
| diff --git a/volume-widget/volume.lua b/volume-widget/volume.lua index cb8c21d..59f0a7a 100644 --- a/volume-widget/volume.lua +++ b/volume-widget/volume.lua @@ -1,181 +1,216 @@  ------------------------------------------------- --- Volume Widget for Awesome Window Manager --- Shows the current volume level +-- The Ultimate Volume Widget for Awesome Window Manager  -- More details could be found here:  -- https://github.com/streetturtle/awesome-wm-widgets/tree/master/volume-widget --- @author Pavel Makhov, Aurélien Lajoie --- @copyright 2018 Pavel Makhov +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov  ------------------------------------------------- +local awful = require("awful")  local wibox = require("wibox")  local spawn = require("awful.spawn") -local naughty = require("naughty") -local gfs = require("gears.filesystem") -local dpi = require('beautiful').xresources.apply_dpi - -local PATH_TO_ICONS = "/usr/share/icons/Arc/status/symbolic/" -local volume_icon_name="audio-volume-high-symbolic" -local GET_VOLUME_CMD = 'amixer sget Master' - -local volume = { -    device = '', -    display_notification = false, -    display_notification_onClick = true, -    notification = nil, -    delta = 5 -} +local gears = require("gears") +local beautiful = require("beautiful") +local watch = require("awful.widget.watch") +local utils = require("awesome-wm-widgets.volume-widget.utils") -function volume:toggle() -    volume:_cmd('amixer ' .. volume.device .. ' sset Master toggle') -end -function volume:raise() -    volume:_cmd('amixer ' .. volume.device .. ' sset Master ' .. tostring(volume.delta) .. '%+') -end -function volume:lower() -    volume:_cmd('amixer ' .. volume.device .. ' sset Master ' .. tostring(volume.delta) .. '%-') -end +local LIST_DEVICES_CMD = [[sh -c "pacmd list-sinks; pacmd list-sources"]] +local GET_VOLUME_CMD = 'amixer -D pulse sget Master' +local INC_VOLUME_CMD = 'amixer -D pulse sset Master 5%+' +local DEC_VOLUME_CMD = 'amixer -D pulse sset Master 5%-' +local TOG_VOLUME_CMD = 'amixer -D pulse sset Master toggle' ---{{{ Icon and notification update - --------------------------------------------------- ---  Set the icon and return the message to display ---  base on sound level and mute --------------------------------------------------- -local function parse_output(stdout) -    local level = string.match(stdout, "(%d?%d?%d)%%") -    if stdout:find("%[off%]") then -        volume_icon_name="audio-volume-muted-symbolic_red" -        return level.."% <span color=\"red\"><b>Mute</b></span>" -    end -    level = tonumber(string.format("% 3d", level)) - -    if (level >= 0 and level < 25) then -        volume_icon_name="audio-volume-muted-symbolic" -    elseif (level < 50) then -        volume_icon_name="audio-volume-low-symbolic" -    elseif (level < 75) then -        volume_icon_name="audio-volume-medium-symbolic" + +local widget_types = { +    icon_and_text = require("awesome-wm-widgets.volume-widget.widgets.icon-and-text-widget"), +    icon = require("awesome-wm-widgets.volume-widget.widgets.icon-widget"), +    arc = require("awesome-wm-widgets.volume-widget.widgets.arc-widget"), +    horizontal_bar = require("awesome-wm-widgets.volume-widget.widgets.horizontal-bar-widget"), +    vertical_bar = require("awesome-wm-widgets.volume-widget.widgets.vertical-bar-widget") +} +local volume = {} + +local rows  = { layout = wibox.layout.fixed.vertical } + +local popup = awful.popup{ +    bg = beautiful.bg_normal, +    ontop = true, +    visible = false, +    shape = gears.shape.rounded_rect, +    border_width = 1, +    border_color = beautiful.bg_focus, +    maximum_width = 400, +    offset = { y = 5 }, +    widget = {} +} + +local function build_main_line(device) +    if device.active_port ~= nil and device.ports[device.active_port] ~= nil then +        return device.properties.device_description .. ' · ' .. device.ports[device.active_port]      else -        volume_icon_name="audio-volume-high-symbolic" +        return device.properties.device_description      end -    return level.."%"  end --------------------------------------------------------- ---Update the icon and the notification if needed --------------------------------------------------------- -local function update_graphic(widget, stdout, _, _, _) -    local txt = parse_output(stdout) -    widget.image = PATH_TO_ICONS .. volume_icon_name .. ".svg" -    if (volume.display_notification or volume.display_notification_onClick) then -        volume.notification.iconbox.image = PATH_TO_ICONS .. volume_icon_name .. ".svg" -        naughty.replace_text(volume.notification, "Volume", txt) -    end -end +local function build_rows(devices, on_checkbox_click, device_type) +    local device_rows  = { layout = wibox.layout.fixed.vertical } +    for _, device in pairs(devices) do + +        local checkbox = wibox.widget { +            checked = device.is_default, +            color = beautiful.bg_normal, +            paddings = 2, +            shape = gears.shape.circle, +            forced_width = 20, +            forced_height = 20, +            check_color = beautiful.fg_urgent, +            widget = wibox.widget.checkbox +        } + +        checkbox:connect_signal("button::press", function() +            spawn.easy_async(string.format([[sh -c 'pacmd set-default-%s "%s"']], device_type, device.name), function() +                on_checkbox_click() +            end) +        end) -local function notif(msg, keep) -    if (volume.display_notification or (keep and volume.display_notification_onClick)) then -        naughty.destroy(volume.notification) -        volume.notification= naughty.notify{ -            text =  msg, -            icon=PATH_TO_ICONS .. volume_icon_name .. ".svg", -            icon_size = dpi(16), -            title = "Volume", -            position = volume.position, -            timeout = keep and 0 or 2, hover_timeout = 0.5, -            width = 200, -            screen = mouse.screen +        local row = wibox.widget { +            { +                { +                    { +                        checkbox, +                        valign = 'center', +                        layout = wibox.container.place, +                    }, +                    { +                        { +                            text = build_main_line(device), +                            align = 'left', +                            widget = wibox.widget.textbox +                        }, +                        left = 10, +                        layout = wibox.container.margin +                    }, +                    spacing = 8, +                    layout = wibox.layout.align.horizontal +                }, +                margins = 4, +                layout = wibox.container.margin +            }, +            bg = beautiful.bg_normal, +            widget = wibox.container.background          } + +        row:connect_signal("mouse::enter", function(c) c:set_bg(beautiful.bg_focus) end) +        row:connect_signal("mouse::leave", function(c) c:set_bg(beautiful.bg_normal) end) + +        local old_cursor, old_wibox +        row:connect_signal("mouse::enter", function() +            local wb = mouse.current_wibox +            old_cursor, old_wibox = wb.cursor, wb +            wb.cursor = "hand1" +        end) +        row:connect_signal("mouse::leave", function() +            if old_wibox then +                old_wibox.cursor = old_cursor +                old_wibox = nil +            end +        end) + +        row:connect_signal("button::press", function() +            spawn.easy_async(string.format([[sh -c 'pacmd set-default-%s "%s"']], device_type, device.name), function() +                on_checkbox_click() +            end) +        end) + +        table.insert(device_rows, row)      end + +    return device_rows +end + +local function build_header_row(text) +    return wibox.widget{ +        { +            markup = "<b>" .. text .. "</b>", +            align = 'center', +            widget = wibox.widget.textbox +        }, +        bg = beautiful.bg_normal, +        widget = wibox.container.background +    } +end + +local function rebuild_popup() +    spawn.easy_async(LIST_DEVICES_CMD, function(stdout) + +        local sinks, sources = utils.extract_sinks_and_sources(stdout) + +        for i = 0, #rows do rows[i]=nil end + +        table.insert(rows, build_header_row("SINKS")) +        table.insert(rows, build_rows(sinks, function() rebuild_popup() end, "sink")) +        table.insert(rows, build_header_row("SOURCES")) +        table.insert(rows, build_rows(sources, function() rebuild_popup() end, "source")) + +        popup:setup(rows) +    end)  end ---}}}  local function worker(user_args) ---{{{ Args +      local args = user_args or {} -    local volume_audio_controller = args.volume_audio_controller or 'pulse' -    volume.display_notification = args.display_notification or false -    volume.display_notification_onClick = args.display_notification_onClick or true -    volume.position = args.notification_position or "top_right" -    if volume_audio_controller == 'pulse' then -        volume.device = '-D pulse' -    end -    volume.delta = args.delta or 5 -    GET_VOLUME_CMD = 'amixer ' .. volume.device.. ' sget Master' ---}}} ---{{{ Check for icon path -    if not gfs.dir_readable(PATH_TO_ICONS) then -        naughty.notify{ -            title = "Volume Widget", -            text = "Folder with icons doesn't exist: " .. PATH_TO_ICONS, -            preset = naughty.config.presets.critical -        } -        return +    local widget_type = args.widget_type +    local refresh_rate = args.refresh_rate or 1 + +    if widget_types[widget_type] == nil then +        volume.widget = widget_types['icon_and_text'].get_widget(user_args.icon_and_text_args) +    else +        volume.widget = widget_types[widget_type].get_widget(args)      end ---}}} ---{{{ Widget creation -    volume.widget = wibox.widget { -        { -            id = "icon", -            image = PATH_TO_ICONS .. "audio-volume-muted-symbolic.svg", -            resize = false, -            widget = wibox.widget.imagebox, -        }, -        margins = 3, -        layout = wibox.container.margin, -        set_image = function(self, path) -            self.icon.image = path + +    local function update_graphic(widget, stdout) +        local mute = string.match(stdout, "%[(o%D%D?)%]")   -- \[(o\D\D?)\] - [on] or [off] +        if mute == 'off' then widget:mute() +        elseif mute == 'on' then widget:unmute()          end -    } ---}}} ---{{{ Spawn functions -    function volume:_cmd(cmd) -        notif("") -        spawn.easy_async(cmd, function(stdout, stderr, exitreason, exitcode) -            update_graphic(volume.widget, stdout, stderr, exitreason, exitcode) -        end) +        local volume_level = string.match(stdout, "(%d?%d?%d)%%") -- (\d?\d?\d)\%) +        volume_level = string.format("% 3d", volume_level) +        widget:set_volume_level(volume_level)      end -    local function show() -        spawn.easy_async(GET_VOLUME_CMD, function(stdout, _, _, _) -            local txt = parse_output(stdout) -            notif(txt, true) -        end -        ) +    function volume:inc() +        spawn.easy_async(INC_VOLUME_CMD, function(stdout) update_graphic(volume.widget, stdout) end)      end ---}}} ---{{{ Mouse event -    --[[ allows control volume level by: -    - clicking on the widget to mute/unmute -    - scrolling when cursor is over the widget -    ]] -    volume.widget:connect_signal("button::press", function(_,_,_,button) -        if (button == 4)     then volume.raise() -        elseif (button == 5) then volume.lower() -        elseif (button == 1) then volume.toggle() -        end -    end) -    if volume.display_notification then -        volume.widget:connect_signal("mouse::enter", function() show() end) -        volume.widget:connect_signal("mouse::leave", function() naughty.destroy(volume.notification) end) -    elseif volume.display_notification_onClick then -        volume.widget:connect_signal("button::press", function(_,_,_,button) -            if (button == 3) then show() end -        end) -        volume.widget:connect_signal("mouse::leave", function() naughty.destroy(volume.notification) end) + +    function volume:dec() +        spawn.easy_async(DEC_VOLUME_CMD, function(stdout) update_graphic(volume.widget, stdout) end)      end ---}}} ---{{{ Set initial icon -    spawn.easy_async(GET_VOLUME_CMD, function(stdout) -        parse_output(stdout) -        volume.widget.image = PATH_TO_ICONS .. volume_icon_name .. ".svg" -    end) ---}}} +    function volume:toggle() +        spawn.easy_async(TOG_VOLUME_CMD, function(stdout) update_graphic(volume.widget, stdout) end) +    end + +    volume.widget:buttons( +            awful.util.table.join( +                    awful.button({}, 3, function() +                        if popup.visible then +                            popup.visible = not popup.visible +                        else +                            rebuild_popup() +                            popup:move_next_to(mouse.current_widget_geometry) +                        end +                    end), +                    awful.button({}, 4, function() volume:inc() end), +                    awful.button({}, 5, function() volume:dec() end), +                    awful.button({}, 1, function() volume:toggle() end) +            ) +    ) + +    watch(GET_VOLUME_CMD, refresh_rate, update_graphic, volume.widget)      return volume.widget  end | 
