local widget_types = { bar = require("classes/volume/bar_widget"), arc = require("classes/volume/arc_widget") } local P = { name = "volume" } --- -- Internal methods --- function P:_full_args() -- Build complete pamixer arguments, combining sink and options. -- Must have a leading space. if (self.pa_sink == "") then return " " .. self.pa_options else return " --sink" .. self.pa_sink .. " " .. self.pa_options end end function P:_get_status() -- Update mute status and volume. -- Values in this table are updated. awful.spawn.easy_async( "pamixer --get-mute --get-volume" .. self:_full_args(), function(stdout, stderr, exitreason, exitcode) -- Get mute state local muted = string.match(stdout, "(%w%w%w%w%w?) ") self.muted = (muted == "true") local value = string.match(stdout, "(%d?%d?%d)") -- (\d?\d?\d)\%) if (value == nil) then self.volume = nil self._ready = false else self.volume = tonumber(string.format("% 3d", value)) self._ready = true end self:_update_widget() end ) -- This is used inside a timer, so return true to keep it running. return true end function P:_update_widget() if (not self._ready) then self.widget:set_state("error") self.widget:set_value(self.max_value); self.widget:set_tooltip("Volume error"); return end self.widget:set_value(self.volume); self.widget:set_tooltip("Volume " .. self.volume .. "%"); if (self.muted) then self.widget:set_state("muted") else self.widget:set_state("unmuted") end end --- -- Simple actions --- function P:volume_up() if (not self._ready) or (self.volume >= self.max_value) then return end if self.muted then self:unmute() end awful.spawn("pamixer --increase 5" .. self:_full_args(), false) wrapper.sound.play("volume_up") self.volume = self.volume + 5 if self.volume > self.max_value then self.volume = self.max_value end self:_update_widget() end function P:volume_down() if (not self._ready) or (self.volume <= 0) then return end if self.muted then self:unmute() end awful.spawn("pamixer --decrease 5" .. self:_full_args(), false) wrapper.sound.play("volume_down") self.volume = self.volume - 5 if self.volume < 0 then self.volume = 0 end self:_update_widget() end function P:volume_set(value) if (not self._ready) then return end if self.muted then self:unmute() end if (value < 0) then value = 0 end if (value > self.max_value) then value = self.max_value end awful.spawn("pamixer --set_volume " .. tostring(value) .. self:_full_args(), false) self.volume = value self:_update_widget() end function P:mute() if (not self._ready) then return end self.muted = true awful.spawn("pamixer --mute" .. self:_full_args(), false) self:_update_widget() end function P:unmute() if (not self._ready) then return end self.muted = false awful.spawn("pamixer --unmute" .. self:_full_args(), false) self:_update_widget() end function P:toggle_mute() if (not self._ready) then return end if self.muted then self:unmute() else self:mute() end end --- -- Create new volume_interface --- function P:new(args) -- Arguments local v = { pa_options = args.cli_options or "", pa_sink = args.pa_sink or "", update_interval = args.update_interal or 5, max_value = args.max_value or 100, widget_type = args.widget_type or "arc", _ready = false, -- is all the information in this class up-to-date? -- if this is false, ui will show an error and most methods will -- do nothing. Updated in _get_status() } -- Create widget for this volume interface v.widget = widget_types[v.widget_type]:new() -- Attach button press signals v.widget.widget:connect_signal("button::press", function(_, _, _, button, mods) if (button == 3) then -- Right-click v:toggle_mute() elseif (button == 4) then -- Scroll up v:volume_up() elseif (button == 5) then -- Scroll down v:volume_down() end end ) setmetatable(v, self) self.__index = self -- This timer keeps mute and volume status up-to-date. v.timer = gears.timer { timeout = v.update_interval, call_now = true, autostart = true, callback = function() v:_get_status() end } return v end return P