From 25d9eecfc68df3251dc96008aaa4cd7c81900da6 Mon Sep 17 00:00:00 2001 From: streetturtle Date: Fri, 19 Mar 2021 20:49:00 -0400 Subject: [PATCH] [volume] BREAKING CHANGE - new widget instead of old ones Having three widgets for volume led to a problem of code duplication - same logic was duplicated three times. However when an issue was discovered and fixed, it was fixed in only one of three widgets. So I decided to create a volume widget from scratch, adding new features, such as selecting input/output, better responsiveness, easily customizable widget ui (bar, text, icon, icon and text, arc). Should close #199, #198, #185, #182, #47, #122, #183. --- experiments/volume/README.md | 112 ------ experiments/volume/volume.lua | 218 ------------ volume-widget/README.md | 159 +++++---- volume-widget/audio-volume-muted-symbolic.png | Bin 435 -> 0 bytes volume-widget/audio-volume-muted-symbolic.svg | 88 ----- .../audio-volume-muted-symbolic_new.svg | 81 ----- .../audio-volume-muted-symbolic_red.png | Bin 450 -> 0 bytes .../audio-volume-muted-symbolic_red.svg | 88 ----- .../icons/audio-volume-high-symbolic.svg | 0 .../icons/audio-volume-low-symbolic.svg | 0 .../icons/audio-volume-medium-symbolic.svg | 0 .../icons/audio-volume-muted-symbolic.svg | 0 .../screenshots/variations.png | Bin .../screenshots/volume-sink-sources.png | Bin .../volume => volume-widget}/utils.lua | 0 volume-widget/vol-widget-1.png | Bin 824 -> 0 bytes .../volume => volume-widget}/volume-2.svg | 0 volume-widget/volume.lua | 333 ++++++++++-------- .../widgets/arc-widget.lua | 2 +- .../widgets/horizontal-bar-widget.lua | 2 +- .../widgets/icon-and-text-widget.lua | 2 +- .../widgets/icon-widget.lua | 2 +- .../widgets/vertical-bar-widget.lua | 2 +- volumearc-widget/README.md | 69 ---- volumearc-widget/custom.png | Bin 9424 -> 0 bytes volumearc-widget/out.gif | Bin 7288 -> 0 bytes volumearc-widget/volumearc.lua | 140 -------- volumebar-widget/README.md | 85 ----- volumebar-widget/custom.png | Bin 9126 -> 0 bytes volumebar-widget/out.gif | Bin 5983 -> 0 bytes volumebar-widget/volumebar.lua | 87 ----- 31 files changed, 270 insertions(+), 1200 deletions(-) delete mode 100644 experiments/volume/README.md delete mode 100644 experiments/volume/volume.lua delete mode 100644 volume-widget/audio-volume-muted-symbolic.png delete mode 100644 volume-widget/audio-volume-muted-symbolic.svg delete mode 100644 volume-widget/audio-volume-muted-symbolic_new.svg delete mode 100644 volume-widget/audio-volume-muted-symbolic_red.png delete mode 100644 volume-widget/audio-volume-muted-symbolic_red.svg rename {experiments/volume => volume-widget}/icons/audio-volume-high-symbolic.svg (100%) rename {experiments/volume => volume-widget}/icons/audio-volume-low-symbolic.svg (100%) rename {experiments/volume => volume-widget}/icons/audio-volume-medium-symbolic.svg (100%) rename {experiments/volume => volume-widget}/icons/audio-volume-muted-symbolic.svg (100%) rename {experiments/volume => volume-widget}/screenshots/variations.png (100%) rename {experiments/volume => volume-widget}/screenshots/volume-sink-sources.png (100%) rename {experiments/volume => volume-widget}/utils.lua (100%) delete mode 100644 volume-widget/vol-widget-1.png rename {experiments/volume => volume-widget}/volume-2.svg (100%) rename {experiments/volume => volume-widget}/widgets/arc-widget.lua (95%) rename {experiments/volume => volume-widget}/widgets/horizontal-bar-widget.lua (97%) rename {experiments/volume => volume-widget}/widgets/icon-and-text-widget.lua (97%) rename {experiments/volume => volume-widget}/widgets/icon-widget.lua (96%) rename {experiments/volume => volume-widget}/widgets/vertical-bar-widget.lua (98%) delete mode 100644 volumearc-widget/README.md delete mode 100644 volumearc-widget/custom.png delete mode 100644 volumearc-widget/out.gif delete mode 100644 volumearc-widget/volumearc.lua delete mode 100644 volumebar-widget/README.md delete mode 100644 volumebar-widget/custom.png delete mode 100644 volumebar-widget/out.gif delete mode 100644 volumebar-widget/volumebar.lua diff --git a/experiments/volume/README.md b/experiments/volume/README.md deleted file mode 100644 index 973bb49..0000000 --- a/experiments/volume/README.md +++ /dev/null @@ -1,112 +0,0 @@ -# Volume widget - -Volume widget based on [amixer](https://linux.die.net/man/1/amixer) (is used for controlling the audio volume) and [pacmd](https://linux.die.net/man/1/pacmd) (is used for selecting a sink/source). Also, the widget provides an easy way to customize how it looks, following types are supported out-of-the-box: - -![types](./screenshots/variations.png) - -From left to right: `horizontal_bar`, `vertical_bar`, `icon`, `icon_and_text`, `arc` - -A right-click on the widget opens a popup where you can choose a sink/source: -![sink-sources](./screenshots/volume-sink-sources.png) - -### Features - - - switch between sinks/sources by right clicking on the widget; - - more responsive than previous versions of volume widget, which were refreshed once a second; - - 5 predefined customizable looks; - -## Installation - -Clone the repo under **~/.config/awesome/** and add widget in **rc.lua**: - -```lua -local volume_widget = require('awesome-wm-widgets.experiments.volume.volume') -... -s.mytasklist, -- Middle widget - { -- Right widgets - layout = wibox.layout.fixed.horizontal, - ... - -- default - volume_widget(), - -- customized - volume_widget{ - type = 'arc' - }, -``` - -### Shortcuts - -To improve responsiveness of the widget when volume level is changed by a shortcut use corresponding methods of the widget: - -```lua -awful.key({ modkey }, "]", function() volume_widget:inc() end), -awful.key({ modkey }, "[", function() volume_widget:dec() end), -awful.key({ modkey }, "\\", function() volume_widget:toggle() end), -``` - -## Customization - -It is possible to customize the widget by providing a table with all or some of the following config parameters: - -### Generic parameter - -| Name | Default | Description | -|---|---|---| -| `type`| `icon_and_text`| Widget type, one of `horizontal_bar`, `vertical_bar`, `icon`, `icon_and_text`, `arc` | - -Depending on the chosen widget type add parameters from the corresponding section below: - -#### `icon` parameters - -| Name | Default | Description | -|---|---|---| -| `icon_dir`| `./icons`| Path to the folder with icons | - -_Note:_ if you are changing icons, the folder should contain following .svg images: - - audio-volume-high-symbolic - - audio-volume-medium-symbolic - - audio-volume-low-symbolic - - audio-volume-muted-symbolic - -#### `icon_and_text` parameters - -| Name | Default | Description | -|---|---|---| -| `icon_dir`| `./icons`| Path to the folder with icons | -| `font` | `beautiful.font` | Font name and size, like `Play 12` | - -#### `arc` parameters - -| Name | Default | Description | -|---|---|---| -| `thickness` | 2 | Thickness of the arc | -| `main_color` | `beautiful.fg_color` | Color of the arc | -| `bg_color` | `#ffffff11` | Color of the arc's background | -| `mute_color` | `beautiful.fg_urgent` | Color of the arc when mute | -| `size` | 18 | Size of the widget | - -#### `horizontal_bar` parameters - -| Name | Default | Description | -|---|---|---| -| `main_color` | `beautiful.fg_normal` | Color of the bar | -| `mute_color` | `beautiful.fg_urgent` | Color of the bar when mute | -| `bg_color` | `'#ffffff11'` | Color of the bar's background | -| `width` | `50` | The bar width | -| `margins` | `10` | Top and bottom margins (if your wibar is 22 px high, bar will be 2 px = 22 - 2*10) | -| `shape` | `'bar'` | [gears.shape](https://awesomewm.org/doc/api/libraries/gears.shape.html), could be `octogon`, `hexagon`, `powerline`, etc | -| `with_icon` | `true` | Show volume icon| - -_Note:_ I didn't figure out how does the `forced_height` property of progressbar widget work (maybe it doesn't work at all), thus there is a workaround with margins. - -#### `vertical_bar` parameters - -| Name | Default | Description | -|---|---|---| -| `main_color` | `beautiful.fg_normal` | Color of the bar | -| `mute_color` | `beautiful.fg_urgent` | Color of the bar when mute | -| `bg_color` | `'#ffffff11'` | Color of the bar's background | -| `width` | `10` | The bar width | -| `margins` | `20` | Top and bottom margins (if your wibar is 22 px high, bar will be 2 px = 22 - 2*10) | -| `shape` | `'bar'` | [gears.shape](https://awesomewm.org/doc/api/libraries/gears.shape.html), could be `octogon`, `hexagon`, `powerline`, etc | -| `with_icon` | `true` | Show volume icon| diff --git a/experiments/volume/volume.lua b/experiments/volume/volume.lua deleted file mode 100644 index 7bdda1c..0000000 --- a/experiments/volume/volume.lua +++ /dev/null @@ -1,218 +0,0 @@ -------------------------------------------------- --- 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 --- @copyright 2020 Pavel Makhov -------------------------------------------------- - -local awful = require("awful") -local wibox = require("wibox") -local spawn = require("awful.spawn") -local gears = require("gears") -local beautiful = require("beautiful") -local watch = require("awful.widget.watch") -local utils = require("awesome-wm-widgets.experiments.volume.utils") - - -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' - - -local widget_types = { - icon_and_text = require("awesome-wm-widgets.experiments.volume.widgets.icon-and-text-widget"), - icon = require("awesome-wm-widgets.experiments.volume.widgets.icon-widget"), - arc = require("awesome-wm-widgets.experiments.volume.widgets.arc-widget"), - horizontal_bar = require("awesome-wm-widgets.experiments.volume.widgets.horizontal-bar-widget"), - vertical_bar = require("awesome-wm-widgets.experiments.volume.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 - return device.properties.device_description - 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 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 = "" .. text .. "", - 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) - - local args = user_args or {} - - 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 - - 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 - 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 - - function volume:inc() - spawn.easy_async(INC_VOLUME_CMD, function(stdout) update_graphic(volume.widget, stdout) end) - end - - function volume:dec() - spawn.easy_async(DEC_VOLUME_CMD, function(stdout) update_graphic(volume.widget, stdout) end) - 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 - -return setmetatable(volume, { __call = function(_, ...) return worker(...) end }) diff --git a/volume-widget/README.md b/volume-widget/README.md index aebcf98..d08b795 100644 --- a/volume-widget/README.md +++ b/volume-widget/README.md @@ -1,109 +1,112 @@ # Volume widget -Simple and easy-to-install widget for Awesome Window Manager which shows the sound level: ![Volume Widget](./vol-widget-1.png) +Volume widget based on [amixer](https://linux.die.net/man/1/amixer) (is used for controlling the audio volume) and [pacmd](https://linux.die.net/man/1/pacmd) (is used for selecting a sink/source). Also, the widget provides an easy way to customize how it looks, following types are supported out-of-the-box: -Note that widget uses the Arc icon theme, so it should be [installed](https://github.com/horst3180/arc-icon-theme#installation) first under **/usr/share/icons/Arc/** folder. +![types](screenshots/variations.png) -## Customization +From left to right: `horizontal_bar`, `vertical_bar`, `icon`, `icon_and_text`, `arc` -It is possible to customize widget by providing a table with all or some of the following config parameters: +A right-click on the widget opens a popup where you can choose a sink/source: +![sink-sources](screenshots/volume-sink-sources.png) -| Name | Default | Description | -|---|---|---| -| `volume_audio_controller`| `pulse` | audio device | -| `display_notification` | `false` | Display a notification on mouseover and keypress | -| `notification_position` | `top_right`| The notification position | -| `delta` | 5 | The volume +/- percentage | +### Features + + - switch between sinks/sources by right clicking on the widget; + - more responsive than previous versions of volume widget, which were refreshed once a second; + - 5 predefined customizable looks; ## Installation -- clone/copy **volume.lua** file; - -- include `volume.lua` and add volume widget to your wibox in rc.lua: +Clone the repo under **~/.config/awesome/** and add widget in **rc.lua**: ```lua -local volume_widget = require("awesome-wm-widgets.volume-widget.volume") -local volume_widget_widget = volume_widget({display_notification = true}) +local volume_widget = require('awesome-wm-widgets.volume-widget.volume') ... - s.mytasklist, -- Middle widget - { -- Right widgets - ... - volume_widget_widget, - ... - +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + volume_widget(), + -- customized + volume_widget{ + type = 'arc' + }, ``` -### Control volume -To mute/unmute click on the widget. To increase/decrease volume scroll up or down when mouse cursor is over the widget. +### Shortcuts -If you want to control volume level by keyboard shortcuts add following lines in shortcut section of the **rc.lua**: -IF you have notification activated, a notification will pop-up on key press +To improve responsiveness of the widget when volume level is changed by a shortcut use corresponding methods of the widget: ```lua --- Key bindings -globalkeys = gears.table.join( - awful.key( - {}, - 'XF86AudioRaiseVolume', - volume_widget.raise, - {description = 'volume up', group = 'hotkeys'} - ), - awful.key( - {}, - 'XF86AudioLowerVolume', - volume_widget.lower, - {description = 'volume down', group = 'hotkeys'} - ), - awful.key( - {}, - 'XF86AudioMute', - volume_widget.toggle, - {description = 'toggle mute', group = 'hotkeys'} - ), +awful.key({ modkey }, "]", function() volume_widget:inc() end), +awful.key({ modkey }, "[", function() volume_widget:dec() end), +awful.key({ modkey }, "\\", function() volume_widget:toggle() end), ``` -### Icons +## Customization -- _Optional step._ In Arc icon theme the muted audio level icon (![Volume-widget](./audio-volume-muted-symbolic.png)) looks like 0 level icon, which could be a bit misleading. - So I decided to use original muted icon for low audio level, and the same icon, but colored in red for muted audio level. Fortunately icons are in svg format, so you can easily recolor them with `sed`, so it would look like this (![Volume Widget](./audio-volume-muted-symbolic_red.png)): +It is possible to customize the widget by providing a table with all or some of the following config parameters: - ```bash - cd /usr/share/icons/Arc/status/symbolic && - sudo cp audio-volume-muted-symbolic.svg audio-volume-muted-symbolic_red.svg && - sudo sed -i 's/bebebe/ed4737/g' ./audio-volume-muted-symbolic_red.svg - ``` +### Generic parameter -### Pulse or ALSA only +| Name | Default | Description | +|---|---|---| +| `type`| `icon_and_text`| Widget type, one of `horizontal_bar`, `vertical_bar`, `icon`, `icon_and_text`, `arc` | -Try running this command: +Depending on the chosen widget type add parameters from the corresponding section below: -```bash -amixer -D pulse sget Master -``` +#### `icon` parameters -If that prints something like this, then the default setting of 'pulse' is probably fine: +| Name | Default | Description | +|---|---|---| +| `icon_dir`| `./icons`| Path to the folder with icons | -``` -Simple mixer control 'Master',0 - Capabilities: pvolume pvolume-joined pswitch pswitch-joined - Playback channels: Mono - Limits: Playback 0 - 64 - Mono: Playback 64 [100%] [0.00dB] [on] +_Note:_ if you are changing icons, the folder should contain following .svg images: + - audio-volume-high-symbolic + - audio-volume-medium-symbolic + - audio-volume-low-symbolic + - audio-volume-muted-symbolic -``` +#### `icon_and_text` parameters -If it prints something like this: +| Name | Default | Description | +|---|---|---| +| `icon_dir`| `./icons`| Path to the folder with icons | +| `font` | `beautiful.font` | Font name and size, like `Play 12` | -```bash -$ amixer -D pulse sget Master -ALSA lib pulse.c:243:(pulse_connect) PulseAudio: Unable to connect: Connection refused +#### `arc` parameters -amixer: Mixer attach pulse error: Connection refused -``` -then set `volume_audio_controller` to `alsa_only` in widget constructor: +| Name | Default | Description | +|---|---|---| +| `thickness` | 2 | Thickness of the arc | +| `main_color` | `beautiful.fg_color` | Color of the arc | +| `bg_color` | `#ffffff11` | Color of the arc's background | +| `mute_color` | `beautiful.fg_urgent` | Color of the arc when mute | +| `size` | 18 | Size of the widget | -```lua -volume_widget({ - volume_audio_controller = 'alsa_only' -}) -``` +#### `horizontal_bar` parameters + +| Name | Default | Description | +|---|---|---| +| `main_color` | `beautiful.fg_normal` | Color of the bar | +| `mute_color` | `beautiful.fg_urgent` | Color of the bar when mute | +| `bg_color` | `'#ffffff11'` | Color of the bar's background | +| `width` | `50` | The bar width | +| `margins` | `10` | Top and bottom margins (if your wibar is 22 px high, bar will be 2 px = 22 - 2*10) | +| `shape` | `'bar'` | [gears.shape](https://awesomewm.org/doc/api/libraries/gears.shape.html), could be `octogon`, `hexagon`, `powerline`, etc | +| `with_icon` | `true` | Show volume icon| + +_Note:_ I didn't figure out how does the `forced_height` property of progressbar widget work (maybe it doesn't work at all), thus there is a workaround with margins. + +#### `vertical_bar` parameters + +| Name | Default | Description | +|---|---|---| +| `main_color` | `beautiful.fg_normal` | Color of the bar | +| `mute_color` | `beautiful.fg_urgent` | Color of the bar when mute | +| `bg_color` | `'#ffffff11'` | Color of the bar's background | +| `width` | `10` | The bar width | +| `margins` | `20` | Top and bottom margins (if your wibar is 22 px high, bar will be 2 px = 22 - 2*10) | +| `shape` | `'bar'` | [gears.shape](https://awesomewm.org/doc/api/libraries/gears.shape.html), could be `octogon`, `hexagon`, `powerline`, etc | +| `with_icon` | `true` | Show volume icon| diff --git a/volume-widget/audio-volume-muted-symbolic.png b/volume-widget/audio-volume-muted-symbolic.png deleted file mode 100644 index b9dffd6440c593283f895948fdae9b33fc2f5a9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 435 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMf`~aU2S0MfW|Ns5__s^U;^URqu zSFc{3G-(o$3uN!yxpUjLZ6{8g*u8rPwWr5;AHbfTAhH!!ApaG@QW%q$jG${%43ua(qVP$1w=iuVzD~i!nmk<`LnJOICmdjJQAyCrH8{CyhPrXcs~lLn^xU!~ss|RN zXE3qw=xmZHih8st>Jkr|S^}e{u4J69tDdeXTk?hmv)$9ACQoA%aAYk}WIE%(SR0~y zipR=Lz**Gd>DD7h8`=)7eaaYpy8mjTgP_Us!wm~m#N?S7yjKgyw9UQY2XqaCr>mdK II;Vst0ICYcwg3PC diff --git a/volume-widget/audio-volume-muted-symbolic.svg b/volume-widget/audio-volume-muted-symbolic.svg deleted file mode 100644 index e577d05..0000000 --- a/volume-widget/audio-volume-muted-symbolic.svg +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/volume-widget/audio-volume-muted-symbolic_new.svg b/volume-widget/audio-volume-muted-symbolic_new.svg deleted file mode 100644 index 504c146..0000000 --- a/volume-widget/audio-volume-muted-symbolic_new.svg +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/volume-widget/audio-volume-muted-symbolic_red.png b/volume-widget/audio-volume-muted-symbolic_red.png deleted file mode 100644 index f47807d9e9cb711941efe32ab71176c929a1a609..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 450 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMf(g2?jS0Merf#JV{!x>}a|3DO~ zecMw7$bIH({K8G|t*7}r7vtxihA*AXzj&Fxb~k?GWcl99>bZx-OBd6ZPR6f2OrE(J zzi~5u?raWJ3{>#i&HRmr=^J+wpx|2%vsZ4WuU$=n%0PPE%wD=0zjZZ#>u&nW&E%b% z$tzcr*KVfo+%4XESiEsJd+P?2LP!7q|6h@#b{6PR(~=;+U>h}+z0gOrB?k;BT z`#UZIIbEJEjv*44YyHFdngSSBFIZqPkDJ{u#zuEX_5amB8IK$h_ - - - - - image/svg+xml - - - - - - - - - - - - - - - diff --git a/experiments/volume/icons/audio-volume-high-symbolic.svg b/volume-widget/icons/audio-volume-high-symbolic.svg similarity index 100% rename from experiments/volume/icons/audio-volume-high-symbolic.svg rename to volume-widget/icons/audio-volume-high-symbolic.svg diff --git a/experiments/volume/icons/audio-volume-low-symbolic.svg b/volume-widget/icons/audio-volume-low-symbolic.svg similarity index 100% rename from experiments/volume/icons/audio-volume-low-symbolic.svg rename to volume-widget/icons/audio-volume-low-symbolic.svg diff --git a/experiments/volume/icons/audio-volume-medium-symbolic.svg b/volume-widget/icons/audio-volume-medium-symbolic.svg similarity index 100% rename from experiments/volume/icons/audio-volume-medium-symbolic.svg rename to volume-widget/icons/audio-volume-medium-symbolic.svg diff --git a/experiments/volume/icons/audio-volume-muted-symbolic.svg b/volume-widget/icons/audio-volume-muted-symbolic.svg similarity index 100% rename from experiments/volume/icons/audio-volume-muted-symbolic.svg rename to volume-widget/icons/audio-volume-muted-symbolic.svg diff --git a/experiments/volume/screenshots/variations.png b/volume-widget/screenshots/variations.png similarity index 100% rename from experiments/volume/screenshots/variations.png rename to volume-widget/screenshots/variations.png diff --git a/experiments/volume/screenshots/volume-sink-sources.png b/volume-widget/screenshots/volume-sink-sources.png similarity index 100% rename from experiments/volume/screenshots/volume-sink-sources.png rename to volume-widget/screenshots/volume-sink-sources.png diff --git a/experiments/volume/utils.lua b/volume-widget/utils.lua similarity index 100% rename from experiments/volume/utils.lua rename to volume-widget/utils.lua diff --git a/volume-widget/vol-widget-1.png b/volume-widget/vol-widget-1.png deleted file mode 100644 index 6f09cce7b84193e8074dc75d049d41c8c16fa9dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 824 zcmV-81IPS{P)4m91G=>@R4K!eh_xKvzD zB$RkOo(4?wLf;vsXk-{gR*&4!8A4JbqtZZAk|b)0EtnibCm`a0BN}a4Sy^JT%lQt3 z-gW7sly^!gCrOSFKR5s7E6=O68qJn>OJr9fS|o!opit{{SBLK$HJLymerFpSGoS%< zc63-R=ALfb`{@q=>T0bXuNQzysg!-x#kyN{603yxz23P;PrWV|h*%J$u#n$* zn#rjt0M*qeR#sL3==J(YI1GT}IN8nb@ytDZ;@xnGEU^Hhq42+@<-G}rMxzSp$`FPD zK;!X*A}53(77DK}Efc9S8JbPDeZCCz*W+*6TABek9KTG*jse(kxmcD35Q#*xS33nm z2sxxIFj&n6g*=gDX7*z&7R%URY+_<@>Boy#Bkip%0KP3O3TicgKrkrZUslPLTTXI3 zpS;_=MyMa?74#)i+A%sdK6vw1|B(IV$Y?UB{=o442P&1)YB2`_f$5o9twsZ2)9p^y zl_1n#?^Bl)CrZaqS(Wv|%hJ()p{Lu{KV+9JO%3(i+rHm_{}>I1U@-L4;h=Gv)(@n@ zWcO%&qP(4aC+1fqkP!0e^Spv(%{4Wka5~qLRlcL$Bw{BAX|xi$_**Lo1uEtG+B!%U z=zRM-P3*ntal^e&G)!J^2ThBX?*1Mi525>nEX#jPB_IdfVZ!nN0000@{ diff --git a/experiments/volume/volume-2.svg b/volume-widget/volume-2.svg similarity index 100% rename from experiments/volume/volume-2.svg rename to volume-widget/volume-2.svg 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 gears = require("gears") +local beautiful = require("beautiful") +local watch = require("awful.widget.watch") +local utils = require("awesome-wm-widgets.volume-widget.utils") -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 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' + + +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 = {} } -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 - ---{{{ 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.."% Mute" - 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 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" - 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) + return device.properties.device_description 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 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 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 = "" .. text .. "", + 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 - 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 - 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 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 - local function show() - spawn.easy_async(GET_VOLUME_CMD, function(stdout, _, _, _) - local txt = parse_output(stdout) - notif(txt, true) + 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 - ) + 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 ---}}} ---{{{ 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) - 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:inc() + spawn.easy_async(INC_VOLUME_CMD, function(stdout) update_graphic(volume.widget, stdout) end) + end + + function volume:dec() + spawn.easy_async(DEC_VOLUME_CMD, function(stdout) update_graphic(volume.widget, stdout) end) + 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 diff --git a/experiments/volume/widgets/arc-widget.lua b/volume-widget/widgets/arc-widget.lua similarity index 95% rename from experiments/volume/widgets/arc-widget.lua rename to volume-widget/widgets/arc-widget.lua index b6c9d22..b512f12 100644 --- a/experiments/volume/widgets/arc-widget.lua +++ b/volume-widget/widgets/arc-widget.lua @@ -1,7 +1,7 @@ local wibox = require("wibox") local beautiful = require('beautiful') -local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/experiments/volume/icons/' +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/volume-widget/icons/' local widget = {} diff --git a/experiments/volume/widgets/horizontal-bar-widget.lua b/volume-widget/widgets/horizontal-bar-widget.lua similarity index 97% rename from experiments/volume/widgets/horizontal-bar-widget.lua rename to volume-widget/widgets/horizontal-bar-widget.lua index 1a1c8a4..be1f38d 100644 --- a/experiments/volume/widgets/horizontal-bar-widget.lua +++ b/volume-widget/widgets/horizontal-bar-widget.lua @@ -2,7 +2,7 @@ local wibox = require("wibox") local beautiful = require('beautiful') local gears = require("gears") -local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/experiments/volume/icons/' +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/volume-widget/icons/' local widget = {} diff --git a/experiments/volume/widgets/icon-and-text-widget.lua b/volume-widget/widgets/icon-and-text-widget.lua similarity index 97% rename from experiments/volume/widgets/icon-and-text-widget.lua rename to volume-widget/widgets/icon-and-text-widget.lua index 0ee8d16..b1a2793 100644 --- a/experiments/volume/widgets/icon-and-text-widget.lua +++ b/volume-widget/widgets/icon-and-text-widget.lua @@ -3,7 +3,7 @@ local beautiful = require('beautiful') local widget = {} -local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/experiments/volume/icons/' +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/volume-widget/icons/' function widget.get_widget(widgets_args) local args = widgets_args or {} diff --git a/experiments/volume/widgets/icon-widget.lua b/volume-widget/widgets/icon-widget.lua similarity index 96% rename from experiments/volume/widgets/icon-widget.lua rename to volume-widget/widgets/icon-widget.lua index f2aca26..cc39a3d 100644 --- a/experiments/volume/widgets/icon-widget.lua +++ b/volume-widget/widgets/icon-widget.lua @@ -2,7 +2,7 @@ local wibox = require("wibox") local widget = {} -local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/experiments/volume/icons/' +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/volume-widget/icons/' function widget.get_widget(widgets_args) local args = widgets_args or {} diff --git a/experiments/volume/widgets/vertical-bar-widget.lua b/volume-widget/widgets/vertical-bar-widget.lua similarity index 98% rename from experiments/volume/widgets/vertical-bar-widget.lua rename to volume-widget/widgets/vertical-bar-widget.lua index 82f4b8a..6f32b50 100644 --- a/experiments/volume/widgets/vertical-bar-widget.lua +++ b/volume-widget/widgets/vertical-bar-widget.lua @@ -2,7 +2,7 @@ local wibox = require("wibox") local beautiful = require('beautiful') local gears = require("gears") -local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/experiments/volume/icons/' +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/volume-widget/icons/' local widget = {} diff --git a/volumearc-widget/README.md b/volumearc-widget/README.md deleted file mode 100644 index 496797e..0000000 --- a/volumearc-widget/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# Volumearc widget - -Almost the same as [volumebar widget](https://github.com/streetturtle/awesome-wm-widgets/tree/master/volumebar-widget), but using [arcchart](https://awesomewm.org/doc/api/classes/wibox.container.arcchart.html): - -![screenshot](./out.gif) - -Supports - - scroll up - increase volume, - - scroll down - decrease volume, - - left click - mute/unmute. - -## Customization - -It is possible to customize widget by providing a table with all or some of the following config parameters: - -| Name | Default | Description | -|---|---|---| -| `main_color` | `beautiful.fg_normal` | Color of the arc | -| `bg_color` | `#ffffff11` | Color of the arc's background | -| `mute_color` | `beautiful.fg_urgent` | Color of the arc when mute | -| `path_to_icon` | /usr/share/icons/Arc/status/symbolic/audio-volume-muted-symbolic.svg | Path to the icon | -| `thickness` | 2 | The arc thickness | -| `height` | `beautiful.fg_normal` | Widget height | -| `timeout` | 1 | How often in seconds the widget refreshes | -| `get_volume_cmd` | `amixer -D pulse sget Master` | Get current volume level | -| `inc_volume_cmd` | `amixer -D pulse sset Master 5%+` | Increase volume level | -| `dec_volume_cmd` | `amixer -D pulse sset Master 5%-` | Decrease volume level | -| `tog_volume_cmd` | `amixer -D pulse sset Master toggle` | Mute / unmute | -| `button_press` | `function(_, _, _, button) end` | Overwrite the 'button\_press' signal for this widget | - -### Example: - -```lua -volumearc_widget({ - main_color = '#af13f7', - mute_color = '#ff0000', - thickness = 5, - height = 25, - button_press = function(_, _, _, button) -- Overwrites the button press behaviour to open pavucontrol when clicked - if (button == 1) then awful.spawn('pavucontrol --tab=3', false) - end - end -}) -``` - -The config above results in the following widget: - -![custom](./custom.png) - -## Installation - -1. Clone this repo under **~/.config/awesome/** - - ```bash - git clone https://github.com/streetturtle/awesome-wm-widgets.git ~/.config/awesome/awesome-wm-widgets - ``` - -1. Require volumearc widget at the beginning of **rc.lua**: - -```lua -local volumearc_widget = require("awesome-wm-widgets.volumearc-widget.volumearc") -... -s.mytasklist, -- Middle widget - { -- Right widgets - layout = wibox.layout.fixed.horizontal, - ... - volumearc_widget(), - ... -``` diff --git a/volumearc-widget/custom.png b/volumearc-widget/custom.png deleted file mode 100644 index f1873452f33ce862bdbe3fdf48f070f504717acb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9424 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