volume.lua 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. -- @author cedlemo
  2. local setmetatable = setmetatable
  3. local math = math
  4. local type = type
  5. local string = string
  6. local awful = require("awful")
  7. local triangular_progress_graph = require('blingbling.triangular_progress_graph')
  8. ---Volume widget
  9. --@module blingbling.volume
  10. local volume = { mt = {} }
  11. local data = setmetatable({}, { __mode = "k" })
  12. -- Isolate and return the default sink info from the given string
  13. local function default_sink_info(output)
  14. local defaultSinkIdx, endMatch = string.find(output, "%s+*%sindex:%s%d+")
  15. if (defaultSinkIdx == nil) then
  16. return nil
  17. end
  18. local nextSinkIdx = string.find(output, "%s+^*%sindex:%s%d+", endMatch)
  19. if (nextSinkIdx == nil) then
  20. nextSinkIdx = output:len()
  21. end
  22. return string.sub(output, defaultSinkIdx, nextSinkIdx)
  23. end
  24. -- Get volume and mute state
  25. local function get_master_infos(volume_graph)
  26. local state, volume = nil, nil
  27. if (data[volume_graph].pulseaudio == true) then
  28. local pastatus = awful.util.pread("pacmd list-sinks")
  29. local defaultSinkInfo = default_sink_info(pastatus)
  30. if (defaultSinkInfo) then
  31. volume = string.match(defaultSinkInfo, "volume:.-%/%s+(%d+)%%")
  32. state = string.match(defaultSinkInfo, "muted:%s(%a+)")
  33. end
  34. else
  35. local f=io.popen(data[volume_graph].cmd .. " get Master")
  36. for line in f:lines() do
  37. if string.match(line, "%s%[%d+%%%]%s") ~= nil then
  38. volume=string.match(line, "%s%[%d+%%%]%s")
  39. volume=string.gsub(volume, "[%[%]%%%s]","")
  40. end
  41. if string.match(line, "%s%[[%l]+%]$") then
  42. state=string.match(line, "%s%[[%l]+%]$")
  43. state=string.gsub(state,"[%[%]%%%s]","")
  44. end
  45. end
  46. f:close()
  47. end
  48. if (not state or state == "yes" or state == "off") then
  49. state = true -- output is mute
  50. else
  51. state = false
  52. end
  53. if (not volume) then
  54. volume = 0
  55. end
  56. return state, volume
  57. end
  58. local function update_master(volume_graph)
  59. local state, value = nil, nil
  60. data[volume_graph].mastertimer = timer({timeout = 0.5})
  61. data[volume_graph].mastertimer:connect_signal("timeout", function()
  62. state, value = get_master_infos(volume_graph)
  63. volume_graph:set_value(value/100)
  64. if state == false then
  65. volume_graph:set_label(data[volume_graph].label)
  66. else
  67. volume_graph:set_label("off")
  68. end
  69. end)
  70. data[volume_graph].mastertimer:start()
  71. return volume_graph
  72. end
  73. local function get_mpd_volume()
  74. local mpd_volume = 0
  75. local pass = "\"\""
  76. local host = "127.0.0.1"
  77. local port = "6600"
  78. -- MPD client command
  79. local mpd_c = "mpc" .. " -h " .. host .. " -p " .. port .. " status 2>&1"
  80. -- Get data from MPD server
  81. local f = io.popen(mpd_c)
  82. for line in f:lines() do
  83. if string.find(line,'error:%sConnection%srefused') then
  84. mpd_vol="-1"
  85. end
  86. if string.find(line,"volume:.%d%d%%") then
  87. mpd_volume = string.match(line,"[%s%d]%d%d")
  88. end
  89. end
  90. f:close()
  91. return mpd_volume
  92. end
  93. ---Link the widget to mpd's volume level.
  94. --@usage myvolume:update_mpd()
  95. local function update_mpd(volume_graph)
  96. local state
  97. local value
  98. data[volume_graph].mastertimer = timer({timeout = 0.5})
  99. data[volume_graph].mastertimer:connect_signal("timeout", function()
  100. value = get_mpd_volume(); volume_graph:set_value(value/100)
  101. end)
  102. data[volume_graph].mastertimer:start()
  103. end
  104. -- Alsa command
  105. local function set_alsa_master(mixer_cmd, parameters)
  106. local cmd = mixer_cmd .. " --quiet set Master " .. parameters
  107. local f=io.popen(cmd)
  108. f:close()
  109. end
  110. local function set_alsa_control(volume_graph)
  111. volume_graph:buttons(awful.util.table.join(
  112. awful.button({ }, 1, function()
  113. set_alsa_master(data[volume_graph].cmd, "toggle")
  114. end),
  115. awful.button({ }, 5, function()
  116. set_alsa_master(data[volume_graph].cmd, data[volume_graph].increment .. "%-")
  117. end),
  118. awful.button({ }, 4, function()
  119. set_alsa_master(data[volume_graph].cmd, data[volume_graph].increment .. "%+")
  120. end)))
  121. end
  122. local function set_pa_control(volume_graph)
  123. volume_graph:buttons(awful.util.table.join(
  124. awful.button({ }, 1, function()
  125. awful.util.spawn_with_shell("pactl set-sink-mute @DEFAULT_SINK@ toggle")
  126. end),
  127. awful.button({ }, 5, function()
  128. awful.util.spawn_with_shell("pactl set-sink-volume @DEFAULT_SINK@ -" .. data[volume_graph].increment .. "%")
  129. end),
  130. awful.button({ }, 4, function()
  131. awful.util.spawn_with_shell("pactl set-sink-volume @DEFAULT_SINK@ +" .. data[volume_graph].increment .. "%")
  132. end)))
  133. end
  134. --- Link the widget to the master channel of your system.
  135. --a left clic toggle mute/unmute, wheel up to increase the volume and wheel down to decrease the volume
  136. --@usage myvolume:set_master_control()
  137. local function set_master_control(volume_graph)
  138. if (data[volume_graph].pulseaudio == true) then
  139. set_pa_control(volume_graph)
  140. else
  141. set_alsa_control(volume_graph)
  142. end
  143. end
  144. --- Create a volume_graph widget.
  145. -- @param args Standard widget() arguments. This is a table that accepts
  146. -- different keys:
  147. --
  148. -- <code>{ cmd = "amixer -c 1", label = "$percent%", graph_color = "#005500" }</code>
  149. --
  150. -- The default cmd is "amixer" which should be good enough if you only have one audio output.
  151. -- You can provide another command based on your need. For example, the "amixer -c 1" command
  152. -- has been given by k3rni contributor and allow to select the audio card. (see
  153. -- https://github.com/cedlemo/blingbling/pull/30 for more information.
  154. --
  155. -- The other keys are those related to the graph itself and are the same that those
  156. -- used with the triangular_progress_graph
  157. -- @return A graph widget.
  158. function volume.new(args)
  159. local args = args or {}
  160. local volume_graph = triangular_progress_graph(args)
  161. data[volume_graph] = {}
  162. data[volume_graph].label = args.label or "$percent%"
  163. data[volume_graph].cmd = args.cmd or "amixer"
  164. data[volume_graph].increment = args.increment or 2
  165. data[volume_graph].pulseaudio = args.pulseaudio or false
  166. volume_graph.update_master = update_master
  167. volume_graph.update_mpd= update_mpd
  168. volume_graph.set_master_control = set_master_control
  169. return volume_graph
  170. end
  171. function volume.mt:__call(...)
  172. return volume.new(...)
  173. end
  174. return setmetatable(volume, volume.mt)