2

I am a new user to awesomewm (but have used other WMs before: i3, bspwm, xmonad, etc). I like to have some shell scripts that I have written in my wibar (I think that's what its called, the bar at the top of the screen with the taglist) to display stuff like battery, audio, etc (as I know is common). Currently I am using the "wibar.widget.watch" to do this, as shown below.

        -- Right widgets
    layout = wibox.layout.fixed.horizontal,
    awful.widget.watch('musicbar', 5),
    wibox.widget.textbox('  |  '),
    awful.widget.watch('wifibar', 5),
    wibox.widget.textbox('  |  '),
    awful.widget.watch('audiobar', 0.5),
    wibox.widget.textbox('  |  '),
    awful.widget.watch('batbar', 5),

In the code above stuff like "audiobar" are scripts that return information as standard output. It all works perfectly, even displays the emojis well :). I have one problem (maybe just an optimization).

Currently I have the audiobar running twice a second, this is because that is the only one which directly changes based on my input (changing volume) and so I want it to change immediately (obviously this still has a <= 0.5 second delay, which is annoying). This means that most of the time it is updating twice a second unnecesarily.

So, I'm wondering if there is a way to have it update when I change the volume, which I've bound to the XF86 audio keys in rc.lua, instead of changing based on a timer. Based on my reading of the documentation, there is no way to do this with the watch widget, but as I said I am new to awesome.

Below is how I bound the keys (shouldn't make a difference, but I imagine that this is where the change would be made).

awful.key(
 {},
 "XF86AudioLowerVolume",
 function()
        awful.spawn("amixer -q set Master 5%-")
 end,
 {description = "lower volume", group = "control"}
),

I know that I can use some of the pre-made widgets on github to display volume and stuff but I like the shell scripts because they let me easily move between WMs and are simpler than those widgets (which I like because it means that I can more easily address problems with them and make them display exactly what I want, as well as learn).

edit: I am willing to learn to do this with lua, I just first want to see if I can easily do it with shell scripts.

E2081114
  • 23
  • 6

2 Answers2

1

You need to keep a reference to the timer around that awful.widget.watch creates internally. To do this, you need to do something like this in global context (i.e. outside of the definitions of the widgets or keybindings):

local musicbar_widget, musicbar_timer = awful.widget.watch('musicbar', 5)

You now add musicbar_widget to your wibox (instead of calling awful.widget.watch there). Now, you can "force-update" the widget via musicbar_timer:emit_signal("timeout"). This "pretends" to the widget that the timeout happened again.

In your keybinding (yes, I am mixing up your widgets here, most likely musicbar has nothing to do with the volume):

awful.key(
 {},
 "XF86AudioLowerVolume",
 function()
        awful.spawn("amixer -q set Master 5%-")
        musicbar_timer:emit_signal("timeout")
 end,
 {description = "lower volume", group = "control"}
),

Note that this might or might not work. awful.spawn only starts the command, but does not wait for it to finish. So now you are changing the volume at the same time that your are querying it. If querying finishes faster than changing the volume, then the update does not actually occur.

To only update the widget after changing the volume is done, do something like the following:

awful.spawn.with_line_callback(
    "amixer -q set Master 5%-", {
    exit = function()
        musicbar_timer:emit_signal("timeout")
    end
})
Uli Schlachter
  • 9,337
  • 1
  • 23
  • 39
1

I ran into a similar problem with streetturtle's volumearc widget. By default, it runs an update command:

  • Everytime the volume is modified through the widget.
  • Every second to catch any external volume changes (like with key bindings defined in rc.lua).
watch(get_volume_cmd, 1, update_graphic, volumearc)

Of course this has the following disadvantages:

  • A slight delay in the volume update (one second in still very perceptible).
  • On my machine, a constant 1% CPU load just for this task. A trifle I know, but little things add up.

Using a returned update function

A possible solution is to return an update function, available in the context of rc.lua and call this update along the volume modifications.

In volumearc.lua, in the worker function, we put the widget update into a dedicated function:

local ext_update = function()
        spawn.easy_async(get_volume_cmd,
            function(stdout, stderr, exitreason, exitcode)
            update_graphic(volumearc, stdout, stderr, exitreason, exitcode) end)
      end

And we return it both from the worker function:

return volumearc, ext_update

and from the widget itself:

volumearc, ext_update = { __call = function(_, ...) return worker(...) end }
return setmetatable(widget, volumearc), ext_update

Now we can use it in rc.lua:

local volumearc_widget = require("widgets.volume-widget.volumearc")
-- ...
local GET_VOLUME = "amixer sget Master"
local INC_VOLUME = "amixer sset Master 3%+"
local DEC_VOLUME = "amixer sset Master 3%-"
local TOG_VOLUME = "amixer sset Master toggle"
myvolume, volume_update = volumearc_widget({get_volume_cmd=GET_VOLUME,
                           inc_volume_cmd=INC_VOLUME,
                           dec_volume_cmd=DEC_VOLUME,
                           tog_volume_cmd=TOG_VOLUME})
-- `myvolume` is the widget that can be added as usual in the wibox.
-- `volume_update` is the function to call in order to trigger an update.
-- ...
-- In the key bindings:
    awful.key({}, "XF86AudioMute",
      function () awful.spawn(TOG_VOLUME) volume_update() end,
      {description="mute", group = "media"}),
    awful.key({}, "XF86AudioRaiseVolume",
      function () awful.spawn(INC_VOLUME) volume_update() end,
      {description="raise volume", group = "media"}),
    awful.key({}, "XF86AudioLowerVolume",
      function () awful.spawn(DEC_VOLUME) volume_update() end,
      {description="lower volume", group="media"}),

Now the volume will be updated both when changed from the widget or from the media keys, instantly and without the need for polling.

Caveat

  • This does not catch changes made through other interfaces, such as alsamixer for example. To catch these changes, you might want to run the watch function with a very low time frequency (say once per minute).
  • The code given here is specific to streetturtle's widget, but the concept of returning an internal update function applies for any widget.
Thomas Houllier
  • 346
  • 2
  • 10