diff --git a/awesome/components/pastel/top-panel.lua b/awesome/components/pastel/top-panel.lua index 17faa60..465c97c 100644 --- a/awesome/components/pastel/top-panel.lua +++ b/awesome/components/pastel/top-panel.lua @@ -16,6 +16,8 @@ local wibox = require("wibox") local theme = require("theme") local lain = require("lain") local dpi = beautiful.xresources.apply_dpi +local battery_widget = require("widgets.battery-widget.battery") +local volume_widget = require("widgets.volume-widget.volume") -- import widgets local task_list = require("widgets.task-list") @@ -67,9 +69,17 @@ top_panel.create = function(s) { layout = wibox.layout.fixed.horizontal, wibox.layout.margin(wibox.widget.systray(), dpi(5), dpi(5), dpi(5), dpi(5)), + wibox.widget.textbox(' '), + volume_widget({widget_type='arc'}), + wibox.widget.textbox(' '), + battery_widget({font=theme.font, display_notification=true}), wibox.widget.textbox(' | '), updates_indicator, wibox.widget.textbox(' | '), + awful.widget.watch('bash -c "cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"', 5), + wibox.widget.textbox(' | '), + wibox.layout.margin(require("widgets.layout-box"), dpi(5), dpi(5), dpi(5), dpi(5)), + wibox.widget.textbox(' | '), lain.widget.mem({ settings = function() widget:set_markup(lain.util.markup.font(theme.font, mem_now.used .. " MB")) @@ -85,10 +95,7 @@ top_panel.create = function(s) awful.widget.watch('bash -c "/usr/bin/cat /proc/cpuinfo | grep MHz | cut -b 12- | sort -r | head -n 1 | xargs printf \'%f / 1000\n\' | bc -l | cut -b -4"', 1), wibox.widget.textbox('GHz | '), awful.widget.watch('bash -c "sensors | grep Package | cut -f 5 -d \' \' | cut -c 2-"', 5), - wibox.widget.textbox(' | '), - awful.widget.watch('bash -c "cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor"', 5), - wibox.widget.textbox(' | '), - wibox.layout.margin(require("widgets.layout-box"), dpi(5), dpi(5), dpi(5), dpi(5)), + wibox.widget.textbox(' '), } } diff --git a/awesome/components/volume-adjust.lua b/awesome/components/volume-adjust.lua deleted file mode 100644 index e456e5c..0000000 --- a/awesome/components/volume-adjust.lua +++ /dev/null @@ -1,95 +0,0 @@ --- ██╗ ██╗ ██████╗ ██╗ ██╗ ██╗███╗ ███╗███████╗ --- ██║ ██║██╔═══██╗██║ ██║ ██║████╗ ████║██╔════╝ --- ██║ ██║██║ ██║██║ ██║ ██║██╔████╔██║█████╗ --- ╚██╗ ██╔╝██║ ██║██║ ██║ ██║██║╚██╔╝██║██╔══╝ --- ╚████╔╝ ╚██████╔╝███████╗╚██████╔╝██║ ╚═╝ ██║███████╗ --- ╚═══╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ - - --- =================================================================== --- Initialization --- =================================================================== - - -local wibox = require("wibox") -local awful = require("awful") -local gears = require("gears") -local beautiful = require("beautiful") -local dpi = beautiful.xresources.apply_dpi - -local offsetx = dpi(56) -local offsety = dpi(300) -local screen = awful.screen.focused() -local icon_dir = gears.filesystem.get_configuration_dir() .. "/icons/volume/" .. beautiful.name .. "/" - - --- =================================================================== --- Appearance & Functionality --- =================================================================== - - --- create the volume_adjust component -local volume_adjust = wibox({ - screen = awful.screen.focused(), - x = screen.geometry.width - offsetx, - y = (screen.geometry.height / 2) - (offsety / 2), - width = dpi(48), - height = offsety, - shape = gears.shape.rounded_rect, - visible = false, - ontop = true -}) - -local volume_bar = wibox.widget{ - widget = wibox.widget.progressbar, - shape = gears.shape.rounded_bar, - color = "#efefef", - background_color = beautiful.bg_focus, - max_value = 100, - value = 0 -} - -volume_adjust:setup { - layout = wibox.layout.align.vertical, - { - wibox.container.margin( - volume_bar, dpi(14), dpi(20), dpi(20), dpi(20) - ), - forced_height = offsety, - direction = "east", - layout = wibox.container.rotate - }, -} - --- create a 4 second timer to hide the volume adjust --- component whenever the timer is started -local hide_volume_adjust = gears.timer { - timeout = 4, - autostart = true, - callback = function() - volume_adjust.visible = false - end -} - --- show volume-adjust when "volume_change" signal is emitted -awesome.connect_signal("volume_change", - function() - -- set new volume value - awful.spawn.easy_async_with_shell( - "amixer sget Master | grep 'Right:' | awk -F '[][]' '{print $2}'| sed 's/[^0-9]//g'", - function(stdout) - local volume_level = tonumber(stdout) - volume_bar.value = volume_level - end, - false - ) - - -- make volume_adjust component visible - if volume_adjust.visible then - hide_volume_adjust:again() - else - volume_adjust.visible = true - hide_volume_adjust:start() - end - end -) diff --git a/awesome/keys.lua b/awesome/keys.lua index 64c7b8e..c503cba 100644 --- a/awesome/keys.lua +++ b/awesome/keys.lua @@ -15,6 +15,7 @@ local awful = require("awful") local gears = require("gears") local naughty = require("naughty") local beautiful = require("beautiful") +local volume_widget = require("widgets.volume-widget.volume") local dpi = beautiful.xresources.apply_dpi -- Define mod keys @@ -188,22 +189,19 @@ keys.globalkeys = gears.table.join( -- ALSA volume control awful.key({}, "XF86AudioRaiseVolume", function() - awful.spawn("amixer -D pulse sset Master 5%+", false) - awesome.emit_signal("volume_change") + volume_widget:inc(5) end, {description = "volume up", group = "hotkeys"} ), awful.key({}, "XF86AudioLowerVolume", function() - awful.spawn("amixer -D pulse sset Master 5%-", false) - awesome.emit_signal("volume_change") + volume_widget:dec(5) end, {description = "volume down", group = "hotkeys"} ), awful.key({}, "XF86AudioMute", function() - awful.spawn("amixer -D pulse set Master 1+ toggle", false) - awesome.emit_signal("volume_change") + volume_widget:toggle() end, {description = "toggle mute", group = "hotkeys"} ), diff --git a/awesome/pastel.lua b/awesome/pastel.lua index 3fa5a61..b223082 100644 --- a/awesome/pastel.lua +++ b/awesome/pastel.lua @@ -27,7 +27,6 @@ pastel.initialize = function() -- Import components require("components.pastel.wallpaper") require("components.exit-screen") - require("components.volume-adjust") -- Import panels local top_panel = require("components.pastel.top-panel") diff --git a/awesome/rc.lua b/awesome/rc.lua index e03f192..288cc18 100644 --- a/awesome/rc.lua +++ b/awesome/rc.lua @@ -38,7 +38,8 @@ network_interfaces = { -- List of apps to run on start-up local run_on_start_up = { "numlockx on", - "bluetoothctl power on" + "bluetoothctl power on", + "nm-applet", } -- =================================================================== diff --git a/awesome/theme.lua b/awesome/theme.lua index 103274b..bbe3edf 100644 --- a/awesome/theme.lua +++ b/awesome/theme.lua @@ -1,4 +1,4 @@ --- ████████╗██╗ ██╗███████╗███╗ ███╗███████╗ +-- ████████╗██╗ ██╗███████╗███╗ ███╗███████╗theme -- ╚══██╔══╝██║ ██║██╔════╝████╗ ████║██╔════╝ -- ██║ ███████║█████╗ ██╔████╔██║█████╗ -- ██║ ██╔══██║██╔══╝ ██║╚██╔╝██║██╔══╝ @@ -95,7 +95,7 @@ theme.titlebars_enabled = false -- theme.layout_floating = "~/.config/awesome/icons/layouts/floating.png" -- theme.layout_max = "~/.config/awesome/icons/layouts/maximized.png" -theme.icon_theme = "Tela-dark" +theme.icon_theme = "Papirus" -- return theme return theme diff --git a/awesome/widgets/battery-widget/README.md b/awesome/widgets/battery-widget/README.md new file mode 100644 index 0000000..b15aac6 --- /dev/null +++ b/awesome/widgets/battery-widget/README.md @@ -0,0 +1,75 @@ +# Battery widget + +Simple and easy-to-install widget for Awesome Window Manager. + +This widget consists of: + + - an icon which shows the battery level: + ![Battery Widget](./bat-wid-1.png) + - a pop-up window, which shows up when you hover over an icon: + ![Battery Widget](./bat-wid-2.png) + Alternatively you can use a tooltip (check the code): + ![Battery Widget](./bat-wid-22.png) + - a pop-up warning message which appears on bottom right corner when battery level is less that 15% (you can get the image [here](https://vk.com/images/stickers/1933/512.png)): + ![Battery Widget](./bat-wid-3.png) + +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. + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `font` | Play 8 | Fond | +| `path_to_icons` | `/usr/share/icons/Arc/status/symbolic/` | Path to the folder with icons* | +| `show_current_level`| false | Show current charge level | +| `margin_right`|0| The right margin of the widget| +| `margin_left`|0| The left margin of the widget| +| `display_notification` | `false` | Display a notification on mouseover | +| `notification_position` | `top_right` | The notification position | +| `timeout` | 10 | How often in seconds the widget refreshes | +| `warning_msg_title` | _Huston, we have a problem_ | Title of the warning popup | +| `warning_msg_text` | _Battery is dying_ | Text of the warning popup | +| `warning_msg_position` | `bottom_right` | Position of the warning popup | +| `warning_msg_icon` | ~/.config/awesome/awesome-wm-widgets/battery-widget/spaceman.jpg | Icon of the warning popup | +| `enable_battery_warning` | `true` | Display low battery warning | + +*Note: the widget expects following icons to be present in the folder: + + - battery-caution-charging-symbolic.svg + - battery-empty-charging-symbolic.svg + - battery-full-charged-symbolic.svg + - battery-full-symbolic.svg + - battery-good-symbolic.svg + - battery-low-symbolic.svg + - battery-caution-symbolic.svg + - battery-empty-symbolic.svg + - battery-full-charging-symbolic.svg + - battery-good-charging-symbolic.svg + - battery-low-charging-symbolic.svg + - battery-missing-symbolic.svg + +## Installation + +This widget reads the output of acpi tool. + +- install `acpi` and check the output: + +```bash +$ sudo apt-get install acpi +$ acpi +Battery 0: Discharging, 66%, 02:34:06 remaining +``` + +```lua +local battery_widget = require("awesome-wm-widgets.battery-widget.battery") + +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + battery_widget(), + ... +``` diff --git a/awesome/widgets/battery-widget/bat-wid-1.png b/awesome/widgets/battery-widget/bat-wid-1.png new file mode 100644 index 0000000..00e1618 Binary files /dev/null and b/awesome/widgets/battery-widget/bat-wid-1.png differ diff --git a/awesome/widgets/battery-widget/bat-wid-2.png b/awesome/widgets/battery-widget/bat-wid-2.png new file mode 100644 index 0000000..ae20af2 Binary files /dev/null and b/awesome/widgets/battery-widget/bat-wid-2.png differ diff --git a/awesome/widgets/battery-widget/bat-wid-22.png b/awesome/widgets/battery-widget/bat-wid-22.png new file mode 100644 index 0000000..38761f7 Binary files /dev/null and b/awesome/widgets/battery-widget/bat-wid-22.png differ diff --git a/awesome/widgets/battery-widget/bat-wid-3.png b/awesome/widgets/battery-widget/bat-wid-3.png new file mode 100644 index 0000000..352b496 Binary files /dev/null and b/awesome/widgets/battery-widget/bat-wid-3.png differ diff --git a/awesome/widgets/battery-widget/battery.lua b/awesome/widgets/battery-widget/battery.lua new file mode 100644 index 0000000..452d7ef --- /dev/null +++ b/awesome/widgets/battery-widget/battery.lua @@ -0,0 +1,200 @@ +------------------------------------------------- +-- Battery Widget for Awesome Window Manager +-- Shows the battery status using the ACPI tool +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/battery-widget + +-- @author Pavel Makhov +-- @copyright 2017 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local naughty = require("naughty") +local watch = require("awful.widget.watch") +local wibox = require("wibox") +local gfs = require("gears.filesystem") +local dpi = require('beautiful').xresources.apply_dpi + +-- acpi sample outputs +-- Battery 0: Discharging, 75%, 01:51:38 remaining +-- Battery 0: Charging, 53%, 00:57:43 until charged + +local HOME = os.getenv("HOME") +local WIDGET_DIR = HOME .. '/.config/awesome/awesome-wm-widgets/battery-widget' + +local battery_widget = {} +local function worker(user_args) + local args = user_args or {} + + local font = args.font or 'Play 8' + local path_to_icons = args.path_to_icons or "/usr/share/icons/Arc/status/symbolic/" + local show_current_level = args.show_current_level or false + local margin_left = args.margin_left or 0 + local margin_right = args.margin_right or 0 + + local display_notification = args.display_notification or false + local display_notification_onClick = args.display_notification_onClick or true + local position = args.notification_position or "top_right" + local timeout = args.timeout or 10 + + local warning_msg_title = args.warning_msg_title or 'Huston, we have a problem' + local warning_msg_text = args.warning_msg_text or 'Battery is dying' + local warning_msg_position = args.warning_msg_position or 'bottom_right' + local warning_msg_icon = args.warning_msg_icon or WIDGET_DIR .. '/spaceman.jpg' + local enable_battery_warning = args.enable_battery_warning + if enable_battery_warning == nil then + enable_battery_warning = true + end + + if not gfs.dir_readable(path_to_icons) then + naughty.notify{ + title = "Battery Widget", + text = "Folder with icons doesn't exist: " .. path_to_icons, + preset = naughty.config.presets.critical + } + end + + local icon_widget = wibox.widget { + { + id = "icon", + widget = wibox.widget.imagebox, + resize = false + }, + valign = 'center', + layout = wibox.container.place, + } + local level_widget = wibox.widget { + font = font, + widget = wibox.widget.textbox + } + + battery_widget = wibox.widget { + icon_widget, + level_widget, + layout = wibox.layout.fixed.horizontal, + } + -- Popup with battery info + -- One way of creating a pop-up notification - naughty.notify + local notification + local function show_battery_status(batteryType) + awful.spawn.easy_async([[bash -c 'acpi']], + function(stdout, _, _, _) + naughty.destroy(notification) + notification = naughty.notify{ + text = stdout, + title = "Battery status", + icon = path_to_icons .. batteryType .. ".svg", + icon_size = dpi(16), + position = position, + timeout = 5, hover_timeout = 0.5, + width = 200, + screen = mouse.screen + } + end + ) + end + + -- Alternative to naughty.notify - tooltip. You can compare both and choose the preferred one + --battery_popup = awful.tooltip({objects = {battery_widget}}) + + -- To use colors from beautiful theme put + -- following lines in rc.lua before require("battery"): + -- beautiful.tooltip_fg = beautiful.fg_normal + -- beautiful.tooltip_bg = beautiful.bg_normal + + local function show_battery_warning() + naughty.notify { + icon = warning_msg_icon, + icon_size = 100, + text = warning_msg_text, + title = warning_msg_title, + timeout = 25, -- show the warning for a longer time + hover_timeout = 0.5, + position = warning_msg_position, + bg = "#F06060", + fg = "#EEE9EF", + width = 300, + screen = mouse.screen + } + end + local last_battery_check = os.time() + local batteryType = "battery-good-symbolic" + + watch("acpi -i", timeout, + function(widget, stdout) + local battery_info = {} + local capacities = {} + for s in stdout:gmatch("[^\r\n]+") do + local status, charge_str, _ = string.match(s, '.+: ([%a%s]+), (%d?%d?%d)%%,?(.*)') + if status ~= nil then + table.insert(battery_info, {status = status, charge = tonumber(charge_str)}) + else + local cap_str = string.match(s, '.+:.+last full capacity (%d+)') + table.insert(capacities, tonumber(cap_str)) + end + end + + local capacity = 0 + for _, cap in ipairs(capacities) do + capacity = capacity + cap + end + + local charge = 0 + local status + for i, batt in ipairs(battery_info) do + if capacities[i] ~= nil then + if batt.charge >= charge then + status = batt.status -- use most charged battery status + -- this is arbitrary, and maybe another metric should be used + end + + charge = charge + batt.charge * capacities[i] + end + end + charge = charge / capacity + + if show_current_level then + level_widget.text = string.format('%d%%', charge) + end + + if (charge >= 1 and charge < 15) then + batteryType = "battery-empty%s-symbolic" + if enable_battery_warning and status ~= 'Charging' and os.difftime(os.time(), last_battery_check) > 300 then + -- if 5 minutes have elapsed since the last warning + last_battery_check = os.time() + + show_battery_warning() + end + elseif (charge >= 15 and charge < 40) then batteryType = "battery-caution%s-symbolic" + elseif (charge >= 40 and charge < 60) then batteryType = "battery-low%s-symbolic" + elseif (charge >= 60 and charge < 80) then batteryType = "battery-good%s-symbolic" + elseif (charge >= 80 and charge <= 100) then batteryType = "battery-full%s-symbolic" + end + + if status == 'Charging' then + batteryType = string.format(batteryType, '-charging') + else + batteryType = string.format(batteryType, '') + end + + widget.icon:set_image(path_to_icons .. batteryType .. ".svg") + + -- Update popup text + -- battery_popup.text = string.gsub(stdout, "\n$", "") + end, + icon_widget) + + if display_notification then + battery_widget:connect_signal("mouse::enter", function() show_battery_status(batteryType) end) + battery_widget:connect_signal("mouse::leave", function() naughty.destroy(notification) end) + elseif display_notification_onClick then + battery_widget:connect_signal("button::press", function(_,_,_,button) + if (button == 3) then show_battery_status(batteryType) end + end) + battery_widget:connect_signal("mouse::leave", function() naughty.destroy(notification) end) + end + + return wibox.container.margin(battery_widget, margin_left, margin_right) +end + +return setmetatable(battery_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/awesome/widgets/battery-widget/spaceman.jpg b/awesome/widgets/battery-widget/spaceman.jpg new file mode 100644 index 0000000..73ddaf3 Binary files /dev/null and b/awesome/widgets/battery-widget/spaceman.jpg differ diff --git a/awesome/widgets/volume-widget/README.md b/awesome/widgets/volume-widget/README.md new file mode 100644 index 0000000..4fc7f55 --- /dev/null +++ b/awesome/widgets/volume-widget/README.md @@ -0,0 +1,119 @@ +# 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) + +Left click toggles mute and middle click opens a mixer ([pavucontrol](https://freedesktop.org/software/pulseaudio/pavucontrol/) by default). + +### Features + + - switch between sinks/sources by right click 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.volume-widget.volume') +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + volume_widget(), + -- customized + volume_widget{ + widget_type = 'arc' + }, +``` + +Note that widget uses following command the get the current volume: `amixer -D pulse sget Master`, so please make sure that it works for you, otherwise you need to set parameter `device = 'default'`. + +### 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(5) end), +awful.key({ modkey }, "[", function() volume_widget:dec(5) 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 | +|---|---|---| +| `mixer_cmd` | `pavucontrol` | command to run on middle click (e.g. a mixer program) | +| `step` | `5` | How much the volume is raised or lowered at once (in %) | +| `widget_type`| `icon_and_text`| Widget type, one of `horizontal_bar`, `vertical_bar`, `icon`, `icon_and_text`, `arc` | +| `device` | `pulse` | Select the device name to control | + +Depends 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/awesome/widgets/volume-widget/icons/audio-volume-high-symbolic.svg b/awesome/widgets/volume-widget/icons/audio-volume-high-symbolic.svg new file mode 100644 index 0000000..985c107 --- /dev/null +++ b/awesome/widgets/volume-widget/icons/audio-volume-high-symbolic.svg @@ -0,0 +1,88 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/awesome/widgets/volume-widget/icons/audio-volume-low-symbolic.svg b/awesome/widgets/volume-widget/icons/audio-volume-low-symbolic.svg new file mode 100644 index 0000000..7eb4531 --- /dev/null +++ b/awesome/widgets/volume-widget/icons/audio-volume-low-symbolic.svg @@ -0,0 +1,88 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/awesome/widgets/volume-widget/icons/audio-volume-medium-symbolic.svg b/awesome/widgets/volume-widget/icons/audio-volume-medium-symbolic.svg new file mode 100644 index 0000000..11e44fe --- /dev/null +++ b/awesome/widgets/volume-widget/icons/audio-volume-medium-symbolic.svg @@ -0,0 +1,88 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/awesome/widgets/volume-widget/icons/audio-volume-muted-symbolic.svg b/awesome/widgets/volume-widget/icons/audio-volume-muted-symbolic.svg new file mode 100644 index 0000000..e577d05 --- /dev/null +++ b/awesome/widgets/volume-widget/icons/audio-volume-muted-symbolic.svg @@ -0,0 +1,88 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/awesome/widgets/volume-widget/screenshots/variations.png b/awesome/widgets/volume-widget/screenshots/variations.png new file mode 100644 index 0000000..21d7ead Binary files /dev/null and b/awesome/widgets/volume-widget/screenshots/variations.png differ diff --git a/awesome/widgets/volume-widget/screenshots/volume-sink-sources.png b/awesome/widgets/volume-widget/screenshots/volume-sink-sources.png new file mode 100644 index 0000000..7d010bc Binary files /dev/null and b/awesome/widgets/volume-widget/screenshots/volume-sink-sources.png differ diff --git a/awesome/widgets/volume-widget/utils.lua b/awesome/widgets/volume-widget/utils.lua new file mode 100644 index 0000000..417a666 --- /dev/null +++ b/awesome/widgets/volume-widget/utils.lua @@ -0,0 +1,105 @@ + + +local utils = {} + +local function split(string_to_split, separator) + if separator == nil then separator = "%s" end + local t = {} + + for str in string.gmatch(string_to_split, "([^".. separator .."]+)") do + table.insert(t, str) + end + + return t +end + +function utils.extract_sinks_and_sources(pacmd_output) + local sinks = {} + local sources = {} + local device + local properties + local ports + local in_sink = false + local in_source = false + local in_device = false + local in_properties = false + local in_ports = false + for line in pacmd_output:gmatch("[^\r\n]+") do + if string.match(line, 'source%(s%) available.') then + in_sink = false + in_source = true + end + if string.match(line, 'sink%(s%) available.') then + in_sink = true + in_source = false + end + + if string.match(line, 'index:') then + in_device = true + in_properties = false + device = { + id = line:match(': (%d+)'), + is_default = string.match(line, '*') ~= nil + } + if in_sink then + table.insert(sinks, device) + elseif in_source then + table.insert(sources, device) + end + end + + if string.match(line, '^\tproperties:') then + in_device = false + in_properties = true + properties = {} + device['properties'] = properties + end + + if string.match(line, 'ports:') then + in_device = false + in_properties = false + in_ports = true + ports = {} + device['ports'] = ports + end + + if string.match(line, 'active port:') then + in_device = false + in_properties = false + in_ports = false + device['active_port'] = line:match(': (.+)'):gsub('<',''):gsub('>','') + end + + if in_device then + local t = split(line, ': ') + local key = t[1]:gsub('\t+', ''):lower() + local value = t[2]:gsub('^<', ''):gsub('>$', '') + device[key] = value + end + + if in_properties then + local t = split(line, '=') + local key = t[1]:gsub('\t+', ''):gsub('%.', '_'):gsub('-', '_'):gsub(':', ''):gsub("%s+$", "") + local value + if t[2] == nil then + value = t[2] + else + value = t[2]:gsub('"', ''):gsub("^%s+", ""):gsub(' Analog Stereo', '') + end + properties[key] = value + end + + if in_ports then + local t = split(line, ': ') + local key = t[1] + if key ~= nil then + key = key:gsub('\t+', '') + end + ports[key] = t[2] + end + end + + return sinks, sources +end + +return utils \ No newline at end of file diff --git a/awesome/widgets/volume-widget/volume-2.svg b/awesome/widgets/volume-widget/volume-2.svg new file mode 100644 index 0000000..10f1c67 --- /dev/null +++ b/awesome/widgets/volume-widget/volume-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/awesome/widgets/volume-widget/volume.lua b/awesome/widgets/volume-widget/volume.lua new file mode 100644 index 0000000..2561eb9 --- /dev/null +++ b/awesome/widgets/volume-widget/volume.lua @@ -0,0 +1,228 @@ +------------------------------------------------- +-- The Ultimate Volume Widget for Awesome Window Manager +-- More details could be found here: +-- https://github.com/streetturtle/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("widgets.volume-widget.utils") + + +local LIST_DEVICES_CMD = [[sh -c "pacmd list-sinks; pacmd list-sources"]] +local function GET_VOLUME_CMD(device) return 'amixer -D ' .. device .. ' sget Master' end +local function INC_VOLUME_CMD(device, step) return 'amixer -D ' .. device .. ' sset Master ' .. step .. '%+' end +local function DEC_VOLUME_CMD(device, step) return 'amixer -D ' .. device .. ' sset Master ' .. step .. '%-' end +local function TOG_VOLUME_CMD(device) return 'amixer -D ' .. device .. ' sset Master toggle' end + + +local widget_types = { + icon_and_text = require("widgets.volume-widget.widgets.icon-and-text-widget"), + icon = require("widgets.volume-widget.widgets.icon-widget"), + arc = require("widgets.volume-widget.widgets.arc-widget"), + horizontal_bar = require("widgets.volume-widget.widgets.horizontal-bar-widget"), + vertical_bar = require("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 + 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 mixer_cmd = args.mixer_cmd or 'pavucontrol' + local widget_type = args.widget_type + local refresh_rate = args.refresh_rate or 1 + local step = args.step or 5 + local device = args.device or 'pulse' + + if widget_types[widget_type] == nil then + volume.widget = widget_types['icon_and_text'].get_widget(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(s) + spawn.easy_async(INC_VOLUME_CMD(device, s or step), function(stdout) update_graphic(volume.widget, stdout) end) + end + + function volume:dec(s) + spawn.easy_async(DEC_VOLUME_CMD(device, s or step), function(stdout) update_graphic(volume.widget, stdout) end) + end + + function volume:toggle() + spawn.easy_async(TOG_VOLUME_CMD(device), function(stdout) update_graphic(volume.widget, stdout) end) + end + + function volume:mixer() + if mixer_cmd then + spawn.easy_async(mixer_cmd) + 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({}, 2, function() volume:mixer() end), + awful.button({}, 1, function() volume:toggle() end) + ) + ) + + watch(GET_VOLUME_CMD(device), refresh_rate, update_graphic, volume.widget) + + return volume.widget +end + +return setmetatable(volume, { __call = function(_, ...) return worker(...) end }) diff --git a/awesome/widgets/volume-widget/widgets/arc-widget.lua b/awesome/widgets/volume-widget/widgets/arc-widget.lua new file mode 100644 index 0000000..7ecef78 --- /dev/null +++ b/awesome/widgets/volume-widget/widgets/arc-widget.lua @@ -0,0 +1,46 @@ +local wibox = require("wibox") +local beautiful = require('beautiful') + +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/widgets/volume-widget/icons/' + +local widget = {} + +function widget.get_widget(widgets_args) + local args = widgets_args or {} + + local thickness = args.thickness or 2 + local main_color = args.main_color or beautiful.fg_color + local bg_color = args.bg_color or '#ffffff11' + local mute_color = args.mute_color or beautiful.fg_urgent + local size = args.size or 18 + + return wibox.widget { + { + id = "icon", + image = ICON_DIR .. 'audio-volume-high-symbolic.svg', + resize = true, + widget = wibox.widget.imagebox, + }, + max_value = 100, + thickness = thickness, + start_angle = 4.71238898, -- 2pi*3/4 + forced_height = size, + forced_width = size, + bg = bg_color, + paddings = 2, + widget = wibox.container.arcchart, + set_volume_level = function(self, new_value) + self.value = new_value + end, + mute = function(self) + self.colors = { mute_color } + end, + unmute = function(self) + self.colors = { main_color } + end + } + +end + + +return widget diff --git a/awesome/widgets/volume-widget/widgets/horizontal-bar-widget.lua b/awesome/widgets/volume-widget/widgets/horizontal-bar-widget.lua new file mode 100644 index 0000000..1484f91 --- /dev/null +++ b/awesome/widgets/volume-widget/widgets/horizontal-bar-widget.lua @@ -0,0 +1,58 @@ +local wibox = require("wibox") +local beautiful = require('beautiful') +local gears = require("gears") + +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/widgets/volume-widget/icons/' + +local widget = {} + +function widget.get_widget(widgets_args) + local args = widgets_args or {} + + local main_color = args.main_color or beautiful.fg_normal + local mute_color = args.mute_color or beautiful.fg_urgent + local bg_color = args.bg_color or '#ffffff11' + local width = args.width or 50 + local margins = args.margins or 10 + local shape = args.shape or 'bar' + local with_icon = args.with_icon == true and true or false + + local bar = wibox.widget { + { + { + id = "icon", + image = ICON_DIR .. 'audio-volume-high-symbolic.svg', + resize = false, + widget = wibox.widget.imagebox, + }, + valign = 'center', + visible = with_icon, + layout = wibox.container.place, + }, + { + id = 'bar', + max_value = 100, + forced_width = width, + color = main_color, + margins = { top = margins, bottom = margins }, + background_color = bg_color, + shape = gears.shape[shape], + widget = wibox.widget.progressbar, + }, + spacing = 4, + layout = wibox.layout.fixed.horizontal, + set_volume_level = function(self, new_value) + self:get_children_by_id('bar')[1]:set_value(tonumber(new_value)) + end, + mute = function(self) + self:get_children_by_id('bar')[1]:set_color(mute_color) + end, + unmute = function(self) + self:get_children_by_id('bar')[1]:set_color(main_color) + end + } + + return bar +end + +return widget diff --git a/awesome/widgets/volume-widget/widgets/icon-and-text-widget.lua b/awesome/widgets/volume-widget/widgets/icon-and-text-widget.lua new file mode 100644 index 0000000..a37af77 --- /dev/null +++ b/awesome/widgets/volume-widget/widgets/icon-and-text-widget.lua @@ -0,0 +1,59 @@ +local wibox = require("wibox") +local beautiful = require('beautiful') + +local widget = {} + +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/widgets/volume-widget/icons/' + +function widget.get_widget(widgets_args) + local args = widgets_args or {} + + local font = args.font or beautiful.font + local icon_dir = args.icon_dir or ICON_DIR + + return wibox.widget { + { + { + id = "icon", + resize = false, + widget = wibox.widget.imagebox, + }, + valign = 'center', + layout = wibox.container.place + }, + { + id = 'txt', + font = font, + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal, + set_volume_level = function(self, new_value) + self:get_children_by_id('txt')[1]:set_text(new_value) + local volume_icon_name + if self.is_muted then + volume_icon_name = 'audio-volume-muted-symbolic' + else + local new_value_num = tonumber(new_value) + if (new_value_num >= 0 and new_value_num < 33) then + volume_icon_name="audio-volume-low-symbolic" + elseif (new_value_num < 66) then + volume_icon_name="audio-volume-medium-symbolic" + else + volume_icon_name="audio-volume-high-symbolic" + end + end + self:get_children_by_id('icon')[1]:set_image(icon_dir .. volume_icon_name .. '.svg') + end, + mute = function(self) + self.is_muted = true + self:get_children_by_id('icon')[1]:set_image(icon_dir .. 'audio-volume-muted-symbolic.svg') + end, + unmute = function(self) + self.is_muted = false + end + } + +end + + +return widget diff --git a/awesome/widgets/volume-widget/widgets/icon-widget.lua b/awesome/widgets/volume-widget/widgets/icon-widget.lua new file mode 100644 index 0000000..ddd52f2 --- /dev/null +++ b/awesome/widgets/volume-widget/widgets/icon-widget.lua @@ -0,0 +1,46 @@ +local wibox = require("wibox") + +local widget = {} + +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/widgets/volume-widget/icons/' + +function widget.get_widget(widgets_args) + local args = widgets_args or {} + + local icon_dir = args.icon_dir or ICON_DIR + + return wibox.widget { + { + id = "icon", + resize = false, + widget = wibox.widget.imagebox, + }, + valign = 'center', + layout = wibox.container.place, + set_volume_level = function(self, new_value) + local volume_icon_name + if self.is_muted then + volume_icon_name = 'audio-volume-muted-symbolic' + else + local new_value_num = tonumber(new_value) + if (new_value_num >= 0 and new_value_num < 33) then + volume_icon_name="audio-volume-low-symbolic" + elseif (new_value_num < 66) then + volume_icon_name="audio-volume-medium-symbolic" + else + volume_icon_name="audio-volume-high-symbolic" + end + end + self:get_children_by_id('icon')[1]:set_image(icon_dir .. volume_icon_name .. '.svg') + end, + mute = function(self) + self.is_muted = true + self:get_children_by_id('icon')[1]:set_image(icon_dir .. 'audio-volume-muted-symbolic.svg') + end, + unmute = function(self) + self.is_muted = false + end + } +end + +return widget diff --git a/awesome/widgets/volume-widget/widgets/vertical-bar-widget.lua b/awesome/widgets/volume-widget/widgets/vertical-bar-widget.lua new file mode 100644 index 0000000..acc60f2 --- /dev/null +++ b/awesome/widgets/volume-widget/widgets/vertical-bar-widget.lua @@ -0,0 +1,64 @@ +local wibox = require("wibox") +local beautiful = require('beautiful') +local gears = require("gears") + +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/widgets/volume-widget/icons/' + +local widget = {} + +function widget.get_widget(widgets_args) + local args = widgets_args or {} + + local main_color = args.main_color or beautiful.fg_normal + local mute_color = args.mute_color or beautiful.fg_urgent + local bg_color = args.bg_color or '#ffffff11' + local width = args.width or 10 + local margins = args.height or 2 + local shape = args.shape or 'bar' + local with_icon = args.with_icon == true and true or false + + local bar = wibox.widget { + { + { + id = "icon", + image = ICON_DIR .. 'audio-volume-high-symbolic.svg', + resize = false, + widget = wibox.widget.imagebox, + }, + valign = 'center', + visible = with_icon, + layout = wibox.container.place, + }, + { + { + id = 'bar', + max_value = 100, + forced_width = width, + forced_height = 5, + margins = { top = margins, bottom = margins }, + color = main_color, + background_color = bg_color, + shape = gears.shape[shape], + widget = wibox.widget.progressbar, + }, + forced_width = width, + direction = 'east', + layout = wibox.container.rotate, + }, + spacing = 4, + layout = wibox.layout.fixed.horizontal, + set_volume_level = function(self, new_value) + self:get_children_by_id('bar')[1]:set_value(tonumber(new_value)) + end, + mute = function(self) + self:get_children_by_id('bar')[1]:set_color(mute_color) + end, + unmute = function(self) + self:get_children_by_id('bar')[1]:set_color(main_color) + end + } + + return bar +end + +return widget