diff --git a/dot_config/awesome/awesome-wm-widgets/CODEOWNERS b/dot_config/awesome/awesome-wm-widgets/CODEOWNERS new file mode 100644 index 0000000..0661fff --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/CODEOWNERS @@ -0,0 +1 @@ +* @streetturtle diff --git a/dot_config/awesome/awesome-wm-widgets/LICENSE b/dot_config/awesome/awesome-wm-widgets/LICENSE new file mode 100644 index 0000000..1f89e49 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2017 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/dot_config/awesome/awesome-wm-widgets/README.md b/dot_config/awesome/awesome-wm-widgets/README.md new file mode 100644 index 0000000..1617fae --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/README.md @@ -0,0 +1,63 @@ +

+ logo +

+ +

+ + + GitHub repo size + GitHub Workflow Status + + + + +

+ +Set of widgets compatible with Awesome Window Manager v.4.3+. + +## Screenshots + +Spotify, CPU, RAM, brightness-arc, volume-arc and battery-arc widgets: + +

+ +

+ +Brightness, volume and battery widgets: + +

+ +

+ +![screenshot](./screenshot.png) + +Some more screenshots in this reddit [post](https://www.reddit.com/r/unixporn/comments/8qijmx/awesomewm_dark_theme/) + +# Installation + +Clone the repo under **~/.config/awesome/**, then follow an Installation section of widget's readme file. + +# Stargazers + +[![Stargazers over time](https://starchart.cc/streetturtle/awesome-wm-widgets.svg)](https://starchart.cc/streetturtle/awesome-wm-widgets) + +# Troubleshooting + +In case of any doubts/questions/problems: + - create an [issue](https://github.com/streetturtle/awesome-wm-widgets/issues/new/choose) + - raise a question on [Discussions](https://github.com/streetturtle/awesome-wm-widgets/discussions)! + - ping me on AwesomeWM's discord, here's an [invite](https://discord.gg/BPat4F87dg) + +# Support + +If you find anything useful here, you can: + - star a repo - this really motivates me to work on this project + - or + - or even become a [sponsor](https://github.com/sponsors/streetturtle) + +# Contributors + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/Screenshot from 2019-03-01 14-28-18.png b/dot_config/awesome/awesome-wm-widgets/Screenshot from 2019-03-01 14-28-18.png new file mode 100644 index 0000000..4c9bd87 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/Screenshot from 2019-03-01 14-28-18.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/apt-widget/README.md b/dot_config/awesome/awesome-wm-widgets/apt-widget/README.md new file mode 100644 index 0000000..f3ac47a --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/apt-widget/README.md @@ -0,0 +1,27 @@ +# APT widget + +Widget which shows a list of APT packages to be updated: + +![screenshot](./screenshots/screenshot.gif) + +Features: + - scrollable list !!! (thanks to this [post](https://www.reddit.com/r/awesomewm/comments/isx89x/scrolling_a_layout_fixed_flexed_layout_widget/) of reddit) + - update single package + - update multiple packages + +## Installation + +Clone the repo under ~/.config/awesome/ folder, then in rc.lua add the following: + +```lua +local apt_widget = require("awesome-wm-widgets.apt-widget.apt-widget") + +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + apt_widget(), + ... +``` + diff --git a/dot_config/awesome/awesome-wm-widgets/apt-widget/apt-widget.lua b/dot_config/awesome/awesome-wm-widgets/apt-widget/apt-widget.lua new file mode 100644 index 0000000..c15c32f --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/apt-widget/apt-widget.lua @@ -0,0 +1,349 @@ +------------------------------------------------- +-- APT Widget for Awesome Window Manager +-- Lists containers and allows to manage them +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/apt-widget + +-- @author Pavel Makhov +-- @copyright 2021 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local spawn = require("awful.spawn") +local naughty = require("naughty") +local gears = require("gears") +local beautiful = require("beautiful") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/apt-widget' +local ICONS_DIR = WIDGET_DIR .. '/icons/' + +local LIST_PACKAGES = [[sh -c "LC_ALL=c apt list --upgradable 2>/dev/null"]] + +--- Utility function to show warning messages +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Docker Widget', + text = message} +end + +local wibox_popup = wibox { + ontop = true, + visible = false, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + border_width = 1, + border_color = beautiful.bg_focus, + max_widget_size = 500, + height = 500, + width = 300, +} + +local apt_widget = wibox.widget { + { + { + id = 'icon', + widget = wibox.widget.imagebox + }, + margins = 4, + layout = wibox.container.margin + }, + layout = wibox.layout.fixed.horizontal, + set_icon = function(self, new_icon) + self:get_children_by_id("icon")[1].image = new_icon + end +} + +--- Parses the line and creates the package table out of it +--- yaru-theme-sound/focal-updates,focal-updates 20.04.10.1 all [upgradable from: 20.04.8] +local parse_package = function(line) + local name,_,nv,type,ov = line:match('(.*)%/(.*)%s(.*)%s(.*)%s%[upgradable from: (.*)]') + + if name == nil then return nil end + + local package = { + name = name, + new_version = nv, + type = type, + old_version = ov + } + return package +end + +local function worker(user_args) + + local args = user_args or {} + + local icon = args.icon or ICONS_DIR .. 'white-black.svg' + + apt_widget:set_icon(icon) + + local pointer = 0 + local min_widgets = 5 + local carousel = false + + local function rebuild_widget(containers, errors, _, _) + + local to_update = {} + + if errors ~= '' then + show_warning(errors) + return + end + + local rows = wibox.layout.fixed.vertical() + rows:connect_signal("button::press", function(_,_,_,button) + if carousel then + if button == 4 then -- up scrolling + local cnt = #rows.children + local first_widget = rows.children[1] + rows:insert(cnt+1, first_widget) + rows:remove(1) + elseif button == 5 then -- down scrolling + local cnt = #rows.children + local last_widget = rows.children[cnt] + rows:insert(1, last_widget) + rows:remove(cnt+1) + end + else + if button == 5 then -- up scrolling + if pointer < #rows.children and ((#rows.children - pointer) >= min_widgets) then + pointer = pointer + 1 + rows.children[pointer].visible = false + end + elseif button == 4 then -- down scrolling + if pointer > 0 then + rows.children[pointer].visible = true + pointer = pointer - 1 + end + end + end + end) + + for line in containers:gmatch("[^\r\n]+") do + local package = parse_package(line) + + if package ~= nil then + + local refresh_button = wibox.widget { + { + { + id = 'icon', + image = ICONS_DIR .. 'refresh-cw.svg', + resize = false, + widget = wibox.widget.imagebox + }, + margins = 4, + widget = wibox.container.margin + }, + shape = gears.shape.circle, + opacity = 0.5, + widget = wibox.container.background + } + local old_cursor, old_wibox + refresh_button:connect_signal("mouse::enter", function(c) + c:set_opacity(1) + c:emit_signal('widget::redraw_needed') + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + refresh_button:connect_signal("mouse::leave", function(c) + c:set_opacity(0.5) + c:emit_signal('widget::redraw_needed') + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + local row = wibox.widget { + { + { + { + { + id = 'checkbox', + checked = false, + color = beautiful.bg_normal, + paddings = 2, + shape = gears.shape.circle, + forced_width = 20, + forced_height = 20, + check_color = beautiful.fg_urgent, + border_color = beautiful.bg_urgent, + border_width = 1, + widget = wibox.widget.checkbox + }, + valign = 'center', + layout = wibox.container.place, + }, + { + { + id = 'name', + markup = '' .. package['name'] .. '', + widget = wibox.widget.textbox + }, + halign = 'left', + layout = wibox.container.place + }, + { + refresh_button, + halign = 'right', + valign = 'center', + fill_horizontal = true, + layout = wibox.container.place, + }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + margins = 8, + layout = wibox.container.margin + }, + id = 'row', + bg = beautiful.bg_normal, + widget = wibox.container.background, + click = function(self, checked) + local a = self:get_children_by_id('checkbox')[1] + if checked == nil then + a:set_checked(not a.checked) + else + a:set_checked(checked) + end + + if a.checked then + to_update[package['name']] = self + else + to_update[package['name']] = false + end + end, + update = function(self) + refresh_button:get_children_by_id('icon')[1]:set_image(ICONS_DIR .. 'watch.svg') + self:get_children_by_id('name')[1]:set_opacity(0.4) + self:get_children_by_id('name')[1]:emit_signal('widget::redraw_needed') + + spawn.easy_async( + string.format([[sh -c 'yes | aptdcon --hide-terminal -u %s']], package['name']), + function(stdout, stderr) -- luacheck:ignore 212 + rows:remove_widgets(self) + end) + + end + } + + 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) + + row:connect_signal("button::press", function(c, _, _, button) + if button == 1 then c:click() end + end) + + refresh_button:buttons(awful.util.table.join(awful.button({}, 1, function() + row:update() + end))) + + rows:add(row) + end + end + + + local header_checkbox = wibox.widget { + checked = false, + color = beautiful.bg_normal, + paddings = 2, + shape = gears.shape.circle, + forced_width = 20, + forced_height = 20, + check_color = beautiful.fg_urgent, + border_color = beautiful.bg_urgent, + border_width = 1, + widget = wibox.widget.checkbox + } + header_checkbox:connect_signal("button::press", function(c) + c:set_checked(not c.checked) + local cbs = rows.children + for _,v in ipairs(cbs) do + v:click(c.checked) + end + end) + + local header_refresh_icon = wibox.widget { + image = ICONS_DIR .. 'refresh-cw.svg', + resize = false, + widget = wibox.widget.imagebox + } + header_refresh_icon:buttons(awful.util.table.join(awful.button({}, 1, function() + print(#to_update) + for _,v in pairs(to_update) do + if v ~= nil then + v:update() + end + end + end))) + + local header_row = wibox.widget { + { + { + { + header_checkbox, + valign = 'center', + layout = wibox.container.place, + }, + { + { + id = 'name', + markup = '' .. #rows.children .. ' packages to update', + widget = wibox.widget.textbox + }, + halign = 'center', + layout = wibox.container.place + }, + { + header_refresh_icon, + halign = 'right', + valign = 'center', + layout = wibox.container.place, + }, + layout = wibox.layout.align.horizontal + }, + margins = 8, + layout = wibox.container.margin + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + } + + wibox_popup:setup { + header_row, + rows, + layout = wibox.layout.fixed.vertical + } + end + + apt_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if wibox_popup.visible then + wibox_popup.visible = not wibox_popup.visible + else + spawn.easy_async(LIST_PACKAGES, + function(stdout, stderr) + rebuild_widget(stdout, stderr) + wibox_popup.visible = true + awful.placement.top(wibox_popup, { margins = { top = 20 }, parent = mouse}) + end) + end + end) + ) + ) + + return apt_widget +end + +return setmetatable(apt_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/black.svg b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/black.svg new file mode 100644 index 0000000..95c8410 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/black.svg @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/help-circle.svg b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/help-circle.svg new file mode 100644 index 0000000..51fddd8 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/help-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/orange.svg b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/orange.svg new file mode 100644 index 0000000..0ec1388 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/orange.svg @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/refresh-cw.svg b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/refresh-cw.svg new file mode 100644 index 0000000..39f52a5 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/refresh-cw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/watch.svg b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/watch.svg new file mode 100644 index 0000000..661a560 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/watch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/white-black.svg b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/white-black.svg new file mode 100644 index 0000000..dc7ee55 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/white-black.svg @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/white-orange.svg b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/white-orange.svg new file mode 100644 index 0000000..c353bb5 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/apt-widget/icons/white-orange.svg @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/apt-widget/screenshots/screenshot.gif b/dot_config/awesome/awesome-wm-widgets/apt-widget/screenshots/screenshot.gif new file mode 100644 index 0000000..6ffe2aa Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/apt-widget/screenshots/screenshot.gif differ diff --git a/dot_config/awesome/awesome-wm-widgets/awesome-o.png b/dot_config/awesome/awesome-wm-widgets/awesome-o.png new file mode 100644 index 0000000..424d008 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/awesome-o.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/awesome.png b/dot_config/awesome/awesome-wm-widgets/awesome.png new file mode 100644 index 0000000..6960955 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/awesome.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/battery-widget/README.md b/dot_config/awesome/awesome-wm-widgets/battery-widget/README.md new file mode 100644 index 0000000..b15aac6 --- /dev/null +++ b/dot_config/awesome/awesome-wm-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/dot_config/awesome/awesome-wm-widgets/battery-widget/bat-wid-1.png b/dot_config/awesome/awesome-wm-widgets/battery-widget/bat-wid-1.png new file mode 100644 index 0000000..00e1618 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/battery-widget/bat-wid-1.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/battery-widget/bat-wid-2.png b/dot_config/awesome/awesome-wm-widgets/battery-widget/bat-wid-2.png new file mode 100644 index 0000000..ae20af2 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/battery-widget/bat-wid-2.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/battery-widget/bat-wid-22.png b/dot_config/awesome/awesome-wm-widgets/battery-widget/bat-wid-22.png new file mode 100644 index 0000000..38761f7 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/battery-widget/bat-wid-22.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/battery-widget/bat-wid-3.png b/dot_config/awesome/awesome-wm-widgets/battery-widget/bat-wid-3.png new file mode 100644 index 0000000..352b496 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/battery-widget/bat-wid-3.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/battery-widget/battery.lua b/dot_config/awesome/awesome-wm-widgets/battery-widget/battery.lua new file mode 100644 index 0000000..452d7ef --- /dev/null +++ b/dot_config/awesome/awesome-wm-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/dot_config/awesome/awesome-wm-widgets/battery-widget/spaceman.jpg b/dot_config/awesome/awesome-wm-widgets/battery-widget/spaceman.jpg new file mode 100644 index 0000000..73ddaf3 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/battery-widget/spaceman.jpg differ diff --git a/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/10_c.png b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/10_c.png new file mode 100644 index 0000000..3faf753 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/10_c.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/10_d.png b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/10_d.png new file mode 100644 index 0000000..c9aa8d3 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/10_d.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/20_c.png b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/20_c.png new file mode 100644 index 0000000..f0a191d Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/20_c.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/20_d.png b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/20_d.png new file mode 100644 index 0000000..15fabed Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/20_d.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/80_c.png b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/80_c.png new file mode 100644 index 0000000..e6dae75 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/80_c.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/80_d.png b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/80_d.png new file mode 100644 index 0000000..220c8e3 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/80_d.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/README.md b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/README.md new file mode 100644 index 0000000..98a2956 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/README.md @@ -0,0 +1,73 @@ +# Batteryarc widget + +[![GitHub issues by-label](https://img.shields.io/github/issues-raw/streetturtle/awesome-wm-widgets/batteryarc)](https://github.com/streetturtle/awesome-wm-widgets/labels/batteryarc) + +This widget is more informative version of [battery widget](https://github.com/streetturtle/awesome-wm-widgets/tree/master/battery-widget). + +Depending of the battery status it could look following ways: + + - ![10_d](./10_d.png) - less than 15 percent + - ![10_c](./10_c.png) - less than 15 percent, charging + - ![20_d](./20_d.png) - between 15 and 40 percent + - ![20_c](./20_c.png) - between 15 and 40 percent, charging + - ![80_d](./80_d.png) - more than 40 percent + - ![80_c](./80_c.png) - more than 40 percent, charging + +If a battery level is low then warning popup will show up: + +![warning](./warning.png) + +## 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 6 | Font | +| `arc_thickness` | 2 | Thickness of the arc | +| `show_current_level`| false | Show current charge level | +| `size`| 18 | Size of the widget | +| `timeout` | 10 | How often in seconds the widget refreshes | +| `main_color` | `beautiful.fg_color` | Color of the text with the current charge level and the arc | +| `bg_color` | `#ffffff11` | Color of the charge level background | +| `low_level_color` | `#e53935` | Arc color when battery charge is less that 15% | +| `medium_level_color` | `#c0ca33` | Arc color when battery charge is between 15% and 40% | +| `charging_color` | `#43a047` | Color of the circle inside the arc when charging | +| `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/batteryarc-widget/spaceman.jpg | Icon of the warning popup | +| `enable_battery_warning` | `true` | Display low battery warning | +| `show_notification_mode` | `on_hover` | How to trigger a notification with the battery status: `on_hover`, `on_click` or `off` | +| `notification_position` | `top_left` | Where to show she notification when triggered. Values: `top_right`, `top_left`, `bottom_left`, `bottom_right`, `top_middle`, `bottom_middle`. (default `top_right`) | + +## Requirements + +This widget requires the `acpi` command to be available to retrieve battery and +power information. + +## Installation + +Clone repo, include widget and use it in **rc.lua**: + +```lua +local batteryarc_widget = require("awesome-wm-widgets.batteryarc-widget.batteryarc") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + --[[default]] + batteryarc_widget(), + --[[or customized]] + batteryarc_widget({ + show_current_level = true, + arc_thickness = 1, + }), + } + ... +``` + +## Troubleshooting + +In case of any doubts or questions please raise an [issue](https://github.com/streetturtle/awesome-wm-widgets/issues/new). diff --git a/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/batteryarc.lua b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/batteryarc.lua new file mode 100644 index 0000000..55a7694 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/batteryarc.lua @@ -0,0 +1,170 @@ +------------------------------------------------- +-- Battery Arc Widget for Awesome Window Manager +-- Shows the battery level of the laptop +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/batteryarc-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local beautiful = require("beautiful") +local naughty = require("naughty") +local wibox = require("wibox") +local watch = require("awful.widget.watch") + +local HOME = os.getenv("HOME") +local WIDGET_DIR = HOME .. '/.config/awesome/awesome-wm-widgets/batteryarc-widget' + +local batteryarc_widget = {} + +local function worker(user_args) + + local args = user_args or {} + + local font = args.font or 'Play 6' + local arc_thickness = args.arc_thickness or 2 + local show_current_level = args.show_current_level or false + local size = args.size or 18 + local timeout = args.timeout or 10 + local show_notification_mode = args.show_notification_mode or 'on_hover' -- on_hover / on_click + local notification_position = args.notification_position or 'top_right' -- see naughty.notify position argument + + local main_color = args.main_color or beautiful.fg_color + local bg_color = args.bg_color or '#ffffff11' + local low_level_color = args.low_level_color or '#e53935' + local medium_level_color = args.medium_level_color or '#c0ca33' + local charging_color = args.charging_color or '#43a047' + + local warning_msg_title = args.warning_msg_title or 'Houston, 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 + + local text = wibox.widget { + font = font, + align = 'center', + valign = 'center', + widget = wibox.widget.textbox + } + + local text_with_background = wibox.container.background(text) + + batteryarc_widget = wibox.widget { + text_with_background, + max_value = 100, + rounded_edge = true, + thickness = arc_thickness, + start_angle = 4.71238898, -- 2pi*3/4 + forced_height = size, + forced_width = size, + bg = bg_color, + paddings = 2, + widget = wibox.container.arcchart + } + + local last_battery_check = os.time() + + --[[ Show warning notification ]] + 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, + } + end + + local function update_widget(widget, stdout) + local charge = 0 + local status + for s in stdout:gmatch("[^\r\n]+") do + local cur_status, charge_str, _ = string.match(s, '.+: ([%a%s]+), (%d?%d?%d)%%,?(.*)') + if cur_status ~= nil and charge_str ~=nil then + local cur_charge = tonumber(charge_str) + if cur_charge > charge then + status = cur_status + charge = cur_charge + end + end + end + + widget.value = charge + + if status == 'Charging' then + text_with_background.bg = charging_color + text_with_background.fg = '#000000' + else + text_with_background.bg = '#00000000' + text_with_background.fg = main_color + end + + if show_current_level == true then + --- if battery is fully charged (100) there is not enough place for three digits, so we don't show any text + text.text = charge == 100 + and '' + or string.format('%d', charge) + else + text.text = '' + end + + if charge < 15 then + widget.colors = { low_level_color } + 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 + widget.colors = { medium_level_color } + else + widget.colors = { main_color } + end + end + + watch("acpi", timeout, update_widget, batteryarc_widget) + + -- Popup with battery info + local notification + local function show_battery_status() + awful.spawn.easy_async([[bash -c 'acpi']], + function(stdout, _, _, _) + naughty.destroy(notification) + notification = naughty.notify { + text = stdout, + title = "Battery status", + timeout = 5, + width = 200, + position = notification_position, + } + end) + end + + if show_notification_mode == 'on_hover' then + batteryarc_widget:connect_signal("mouse::enter", function() show_battery_status() end) + batteryarc_widget:connect_signal("mouse::leave", function() naughty.destroy(notification) end) + elseif show_notification_mode == 'on_click' then + batteryarc_widget:connect_signal('button::press', function(_, _, _, button) + if (button == 1) then show_battery_status() end + end) + end + + return batteryarc_widget + +end + +return setmetatable(batteryarc_widget, { __call = function(_, ...) + return worker(...) +end }) diff --git a/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/spaceman.jpg b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/spaceman.jpg new file mode 100644 index 0000000..73ddaf3 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/spaceman.jpg differ diff --git a/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/warning.png b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/warning.png new file mode 100644 index 0000000..55ca790 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/batteryarc-widget/warning.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/README.md b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/README.md new file mode 100644 index 0000000..197a765 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/README.md @@ -0,0 +1,69 @@ +# Bitbucket widget + +The widget shows the number of pull requests assigned to the user and when clicked shows them in the list with some additional information. When item in the list is clicked - it opens the pull request in the browser. + +## How it works + +Widget uses cURL to query Bitbucket's [REST API](https://developer.atlassian.com/bitbucket/api/2/reference/). In order to be authenticated, widget uses a [netrc](https://ec.haxx.se/usingcurl/usingcurl-netrc) feature of the cURL, which is basically allows storing basic auth credentials in a **.netrc** file in home folder. + +Bitbucket allows using [App Passwords](https://confluence.atlassian.com/bitbucket/app-passwords-828781300.html) (available in the account settings) - simply generate one for the widget and use it as password in **.netrc** file. + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `icon` | `~/.config/awesome/awesome-wm-widgets/bitbucket-widget/bitbucket-icon-gradient-blue.svg` | Path to the icon | +| `host` | Required | e.g _http://api.bitbucket.org_ | +| `uuid` | Required | e.g _{123e4567-e89b-12d3-a456-426614174000}_ | +| `workspace` | Required | Workspace ID| +| `repo_slug` | Required | Repository slug | +| `timeout` | 60 | How often in seconds the widget refreshes | + +Note: + - host most likely should start with _api._ + - to get your UUID you may call `curl -s -n 'https://api.bitbucket.org/2.0/user'` + +## Installation + +Create a **.netrc** file in you home directory with following content: + +```bash +machine api.bitbucket.org +login mikey@tmnt.com +password cowabunga +``` + +Then change file's permissions to 600 (so only you can read/write it): + +```bash +chmod 600 ~/.netrc +``` +And test if it works by calling the API: + +```bash +curl -s -n 'https://api.bitbucket.org/2.0/repositories/' +``` + +Also, to properly setup required parameters you can use `test_bitbucket_api.sh` script - it uses the same curl call as widget. + +Then clone/download repo and use widget in **rc.lua**: + +```lua +local bitbucket_widget = require("awesome-wm-widgets.bitbucket-widget.bitbucket") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + bitbucket_widget({ + host = 'https://api.bitbucket.org', + uuid = '{123e4567-e89b-12d3-a456-426614174000}', + workspace = 'workspace', + repo_slug = 'slug' + + }}), + ... +``` diff --git a/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/bitbucket-icon-gradient-blue.svg b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/bitbucket-icon-gradient-blue.svg new file mode 100644 index 0000000..ea700ea --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/bitbucket-icon-gradient-blue.svg @@ -0,0 +1 @@ +bitbucket-icon-gradient-blue \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/bitbucket.lua b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/bitbucket.lua new file mode 100644 index 0000000..b85e653 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/bitbucket.lua @@ -0,0 +1,371 @@ +------------------------------------------------- +-- Bitbucket Widget for Awesome Window Manager +-- Shows the number of currently assigned pull requests +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/bitbucket-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") +local json = require("json") +local spawn = require("awful.spawn") +local naughty = require("naughty") +local gears = require("gears") +local beautiful = require("beautiful") +local gfs = require("gears.filesystem") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/bitbucket-widget/' + +local GET_PRS_CMD= [[bash -c "curl -s --show-error -n ]] + .. [['%s/2.0/repositories/%s/%s/pullrequests]] + .. [[?fields=values.participants.approved,values.title,values.links.html,values.author.display_name,]] + .. [[values.author.uuid,values.author.links.avatar,values.source.branch,values.destination.branch,]] + .. [[values.comment_count,values.created_on&q=reviewers.uuid+%%3D+%%22%s%%22+AND+state+%%3D+%%22OPEN%%22']] + .. [[ | jq '.[] '"]] +local DOWNLOAD_AVATAR_CMD = [[bash -c "curl -L -n --create-dirs -o %s/.cache/awmw/bitbucket-widget/avatars/%s %s"]] + +local bitbucket_widget = wibox.widget { + { + { + id = 'icon', + widget = wibox.widget.imagebox + }, + margins = 4, + layout = wibox.container.margin + }, + { + id = "txt", + widget = wibox.widget.textbox + }, + { + id = "new_pr", + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal, + set_text = function(self, new_value) + self.txt.text = new_value + end, + set_icon = function(self, new_value) + self:get_children_by_id('icon')[1]:set_image(new_value) + end +} + +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Bitbucket Widget', + text = message} +end + +local popup = awful.popup{ + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + maximum_width = 400, + offset = { y = 5 }, + widget = {} +} + +--- Converts string representation of date (2020-06-02T11:25:27Z) to date +local function parse_date(date_str) + local pattern = "(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)%Z" + local y, m, d, h, min, sec, _ = date_str:match(pattern) + + return os.time{year = y, month = m, day = d, hour = h, min = min, sec = sec} +end + +--- Converts seconds to "time ago" represenation, like '1 hour ago' +local function to_time_ago(seconds) + local days = seconds / 86400 + if days > 1 then + days = math.floor(days + 0.5) + return days .. (days == 1 and ' day' or ' days') .. ' ago' + end + + local hours = (seconds % 86400) / 3600 + if hours > 1 then + hours = math.floor(hours + 0.5) + return hours .. (hours == 1 and ' hour' or ' hours') .. ' ago' + end + + local minutes = ((seconds % 86400) % 3600) / 60 + if minutes > 1 then + minutes = math.floor(minutes + 0.5) + return minutes .. (minutes == 1 and ' minute' or ' minutes') .. ' ago' + end +end + +local function ellipsize(text, length) + return (text:len() > length and length > 0) + and text:sub(0, length - 3) .. '...' + or text +end + +local function count_approves(participants) + local res = 0 + for i = 1, #participants do + if participants[i]['approved'] then res = res + 1 end + end + return res +end + +local function worker(user_args) + + local args = user_args or {} + + local icon = args.icon or WIDGET_DIR .. '/bitbucket-icon-gradient-blue.svg' + local host = args.host or show_warning('Bitbucket host is not set') + local uuid = args.uuid or show_warning('UUID is not set') + local workspace = args.workspace or show_warning('Workspace is not set') + local repo_slug = args.repo_slug or show_warning('Repo slug is not set') + local timeout = args.timeout or 60 + + local current_number_of_prs + + local to_review_rows = {layout = wibox.layout.fixed.vertical} + local my_review_rows = {layout = wibox.layout.fixed.vertical} + local rows = {layout = wibox.layout.fixed.vertical} + + bitbucket_widget:set_icon(icon) + + local update_widget = function(widget, stdout, stderr, _, _) + if stderr ~= '' then + show_warning(stderr) + return + end + + local result = json.decode(stdout) + + current_number_of_prs = rawlen(result) + + if current_number_of_prs == 0 then + widget:set_visible(false) + return + end + + widget:set_visible(true) + widget:set_text(current_number_of_prs) + + for i = 0, #rows do rows[i]=nil end + + for i = 0, #to_review_rows do to_review_rows[i]=nil end + table.insert(to_review_rows, { + { + markup = 'PRs to review', + align = 'center', + forced_height = 20, + widget = wibox.widget.textbox + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + }) + + for i = 0, #my_review_rows do my_review_rows[i]=nil end + table.insert(my_review_rows, { + { + markup = 'My PRs', + align = 'center', + forced_height = 20, + widget = wibox.widget.textbox + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + }) + local current_time = os.time(os.date("!*t")) + + for _, pr in ipairs(result) do + local path_to_avatar = os.getenv("HOME") ..'/.cache/awmw/bitbucket-widget/avatars/' .. pr.author.uuid + local number_of_approves = count_approves(pr.participants) + + local row = wibox.widget { + { + { + { + { + resize = true, + image = path_to_avatar, + forced_width = 40, + forced_height = 40, + widget = wibox.widget.imagebox + }, + id = 'avatar', + margins = 8, + layout = wibox.container.margin + }, + { + { + id = 'title', + markup = '' .. ellipsize(pr.title, 50) .. '', + widget = wibox.widget.textbox, + forced_width = 400 + }, + { + { + { + { + text = ellipsize(pr.source.branch.name, 30), + widget = wibox.widget.textbox + }, + { + text = '->', + widget = wibox.widget.textbox + }, + { + text = pr.destination.branch.name, + widget = wibox.widget.textbox + }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + { + { + text = pr.author.display_name, + widget = wibox.widget.textbox + }, + { + text = to_time_ago(os.difftime(current_time, parse_date(pr.created_on))), + widget = wibox.widget.textbox + }, + spacing = 8, + expand = 'none', + layout = wibox.layout.fixed.horizontal + }, + forced_width = 285, + layout = wibox.layout.fixed.vertical + }, + { + { + { + image = WIDGET_DIR .. '/check.svg', + resize = false, + widget = wibox.widget.imagebox + }, + { + text = number_of_approves, + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal + }, + { + { + image = WIDGET_DIR .. '/message-circle.svg', + resize = false, + widget = wibox.widget.imagebox + }, + { + text = pr.comment_count, + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal + }, + layout = wibox.layout.fixed.vertical + }, + layout = wibox.layout.fixed.horizontal + }, + + spacing = 8, + layout = wibox.layout.fixed.vertical + }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + margins = 8, + layout = wibox.container.margin + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + } + + if not gfs.file_readable(path_to_avatar) then + local cmd = string.format(DOWNLOAD_AVATAR_CMD, HOME_DIR, pr.author.uuid, pr.author.links.avatar.href) + spawn.easy_async(cmd, function() row:get_children_by_id('avatar')[1]:set_image(path_to_avatar) end) + end + + + 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) + + row:get_children_by_id('title')[1]:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell("xdg-open " .. pr.links.html.href) + popup.visible = false + end) + ) + ) + row:get_children_by_id('avatar')[1]:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell( + string.format('xdg-open "https://bitbucket.org/%s/%s/pull-requests?state=OPEN&author=%s"', + workspace, repo_slug, pr.author.uuid) + ) + popup.visible = false + end) + ) + ) + + local old_cursor, old_wibox + row:get_children_by_id('title')[1]:connect_signal("mouse::enter", function() + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + row:get_children_by_id('title')[1]:connect_signal("mouse::leave", function() + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + row:get_children_by_id('avatar')[1]:connect_signal("mouse::enter", function() + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + row:get_children_by_id('avatar')[1]:connect_signal("mouse::leave", function() + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + if (pr.author.uuid == '{' .. uuid .. '}') then + table.insert(my_review_rows, row) + else + table.insert(to_review_rows, row) + end + end + + table.insert(rows, to_review_rows) + if (#my_review_rows > 1) then + table.insert(rows, my_review_rows) + end + popup:setup(rows) + end + + bitbucket_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if popup.visible then + popup.visible = not popup.visible + else + popup:move_next_to(mouse.current_widget_geometry) + end + end) + ) + ) + + watch(string.format(GET_PRS_CMD, host, workspace, repo_slug, uuid, uuid), + timeout, update_widget, bitbucket_widget) + return bitbucket_widget +end + +return setmetatable(bitbucket_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/check.svg b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/check.svg new file mode 100644 index 0000000..e9e44ac --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/clipboard.svg b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/clipboard.svg new file mode 100644 index 0000000..5c6dfd3 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/clipboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/copy.svg b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/copy.svg new file mode 100644 index 0000000..bab2098 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/executable_test_bitbucket_api.sh b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/executable_test_bitbucket_api.sh new file mode 100644 index 0000000..378b3ef --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/executable_test_bitbucket_api.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +HOST='https://api.bitbucket.org' +ACCOUNT_ID='' +WORKSPACE='' +REPO_SLUG='' + +curl -s -n "${HOST}/2.0/repositories/${WORKSPACE}/${REPO_SLUG}/pullrequests?fields=values.title,values.links.html,values.author.display_name,values.author.links.avatar&q=reviewers.account_id+%3D+%22${ACCOUNT_ID}%22" \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/git-pull-request.svg b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/git-pull-request.svg new file mode 100644 index 0000000..c2e2867 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/git-pull-request.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/message-circle.svg b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/message-circle.svg new file mode 100644 index 0000000..43eacbb --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/message-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/user.svg b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/user.svg new file mode 100644 index 0000000..4058dee --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/bitbucket-widget/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/brightness-widget/README.md b/dot_config/awesome/awesome-wm-widgets/brightness-widget/README.md new file mode 100644 index 0000000..0cdb774 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/brightness-widget/README.md @@ -0,0 +1,94 @@ +# Brightness widget + +This widget represents current brightness level, depending on config parameters could be an arcchart or icon with text: ![Brightness widget](./br-wid-1.png) + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `type`| `arc` | The widget type. Could be `arc` or `icon_and_text` | +| `program` | `light` | The program used to control the brightness, either `light`, `xbacklight`, or `brightnessctl`. | +| `step` | 5 | Step | +| `base` | 20 | Base level to set brightness to on left click. | +| `path_to_icon` | `/usr/share/icons/Arc/status/symbolic/display-brightness-symbolic.svg` | Path to the icon | +| `font` | `beautiful.font` | Font name and size, like `Play 12` | +| `timeout` | 1 | How often in seconds the widget refreshes. Check the note below | +| `tooltip` | false | Display brightness level in a tooltip when the mouse cursor hovers the widget | +| `percentage` | false | Display a '%' character after the brightness level | + +_Note:_ If brightness is controlled only by the widget (either by a mouse, or by a shortcut, then the `timeout` could be quite big, as there is no reason to synchronize the brightness level). + +## Installation + +To choose the right `program` argument, first you need to check which of them works better for you. + + - using `xbacklight`: + + Install (on Ubuntu it's available in the apt repository) it and check if it works by running: + + ```bash + xbacklight -get + ``` + + If there is no output it means that it doesn't work, you can either try to fix it, or try to use `light`. + + - using `light` command: + + Install (on Ubuntu it's available in the apt repository) from the repo: [github.com/haikarainen/light](https://github.com/haikarainen/light) and check if it works by running + + ```bash + light -G + 49.18 + light -A 5 + ``` + If you're on Ubuntu/debian and if the brightness level doesn't change, try to do this: https://github.com/haikarainen/light/issues/113#issuecomment-632638436. + + - using `brightnessctl`: + + On Ubuntu it is available in the apt repository. Install and check the ouptut of the following command. + ```bash + brightnessctl --list + ``` + +Then clone this repo under **~/.config/awesome/**: + +```bash +git clone https://github.com/streetturtle/awesome-wm-widgets.git ~/.config/awesome/awesome-wm-widgets +``` + +Require widget at the beginning of **rc.lua**: + +```lua +local brightness_widget = require("awesome-wm-widgets.brightness-widget.brightness") +``` + +Add the widget to the tasklist: + +```lua +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + brightness_widget(), + -- or customized + brightness_widget{ + type = 'icon_and_text', + program = 'xbacklight', + step = 2, + } + } + ... +``` + +## Controls + +In order to change brightness by shortcuts you can add them to the `globalkeys` table in the **rc.lua**: + +```lua +awful.key({ modkey }, ";", function () brightness_widget:inc() end, {description = "increase brightness", group = "custom"}), +awful.key({ modkey, "Shift"}, ";", function () brightness_widget:dec() end, {description = "decrease brightness", group = "custom"}), +``` +On a laptop you can use `XF86MonBrightnessUp` and `XF86MonBrightnessDown` keys. diff --git a/dot_config/awesome/awesome-wm-widgets/brightness-widget/br-wid-1.png b/dot_config/awesome/awesome-wm-widgets/brightness-widget/br-wid-1.png new file mode 100644 index 0000000..b00b0e6 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/brightness-widget/br-wid-1.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/brightness-widget/brightness.lua b/dot_config/awesome/awesome-wm-widgets/brightness-widget/brightness.lua new file mode 100644 index 0000000..affaf47 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/brightness-widget/brightness.lua @@ -0,0 +1,195 @@ +------------------------------------------------- +-- Brightness Widget for Awesome Window Manager +-- Shows the brightness level of the laptop display +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/brightness-widget + +-- @author Pavel Makhov +-- @copyright 2021 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") +local spawn = require("awful.spawn") +local gfs = require("gears.filesystem") +local naughty = require("naughty") +local beautiful = require("beautiful") + +local ICON_DIR = gfs.get_configuration_dir() .. "awesome-wm-widgets/brightness-widget/" +local get_brightness_cmd +local set_brightness_cmd +local inc_brightness_cmd +local dec_brightness_cmd + +local brightness_widget = {} + +local function show_warning(message) + naughty.notify({ + preset = naughty.config.presets.critical, + title = "Brightness Widget", + text = message, + }) +end + +local function worker(user_args) + local args = user_args or {} + + local type = args.type or 'arc' -- arc or icon_and_text + local path_to_icon = args.path_to_icon or ICON_DIR .. 'brightness.svg' + local font = args.font or beautiful.font + local timeout = args.timeout or 100 + + local program = args.program or 'light' + local step = args.step or 5 + local base = args.base or 20 + local current_level = 0 -- current brightness value + local tooltip = args.tooltip or false + local percentage = args.percentage or false + if program == 'light' then + get_brightness_cmd = 'light -G' + set_brightness_cmd = 'light -S %d' -- + inc_brightness_cmd = 'light -A ' .. step + dec_brightness_cmd = 'light -U ' .. step + elseif program == 'xbacklight' then + get_brightness_cmd = 'xbacklight -get' + set_brightness_cmd = 'xbacklight -set %d' -- + inc_brightness_cmd = 'xbacklight -inc ' .. step + dec_brightness_cmd = 'xbacklight -dec ' .. step + elseif program == 'brightnessctl' then + get_brightness_cmd = "brightnessctl get" + set_brightness_cmd = "brightnessctl set %d%%" -- + inc_brightness_cmd = "brightnessctl set +" .. step .. "%" + dec_brightness_cmd = "brightnessctl set " .. step .. "-%" + else + show_warning(program .. " command is not supported by the widget") + return + end + + if type == 'icon_and_text' then + brightness_widget.widget = wibox.widget { + { + { + image = path_to_icon, + resize = false, + widget = wibox.widget.imagebox, + }, + valign = 'center', + layout = wibox.container.place + }, + { + id = 'txt', + font = font, + widget = wibox.widget.textbox + }, + spacing = 4, + layout = wibox.layout.fixed.horizontal, + set_value = function(self, level) + local display_level = level + if percentage then + display_level = display_level .. '%' + end + self:get_children_by_id('txt')[1]:set_text(display_level) + end + } + elseif type == 'arc' then + brightness_widget.widget = wibox.widget { + { + { + image = path_to_icon, + resize = true, + widget = wibox.widget.imagebox, + }, + valign = 'center', + layout = wibox.container.place + }, + max_value = 100, + thickness = 2, + start_angle = 4.71238898, -- 2pi*3/4 + forced_height = 18, + forced_width = 18, + paddings = 2, + widget = wibox.container.arcchart, + set_value = function(self, level) + self:set_value(level) + end + } + else + show_warning(type .. " type is not supported by the widget") + return + + end + + local update_widget = function(widget, stdout, _, _, _) + local brightness_level = tonumber(string.format("%.0f", stdout)) + current_level = brightness_level + widget:set_value(brightness_level) + end + + function brightness_widget:set(value) + current_level = value + spawn.easy_async(string.format(set_brightness_cmd, value), function() + spawn.easy_async(get_brightness_cmd, function(out) + update_widget(brightness_widget.widget, out) + end) + end) + end + local old_level = 0 + function brightness_widget:toggle() + if old_level < 0.1 then + -- avoid toggling between '0' and 'almost 0' + old_level = 1 + end + if current_level < 0.1 then + -- restore previous level + current_level = old_level + else + -- save current brightness for later + old_level = current_level + current_level = 0 + end + brightness_widget:set(current_level) + end + function brightness_widget:inc() + spawn.easy_async(inc_brightness_cmd, function() + spawn.easy_async(get_brightness_cmd, function(out) + update_widget(brightness_widget.widget, out) + end) + end) + end + function brightness_widget:dec() + spawn.easy_async(dec_brightness_cmd, function() + spawn.easy_async(get_brightness_cmd, function(out) + update_widget(brightness_widget.widget, out) + end) + end) + end + + brightness_widget.widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() brightness_widget:set(base) end), + awful.button({}, 3, function() brightness_widget:toggle() end), + awful.button({}, 4, function() brightness_widget:inc() end), + awful.button({}, 5, function() brightness_widget:dec() end) + ) + ) + + watch(get_brightness_cmd, timeout, update_widget, brightness_widget.widget) + + if tooltip then + awful.tooltip { + objects = { brightness_widget.widget }, + timer_function = function() + return current_level .. " %" + end, + } + end + + return brightness_widget.widget +end + +return setmetatable(brightness_widget, { + __call = function(_, ...) + return worker(...) + end, +}) diff --git a/dot_config/awesome/awesome-wm-widgets/brightness-widget/brightness.svg b/dot_config/awesome/awesome-wm-widgets/brightness-widget/brightness.svg new file mode 100644 index 0000000..d334372 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/brightness-widget/brightness.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/calendar-widget/README.md b/dot_config/awesome/awesome-wm-widgets/calendar-widget/README.md new file mode 100644 index 0000000..b663a18 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/calendar-widget/README.md @@ -0,0 +1,89 @@ +# Calendar Widget + +Calendar widget for Awesome WM - slightly improved version of the `wibox.widget.calendar`. + +## Features + + +### Customization + +| Name | Default | Description | +|---|---|---| +| theme | `naughty` | The theme to use | +| placement | `top` | The position of the popup | +| radius | 8 | The popup radius | +| start_sunday | false | Start the week on Sunday | + + - themes: + + | Name | Screenshot | + |---|---| + | nord | ![nord_theme](./nord.png) | + | outrun | ![outrun_theme](./outrun.png) | + | light | ![outrun_theme](./light.png) | + | dark | ![outrun_theme](./dark.png) | + | naughty (default) | from local theme | + + - setup widget placement + + top center - in case you clock is centered: + + ![calendar_top](./calendar_top.png) + + top right - for default awesome config: + + ![calendar_top_right](./calendar_top_right.png) + + bottom right - in case your wibar at the bottom: + + ![calendar_bottom_right](./calendar_bottom_right.png) + + - setup first day of week + + By setting `start_sunday` to true: + ![calendar_start_sunday](./calendar_start_sunday.png) + + - mouse support: + move to the next and previous month. Using mouse buttons or scroll wheel. + + You can configure this by specifying the button to move to next/previous. + Usually these are configured as follows. If you want to use other mouse buttons, you can find their number using `xev`. + + | number | button | + |--------|---------------| + | 4 | scroll up | + | 5 | scroll down | + | 1 | left click | + | 2 | right click | + | 3 | middles click | + + By default `previous_month_button` is 5, `next_month_button` is 4. + + +## How to use + +This widget needs an 'anchor' - another widget which triggers visibility of the calendar. Default `mytextclock` is the perfect candidate! +Just after mytextclock is instantiated, create the widget and add the mouse listener to it. + +```lua +local calendar_widget = require("awesome-wm-widgets.calendar-widget.calendar") +-- ... +-- Create a textclock widget +mytextclock = wibox.widget.textclock() +-- default +local cw = calendar_widget() +-- or customized +local cw = calendar_widget({ + theme = 'outrun', + placement = 'bottom_right', + start_sunday = true, + radius = 8, +-- with customized next/previous (see table above) + previous_month_button = 1, + next_month_button = 3, +}) +mytextclock:connect_signal("button::press", + function(_, _, _, button) + if button == 1 then cw.toggle() end + end) +``` diff --git a/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar.lua b/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar.lua new file mode 100644 index 0000000..bc4a877 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar.lua @@ -0,0 +1,258 @@ +------------------------------------------------- +-- Calendar Widget for Awesome Window Manager +-- Shows the current month and supports scroll up/down to switch month +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/calendar-widget + +-- @author Pavel Makhov +-- @copyright 2019 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local beautiful = require("beautiful") +local wibox = require("wibox") +local gears = require("gears") +local naughty = require("naughty") + +local calendar_widget = {} + +local function worker(user_args) + + local calendar_themes = { + nord = { + bg = '#2E3440', + fg = '#D8DEE9', + focus_date_bg = '#88C0D0', + focus_date_fg = '#000000', + weekend_day_bg = '#3B4252', + weekday_fg = '#88C0D0', + header_fg = '#E5E9F0', + border = '#4C566A' + }, + outrun = { + bg = '#0d0221', + fg = '#D8DEE9', + focus_date_bg = '#650d89', + focus_date_fg = '#2de6e2', + weekend_day_bg = '#261447', + weekday_fg = '#2de6e2', + header_fg = '#f6019d', + border = '#261447' + }, + dark = { + bg = '#000000', + fg = '#ffffff', + focus_date_bg = '#ffffff', + focus_date_fg = '#000000', + weekend_day_bg = '#444444', + weekday_fg = '#ffffff', + header_fg = '#ffffff', + border = '#333333' + }, + light = { + bg = '#ffffff', + fg = '#000000', + focus_date_bg = '#000000', + focus_date_fg = '#ffffff', + weekend_day_bg = '#AAAAAA', + weekday_fg = '#000000', + header_fg = '#000000', + border = '#CCCCCC' + }, + monokai = { + bg = '#272822', + fg = '#F8F8F2', + focus_date_bg = '#AE81FF', + focus_date_fg = '#ffffff', + weekend_day_bg = '#75715E', + weekday_fg = '#FD971F', + header_fg = '#F92672', + border = '#75715E' + }, + naughty = { + bg = beautiful.notification_bg or beautiful.bg, + fg = beautiful.notification_fg or beautiful.fg, + focus_date_bg = beautiful.notification_fg or beautiful.fg, + focus_date_fg = beautiful.notification_bg or beautiful.bg, + weekend_day_bg = beautiful.bg_focus, + weekday_fg = beautiful.fg, + header_fg = beautiful.fg, + border = beautiful.border_normal + } + + } + + local args = user_args or {} + + if args.theme ~= nil and calendar_themes[args.theme] == nil then + naughty.notify({ + preset = naughty.config.presets.critical, + title = 'Calendar Widget', + text = 'Theme "' .. args.theme .. '" not found, fallback to default'}) + args.theme = 'naughty' + end + + local theme = args.theme or 'naughty' + local placement = args.placement or 'top' + local radius = args.radius or 8 + local next_month_button = args.next_month_button or 4 + local previous_month_button = args.previous_month_button or 5 + local start_sunday = args.start_sunday or false + + local styles = {} + local function rounded_shape(size) + return function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, size) + end + end + + styles.month = { + padding = 4, + bg_color = calendar_themes[theme].bg, + border_width = 0, + } + + styles.normal = { + markup = function(t) return t end, + shape = rounded_shape(4) + } + + styles.focus = { + fg_color = calendar_themes[theme].focus_date_fg, + bg_color = calendar_themes[theme].focus_date_bg, + markup = function(t) return '' .. t .. '' end, + shape = rounded_shape(4) + } + + styles.header = { + fg_color = calendar_themes[theme].header_fg, + bg_color = calendar_themes[theme].bg, + markup = function(t) return '' .. t .. '' end + } + + styles.weekday = { + fg_color = calendar_themes[theme].weekday_fg, + bg_color = calendar_themes[theme].bg, + markup = function(t) return '' .. t .. '' end, + } + + local function decorate_cell(widget, flag, date) + if flag == 'monthheader' and not styles.monthheader then + flag = 'header' + end + + -- highlight only today's day + if flag == 'focus' then + local today = os.date('*t') + if not (today.month == date.month and today.year == date.year) then + flag = 'normal' + end + end + + local props = styles[flag] or {} + if props.markup and widget.get_text and widget.set_markup then + widget:set_markup(props.markup(widget:get_text())) + end + -- Change bg color for weekends + local d = { year = date.year, month = (date.month or 1), day = (date.day or 1) } + local weekday = tonumber(os.date('%w', os.time(d))) + local default_bg = (weekday == 0 or weekday == 6) + and calendar_themes[theme].weekend_day_bg + or calendar_themes[theme].bg + local ret = wibox.widget { + { + { + widget, + halign = 'center', + widget = wibox.container.place + }, + margins = (props.padding or 2) + (props.border_width or 0), + widget = wibox.container.margin + }, + shape = props.shape, + shape_border_color = props.border_color or '#000000', + shape_border_width = props.border_width or 0, + fg = props.fg_color or calendar_themes[theme].fg, + bg = props.bg_color or default_bg, + widget = wibox.container.background + } + + return ret + end + + local cal = wibox.widget { + date = os.date('*t'), + font = beautiful.get_font(), + fn_embed = decorate_cell, + long_weekdays = true, + start_sunday = start_sunday, + widget = wibox.widget.calendar.month + } + + local popup = awful.popup { + ontop = true, + visible = false, + shape = rounded_shape(radius), + offset = { y = 5 }, + border_width = 1, + border_color = calendar_themes[theme].border, + widget = cal + } + + popup:buttons( + awful.util.table.join( + awful.button({}, next_month_button, function() + local a = cal:get_date() + a.month = a.month + 1 + cal:set_date(nil) + cal:set_date(a) + popup:set_widget(cal) + end), + awful.button({}, previous_month_button, function() + local a = cal:get_date() + a.month = a.month - 1 + cal:set_date(nil) + cal:set_date(a) + popup:set_widget(cal) + end) + ) + ) + + function calendar_widget.toggle() + + if popup.visible then + -- to faster render the calendar refresh it and just hide + cal:set_date(nil) -- the new date is not set without removing the old one + cal:set_date(os.date('*t')) + popup:set_widget(nil) -- just in case + popup:set_widget(cal) + popup.visible = not popup.visible + else + if placement == 'top' then + awful.placement.top(popup, { margins = { top = 30 }, parent = awful.screen.focused() }) + elseif placement == 'top_right' then + awful.placement.top_right(popup, { margins = { top = 30, right = 10}, parent = awful.screen.focused() }) + elseif placement == 'top_left' then + awful.placement.top_left(popup, { margins = { top = 30, left = 10}, parent = awful.screen.focused() }) + elseif placement == 'bottom_right' then + awful.placement.bottom_right(popup, { margins = { bottom = 30, right = 10}, + parent = awful.screen.focused() }) + elseif placement == 'bottom_left' then + awful.placement.bottom_left(popup, { margins = { bottom = 30, left = 10}, + parent = awful.screen.focused() }) + else + awful.placement.top(popup, { margins = { top = 30 }, parent = awful.screen.focused() }) + end + + popup.visible = true + + end + end + + return calendar_widget + +end + +return setmetatable(calendar_widget, { __call = function(_, ...) + return worker(...) +end }) diff --git a/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar_bottom_right.png b/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar_bottom_right.png new file mode 100644 index 0000000..2bc2e82 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar_bottom_right.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar_start_sunday.png b/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar_start_sunday.png new file mode 100644 index 0000000..126a218 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar_start_sunday.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar_top.png b/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar_top.png new file mode 100644 index 0000000..3e6b66b Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar_top.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar_top_right.png b/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar_top_right.png new file mode 100644 index 0000000..4a29022 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/calendar-widget/calendar_top_right.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/calendar-widget/dark.png b/dot_config/awesome/awesome-wm-widgets/calendar-widget/dark.png new file mode 100644 index 0000000..540289f Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/calendar-widget/dark.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/calendar-widget/light.png b/dot_config/awesome/awesome-wm-widgets/calendar-widget/light.png new file mode 100644 index 0000000..ab675d1 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/calendar-widget/light.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/calendar-widget/nord.png b/dot_config/awesome/awesome-wm-widgets/calendar-widget/nord.png new file mode 100644 index 0000000..94f9f7e Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/calendar-widget/nord.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/calendar-widget/outrun.png b/dot_config/awesome/awesome-wm-widgets/calendar-widget/outrun.png new file mode 100644 index 0000000..d59c123 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/calendar-widget/outrun.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/cmus-widget/README.md b/dot_config/awesome/awesome-wm-widgets/cmus-widget/README.md new file mode 100644 index 0000000..e33655e --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/cmus-widget/README.md @@ -0,0 +1,53 @@ +# Cmus widget + +Cmus widget that shows the current playing track. + +![widget](./screenshots/cmus-widget.png) + +Left click toggles playback. + +## Installation + +Clone the repo under **~/.config/awesome/** and add widget in **rc.lua**: + +```lua +local cmus_widget = require('awesome-wm-widgets.cmus-widget.cmus') +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + cmus_widget(), + -- customized + cmus_widget{ + space = 5, + timeout = 5 + }, +``` + +### Shortcuts + +To improve responsiveness of the widget when playback is changed by a shortcut use corresponding methods of the widget: + +```lua +awful.key({ modkey, "Shift" }, "p", function () cmus_widget:play_pause() end, {description = "toggle track", group = "cmus"}), +awful.key({ }, "XF86AudioPlay", function () cmus_widget:play() end, {description = "play track", group = "cmus"}), +awful.key({ }, "XF86AudioPause", function () cmus_widget:play() end, {description = "pause track", group = "cmus"}), +awful.key({ }, "XF86AudioNext", function () cmus_widget:next_track() end, {description = "next track", group = "cmus"}), +awful.key({ }, "XF86AudioPrev", function () cmus_widget:prev_track() end, {description = "previous track", group = "cmus"}), +awful.key({ }, "XF86AudioStop", function () cmus_widget:stop() end, {description = "stop cmus", group = "cmus"}), +``` + +## 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 | +|---|---|---| +| `font` | `beautiful.font` | Font name and size, like `Play 12` | +| `path_to_icons` | `/usr/share/icons/Arc/actions/symbolic/` | Alternative path for the icons | +| `timeout`| `10` | Refresh cooldown | +| `space` | `3` | Space between icon and track title | diff --git a/dot_config/awesome/awesome-wm-widgets/cmus-widget/cmus.lua b/dot_config/awesome/awesome-wm-widgets/cmus-widget/cmus.lua new file mode 100644 index 0000000..b1287c5 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/cmus-widget/cmus.lua @@ -0,0 +1,149 @@ +------------------------------------------------- +-- Cmus Widget for Awesome Window Manager +-- Show what's playing, play/pause, etc + +-- @author Augusto Gunsch +-- @copyright 2022 Augusto Gunsch +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") +local spawn = require("awful.spawn") +local beautiful = require('beautiful') + +local cmus_widget = {} + +local function worker(user_args) + + local args = user_args or {} + local font = args.font or beautiful.font + + local path_to_icons = args.path_to_icons or "/usr/share/icons/Arc/actions/symbolic/" + local timeout = args.timeout or 10 + local space = args.space or 3 + + cmus_widget.widget = wibox.widget { + { + { + id = "playback_icon", + resize = false, + widget = wibox.widget.imagebox, + }, + layout = wibox.container.place + }, + { + id = "text", + font = font, + widget = wibox.widget.textbox + }, + spacing = space, + layout = wibox.layout.fixed.horizontal, + update_icon = function(self, name) + self:get_children_by_id("playback_icon")[1]:set_image(path_to_icons .. name) + end, + set_title = function(self, title) + self:get_children_by_id("text")[1]:set_text(title) + end + } + + local function update_widget(widget, stdout, _, _, code) + if code == 0 then + local cmus_info = {} + + for s in stdout:gmatch("[^\r\n]+") do + local key, val = string.match(s, "^tag (%a+) (.+)$") + + if key and val then + cmus_info[key] = val + else + key, val = string.match(s, "^set (%a+) (.+)$") + + if key and val then + cmus_info[key] = val + else + key, val = string.match(s, "^(%a+) (.+)$") + if key and val then + cmus_info[key] = val + end + end + end + end + + local title = cmus_info.title + + if not title and cmus_info.file then + title = cmus_info.file:gsub("%..-$", "") + title = title:gsub("^.+/", "") + end + + if title then + if cmus_info["status"] == "playing" then + widget:update_icon("media-playback-start-symbolic.svg") + elseif cmus_info["status"] == "paused" then + widget:update_icon("media-playback-pause-symbolic.svg") + else + widget:update_icon("media-playback-stop-symbolic.svg") + end + + widget:set_title(title) + widget.visible = true + else + widget.visible = false + end + else + widget.visible = false + end + end + + function cmus_widget:update() + spawn.easy_async("cmus-remote -Q", + function(stdout, _, _, code) + update_widget(cmus_widget.widget, stdout, _, _, code) + end) + end + + function cmus_widget:play_pause() + spawn("cmus-remote -u") + cmus_widget.update() + end + + function cmus_widget:pause() + spawn("cmus-remote -U") + cmus_widget.update() + end + + function cmus_widget:play() + spawn("cmus-remote -p") + cmus_widget.update() + end + + function cmus_widget:next_track() + spawn("cmus-remote -n") + cmus_widget.update() + end + + function cmus_widget:prev_track() + spawn("cmus-remote -r") + cmus_widget.update() + end + + function cmus_widget:stop() + spawn("cmus-remote -s") + cmus_widget.update() + end + + cmus_widget.widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() cmus_widget:play_pause() end) + ) + ) + + watch("cmus-remote -Q", timeout, update_widget, cmus_widget.widget) + + return cmus_widget.widget +end + +return setmetatable(cmus_widget, { __call = function(_, ...) + return worker(...) +end }) diff --git a/dot_config/awesome/awesome-wm-widgets/cmus-widget/screenshots/cmus-widget.png b/dot_config/awesome/awesome-wm-widgets/cmus-widget/screenshots/cmus-widget.png new file mode 100644 index 0000000..ba04401 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/cmus-widget/screenshots/cmus-widget.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/cpu-widget/README.md b/dot_config/awesome/awesome-wm-widgets/cpu-widget/README.md new file mode 100644 index 0000000..b323f9b --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/cpu-widget/README.md @@ -0,0 +1,71 @@ +# CPU widget + +[![GitHub issues by-label](https://img.shields.io/github/issues-raw/streetturtle/awesome-wm-widgets/cpu)](https://github.com/streetturtle/awesome-wm-widgets/labels/cpu) + +This widget shows the average CPU load among all cores of the machine: + +![screenshot](./cpu.gif) + +## How it works + +To measure the load I took Paul Colby's bash [script](http://colby.id.au/calculating-cpu-usage-from-proc-stat/) and rewrote it in Lua, which was quite simple. +So awesome simply reads the first line of /proc/stat: + +```bash +$ cat /proc/stat | grep '^cpu ' +cpu 197294 718 50102 2002182 3844 0 2724 0 0 0 +``` + +and calculates the percentage. + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `width` | 50 | Width of the widget | +| `step_width` | 2 | Width of the step | +| `step_spacing` | 1 | Space size between steps | +| `color` | `beautiful.fg_normal` | Color of the graph | +| `enable_kill_button` | `false` | Show button which kills the process | +| `process_info_max_length` | `-1` | Truncate the process information. Some processes may have a very long list of parameters which won't fit in the screen, this options allows to truncate it to the given length. | +| `timeout` | 1 | How often in seconds the widget refreshes | + +### Example + +```lua +cpu_widget({ + width = 70, + step_width = 2, + step_spacing = 0, + color = '#434c5e' +}) +``` + +The config above results in the following widget: + +![custom](./custom.png) + +## Installation + +Clone/download repo and use widget in **rc.lua**: + +```lua +local cpu_widget = require("awesome-wm-widgets.cpu-widget.cpu-widget") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + cpu_widget(), + -- or custom + cpu_widget({ + width = 70, + step_width = 2, + step_spacing = 0, + color = '#434c5e' + }) + ... +``` diff --git a/dot_config/awesome/awesome-wm-widgets/cpu-widget/cpu-widget.lua b/dot_config/awesome/awesome-wm-widgets/cpu-widget/cpu-widget.lua new file mode 100644 index 0000000..11debe8 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/cpu-widget/cpu-widget.lua @@ -0,0 +1,339 @@ +------------------------------------------------- +-- CPU Widget for Awesome Window Manager +-- Shows the current CPU utilization +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/cpu-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local watch = require("awful.widget.watch") +local wibox = require("wibox") +local beautiful = require("beautiful") +local gears = require("gears") + +local CMD = [[sh -c "grep '^cpu.' /proc/stat; ps -eo '%p|%c|%C|' -o "%mem" -o '|%a' --sort=-%cpu ]] + .. [[| head -11 | tail -n +2"]] + +-- A smaller command, less resource intensive, used when popup is not shown. +local CMD_slim = [[grep --max-count=1 '^cpu.' /proc/stat]] + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/cpu-widget' + +local cpu_widget = {} +local cpu_rows = { + spacing = 4, + layout = wibox.layout.fixed.vertical, +} +local is_update = true +local process_rows = { + layout = wibox.layout.fixed.vertical, +} + +-- Splits the string by separator +-- @return table with separated substrings +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 + +-- Checks if a string starts with a another string +local function starts_with(str, start) + return str:sub(1, #start) == start +end + + +local function create_textbox(args) + return wibox.widget{ + text = args.text, + align = args.align or 'left', + markup = args.markup, + forced_width = args.forced_width or 40, + widget = wibox.widget.textbox + } +end + +local function create_process_header(params) + local res = wibox.widget{ + create_textbox{markup = 'PID'}, + create_textbox{markup = 'Name'}, + { + create_textbox{markup = '%CPU'}, + create_textbox{markup = '%MEM'}, + params.with_action_column and create_textbox{forced_width = 20} or nil, + layout = wibox.layout.align.horizontal + }, + layout = wibox.layout.ratio.horizontal + } + res:ajust_ratio(2, 0.2, 0.47, 0.33) + + return res +end + +local function create_kill_process_button() + return wibox.widget{ + { + id = "icon", + image = WIDGET_DIR .. '/window-close-symbolic.svg', + resize = false, + opacity = 0.1, + widget = wibox.widget.imagebox + }, + widget = wibox.container.background + } +end + +local function worker(user_args) + + local args = user_args or {} + + local width = args.width or 50 + local step_width = args.step_width or 2 + local step_spacing = args.step_spacing or 1 + local color = args.color or beautiful.fg_normal + local background_color = args.background_color or "#00000000" + local enable_kill_button = args.enable_kill_button or false + local process_info_max_length = args.process_info_max_length or -1 + local timeout = args.timeout or 1 + + local cpugraph_widget = wibox.widget { + max_value = 100, + background_color = background_color, + forced_width = width, + step_width = step_width, + step_spacing = step_spacing, + widget = wibox.widget.graph, + color = "linear:0,0:0,20:0,#FF0000:0.3,#FFFF00:0.6," .. color + } + + -- This timer periodically executes the heavy command while the popup is open. + -- It is stopped when the popup is closed and only the slim command is run then. + -- This greatly improves performance while the popup is closed at the small cost + -- of a slightly longer popup opening time. + local popup_timer = gears.timer { + timeout = timeout + } + + local popup = awful.popup{ + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_normal, + maximum_width = 300, + offset = { y = 5 }, + widget = {} + } + + -- Do not update process rows when mouse cursor is over the widget + popup:connect_signal("mouse::enter", function() is_update = false end) + popup:connect_signal("mouse::leave", function() is_update = true end) + + cpugraph_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if popup.visible then + popup.visible = not popup.visible + -- When the popup is not visible, stop the timer + popup_timer:stop() + else + popup:move_next_to(mouse.current_widget_geometry) + -- Restart the timer, when the popup becomes visible + -- Emit the signal to start the timer directly and not wait the timeout first + popup_timer:start() + popup_timer:emit_signal("timeout") + end + end) + ) + ) + + --- By default graph widget goes from left to right, so we mirror it and push up a bit + cpu_widget = wibox.widget { + { + cpugraph_widget, + reflection = {horizontal = true}, + layout = wibox.container.mirror + }, + bottom = 2, + color = background_color, + widget = wibox.container.margin + } + + -- This part runs constantly, also when the popup is closed. + -- It updates the graph widget in the bar. + local maincpu = {} + watch(CMD_slim, timeout, function(widget, stdout) + + local _, user, nice, system, idle, iowait, irq, softirq, steal, _, _ = + stdout:match('(%w+)%s+(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)') + + local total = user + nice + system + idle + iowait + irq + softirq + steal + + local diff_idle = idle - tonumber(maincpu['idle_prev'] == nil and 0 or maincpu['idle_prev']) + local diff_total = total - tonumber(maincpu['total_prev'] == nil and 0 or maincpu['total_prev']) + local diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10 + + maincpu['total_prev'] = total + maincpu['idle_prev'] = idle + + widget:add_value(diff_usage) + end, + cpugraph_widget + ) + + -- This part runs whenever the timer is fired. + -- It therefore only runs when the popup is open. + local cpus = {} + popup_timer:connect_signal('timeout', function() + awful.spawn.easy_async(CMD, function(stdout, _, _, _) + local i = 1 + local j = 1 + for line in stdout:gmatch("[^\r\n]+") do + if starts_with(line, 'cpu') then + + if cpus[i] == nil then cpus[i] = {} end + + local name, user, nice, system, idle, iowait, irq, softirq, steal, _, _ = + line:match('(%w+)%s+(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)') + + local total = user + nice + system + idle + iowait + irq + softirq + steal + + local diff_idle = idle - tonumber(cpus[i]['idle_prev'] == nil and 0 or cpus[i]['idle_prev']) + local diff_total = total - tonumber(cpus[i]['total_prev'] == nil and 0 or cpus[i]['total_prev']) + local diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10 + + cpus[i]['total_prev'] = total + cpus[i]['idle_prev'] = idle + + local row = wibox.widget + { + create_textbox{text = name}, + create_textbox{text = math.floor(diff_usage) .. '%'}, + { + max_value = 100, + value = diff_usage, + forced_height = 20, + forced_width = 150, + paddings = 1, + margins = 4, + border_width = 1, + border_color = beautiful.bg_focus, + background_color = beautiful.bg_normal, + bar_border_width = 1, + bar_border_color = beautiful.bg_focus, + color = "linear:150,0:0,0:0,#D08770:0.3,#BF616A:0.6," .. beautiful.fg_normal, + widget = wibox.widget.progressbar, + + }, + layout = wibox.layout.ratio.horizontal + } + row:ajust_ratio(2, 0.15, 0.15, 0.7) + cpu_rows[i] = row + i = i + 1 + else + if is_update == true then + + local columns = split(line, '|') + + local pid = columns[1] + local comm = columns[2] + local cpu = columns[3] + local mem = columns[4] + local cmd = columns[5] + + local kill_proccess_button = enable_kill_button and create_kill_process_button() or nil + + local pid_name_rest = wibox.widget{ + create_textbox{text = pid}, + create_textbox{text = comm}, + { + create_textbox{text = cpu, align = 'center'}, + create_textbox{text = mem, align = 'center'}, + kill_proccess_button, + layout = wibox.layout.fixed.horizontal + }, + layout = wibox.layout.ratio.horizontal + } + pid_name_rest:ajust_ratio(2, 0.2, 0.47, 0.33) + + local row = wibox.widget { + { + pid_name_rest, + top = 4, + bottom = 4, + widget = wibox.container.margin + }, + 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) + + if enable_kill_button then + row:connect_signal("mouse::enter", function() kill_proccess_button.icon.opacity = 1 end) + row:connect_signal("mouse::leave", function() kill_proccess_button.icon.opacity = 0.1 end) + + kill_proccess_button:buttons( + awful.util.table.join( awful.button({}, 1, function() + row:set_bg('#ff0000') + awful.spawn.with_shell('kill -9 ' .. pid) + end) ) ) + end + + awful.tooltip { + objects = { row }, + mode = 'outside', + preferred_positions = {'bottom'}, + timer_function = function() + local text = cmd + if process_info_max_length > 0 and text:len() > process_info_max_length then + text = text:sub(0, process_info_max_length - 3) .. '...' + end + + return text + :gsub('%s%-', '\n\t-') -- put arguments on a new line + :gsub(':/', '\n\t\t:/') -- java classpath uses : to separate jars + end, + } + + process_rows[j] = row + + j = j + 1 + end + + end + end + popup:setup { + { + cpu_rows, + { + orientation = 'horizontal', + forced_height = 15, + color = beautiful.bg_focus, + widget = wibox.widget.separator + }, + create_process_header{with_action_column = enable_kill_button}, + process_rows, + layout = wibox.layout.fixed.vertical, + }, + margins = 8, + widget = wibox.container.margin + } + end) + end) + + return cpu_widget +end + +return setmetatable(cpu_widget, { __call = function(_, ...) + return worker(...) +end }) diff --git a/dot_config/awesome/awesome-wm-widgets/cpu-widget/cpu.gif b/dot_config/awesome/awesome-wm-widgets/cpu-widget/cpu.gif new file mode 100644 index 0000000..cb97262 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/cpu-widget/cpu.gif differ diff --git a/dot_config/awesome/awesome-wm-widgets/cpu-widget/cpu.png b/dot_config/awesome/awesome-wm-widgets/cpu-widget/cpu.png new file mode 100644 index 0000000..96ba29f Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/cpu-widget/cpu.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/cpu-widget/custom.png b/dot_config/awesome/awesome-wm-widgets/cpu-widget/custom.png new file mode 100644 index 0000000..be275e4 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/cpu-widget/custom.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/cpu-widget/window-close-symbolic.svg b/dot_config/awesome/awesome-wm-widgets/cpu-widget/window-close-symbolic.svg new file mode 100644 index 0000000..46ff888 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/cpu-widget/window-close-symbolic.svg @@ -0,0 +1,95 @@ + + + + + + + + Gnome Symbolic Icon Theme + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/README.md b/dot_config/awesome/awesome-wm-widgets/docker-widget/README.md new file mode 100644 index 0000000..01c1fbf --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/docker-widget/README.md @@ -0,0 +1,38 @@ +# Docker Widget + +[![GitHub issues by-label](https://img.shields.io/github/issues-raw/streetturtle/awesome-wm-widgets/docker)](https://github.com/streetturtle/awesome-wm-widgets/labels/docker) +![Twitter URL](https://img.shields.io/twitter/url?url=https%3A%2F%2Fgithub.com%2Fstreetturtle%2Fawesome-wm-widgets%2Fedit%2Fmaster%2Fdocker-widget) + +The widget allows to manage docker containers, namely start/stop/pause/unpause: + +

+ +

+ +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `icon` | `./docker-widget/icons/docker.svg` | Path to the icon | +| `number_of_containers` | `-1` | Number of last created containers to show | + +## Installation + +Clone the repo under **~/.config/awesome/** and add widget in **rc.lua**: + +```lua +local docker_widget = require("awesome-wm-widgets.docker-widget.docker") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + docker_widget(), + -- customized + docker_widget{ + number_of_containers = 5 + }, +``` diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/docker.gif b/dot_config/awesome/awesome-wm-widgets/docker-widget/docker.gif new file mode 100644 index 0000000..3b39b5f Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/docker-widget/docker.gif differ diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/docker.lua b/dot_config/awesome/awesome-wm-widgets/docker-widget/docker.lua new file mode 100644 index 0000000..f5ce7fa --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/docker-widget/docker.lua @@ -0,0 +1,377 @@ +------------------------------------------------- +-- Docker Widget for Awesome Window Manager +-- Lists containers and allows to manage them +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/docker-widget + +-- @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 gears = require("gears") +local beautiful = require("beautiful") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/docker-widget' +local ICONS_DIR = WIDGET_DIR .. '/icons/' + +local LIST_CONTAINERS_CMD = [[bash -c "docker container ls -a -s -n %s]] + .. [[ --format '{{.Names}}::{{.ID}}::{{.Image}}::{{.Status}}::{{.Size}}'"]] + +--- Utility function to show warning messages +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Docker Widget', + text = message} +end + +local popup = awful.popup{ + 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 docker_widget = wibox.widget { + { + { + id = 'icon', + widget = wibox.widget.imagebox + }, + margins = 4, + layout = wibox.container.margin + }, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + widget = wibox.container.background, + set_icon = function(self, new_icon) + self:get_children_by_id("icon")[1].image = new_icon + end +} + +local parse_container = function(line) + local name, id, image, status, how_long, size = line:match('(.*)::(.*)::(.*)::(%w*) (.*)::(.*)') + local actual_status + if status == 'Up' and how_long:find('Paused') then actual_status = 'Paused' + else actual_status = status end + + how_long = how_long:gsub('%s?%(.*%)%s?', '') + + local container = { + name = name, + id = id, + image = image, + status = actual_status, + how_long = how_long, + size = size, + is_up = function() return status == 'Up' end, + is_paused = function() return actual_status:find('Paused') end, + is_exited = function() return status == 'Exited' end + } + return container +end + +local status_to_icon_name = { + Up = ICONS_DIR .. 'play.svg', + Exited = ICONS_DIR .. 'square.svg', + Paused = ICONS_DIR .. 'pause.svg' +} + +local function worker(user_args) + + local args = user_args or {} + + local icon = args.icon or ICONS_DIR .. 'docker.svg' + local number_of_containers = args.number_of_containers or -1 + + docker_widget:set_icon(icon) + + local rows = { + { widget = wibox.widget.textbox }, + layout = wibox.layout.fixed.vertical, + } + + local function rebuild_widget(containers, errors, _, _) + if errors ~= '' then + show_warning(errors) + return + end + + for i = 0, #rows do rows[i]=nil end + + for line in containers:gmatch("[^\r\n]+") do + + local container = parse_container(line) + + + local status_icon = wibox.widget { + image = status_to_icon_name[container['status']], + resize = false, + widget = wibox.widget.imagebox + } + + + local start_stop_button + if container.is_up() or container.is_exited() then + start_stop_button = wibox.widget { + { + { + id = 'icon', + image = ICONS_DIR .. (container:is_up() and 'stop-btn.svg' or 'play-btn.svg'), + opacity = 0.4, + resize = false, + widget = wibox.widget.imagebox + }, + left = 2, + right = 2, + layout = wibox.container.margin + }, + shape = gears.shape.circle, + bg = '#00000000', + widget = wibox.container.background + } + local old_cursor, old_wibox + start_stop_button:connect_signal("mouse::enter", function(c) + c:set_bg('#3B4252') + + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + c:get_children_by_id("icon")[1]:set_opacity(1) + c:get_children_by_id("icon")[1]:emit_signal('widget::redraw_needed') end) + start_stop_button:connect_signal("mouse::leave", function(c) + c:set_bg('#00000000') + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + c:get_children_by_id("icon")[1]:set_opacity(0.4) + c:get_children_by_id("icon")[1]:emit_signal('widget::redraw_needed') + end) + + start_stop_button:buttons( + gears.table.join( awful.button({}, 1, function() + local command + if container:is_up() then command = 'stop' else command = 'start' end + + status_icon:set_opacity(0.2) + status_icon:emit_signal('widget::redraw_needed') + + spawn.easy_async('docker ' .. command .. ' ' .. container['name'], function() + if errors ~= '' then show_warning(errors) end + spawn.easy_async(string.format(LIST_CONTAINERS_CMD, number_of_containers), + function(stdout, stderr) + rebuild_widget(stdout, stderr) + end) + end) + end) ) ) + else + start_stop_button = nil + end + + + local pause_unpause_button + if container.is_up() then + pause_unpause_button = wibox.widget { + { + { + id = 'icon', + image = ICONS_DIR .. (container:is_paused() and 'unpause-btn.svg' or 'pause-btn.svg'), + opacity = 0.4, + resize = false, + widget = wibox.widget.imagebox + }, + left = 2, + right = 2, + layout = wibox.container.margin + }, + shape = gears.shape.circle, + bg = '#00000000', + widget = wibox.container.background + } + local old_cursor, old_wibox + pause_unpause_button:connect_signal("mouse::enter", function(c) + c:set_bg('#3B4252') + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + c:get_children_by_id("icon")[1]:set_opacity(1) + c:get_children_by_id("icon")[1]:emit_signal('widget::redraw_needed') + end) + pause_unpause_button:connect_signal("mouse::leave", function(c) + c:set_bg('#00000000') + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + c:get_children_by_id("icon")[1]:set_opacity(0.4) + c:get_children_by_id("icon")[1]:emit_signal('widget::redraw_needed') + end) + + pause_unpause_button:buttons( + gears.table.join( awful.button({}, 1, function() + local command + if container:is_paused() then command = 'unpause' else command = 'pause' end + + status_icon:set_opacity(0.2) + status_icon:emit_signal('widget::redraw_needed') + + awful.spawn.easy_async('docker ' .. command .. ' ' .. container['name'], function(_, stderr) + if stderr ~= '' then show_warning(stderr) end + spawn.easy_async(string.format(LIST_CONTAINERS_CMD, number_of_containers), + function(stdout, container_errors) + rebuild_widget(stdout, container_errors) + end) + end) + end) ) ) + else + pause_unpause_button = nil + end + + local delete_button + if not container.is_up() then + delete_button = wibox.widget { + { + { + id = 'icon', + image = ICONS_DIR .. 'trash-btn.svg', + opacity = 0.4, + resize = false, + widget = wibox.widget.imagebox + }, + margins = 4, + layout = wibox.container.margin + }, + shape = gears.shape.circle, + bg = '#00000000', + widget = wibox.container.background + } + delete_button:buttons( + gears.table.join( awful.button({}, 1, function() + awful.spawn.easy_async('docker rm ' .. container['name'], function(_, rm_stderr) + if rm_stderr ~= '' then show_warning(rm_stderr) end + spawn.easy_async(string.format(LIST_CONTAINERS_CMD, number_of_containers), + function(lc_stdout, lc_stderr) + rebuild_widget(lc_stdout, lc_stderr) end) + end) + end))) + + local old_cursor, old_wibox + delete_button:connect_signal("mouse::enter", function(c) + c:set_bg('#3B4252') + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + c:get_children_by_id("icon")[1]:set_opacity(1) + c:get_children_by_id("icon")[1]:emit_signal('widget::redraw_needed') + end) + delete_button:connect_signal("mouse::leave", function(c) + c:set_bg('#00000000') + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + c:get_children_by_id("icon")[1]:set_opacity(0.4) + c:get_children_by_id("icon")[1]:emit_signal('widget::redraw_needed') + end) + else + delete_button = nil + end + + + local row = wibox.widget { + { + { + { + { + status_icon, + margins = 8, + layout = wibox.container.margin + }, + valign = 'center', + layout = wibox.container.place + }, + { + { + { + markup = '' .. container['name'] .. '', + widget = wibox.widget.textbox + }, + { + text = container['size'], + widget = wibox.widget.textbox + }, + { + text = container['how_long'], + widget = wibox.widget.textbox + }, + forced_width = 180, + layout = wibox.layout.fixed.vertical + }, + valign = 'center', + layout = wibox.container.place + }, + { + { + start_stop_button, + pause_unpause_button, + delete_button, + layout = wibox.layout.align.horizontal + }, + forced_width = 90, + valign = 'center', + haligh = 'center', + layout = wibox.container.place, + }, + spacing = 8, + layout = wibox.layout.align.horizontal + }, + margins = 8, + 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) + + table.insert(rows, row) + end + + popup:setup(rows) + end + + docker_widget:buttons( + gears.table.join( + awful.button({}, 1, function() + if popup.visible then + docker_widget:set_bg('#00000000') + popup.visible = not popup.visible + else + docker_widget:set_bg(beautiful.bg_focus) + spawn.easy_async(string.format(LIST_CONTAINERS_CMD, number_of_containers), + function(stdout, stderr) + rebuild_widget(stdout, stderr) + popup:move_next_to(mouse.current_widget_geometry) + end) + end + end) + ) + ) + + return docker_widget +end + +return setmetatable(docker_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/docker.svg b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/docker.svg new file mode 100644 index 0000000..468ce94 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/docker.svg @@ -0,0 +1 @@ +Docker icon diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/pause-btn.svg b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/pause-btn.svg new file mode 100644 index 0000000..ac2900b --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/pause-btn.svg @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/pause.svg b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/pause.svg new file mode 100644 index 0000000..33f1ad2 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/pause.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/play-btn.svg b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/play-btn.svg new file mode 100644 index 0000000..573ae7e --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/play-btn.svg @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/play-btn.svg- b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/play-btn.svg- new file mode 100644 index 0000000..455a61d --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/play-btn.svg- @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/play.svg b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/play.svg new file mode 100644 index 0000000..4f0ee04 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/play.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/square.svg b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/square.svg new file mode 100644 index 0000000..d8424d1 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/square.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/stop-btn.svg b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/stop-btn.svg new file mode 100644 index 0000000..f676d01 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/stop-btn.svg @@ -0,0 +1,10 @@ + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/trash-btn.svg b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/trash-btn.svg new file mode 100644 index 0000000..78d8035 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/trash-btn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/unpause-btn.svg b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/unpause-btn.svg new file mode 100644 index 0000000..db5b25f --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/docker-widget/icons/unpause-btn.svg @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/FETCH_HEAD b/dot_config/awesome/awesome-wm-widgets/dot_git/FETCH_HEAD new file mode 100644 index 0000000..448c785 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/FETCH_HEAD @@ -0,0 +1,10 @@ +3bb3d56c26ac3500aab33381af0cccebf6aaa05c branch 'master' of https://github.com/streetturtle/awesome-wm-widgets +4859e55c2402405ac84c30fd97df037d23f6b20b not-for-merge branch '274-make-beautiful-optional' of https://github.com/streetturtle/awesome-wm-widgets +5dc3cb7c1ea76159fbb33eb0a101eddd86a5d7d9 not-for-merge branch '4.2-human-after-all' of https://github.com/streetturtle/awesome-wm-widgets +bcd001e487d4d1a9c0ec5335b662d67496e10a4e not-for-merge branch '74-externalize-config' of https://github.com/streetturtle/awesome-wm-widgets +f62cf5f0fbad2d0d5a934c04b00f4b9f2954b8a2 not-for-merge branch 'TD-Sky-master' of https://github.com/streetturtle/awesome-wm-widgets +ed2b256407291d8edadfcea9380029633cc7d9d8 not-for-merge branch 'comply-with-luacheck' of https://github.com/streetturtle/awesome-wm-widgets +acb569b5146aa7140667b899d22bda915e483472 not-for-merge branch 'gh-pages' of https://github.com/streetturtle/awesome-wm-widgets +de14c1325fc2bed47dcc46a8c316a96d43205392 not-for-merge branch 'pr-213' of https://github.com/streetturtle/awesome-wm-widgets +96592eb4885c0062de6ee1f948f2ad980464d585 not-for-merge branch 'rocks' of https://github.com/streetturtle/awesome-wm-widgets +f38996923479001eb34eaef13f21616f19b41aa7 not-for-merge branch 'ubuntu-updates-widget' of https://github.com/streetturtle/awesome-wm-widgets diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/HEAD b/dot_config/awesome/awesome-wm-widgets/dot_git/HEAD new file mode 100644 index 0000000..cb089cd --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/branches/.keep b/dot_config/awesome/awesome-wm-widgets/dot_git/branches/.keep new file mode 100644 index 0000000..e69de29 diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/config b/dot_config/awesome/awesome-wm-widgets/dot_git/config new file mode 100644 index 0000000..6bd6927 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/config @@ -0,0 +1,11 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true +[remote "origin"] + url = https://github.com/streetturtle/awesome-wm-widgets.git + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/description b/dot_config/awesome/awesome-wm-widgets/dot_git/description new file mode 100644 index 0000000..498b267 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_applypatch-msg.sample b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_applypatch-msg.sample new file mode 100644 index 0000000..a5d7b84 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_commit-msg.sample b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_commit-msg.sample new file mode 100644 index 0000000..b58d118 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_fsmonitor-watchman.sample b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_fsmonitor-watchman.sample new file mode 100644 index 0000000..23e856f --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_fsmonitor-watchman.sample @@ -0,0 +1,174 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 2) and last update token +# formatted as a string and outputs to stdout a new update token and +# all files that have been modified since the update token. Paths must +# be relative to the root of the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $last_update_token) = @ARGV; + +# Uncomment for debugging +# print STDERR "$0 $version $last_update_token\n"; + +# Check the hook interface version +if ($version ne 2) { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree = get_working_dir(); + +my $retry = 1; + +my $json_pkg; +eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; +} or do { + require JSON::PP; + $json_pkg = "JSON::PP"; +}; + +launch_watchman(); + +sub launch_watchman { + my $o = watchman_query(); + if (is_work_tree_watched($o)) { + output_result($o->{clock}, @{$o->{files}}); + } +} + +sub output_result { + my ($clockid, @files) = @_; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # binmode $fh, ":utf8"; + # print $fh "$clockid\n@files\n"; + # close $fh; + + binmode STDOUT, ":utf8"; + print $clockid; + print "\0"; + local $, = "\0"; + print @files; +} + +sub watchman_clock { + my $response = qx/watchman clock "$git_work_tree"/; + die "Failed to get clock id on '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + return $json_pkg->new->utf8->decode($response); +} + +sub watchman_query { + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $last_update_token but not from the .git folder. + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + my $last_update_line = ""; + if (substr($last_update_token, 0, 1) eq "c") { + $last_update_token = "\"$last_update_token\""; + $last_update_line = qq[\n"since": $last_update_token,]; + } + my $query = <<" END"; + ["query", "$git_work_tree", {$last_update_line + "fields": ["name"], + "expression": ["not", ["dirname", ".git"]] + }] + END + + # Uncomment for debugging the watchman query + # open (my $fh, ">", ".git/watchman-query.json"); + # print $fh $query; + # close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; }; + + # Uncomment for debugging the watch response + # open ($fh, ">", ".git/watchman-response.json"); + # print $fh $response; + # close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + return $json_pkg->new->utf8->decode($response); +} + +sub is_work_tree_watched { + my ($output) = @_; + my $error = $output->{error}; + if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { + $retry--; + my $response = qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + $output = $json_pkg->new->utf8->decode($response); + $error = $output->{error}; + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + # Uncomment for debugging watchman output + # open (my $fh, ">", ".git/watchman-output.out"); + # close $fh; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + my $o = watchman_clock(); + $error = $output->{error}; + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + output_result($o->{clock}, ("/")); + $last_update_token = $o->{clock}; + + eval { launch_watchman() }; + return 0; + } + + die "Watchman: $error.\n" . + "Falling back to scanning...\n" if $error; + + return 1; +} + +sub get_working_dir { + my $working_dir; + if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $working_dir = Win32::GetCwd(); + $working_dir =~ tr/\\/\//; + } else { + require Cwd; + $working_dir = Cwd::cwd(); + } + + return $working_dir; +} diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_post-update.sample b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_post-update.sample new file mode 100644 index 0000000..ec17ec1 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-applypatch.sample b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-applypatch.sample new file mode 100644 index 0000000..4142082 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-commit.sample b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-commit.sample new file mode 100644 index 0000000..e144712 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=$(git hash-object -t tree /dev/null) +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --type=bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-merge-commit.sample b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-merge-commit.sample new file mode 100644 index 0000000..399eab1 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-merge-commit.sample @@ -0,0 +1,13 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git merge" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message to +# stderr if it wants to stop the merge commit. +# +# To enable this hook, rename this file to "pre-merge-commit". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" +: diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-push.sample b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-push.sample new file mode 100644 index 0000000..4ce688d --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +zero=$(git hash-object --stdin &2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-rebase.sample b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-rebase.sample new file mode 100644 index 0000000..6cbef5c --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up to date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +<<\DOC_END + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". + +DOC_END diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-receive.sample b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-receive.sample new file mode 100644 index 0000000..a1fd29e --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_pre-receive.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to make use of push options. +# The example simply echoes all push options that start with 'echoback=' +# and rejects all pushes when the "reject" push option is used. +# +# To enable this hook, rename this file to "pre-receive". + +if test -n "$GIT_PUSH_OPTION_COUNT" +then + i=0 + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT" + do + eval "value=\$GIT_PUSH_OPTION_$i" + case "$value" in + echoback=*) + echo "echo from the pre-receive-hook: ${value#*=}" >&2 + ;; + reject) + exit 1 + esac + i=$((i + 1)) + done +fi diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_prepare-commit-msg.sample b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_prepare-commit-msg.sample new file mode 100644 index 0000000..10fa14c --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_prepare-commit-msg.sample @@ -0,0 +1,42 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first one removes the +# "# Please enter the commit message..." help message. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +COMMIT_MSG_FILE=$1 +COMMIT_SOURCE=$2 +SHA1=$3 + +/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE" + +# case "$COMMIT_SOURCE,$SHA1" in +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;; +# *) ;; +# esac + +# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE" +# if test -z "$COMMIT_SOURCE" +# then +# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE" +# fi diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_push-to-checkout.sample b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_push-to-checkout.sample new file mode 100644 index 0000000..af5a0c0 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/hooks/executable_push-to-checkout.sample @@ -0,0 +1,78 @@ +#!/bin/sh + +# An example hook script to update a checked-out tree on a git push. +# +# This hook is invoked by git-receive-pack(1) when it reacts to git +# push and updates reference(s) in its repository, and when the push +# tries to update the branch that is currently checked out and the +# receive.denyCurrentBranch configuration variable is set to +# updateInstead. +# +# By default, such a push is refused if the working tree and the index +# of the remote repository has any difference from the currently +# checked out commit; when both the working tree and the index match +# the current commit, they are updated to match the newly pushed tip +# of the branch. This hook is to be used to override the default +# behaviour; however the code below reimplements the default behaviour +# as a starting point for convenient modification. +# +# The hook receives the commit with which the tip of the current +# branch is going to be updated: +commit=$1 + +# It can exit with a non-zero status to refuse the push (when it does +# so, it must not modify the index or the working tree). +die () { + echo >&2 "$*" + exit 1 +} + +# Or it can make any necessary changes to the working tree and to the +# index to bring them to the desired state when the tip of the current +# branch is updated to the new commit, and exit with a zero status. +# +# For example, the hook can simply run git read-tree -u -m HEAD "$1" +# in order to emulate git fetch that is run in the reverse direction +# with git push, as the two-tree form of git read-tree -u -m is +# essentially the same as git switch or git checkout that switches +# branches while keeping the local changes in the working tree that do +# not interfere with the difference between the branches. + +# The below is a more-or-less exact translation to shell of the C code +# for the default behaviour for git's push-to-checkout hook defined in +# the push_to_deploy() function in builtin/receive-pack.c. +# +# Note that the hook will be executed from the repository directory, +# not from the working tree, so if you want to perform operations on +# the working tree, you will have to adapt your code accordingly, e.g. +# by adding "cd .." or using relative paths. + +if ! git update-index -q --ignore-submodules --refresh +then + die "Up-to-date check failed" +fi + +if ! git diff-files --quiet --ignore-submodules -- +then + die "Working directory has unstaged changes" +fi + +# This is a rough translation of: +# +# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX +if git cat-file -e HEAD 2>/dev/null +then + head=HEAD +else + head=$(git hash-object -t tree --stdin &2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --type=bool hooks.allowunannotated) +allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch) +denycreatebranch=$(git config --type=bool hooks.denycreatebranch) +allowdeletetag=$(git config --type=bool hooks.allowdeletetag) +allowmodifytag=$(git config --type=bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero=$(git hash-object --stdin &2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/index b/dot_config/awesome/awesome-wm-widgets/dot_git/index new file mode 100644 index 0000000..b9ccf0c Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/dot_git/index differ diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/info/exclude b/dot_config/awesome/awesome-wm-widgets/dot_git/info/exclude new file mode 100644 index 0000000..a5196d1 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/logs/HEAD b/dot_config/awesome/awesome-wm-widgets/dot_git/logs/HEAD new file mode 100644 index 0000000..161859f --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 3bb3d56c26ac3500aab33381af0cccebf6aaa05c Florian RICHER 1664126762 +0200 clone: from https://github.com/streetturtle/awesome-wm-widgets.git diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/logs/refs/heads/master b/dot_config/awesome/awesome-wm-widgets/dot_git/logs/refs/heads/master new file mode 100644 index 0000000..161859f --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 3bb3d56c26ac3500aab33381af0cccebf6aaa05c Florian RICHER 1664126762 +0200 clone: from https://github.com/streetturtle/awesome-wm-widgets.git diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/logs/refs/remotes/origin/HEAD b/dot_config/awesome/awesome-wm-widgets/dot_git/logs/refs/remotes/origin/HEAD new file mode 100644 index 0000000..161859f --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 3bb3d56c26ac3500aab33381af0cccebf6aaa05c Florian RICHER 1664126762 +0200 clone: from https://github.com/streetturtle/awesome-wm-widgets.git diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/objects/info/.keep b/dot_config/awesome/awesome-wm-widgets/dot_git/objects/info/.keep new file mode 100644 index 0000000..e69de29 diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/objects/pack/readonly_pack-1e9e7e2689ef151bd360434326e2a844681c621b.idx b/dot_config/awesome/awesome-wm-widgets/dot_git/objects/pack/readonly_pack-1e9e7e2689ef151bd360434326e2a844681c621b.idx new file mode 100644 index 0000000..f356a96 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/dot_git/objects/pack/readonly_pack-1e9e7e2689ef151bd360434326e2a844681c621b.idx differ diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/objects/pack/readonly_pack-1e9e7e2689ef151bd360434326e2a844681c621b.pack b/dot_config/awesome/awesome-wm-widgets/dot_git/objects/pack/readonly_pack-1e9e7e2689ef151bd360434326e2a844681c621b.pack new file mode 100644 index 0000000..38e0344 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/dot_git/objects/pack/readonly_pack-1e9e7e2689ef151bd360434326e2a844681c621b.pack differ diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/packed-refs b/dot_config/awesome/awesome-wm-widgets/dot_git/packed-refs new file mode 100644 index 0000000..4633d27 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/packed-refs @@ -0,0 +1,12 @@ +# pack-refs with: peeled fully-peeled sorted +4859e55c2402405ac84c30fd97df037d23f6b20b refs/remotes/origin/274-make-beautiful-optional +5dc3cb7c1ea76159fbb33eb0a101eddd86a5d7d9 refs/remotes/origin/4.2-human-after-all +bcd001e487d4d1a9c0ec5335b662d67496e10a4e refs/remotes/origin/74-externalize-config +f62cf5f0fbad2d0d5a934c04b00f4b9f2954b8a2 refs/remotes/origin/TD-Sky-master +ed2b256407291d8edadfcea9380029633cc7d9d8 refs/remotes/origin/comply-with-luacheck +acb569b5146aa7140667b899d22bda915e483472 refs/remotes/origin/gh-pages +3bb3d56c26ac3500aab33381af0cccebf6aaa05c refs/remotes/origin/master +de14c1325fc2bed47dcc46a8c316a96d43205392 refs/remotes/origin/pr-213 +96592eb4885c0062de6ee1f948f2ad980464d585 refs/remotes/origin/rocks +f38996923479001eb34eaef13f21616f19b41aa7 refs/remotes/origin/ubuntu-updates-widget +54924ef0645c8b7b212066670e00a164c280ed2f refs/tags/v1.0 diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/refs/heads/master b/dot_config/awesome/awesome-wm-widgets/dot_git/refs/heads/master new file mode 100644 index 0000000..5f874dc --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/refs/heads/master @@ -0,0 +1 @@ +3bb3d56c26ac3500aab33381af0cccebf6aaa05c diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/refs/remotes/origin/HEAD b/dot_config/awesome/awesome-wm-widgets/dot_git/refs/remotes/origin/HEAD new file mode 100644 index 0000000..6efe28f --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_git/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/dot_config/awesome/awesome-wm-widgets/dot_git/refs/tags/.keep b/dot_config/awesome/awesome-wm-widgets/dot_git/refs/tags/.keep new file mode 100644 index 0000000..e69de29 diff --git a/dot_config/awesome/awesome-wm-widgets/dot_github/CODEOWNERS b/dot_config/awesome/awesome-wm-widgets/dot_github/CODEOWNERS new file mode 100644 index 0000000..3bb08e0 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_github/CODEOWNERS @@ -0,0 +1 @@ +@streetturtle diff --git a/dot_config/awesome/awesome-wm-widgets/dot_github/FUNDING.yml b/dot_config/awesome/awesome-wm-widgets/dot_github/FUNDING.yml new file mode 100644 index 0000000..5bd55b0 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_github/FUNDING.yml @@ -0,0 +1 @@ +github: streetturtle diff --git a/dot_config/awesome/awesome-wm-widgets/dot_github/workflows/build.yml b/dot_config/awesome/awesome-wm-widgets/dot_github/workflows/build.yml new file mode 100644 index 0000000..bdb08e1 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_github/workflows/build.yml @@ -0,0 +1,30 @@ +# This is a basic workflow to help you get started with Actions + +name: luacheck + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: + - '*' + paths: + - '**.lua' + pull_request: + branches: [ master ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + - uses: actions/checkout@v2 + - name: Run Luacheck + uses: nebularg/actions-luacheck@v1.1.0 diff --git a/dot_config/awesome/awesome-wm-widgets/dot_github/workflows/executable_update-site.yml b/dot_config/awesome/awesome-wm-widgets/dot_github/workflows/executable_update-site.yml new file mode 100644 index 0000000..7415c2e --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_github/workflows/executable_update-site.yml @@ -0,0 +1,43 @@ +name: update site + +on: + push: + branches: + - 'master' + paths: + - '**/README.md' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Generate md + run: ./scripts/update_site.sh + + - name: Push to gh-pages branch + run: | + git config --global user.name 'GitHub Action' + git config --global user.email 'action@github.com' + git add ./_widgets + git add ./assets/img/widgets + git stash + git fetch + echo "git checkout gh-pages" + git checkout gh-pages + rm -rf ./_widgets + rm -rf ./assets/img/widgets + ls -alF + echo "git stash pop" + git checkout stash -- ./_widgets + git checkout stash -- ./assets/img/widgets + git add ./_widgets + git add ./assets/img/widgets + git commit -m "update from master" + git push origin gh-pages + diff --git a/dot_config/awesome/awesome-wm-widgets/dot_gitignore b/dot_config/awesome/awesome-wm-widgets/dot_gitignore new file mode 100644 index 0000000..26a38a3 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_gitignore @@ -0,0 +1,48 @@ +# Compiled Lua sources +luac.out + +# luarocks build files +*.src.rock +*.zip +*.tar.gz + +# Object files +*.o +*.os +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo +*.def +*.exp + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# IDEA files +.idea + +# vscode files +.history +/_site/ +/.jekyll-cache/ diff --git a/dot_config/awesome/awesome-wm-widgets/dot_luacheckrc b/dot_config/awesome/awesome-wm-widgets/dot_luacheckrc new file mode 100644 index 0000000..e4f47fa --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/dot_luacheckrc @@ -0,0 +1,24 @@ +self = false + +globals = { + "screen", + "mouse", + "root", + "client" +} + +read_globals = { + "awesome", + "button", + "dbus", + "drawable", + "drawin", + "key", + "keygrabber", + "mousegrabber", + "selection", + "tag", + "window", + "table.unpack", + "math.atan2", +} \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/email-widget/README.md b/dot_config/awesome/awesome-wm-widgets/email-widget/README.md new file mode 100644 index 0000000..510792d --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/email-widget/README.md @@ -0,0 +1,36 @@ +# Email widget + +This widget consists of an icon with counter which shows number of unread emails: ![email icon](./em-wid-1.png) +and a popup message which appears when mouse hovers over an icon: ![email popup](./em-wid-2.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. + +## Installation + +To install it put **email.lua** and **email-widget** folder under **~/.config/awesome**. Then + + - in **email.lua** change path to python scripts; + - in python scripts add your credentials (note that password should be encrypted using pgp for example); + - add widget to awesome: + +```lua +local email_widget, email_icon = require("email") + +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + email_icon, + email_widget, + ... +``` + +## How it works + +This widget uses the output of two python scripts, first is called every 20 seconds - it returns number of unread emails and second is called when mouse hovers over an icon and displays content of those emails. For both of them you'll need to provide your credentials and imap server. For testing, they can simply be called from console: + +``` bash +python ~/.config/awesome/email/count_unread_emails.py +python ~/.config/awesome/email/read_emails.py +``` diff --git a/dot_config/awesome/awesome-wm-widgets/email-widget/count_unread_emails.py b/dot_config/awesome/awesome-wm-widgets/email-widget/count_unread_emails.py new file mode 100644 index 0000000..a843814 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/email-widget/count_unread_emails.py @@ -0,0 +1,16 @@ +#!/usr/bin/python + +import imaplib +import re + +M=imaplib.IMAP4_SSL("mail.teenagemutantninjaturtles.com", 993) +M.login("mickey@tmnt.com","cowabunga") + +status, counts = M.status("INBOX","(MESSAGES UNSEEN)") + +if status == "OK": + unread = re.search(r'UNSEEN\s(\d+)', counts[0].decode('utf-8')).group(1) +else: + unread = "N/A" + +print(unread) diff --git a/dot_config/awesome/awesome-wm-widgets/email-widget/em-wid-1.png b/dot_config/awesome/awesome-wm-widgets/email-widget/em-wid-1.png new file mode 100644 index 0000000..5290ea8 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/email-widget/em-wid-1.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/email-widget/em-wid-2.png b/dot_config/awesome/awesome-wm-widgets/email-widget/em-wid-2.png new file mode 100644 index 0000000..0a0fd3a Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/email-widget/em-wid-2.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/email-widget/email.lua b/dot_config/awesome/awesome-wm-widgets/email-widget/email.lua new file mode 100644 index 0000000..df80678 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/email-widget/email.lua @@ -0,0 +1,44 @@ +local wibox = require("wibox") +local awful = require("awful") +local naughty = require("naughty") +local watch = require("awful.widget.watch") + +local path_to_icons = "/usr/share/icons/Arc/actions/22/" + +local email_widget = wibox.widget.textbox() +email_widget:set_font('Play 9') + +local email_icon = wibox.widget.imagebox() +email_icon:set_image(path_to_icons .. "/mail-mark-new.png") + +watch( + "python /home//.config/awesome/email-widget/count_unread_emails.py", 20, + function(_, stdout) + local unread_emails_num = tonumber(stdout) or 0 + if (unread_emails_num > 0) then + email_icon:set_image(path_to_icons .. "/mail-mark-unread.png") + email_widget:set_text(stdout) + elseif (unread_emails_num == 0) then + email_icon:set_image(path_to_icons .. "/mail-message-new.png") + email_widget:set_text("") + end + end +) + + +local function show_emails() + awful.spawn.easy_async([[bash -c 'python /home//.config/awesome/email-widget/read_unread_emails.py']], + function(stdout) + naughty.notify{ + text = stdout, + title = "Unread Emails", + timeout = 5, hover_timeout = 0.5, + width = 400, + } + end + ) +end + +email_icon:connect_signal("mouse::enter", function() show_emails() end) + +return email_widget, email_icon diff --git a/dot_config/awesome/awesome-wm-widgets/email-widget/read_unread_emails.py b/dot_config/awesome/awesome-wm-widgets/email-widget/read_unread_emails.py new file mode 100644 index 0000000..fda8188 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/email-widget/read_unread_emails.py @@ -0,0 +1,44 @@ +#!/usr/bin/python + +import imaplib +import email +import datetime + +def process_mailbox(M): + rv, data = M.search(None, "(UNSEEN)") + if rv != 'OK': + print "No messages found!" + return + + for num in data[0].split(): + rv, data = M.fetch(num, '(BODY.PEEK[])') + if rv != 'OK': + print "ERROR getting message", num + return + msg = email.message_from_bytes(data[0][1]) + for header in [ 'From', 'Subject', 'Date' ]: + hdr = email.header.make_header(email.header.decode_header(msg[header])) + if header == 'Date': + date_tuple = email.utils.parsedate_tz(str(hdr)) + if date_tuple: + local_date = datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple)) + print("{}: {}".format(header, local_date.strftime("%a, %d %b %Y %H:%M:%S"))) + else: + print('{}: {}'.format(header, hdr)) + # with code below you can process text of email + # if msg.is_multipart(): + # for payload in msg.get_payload(): + # if payload.get_content_maintype() == 'text': + # print payload.get_payload() + # else: + # print msg.get_payload() + + +M=imaplib.IMAP4_SSL("mail.teenagemutantninjaturtles.com", 993) +M.login("mickey@tmnt.com","cowabunga") + +rv, data = M.select("INBOX") +if rv == 'OK': + process_mailbox(M) +M.close() +M.logout() diff --git a/dot_config/awesome/awesome-wm-widgets/experiments/spotify-player/README.md b/dot_config/awesome/awesome-wm-widgets/experiments/spotify-player/README.md new file mode 100644 index 0000000..b19d5ac --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/experiments/spotify-player/README.md @@ -0,0 +1,5 @@ +# Spotify Player + +In progress + +![spotify-player](./spotify-player.png) \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-indicator.svg b/dot_config/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-indicator.svg new file mode 100644 index 0000000..0b96c0a --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-indicator.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-player.lua b/dot_config/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-player.lua new file mode 100644 index 0000000..981978b --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-player.lua @@ -0,0 +1,192 @@ +------------------------------------------------- +-- Spotify Player Widget for Awesome Window Manager +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/spotify-player + +-- @author Pavel Makhov +-- @copyright 2021 Pavel Makhov +------------------------------------------------- +--luacheck:ignore +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") +local spawn = require("awful.spawn") +local naughty = require("naughty") +local gears = require("gears") +local beautiful = require("beautiful") +local gfs = require("gears.filesystem") +local gs = require("gears.string") +local awesomebuttons = require("awesome-buttons.awesome-buttons") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/experiments/spotify-player/' +local ICON_DIR = WIDGET_DIR + +local spotify_player = {} + +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Spotify Player Widget', + text = message} +end + +local function worker(user_args) + + local args = user_args or {} + local artwork_size = args.artwork_size or 300 + + local timeout = args.timeout or 1 + + local popup = awful.popup{ + ontop = true, + bg = beautiful.bg_normal .. '88', + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + width = artwork_size, + maximum_width = 300, + offset = { y = 5 }, + widget = {} + } + + local rows = { + expand = 'none', + layout = wibox.layout.align.vertical, + } + + spotify_player.widget = wibox.widget { + image = ICON_DIR .. 'spotify-indicator.svg', + widget = wibox.widget.imagebox + } + + local artwork_widget = wibox.widget { + forced_height = artwork_size, + forced_width = artwork_size, + widget = wibox.widget.imagebox + } + + local artist_w = wibox.widget { + align = 'center', + widget = wibox.widget.textbox, + set_artist = function(self, artist) + self:set_markup('' .. artist .. '') + end + } + + local title_w = wibox.widget { + align = 'center', + forced_height = 30, + widget = wibox.widget.textbox, + set_title = function(self, title) + self:set_markup('' .. title .. '') + end + } + + local play_pause_btn = awesomebuttons.with_icon{ type = 'outline', icon = 'play', icon_size = 32, icon_margin = 8, color = '#1DB954', shape = 'circle', onclick = function() + spawn.with_shell('sp play') + end} + + local buttons_w = wibox.widget { + { + awesomebuttons.with_icon{ icon = 'rewind', icon_size = 32, icon_margin = 8, color = '#18800000', shape = 'circle', onclick = function() + spawn.with_shell('sp prev') + end}, + play_pause_btn, + awesomebuttons.with_icon{ icon = 'fast-forward', icon_size = 32, icon_margin = 8, color = '#18800000', shape = 'circle', onclick = function() + spawn.with_shell('sp next') + end}, + spacing = 16, + layout = wibox.layout.fixed.horizontal + }, + halign = 'center', + layout = wibox.container.place, + } + + local some_w = wibox.widget { + artwork_widget, + { + { + { + { + title_w, + artist_w, + buttons_w, + layout = wibox.layout.fixed.vertical + }, + top = 8, + bottom = 8, + widget = wibox.container.margin + }, + bg = '#33333388', + widget = wibox.container.background + }, + valign = 'bottom', + content_fill_horizontal = true, + layout = wibox.container.place, + }, + layout = wibox.layout.stack + } + + popup:setup({ + some_w, + layout = wibox.layout.fixed.vertical, + }) + + local update_widget = function(widget, stdout, stderr, _, _) + for i = 0, #rows do rows[i]=nil end + + if string.find(stdout, 'Error: Spotify is not running.') ~= nil then + return + end + + local track_id, length, art_url, album, album_artist, artist, auto_rating, disc_number, title, track_number, url = + string.match(stdout, 'trackid|(.*)\nlength|(.*)\nartUrl|(.*)\nalbum|(.*)\nalbumArtist|(.*)\nartist|(.*)\nautoRating|(.*)\ndiscNumber|(.*)\ntitle|(.*)\ntrackNumber|(.*)\nurl|(.*)') + + title = string.gsub(title, "&", '&') + artist_w:set_artist(artist) + title_w:set_title(title) + + -- spotify client bug: https://community.spotify.com/t5/Desktop-Linux/MPRIS-cover-art-url-file-not-found/td-p/4920104 + art_url = art_url:gsub('https://open.spotify.com', 'https://i.scdn.co') + if ((art_url ~= nil or art_url ~='') and not gfs.file_readable('/tmp/' .. track_id)) then + spawn.easy_async('touch /tmp/' .. track_id, function() + spawn.easy_async('curl -L -s --show-error --create-dirs -o /tmp/' .. track_id .. ' '.. art_url, function(stdout, stderr) + if stderr ~= '' then + show_warning(stderr) + return + end + artwork_widget:set_image('/tmp/' .. track_id) + end) + end) + else + artwork_widget:set_image('/tmp/' .. track_id) + end + end + + function spotify_player:tog() + if popup.visible then + popup.visible = not popup.visible + else + popup:move_next_to(mouse.current_widget_geometry) + end + end + + spotify_player.widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() spotify_player:tog() end) + ) + ) + + watch('sp metadata', timeout, update_widget) + + watch('sp status', 1, function(_, stdout) + stdout = string.gsub(stdout, "\n", "") + play_pause_btn:set_icon(stdout == 'Playing' and 'pause' or 'play') + end) + + return spotify_player +end + +return setmetatable(spotify_player, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-player.png b/dot_config/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-player.png new file mode 100644 index 0000000..7bfecfc Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-player.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/fs-widget/README.md b/dot_config/awesome/awesome-wm-widgets/fs-widget/README.md new file mode 100644 index 0000000..4657e9e --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/fs-widget/README.md @@ -0,0 +1,29 @@ +# Filesystem Widget + +This widget shows file system disk space usage which is based on the `df` output. When clicked another widget appears with more detailed information. By default, it monitors the "/" mount. It can be configured with a list of mounts to monitor though only the first will show in the wibar. To have multiple mounts displayed on the wibar simply define multiple `fs_widgets` with different mounts as arguments. + +![](./screenshot.png) + +## Customizations + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `mounts` | `{'/'}` | Table with mounts to monitor, check the output from a `df` command for available options (column 'Mounted on') | +| `timeout` | 60 | How often in seconds the widget refreshes | + +## Installation + +Clone/download repo and use the widget in **rc.lua**: + +```lua + local fs_widget = require("awesome-wm-widgets.fs-widget.fs-widget") + ... + s.mywibox:setup { + s.mytasklist, -- Middle widget + { -- Right widgets + fs_widget(), --default + fs_widget({ mounts = { '/', '/mnt/music' } }), -- multiple mounts + ... +``` diff --git a/dot_config/awesome/awesome-wm-widgets/fs-widget/fs-widget.lua b/dot_config/awesome/awesome-wm-widgets/fs-widget/fs-widget.lua new file mode 100644 index 0000000..ca76193 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/fs-widget/fs-widget.lua @@ -0,0 +1,190 @@ +local awful = require("awful") +local watch = require("awful.widget.watch") +local wibox = require("wibox") +local beautiful = require("beautiful") +local gears = require("gears") + +local storage_bar_widget = {} + +--- Table with widget configuration, consists of three sections: +--- - general - general configuration +--- - widget - configuration of the widget displayed on the wibar +--- - popup - configuration of the popup +local config = {} + +-- general +config.mounts = { '/' } +config.refresh_rate = 60 + +-- wibar widget +config.widget_width = 40 +config.widget_bar_color = '#aaaaaa' +config.widget_onclick_bg = '#ff0000' +config.widget_border_color = '#535d6c66' +config.widget_background_color = '#22222233' + +-- popup +config.popup_bg = '#22222233' +config.popup_border_width = 1 +config.popup_border_color = '#535d6c66' +config.popup_bar_color = '#aaaaaa' +config.popup_bar_background_color = '#22222233' +config.popup_bar_border_color = '#535d6c66' + +local function worker(user_args) + local args = user_args or {} + + -- Setup config for the widget instance. + -- The `_config` table will keep the first existing value after checking + -- in this order: user parameter > beautiful > module default. + local _config = {} + for prop, value in pairs(config) do + _config[prop] = args[prop] or beautiful[prop] or value + end + + storage_bar_widget = wibox.widget { + { + id = 'progressbar', + color = _config.widget_bar_color, + max_value = 100, + forced_height = 20, + forced_width = _config.widget_width, + paddings = 2, + margins = 4, + border_width = 1, + border_radius = 2, + border_color = _config.widget_border_color, + background_color = _config.widget_background_color, + widget = wibox.widget.progressbar + }, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + widget = wibox.container.background, + set_value = function(self, new_value) + self:get_children_by_id("progressbar")[1].value = new_value + end + } + + local disk_rows = { + { widget = wibox.widget.textbox }, + spacing = 4, + layout = wibox.layout.fixed.vertical, + } + + local disk_header = wibox.widget { + { + markup = 'Mount', + forced_width = 150, + align = 'left', + widget = wibox.widget.textbox, + }, + { + markup = 'Used', + align = 'left', + widget = wibox.widget.textbox, + }, + layout = wibox.layout.ratio.horizontal + } + disk_header:ajust_ratio(1, 0, 0.3, 0.7) + + local popup = awful.popup { + bg = _config.popup_bg, + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = _config.popup_border_width, + border_color = _config.popup_border_color, + maximum_width = 400, + offset = { y = 5 }, + widget = {} + } + + storage_bar_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if popup.visible then + popup.visible = not popup.visible + storage_bar_widget:set_bg('#00000000') + else + storage_bar_widget:set_bg(_config.widget_background_color) + popup:move_next_to(mouse.current_widget_geometry) + end + end) + ) + ) + + local disks = {} + watch([[bash -c "df | tail -n +2"]], _config.refresh_rate, + function(widget, stdout) + for line in stdout:gmatch("[^\r\n$]+") do + local filesystem, size, used, avail, perc, mount = + line:match('([%p%w]+)%s+([%d%w]+)%s+([%d%w]+)%s+([%d%w]+)%s+([%d]+)%%%s+([%p%w]+)') + + disks[mount] = {} + disks[mount].filesystem = filesystem + disks[mount].size = size + disks[mount].used = used + disks[mount].avail = avail + disks[mount].perc = perc + disks[mount].mount = mount + + if disks[mount].mount == _config.mounts[1] then + widget:set_value(tonumber(disks[mount].perc)) + end + end + + for k, v in ipairs(_config.mounts) do + + local row = wibox.widget { + { + text = disks[v].mount, + forced_width = 150, + widget = wibox.widget.textbox + }, + { + color = _config.popup_bar_color, + max_value = 100, + value = tonumber(disks[v].perc), + forced_height = 20, + paddings = 1, + margins = 4, + border_width = 1, + border_color = _config.popup_bar_border_color, + background_color = _config.popup_bar_background_color, + bar_border_width = 1, + bar_border_color = _config.popup_bar_border_color, + widget = wibox.widget.progressbar, + }, + { + text = math.floor(disks[v].used / 1024 / 1024) + .. '/' + .. math.floor(disks[v].size / 1024 / 1024) .. 'GB(' + .. math.floor(disks[v].perc) .. '%)', + widget = wibox.widget.textbox + }, + layout = wibox.layout.ratio.horizontal + } + row:ajust_ratio(2, 0.3, 0.3, 0.4) + + disk_rows[k] = row + end + popup:setup { + { + disk_header, + disk_rows, + layout = wibox.layout.fixed.vertical, + }, + margins = 8, + widget = wibox.container.margin + } + end, + storage_bar_widget + ) + + return storage_bar_widget +end + +return setmetatable(storage_bar_widget, { __call = function(_, ...) + return worker(...) +end }) diff --git a/dot_config/awesome/awesome-wm-widgets/fs-widget/screenshot.png b/dot_config/awesome/awesome-wm-widgets/fs-widget/screenshot.png new file mode 100644 index 0000000..41e6ccc Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/fs-widget/screenshot.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/gerrit-widget/README.md b/dot_config/awesome/awesome-wm-widgets/gerrit-widget/README.md new file mode 100644 index 0000000..62c89a1 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/gerrit-widget/README.md @@ -0,0 +1,77 @@ +# Gerrit widget + +It shows number of currently assigned reviews in [Gerrit](https://www.gerritcodereview.com/) to the user (by default) : + + ![gerrit_widget](./gerrit_widget.png) + + when clicked it shows reviews in a list: + + ![popup](./popup.png) + + left click on an item will open review in the default browser, right click will copy the review number, which you can use to checkout this review by running `git-review -d `. + + Also, if a new review is assigned to the user, there will be a pop-up: + + ![new_review](./new_review.png) + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `icon`| `/.config/awesome/awesome-wm-widgets/gerrit-widget/gerrit_icon.svg`| Path to the icon | +| `host` | Required | Ex https://gerrit.tmnt.com | +| `query` | `is:reviewer AND status:open AND NOT is:wip` | Query to retrieve reviews | +| `timeout` | 10 | How often in seconds the widget refreshes | + +## Prerequisite + + - [curl](https://curl.haxx.se/) - is used to communicate with gerrit's [REST API](https://gerrit-review.googlesource.com/Documentation/rest-api.html) + - setup [netrc](https://ec.haxx.se/usingcurl-netrc.html) which is used to store username and password in order to call API's endpoints. + +## Installation + +1. This widget relies on Gerrit [REST API](https://gerrit-review.googlesource.com/Documentation/rest-api.html), so you need to have a permission to access it. You also need to setup [netrc](https://ec.haxx.se/usingcurl-netrc.html), as widget uses curl to communicate with API and you have to be authenticated. +To test if you have access to API and netrc setup is correct run following command, you should have a json response: + + ```bash + curl -s --request GET --netrc https://gerrit-host.com/a/changes/\?q\=status:open+AND+NOT+is:wip+AND+is:reviewer | tail -n +2 + ``` + Note: `tail -n +2` is needed to skip first line of the response, as gerrit returns some characters there in order to prevent XSS hacks. + +1. Download json parser for lua from [github.com/rxi/json.lua](https://github.com/rxi/json.lua) and place it under **~/.config/awesome/** (don't forget to star a repo): + + ```bash + wget -P ~/.config/awesome/ https://raw.githubusercontent.com/rxi/json.lua/master/json.lua + ``` + +1. Clone this repo (if not cloned yet) under **~/.config/awesome/**: + + ```bash + git clone https://github.com/streetturtle/awesome-wm-widgets.git ~/.config/awesome/ + ``` + +1. Require widget at the top of the **rc.lua**: + + ```lua + local gerrit_widget = require("awesome-wm-widgets.gerrit-widget.gerrit") + ``` + +1. Add widget to the tasklist: + + ```lua + s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + --default + gerrit_widget({host = 'https://gerrit.tmnt.com'}), + --customized + gerrit_widget({ + host = 'https://gerrit.tmnt.com', + query = 'is:reviewer AND is:wip' + }) + ... + ``` + diff --git a/dot_config/awesome/awesome-wm-widgets/gerrit-widget/gerrit.lua b/dot_config/awesome/awesome-wm-widgets/gerrit-widget/gerrit.lua new file mode 100644 index 0000000..682eb0f --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/gerrit-widget/gerrit.lua @@ -0,0 +1,230 @@ +------------------------------------------------- +-- Gerrit Widget for Awesome Window Manager +-- Shows the number of currently assigned reviews +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/gerrit-widget + +-- @author Pavel Makhov +-- @copyright 2019 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") +local json = require("json") +local spawn = require("awful.spawn") +local naughty = require("naughty") +local gears = require("gears") +local beautiful = require("beautiful") +local gfs = require("gears.filesystem") + +local HOME_DIR = os.getenv("HOME") +local PATH_TO_AVATARS = HOME_DIR .. '/.cache/awmw/gerrit-widget/avatars/' + +local GET_CHANGES_CMD = [[bash -c "curl -s -X GET -n %s/a/changes/\\?q\\=%s | tail -n +2"]] +local GET_USER_CMD = [[bash -c "curl -s -X GET -n %s/accounts/%s/ | tail -n +2"]] +local DOWNLOAD_AVATAR_CMD = [[bash -c "curl --create-dirs -o %s %s"]] + +local gerrit_widget = {} + +local function worker(user_args) + + local args = user_args or {} + + local icon = args.icons or HOME_DIR .. '/.config/awesome/awesome-wm-widgets/gerrit-widget/gerrit_icon.svg' + local host = args.host or naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Gerrit Widget', + text = 'Gerrit host is unknown' + } + local query = args.query or 'is:reviewer AND status:open AND NOT is:wip' + local timeout = args.timeout or 10 + + local current_number_of_reviews + local previous_number_of_reviews = 0 + local name_dict = {} + + local rows = { + { widget = wibox.widget.textbox }, + layout = wibox.layout.fixed.vertical, + } + + local popup = awful.popup{ + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + maximum_width = 400, + offset = { y = 5 }, + widget = {} + } + + gerrit_widget = wibox.widget { + { + { + image = icon, + widget = wibox.widget.imagebox + }, + margins = 4, + layout = wibox.container.margin + }, + { + id = "txt", + widget = wibox.widget.textbox + }, + { + id = "new_rev", + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal, + set_text = function(self, new_value) + self.txt.text = new_value + end, + set_unseen_review = function(self, is_new_review) + self.new_rev.text = is_new_review and '*' or '' + end + } + + local function get_name_by_user_id(user_id) + if name_dict[user_id] == nil then + name_dict[user_id] = {} + end + + if name_dict[user_id].username == nil then + name_dict[user_id].username = '' + spawn.easy_async(string.format(GET_USER_CMD, host, user_id), function(stdout) + local user = json.decode(stdout) + name_dict[tonumber(user_id)].username = user.name + if not gfs.file_readable(PATH_TO_AVATARS .. user_id) then + spawn.easy_async( + string.format(DOWNLOAD_AVATAR_CMD, PATH_TO_AVATARS .. user_id, user.avatars[1].url)) + end + end) + return name_dict[user_id].username + end + + return name_dict[user_id].username + end + + local update_widget = function(widget, stdout, _, _, _) + local reviews = json.decode(stdout) + + current_number_of_reviews = rawlen(reviews) + + if current_number_of_reviews == 0 then + widget:set_visible(false) + return + else + widget:set_visible(true) + end + + widget:set_visible(true) + if current_number_of_reviews > previous_number_of_reviews then + widget:set_unseen_review(true) + naughty.notify{ + icon = HOME_DIR ..'/.config/awesome/awesome-wm-widgets/gerrit-widget/gerrit_icon.svg', + title = 'New Incoming Review', + text = reviews[1].project .. '\n' .. get_name_by_user_id(reviews[1].owner._account_id) .. + reviews[1].subject .. '\n', + run = function() spawn.with_shell("xdg-open https://" .. host .. '/' .. reviews[1]._number) end + } + end + + previous_number_of_reviews = current_number_of_reviews + widget:set_text(current_number_of_reviews) + + for i = 0, #rows do rows[i]=nil end + for _, review in ipairs(reviews) do + + local row = wibox.widget { + { + { + { + { + resize = true, + image = PATH_TO_AVATARS .. review.owner._account_id, + forced_width = 40, + forced_height = 40, + widget = wibox.widget.imagebox + }, + margins = 8, + layout = wibox.container.margin + }, + { + { + markup = '' .. review.project .. '', + align = 'center', + widget = wibox.widget.textbox + }, + { + text = ' ' .. review.subject, + widget = wibox.widget.textbox + }, + { + text = ' ' .. get_name_by_user_id(review.owner._account_id), + widget = wibox.widget.textbox + }, + layout = wibox.layout.align.vertical + }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + margins = 8, + layout = wibox.container.margin + }, + widget = wibox.container.background + } + + row:connect_signal("button::release", function() + spawn.with_shell("xdg-open " .. host .. '/' .. review._number) + end) + + 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) + + row:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell("xdg-open " .. host .. '/' .. review._number) + popup.visible = false + end), + awful.button({}, 3, function() + spawn.with_shell("echo '" .. review._number .."' | xclip -selection clipboard") + popup.visible = false + end) + ) + ) + + table.insert(rows, row) + end + + popup:setup(rows) + end + + gerrit_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + gerrit_widget:set_unseen_review(false) + if popup.visible then + popup.visible = not popup.visible + else + --local geo = mouse.current_widget_geometry + --if theme.calendar_placement == 'center' then + -- local x = geo.x + (geo.width / 2) - (popup:geometry().width / 2) -- align two widgets + -- popup:move_next_to({x = x, y = geo.y + 22, width = 0, height = geo.height}) + --else + -- popup:move_next_to(geo) + --end + + popup:move_next_to(mouse.current_widget_geometry) + end + end) + ) + ) + + watch(string.format(GET_CHANGES_CMD, host, query:gsub(" ", "+")), timeout, update_widget, gerrit_widget) + return gerrit_widget +end + +return setmetatable(gerrit_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/gerrit-widget/gerrit_icon.svg b/dot_config/awesome/awesome-wm-widgets/gerrit-widget/gerrit_icon.svg new file mode 100644 index 0000000..4ac5652 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/gerrit-widget/gerrit_icon.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/gerrit-widget/gerrit_widget.png b/dot_config/awesome/awesome-wm-widgets/gerrit-widget/gerrit_widget.png new file mode 100644 index 0000000..926bc4b Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/gerrit-widget/gerrit_widget.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/gerrit-widget/new_review.png b/dot_config/awesome/awesome-wm-widgets/gerrit-widget/new_review.png new file mode 100644 index 0000000..ebc9bad Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/gerrit-widget/new_review.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/gerrit-widget/popup.png b/dot_config/awesome/awesome-wm-widgets/gerrit-widget/popup.png new file mode 100644 index 0000000..e08879a Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/gerrit-widget/popup.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-activity-widget/README.md b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/README.md new file mode 100644 index 0000000..2dbf98b --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/README.md @@ -0,0 +1,89 @@ +# GitHub Activity Widget + +Widget shows recent activities on GitHub. It is very similar to the GitHub's "All activity" feed on the main page: + +

+ +

+ +Mouse click on the item opens repo/issue/pr depending on the type of the activity. Mouse click on user's avatar opens user GitHub profile. + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `icon` | github.png from the widget sources | Widget icon displayed on the wibar | +| `username` | your username | Required parameter | +| `number_of_events` | 10 | Number of events to display in the list | + +## Installation + +Clone repo under **~/.config/awesome/** and add widget in **rc.lua**: + +```lua +local github_activity_widget = require("awesome-wm-widgets.github-activity-widget.github-activity-widget") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + github_activity_widget{ + username = 'streetturtle', + }, + -- customized + github_activity_widget{ + username = 'streetturtle', + number_of_events = 5 + }, + +``` + + +## How it works + +Everything starts with this timer, which gets recent activities by calling GitHub [Events API](https://developer.github.com/v3/activity/events/) and stores the response under /.cache/awmw/github-activity-widget/activity.json directory: + +```lua +gears.timer { + timeout = 600, -- calls every ten minutes + call_now = true, + autostart = true, + callback = function() + spawn.easy_async(string.format(UPDATE_EVENTS_CMD, username, CACHE_DIR), function(stdout, stderr) + if stderr ~= '' then show_warning(stderr) return end + end) + end +} +``` + +There are several reasons to store output in a file and then use it as a source to build the widget, instead of calling it everytime the widget is opened: + - activity feed does not update that often + - events API doesn't provide filtering of fields, so the output is quite large (300 events) + - it's much faster to read file from filesystem + + Next important part is **rebuild_widget** function, which is called when mouse button clicks on the widget on the wibar. It receives a json string which contains first n events from the cache file. Those events are processed by `jq` (get first n events, remove unused fields, slightly change the json structure to simplify serialization to lua table). And then it builds a widget, row by row in a loop. To display the text part of the row we already have all neccessary information in the json string which was converted to lua table. But to show an avatar we should download it first. This is done in the following snippet. First it creates a template and then checks if file already exists, and sets it in template, otherwise, downloads it asynchronously and only then sets in: + + ```lua +local avatar_img = wibox.widget { + resize = true, + forced_width = 40, + forced_height = 40, + widget = wibox.widget.imagebox +} + +if gfs.file_readable(path_to_avatar) then + avatar_img:set_image(path_to_avatar) +else + -- download it first + spawn.easy_async(string.format( + DOWNLOAD_AVATAR_CMD, + CACHE_DIR, + event.actor.id, + event.actor.avatar_url), + -- and then set + function() avatar_img:set_image(path_to_avatar) end) +end + ``` diff --git a/dot_config/awesome/awesome-wm-widgets/github-activity-widget/github-activity-widget.lua b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/github-activity-widget.lua new file mode 100644 index 0000000..af29b35 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/github-activity-widget.lua @@ -0,0 +1,294 @@ +------------------------------------------------- +-- GitHub Widget for Awesome Window Manager +-- Shows the recent activity from GitHub +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/github-activity-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local json = require("json") +local spawn = require("awful.spawn") +local naughty = require("naughty") +local gears = require("gears") +local beautiful = require("beautiful") +local gfs = require("gears.filesystem") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/github-activity-widget' +local ICONS_DIR = WIDGET_DIR .. '/icons/' +local CACHE_DIR = HOME_DIR .. '/.cache/awmw/github-activity-widget' + +local GET_EVENTS_CMD = [[sh -c "cat %s/activity.json | jq '.[:%d] | [.[] ]] + .. [[| {type: .type, actor: .actor, repo: .repo, action: .payload.action, issue_url: .payload.issue.html_url, ]] + .. [[pr_url: .payload.pull_request.html_url, created_at: .created_at}]'"]] +local DOWNLOAD_AVATAR_CMD = [[sh -c "curl -n --create-dirs -o %s/avatars/%s %s"]] +local UPDATE_EVENTS_CMD = [[sh -c "curl -s --show-error https://api.github.com/users/%s/received_events ]] + ..[[> %s/activity.json"]] + +--- Utility function to show warning messages +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'GitHub Activity Widget', + text = message} +end + +--- Converts string representation of date (2020-06-02T11:25:27Z) to date +local function parse_date(date_str) + local pattern = "(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)%Z" + local y, m, d, h, min, sec, _ = date_str:match(pattern) + + return os.time{year = y, month = m, day = d, hour = h, min = min, sec = sec} +end + +--- Converts seconds to "time ago" representation, like '1 hour ago' +local function to_time_ago(seconds) + local days = seconds / 86400 + if days > 1 then + days = math.floor(days + 0.5) + return days .. (days == 1 and ' day' or ' days') .. ' ago' + end + + local hours = (seconds % 86400) / 3600 + if hours > 1 then + hours = math.floor(hours + 0.5) + return hours .. (hours == 1 and ' hour' or ' hours') .. ' ago' + end + + local minutes = ((seconds % 86400) % 3600) / 60 + if minutes > 1 then + minutes = math.floor(minutes + 0.5) + return minutes .. (minutes == 1 and ' minute' or ' minutes') .. ' ago' + end +end + + +local popup = awful.popup{ + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + maximum_width = 350, + offset = { y = 5 }, + widget = {} +} + +local function generate_action_string(event) + local action_string = event.type + local icon = 'repo.svg' + local link = 'http://github.com/' .. event.repo.name + + if (event.type == "PullRequestEvent") then + action_string = event.action .. ' a pull request in' + link = event.pr_url + icon = 'git-pull-request.svg' + elseif (event.type == "IssuesEvent") then + action_string = event.action .. ' an issue in' + link = event.issue_url + icon = 'alert-circle.svg' + elseif (event.type == "IssueCommentEvent") then + action_string = event.action == 'created' and 'commented in issue' or event.action .. ' a comment in' + link = event.issue_url + icon = 'message-square.svg' + elseif (event.type == "WatchEvent") then + action_string = 'starred' + icon = 'star.svg' + elseif (event.type == "ForkEvent") then + action_string = 'forked' + icon = 'git-branch.svg' + elseif (event.type == "CreateEvent") then + action_string = 'created' + end + + return { action_string = action_string, link = link, icon = icon } +end + +local github_widget = wibox.widget { + { + { + { + id = 'icon', + widget = wibox.widget.imagebox + }, + id = "m", + margins = 4, + layout = wibox.container.margin + }, + layout = wibox.layout.fixed.horizontal, + }, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + widget = wibox.container.background, + set_icon = function(self, new_icon) + self:get_children_by_id("icon")[1].image = new_icon + end +} + + +local function worker(user_args) + + if not gfs.dir_readable(CACHE_DIR) then + gfs.make_directories(CACHE_DIR) + end + + local args = user_args or {} + + local icon = args.icon or ICONS_DIR .. 'github.png' + local username = args.username or show_warning('No username provided') + local number_of_events = args.number_of_events or 10 + + github_widget:set_icon(icon) + + local rows = { + layout = wibox.layout.fixed.vertical, + } + + local rebuild_widget = function(stdout, stderr, _, _) + if stderr ~= '' then + show_warning(stderr) + return + end + + local current_time = os.time(os.date("!*t")) + + local events = json.decode(stdout) + + for i = 0, #rows do rows[i]=nil end + for _, event in ipairs(events) do + local path_to_avatar = CACHE_DIR .. '/avatars/' .. event.actor.id + + local avatar_img = wibox.widget { + resize = true, + forced_width = 40, + forced_height = 40, + widget = wibox.widget.imagebox + } + + if not gfs.file_readable(path_to_avatar) then + -- download it first + spawn.easy_async(string.format( + DOWNLOAD_AVATAR_CMD, + CACHE_DIR, + event.actor.id, + event.actor.avatar_url), function() avatar_img:set_image(path_to_avatar) end) + else + avatar_img:set_image(path_to_avatar) + end + + local action_and_link = generate_action_string(event) + + local avatar = wibox.widget { + avatar_img, + margins = 8, + layout = wibox.container.margin + } + avatar:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell('xdg-open http://github.com/' .. event.actor.login) + popup.visible = false + end) + ) + ) + + local repo_info = wibox.widget { + { + markup = ' ' .. event.actor.display_login .. ' ' .. action_and_link.action_string + .. ' ' .. event.repo.name .. '', + wrap = 'word', + widget = wibox.widget.textbox + }, + { + { + { + image = ICONS_DIR .. action_and_link.icon, + resize = true, + forced_height = 16, + forced_width = 16, + widget = wibox.widget.imagebox + }, + valign = 'center', + layout = wibox.container.place + }, + { + markup = to_time_ago(os.difftime(current_time, parse_date(event.created_at))), + widget = wibox.widget.textbox + }, + spacing = 4, + layout = wibox.layout.fixed.horizontal, + }, + layout = wibox.layout.align.vertical + } + repo_info:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell("xdg-open " .. action_and_link.link) + popup.visible = false + end) + ) + ) + + local row = wibox.widget { + { + { + avatar, + repo_info, + spacing = 4, + layout = wibox.layout.fixed.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) + + table.insert(rows, row) + end + + popup:setup(rows) + end + + github_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if popup.visible then + popup.visible = not popup.visible + github_widget:set_bg('#00000000') + else + github_widget:set_bg(beautiful.bg_focus) + spawn.easy_async(string.format(GET_EVENTS_CMD, CACHE_DIR, number_of_events), + function (stdout, stderr) + rebuild_widget(stdout, stderr) + popup:move_next_to(mouse.current_widget_geometry) + end) + end + end) + ) + ) + + -- Calls GitHub event API and stores response in "cache" file + gears.timer { + timeout = 600, + call_now = true, + autostart = true, + callback = function() + spawn.easy_async(string.format(UPDATE_EVENTS_CMD, username, CACHE_DIR), function(_, stderr) + if stderr ~= '' then show_warning(stderr) return end + end) + end + } + + return github_widget +end + +return setmetatable(github_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/alert-circle.svg b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/alert-circle.svg new file mode 100644 index 0000000..1c42eaf --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/alert-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/git-branch.svg b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/git-branch.svg new file mode 100644 index 0000000..3f06c34 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/git-branch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/git-pull-request.svg b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/git-pull-request.svg new file mode 100644 index 0000000..c2e2867 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/git-pull-request.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/github.png b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/github.png new file mode 100644 index 0000000..628da97 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/github.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/message-square.svg b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/message-square.svg new file mode 100644 index 0000000..758ba42 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/message-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/repo.svg b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/repo.svg new file mode 100644 index 0000000..f74a595 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/repo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/star.svg b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/star.svg new file mode 100644 index 0000000..0a3d39e --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/icons/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/github-activity-widget/screenshot.png b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/screenshot.png new file mode 100644 index 0000000..f066cbc Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-activity-widget/screenshot.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/README.md b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/README.md new file mode 100644 index 0000000..7d02008 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/README.md @@ -0,0 +1,63 @@ +# Github Contributions Widget + +The widget is inspired by the https://github-contributions.now.sh/ and relies on it's API. + +It shows the contribution graph, similar to the one on the github profile page: ![screenshot](./screenshots/screenshot.jpg) + +You might wonder what could be the reason to have your github's contributions in front of you all day long? The more you contribute, the nicer widget looks! Check out [Thomashighbaugh](https://github.com/Thomashighbaugh)'s graph: + +![](./screenshots/Thomashighbaugh.png) + +## Customization + +It is possible to customize the widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `username` | `streetturtle` | GitHub username | +| `days` | `365` | Number of days in the past, more days - wider the widget | +| `color_of_empty_cells` | Theme's default | Color of the days with no contributions | +| `with_border` | `true` | Should the graph contains border or not | +| `margin_top` | `1` | Top margin | +| `theme` | `standard` | Color theme of the graph, see below | + +_Note:_ widget height is 21px (7 rows of 3x3 cells). So it would look nice on the wibar of 22-24px height. + +### Themes + +Following themes are available: + +| Theme name | Preview | +|---|---| +| standard | ![standard](./screenshots/standard.png) | +| classic | ![classic](./screenshots/classic.png) | +| teal | ![teal](./screenshots/teal.png) | +| leftpad | ![leftpad](./screenshots/leftpad.png) | +| dracula | ![dracula](./screenshots/dracula.png) | +| pink | ![pink](./screenshots/pink.png) | + +To add a new theme, simply add a new entry in `themes` table (themes.lua) with the colors of your theme. + +### Screenshots + +1000 days, with border: +![screenshot1](./screenshots/screenshot1.jpg) + +365 days, no border: +![screenshot2](./screenshots/screenshot2.jpg) + +## Installation + +Clone/download repo under **~/.config/awesome** and use widget in **rc.lua**: + +```lua +local github_contributions_widget = require("awesome-wm-widgets.github-contributions-widget.github-contributions-widget") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + github_contributions_widget({username = ''}), + ... +``` diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/github-contributions-widget.lua b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/github-contributions-widget.lua new file mode 100644 index 0000000..113d474 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/github-contributions-widget.lua @@ -0,0 +1,104 @@ +------------------------------------------------- +-- Github Contributions Widget for Awesome Window Manager +-- Shows the contributions graph +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/github-contributions-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local naughty = require("naughty") +local wibox = require("wibox") +local gears = require("gears") +local widget_themes = require("awesome-wm-widgets.github-contributions-widget.themes") + +local GET_CONTRIBUTIONS_CMD = [[bash -c "curl -s https://github-contributions.vercel.app/api/v1/%s]] + .. [[ | jq -r '[.contributions[] ]] + .. [[ | select ( .date | strptime(\"%%Y-%%m-%%d\") | mktime < now)][:%s]| .[].intensity'"]] + +local github_contributions_widget = wibox.widget{ + reflection = { + horizontal = true, + vertical = true, + }, + widget = wibox.container.mirror +} + +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Github Contributions Widget', + text = message} +end + +local function worker(user_args) + + local args = user_args or {} + local username = args.username or 'streetturtle' + local days = args.days or 365 + local color_of_empty_cells = args.color_of_empty_cells + local with_border = args.with_border + local margin_top = args.margin_top or 1 + local theme = args.theme or 'standard' + + if widget_themes[theme] == nil then + show_warning('Theme ' .. theme .. ' does not exist') + theme = 'standard' + end + + if with_border == nil then with_border = true end + + local function get_square(color) + if color_of_empty_cells ~= nil and color == widget_themes[theme][0] then + color = color_of_empty_cells + end + + return wibox.widget{ + fit = function() + return 3, 3 + end, + draw = function(_, _, cr, _, _) + cr:set_source(gears.color(color)) + cr:rectangle(0, 0, with_border and 2 or 3, with_border and 2 or 3) + cr:fill() + end, + layout = wibox.widget.base.make_widget + } + end + + local col = {layout = wibox.layout.fixed.vertical} + local row = {layout = wibox.layout.fixed.horizontal} + local day_idx = 5 - os.date('%w') + for _ = 0, day_idx do + table.insert(col, get_square(color_of_empty_cells)) + end + + local update_widget = function(_, stdout, _, _, _) + for intensity in stdout:gmatch("[^\r\n]+") do + if day_idx %7 == 0 then + table.insert(row, col) + col = {layout = wibox.layout.fixed.vertical} + end + table.insert(col, get_square(widget_themes[theme][tonumber(intensity)])) + day_idx = day_idx + 1 + end + github_contributions_widget:setup( + { + row, + top = margin_top, + layout = wibox.container.margin + } + ) + end + + awful.spawn.easy_async(string.format(GET_CONTRIBUTIONS_CMD, username, days), + function(stdout) + update_widget(github_contributions_widget, stdout) + end) + + return github_contributions_widget +end + +return setmetatable(github_contributions_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/Thomashighbaugh.png b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/Thomashighbaugh.png new file mode 100644 index 0000000..b31245b Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/Thomashighbaugh.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/classic.png b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/classic.png new file mode 100644 index 0000000..4652140 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/classic.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/dracula.png b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/dracula.png new file mode 100644 index 0000000..65fb769 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/dracula.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/leftpad.png b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/leftpad.png new file mode 100644 index 0000000..19e4f64 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/leftpad.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/pink.png b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/pink.png new file mode 100644 index 0000000..2fb7bc6 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/pink.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot.jpg b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot.jpg new file mode 100644 index 0000000..15ad456 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot.jpg differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot1.jpg b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot1.jpg new file mode 100644 index 0000000..d1eeb44 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot1.jpg differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot2.jpg b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot2.jpg new file mode 100644 index 0000000..5ce47f2 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot2.jpg differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/standard.png b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/standard.png new file mode 100644 index 0000000..e10479a Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/standard.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/teal.png b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/teal.png new file mode 100644 index 0000000..f10de7a Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/teal.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/themes.lua b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/themes.lua new file mode 100644 index 0000000..574f8fa --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-contributions-widget/themes.lua @@ -0,0 +1,46 @@ +local themes = { + standard = { + [4] = '#216e39', + [3] = '#30a14e', + [2] = '#40c463', + [1] = '#9be9a8', + [0] = '#ebedf0' + }, + classic = { + [4] = '#196127', + [3] = '#239a3b', + [2] = '#7bc96f', + [1] = '#c6e48b', + [0] = '#ebedf0', + }, + teal = { + [4] = '#458B74', + [3] = '#66CDAA', + [2] = '#76EEC6', + [1] = '#7FFFD4', + [0] = '#ebedf0', + }, + leftpad = { + [4] = '#F6F6F6', + [3] = '#DDDDDD', + [2] = '#A5A5A5', + [1] = '#646464', + [0] = '#2F2F2F', + }, + dracula = { + [4] = '#ff79c6', + [3] = '#bd93f9', + [2] = '#6272a4', + [1] = '#44475a', + [0] = '#282a36' + }, + pink = { + [4] = '#61185f', + [3] = '#a74aa8', + [2] = '#ca5bcc', + [1] = '#e48bdc', + [0] = '#ebedf0', + } +} + +return themes \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/github-prs-widget/README.md b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/README.md new file mode 100644 index 0000000..1d4c27e --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/README.md @@ -0,0 +1,43 @@ +# GitHub PRs Widget + +

+ GitHub issues by-label +

+ +The widget shows the number of pull requests assigned to the user and when clicked shows additional information, such as + - author's name and avatar (opens user profile page when clicked); + - PR name (opens MR when clicked); + - name of the repository; + - when was created; + - number of comments; + +

+ +

+ +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `reviewer` | Required | github user login | + +## Installation + +Install and setup [GitHub CLI](https://cli.github.com/) +Clone/download repo and use widget in **rc.lua**: + +```lua +local github_prs_widget = require("awesome-wm-widgets.github-prs-widget") +... +s.mytasklist, -- Middle widget +{ -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + github_prs_widget { + reviewer = 'streetturtle' + }, +} +... +``` diff --git a/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/book.svg b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/book.svg new file mode 100644 index 0000000..7833095 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/book.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/calendar.svg b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/calendar.svg new file mode 100644 index 0000000..45a15fe --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/git-pull-request.svg b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/git-pull-request.svg new file mode 100644 index 0000000..54c92b9 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/git-pull-request.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/message-square.svg b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/message-square.svg new file mode 100644 index 0000000..e37df4b --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/message-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/user.svg b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/user.svg new file mode 100644 index 0000000..7704341 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/icons/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/github-prs-widget/init.lua b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/init.lua new file mode 100644 index 0000000..8d59ac8 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/init.lua @@ -0,0 +1,434 @@ +------------------------------------------------- +-- GitHub Widget for Awesome Window Manager +-- Shows the number of currently assigned merge requests +-- and information about them +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/github-prs-widget + +-- @author Pavel Makhov +-- @copyright 2021 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") +local json = require("json") +local spawn = require("awful.spawn") +local naughty = require("naughty") +local gears = require("gears") +local beautiful = require("beautiful") +local gfs = require("gears.filesystem") +local color = require("gears.color") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/github-prs-widget/' +local ICONS_DIR = WIDGET_DIR .. 'icons/' + +local AVATARS_DIR = HOME_DIR .. '/.cache/awmw/github-widget/avatars/' +local DOWNLOAD_AVATAR_CMD = [[sh -c "curl -L --create-dirs -o ''\\]] .. AVATARS_DIR .. [[%s %s"]] + +local GET_PRS_CMD = "gh api -X GET search/issues " + .. "-f 'q=review-requested:%s is:unmerged is:open' " + .. "-f per_page=30 " + .. "--jq '[.items[] | {url,repository_url,title,html_url,comments,assignees,user,created_at,draft}]'" + +local github_widget = wibox.widget { + { + { + { + { + { + id = 'icon', + widget = wibox.widget.imagebox + }, + { + id = 'error_marker', + draw = function(_, _, cr, width, height) + cr:set_source(color('#BF616A')) + cr:arc(width - height / 6, height / 6, height / 6, 0, math.pi * 2) + cr:fill() + end, + visible = false, + layout = wibox.widget.base.make_widget, + }, + layout = wibox.layout.stack + }, + margins = 4, + layout = wibox.container.margin + }, + { + id = "txt", + widget = wibox.widget.textbox + }, + { + id = "new_pr", + widget = wibox.widget.textbox + }, + spacing = 4, + layout = wibox.layout.fixed.horizontal, + }, + left = 4, + right = 4, + widget = wibox.container.margin + }, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + widget = wibox.container.background, + set_text = function(self, new_value) + self:get_children_by_id('txt')[1]:set_text(new_value) + end, + set_icon = function(self, new_value) + self:get_children_by_id('icon')[1]:set_image(new_value) + end, + is_everything_ok = function(self, is_ok) + if is_ok then + self:get_children_by_id('error_marker')[1]:set_visible(false) + self:get_children_by_id('icon')[1]:set_opacity(1) + self:get_children_by_id('icon')[1]:emit_signal('widget:redraw_needed') + else + self.txt:set_text('') + self:get_children_by_id('error_marker')[1]:set_visible(true) + self:get_children_by_id('icon')[1]:set_opacity(0.2) + self:get_children_by_id('icon')[1]:emit_signal('widget:redraw_needed') + end + end +} + +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'GitHub PRs Widget', + text = message} +end + +local popup = awful.popup{ + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + maximum_width = 400, + offset = { y = 5 }, + widget = {} +} + +--- Converts string representation of date (2020-06-02T11:25:27Z) to date +local function parse_date(date_str) + local pattern = "(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)%Z" + local y, m, d, h, min, sec, _ = date_str:match(pattern) + + return os.time{year = y, month = m, day = d, hour = h, min = min, sec = sec} +end + +--- Converts seconds to "time ago" represenation, like '1 hour ago' +local function to_time_ago(seconds) + local days = seconds / 86400 + if days > 1 then + days = math.floor(days + 0.5) + return days .. (days == 1 and ' day' or ' days') .. ' ago' + end + + local hours = (seconds % 86400) / 3600 + if hours > 1 then + hours = math.floor(hours + 0.5) + return hours .. (hours == 1 and ' hour' or ' hours') .. ' ago' + end + + local minutes = ((seconds % 86400) % 3600) / 60 + if minutes > 1 then + minutes = math.floor(minutes + 0.5) + return minutes .. (minutes == 1 and ' minute' or ' minutes') .. ' ago' + end +end + +local function ellipsize(text, length) + return (text:len() > length and length > 0) + and text:sub(0, length - 3) .. '...' + or text +end + +local warning_shown = false +local tooltip = awful.tooltip { + mode = 'outside', + preferred_positions = {'bottom'}, +} + +local config = {} + +config.reviewer = nil + +config.bg_normal = '#aaaaaa' +config.bg_focus = '#ffffff' + + +local function worker(user_args) + + local args = user_args or {} + + -- Setup config for the widget instance. + -- The `_config` table will keep the first existing value after checking + -- in this order: user parameter > beautiful > module default + local _config = {} + for prop, value in pairs(config) do + _config[prop] = args[prop] or beautiful[prop] or value + end + + local icon = args.icon or ICONS_DIR .. 'git-pull-request.svg' + local reviewer = args.reviewer + local timeout = args.timeout or 60 + + local current_number_of_prs + + local to_review_rows = {layout = wibox.layout.fixed.vertical} + local rows = {layout = wibox.layout.fixed.vertical} + + github_widget:set_icon(icon) + + local update_widget = function(widget, stdout, stderr, _, _) + + if stderr ~= '' then + if not warning_shown then + show_warning(stderr) + warning_shown = true + widget:is_everything_ok(false) + tooltip:add_to_object(widget) + + widget:connect_signal('mouse::enter', function() + tooltip.text = stderr + end) + end + return + end + + warning_shown = false + tooltip:remove_from_object(widget) + widget:is_everything_ok(true) + + local prs = json.decode(stdout) + + current_number_of_prs = #prs + + if current_number_of_prs == 0 then + widget:set_visible(false) + return + end + + widget:set_visible(true) + widget:set_text(current_number_of_prs) + + for i = 0, #rows do rows[i]=nil end + + for i = 0, #to_review_rows do to_review_rows[i]=nil end + table.insert(to_review_rows, { + { + markup = 'PRs to review', + align = 'center', + forced_height = 20, + widget = wibox.widget.textbox + }, + bg = _config.bg_normal, + widget = wibox.container.background + }) + + local current_time = os.time(os.date("!*t")) + + for _, pr in ipairs(prs) do + local path_to_avatar = AVATARS_DIR .. pr.user.id + local index = string.find(pr.repository_url, "/[^/]*$") + local repo = string.sub(pr.repository_url, index + 1) + + local row = wibox.widget { + { + { + { + { + resize = true, + image = path_to_avatar, + forced_width = 40, + forced_height = 40, + widget = wibox.widget.imagebox + }, + id = 'avatar', + margins = 4, + layout = wibox.container.margin + }, + { + { + id = 'title', + markup = '' .. ellipsize(pr.title, 60) .. '', + widget = wibox.widget.textbox, + forced_width = 400 + }, + { + { + { + { + image = ICONS_DIR .. 'book.svg', + forced_width = 12, + forced_height = 12, + resize = true, + widget = wibox.widget.imagebox + }, + { + text = repo, + widget = wibox.widget.textbox + }, + spacing = 4, + expand = 'none', + layout = wibox.layout.fixed.horizontal + }, + { + { + image = ICONS_DIR .. 'user.svg', + forced_width = 12, + forced_height = 12, + resize = true, + widget = wibox.widget.imagebox + }, + { + text = pr.user.login, + widget = wibox.widget.textbox + }, + spacing = 4, + expand = 'none', + layout = wibox.layout.fixed.horizontal + }, + spacing = 8, + expand = 'none', + layout = wibox.layout.fixed.horizontal + }, + { + { + { + image = ICONS_DIR .. 'user.svg', + forced_width = 12, + forced_height = 12, + resize = true, + widget = wibox.widget.imagebox + }, + { + text = to_time_ago(os.difftime(current_time, parse_date(pr.created_at))), + widget = wibox.widget.textbox + }, + spacing = 4, + expand = 'none', + layout = wibox.layout.fixed.horizontal + + }, + { + { + image = ICONS_DIR .. 'message-square.svg', + forced_width = 12, + forced_height = 12, + resize = true, + widget = wibox.widget.imagebox + }, + { + text = pr.comments, + widget = wibox.widget.textbox + }, + spacing = 4, + expand = 'none', + layout = wibox.layout.fixed.horizontal + + }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + layout = wibox.layout.fixed.vertical + }, + spacing = 4, + layout = wibox.layout.fixed.vertical + }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + margins = 8, + layout = wibox.container.margin + }, + bg = _config.bg_normal, + widget = wibox.container.background + } + + if not gfs.file_readable(path_to_avatar) then + spawn.easy_async(string.format( + DOWNLOAD_AVATAR_CMD, + pr.user.id, + pr.user.avatar_url), function() + row:get_children_by_id('avatar')[1]:set_image(path_to_avatar) + end) + end + + row:connect_signal("mouse::enter", function(c) c:set_bg(_config.bg_focus) end) + row:connect_signal("mouse::leave", function(c) c:set_bg(_config.bg_normal) end) + + row:get_children_by_id('title')[1]:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell("xdg-open " .. pr.html_url) + popup.visible = false + end) + ) + ) + row:get_children_by_id('avatar')[1]:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell("xdg-open " .. pr.user.html_url) + popup.visible = false + end) + ) + ) + + local old_cursor, old_wibox + row:get_children_by_id('title')[1]:connect_signal("mouse::enter", function() + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + row:get_children_by_id('title')[1]:connect_signal("mouse::leave", function() + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + row:get_children_by_id('avatar')[1]:connect_signal("mouse::enter", function() + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + row:get_children_by_id('avatar')[1]:connect_signal("mouse::leave", function() + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + table.insert(to_review_rows, row) + end + + table.insert(rows, to_review_rows) + popup:setup(rows) + end + + github_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if popup.visible then + popup.visible = not popup.visible + github_widget:set_bg('#00000000') + else + github_widget:set_bg(beautiful.bg_focus) + popup:move_next_to(mouse.current_widget_geometry) + end + end) + ) + ) + + watch(string.format(GET_PRS_CMD, reviewer), + timeout, update_widget, github_widget) + + return github_widget +end + +return setmetatable(github_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/github-prs-widget/screenshots/screenshot1.png b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/screenshots/screenshot1.png new file mode 100644 index 0000000..295b56e Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/github-prs-widget/screenshots/screenshot1.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/gitlab-widget/README.md b/dot_config/awesome/awesome-wm-widgets/gitlab-widget/README.md new file mode 100644 index 0000000..17007bc --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/gitlab-widget/README.md @@ -0,0 +1,48 @@ +# Gitlab widget + +

+ GitHub issues by-label +

+ +The widget shows the number of merge requests assigned to the user and when clicked shows additional information, such as + - author's name and avatar (opens user profile page when clicked); + - MR name (opens MR when clicked); + - source and target branches; + - when was created; + - number of comments; + - number of approvals. + +![screenshot](./screenshot.png) + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `icon` | `./icons/gitlab-icon.svg` | Path to the icon | +| `host` | Required | e.g _https://gitlab.yourcompany.com_ | +| `access_token` | Required | e.g _h2v531iYASDz6McxYk4A_ | +| `timeout` | 60 | How often in seconds the widget should be refreshed | + +_Note:_ + - to get the access token, go to **User Settings** -> **Access Tokens** and generate a token with **api** scope + +## Installation + +Clone/download repo and use widget in **rc.lua**: + +```lua +local gitlab_widget = require("awesome-wm-widgets.gitlab-widget.gitlab") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + gitlab_widget{ + host = 'https://gitlab.yourcompany.com', + access_token = 'h2v531iYASDz6McxYk4A' + }, + ... +``` diff --git a/dot_config/awesome/awesome-wm-widgets/gitlab-widget/gitlab.lua b/dot_config/awesome/awesome-wm-widgets/gitlab-widget/gitlab.lua new file mode 100644 index 0000000..c0716d7 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/gitlab-widget/gitlab.lua @@ -0,0 +1,401 @@ +------------------------------------------------- +-- Gitlab Widget for Awesome Window Manager +-- Shows the number of currently assigned merge requests +-- and information about them +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/gitlab-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") +local json = require("json") +local spawn = require("awful.spawn") +local naughty = require("naughty") +local gears = require("gears") +local beautiful = require("beautiful") +local gfs = require("gears.filesystem") +local color = require("gears.color") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/gitlab-widget/' +local GET_PRS_CMD= [[sh -c "curl -s --connect-timeout 5 --show-error --header 'PRIVATE-TOKEN: %s']] + ..[[ '%s/api/v4/merge_requests?state=opened'"]] +local DOWNLOAD_AVATAR_CMD = [[sh -c "curl -L --create-dirs -o %s/.cache/awmw/gitlab-widget/avatars/%s %s"]] + +local gitlab_widget = wibox.widget { + { + { + { + id = 'icon', + widget = wibox.widget.imagebox + }, + { + id = 'error_marker', + draw = function(_, _, cr, width, height) + cr:set_source(color(beautiful.fg_urgent)) + cr:arc(width - height/6, height/6, height/6, 0, math.pi*2) + cr:fill() + end, + visible = false, + layout = wibox.widget.base.make_widget, + }, + layout = wibox.layout.stack + }, + margins = 4, + layout = wibox.container.margin + }, + { + id = "txt", + widget = wibox.widget.textbox + }, + { + id = "new_pr", + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal, + set_text = function(self, new_value) + self.txt.text = new_value + end, + set_icon = function(self, new_value) + self:get_children_by_id('icon')[1]:set_image(new_value) + end, + is_everything_ok = function(self, is_ok) + if is_ok then + self:get_children_by_id('error_marker')[1]:set_visible(false) + self:get_children_by_id('icon')[1]:set_opacity(1) + self:get_children_by_id('icon')[1]:emit_signal('widget:redraw_needed') + else + self.txt:set_text('') + self:get_children_by_id('error_marker')[1]:set_visible(true) + self:get_children_by_id('icon')[1]:set_opacity(0.2) + self:get_children_by_id('icon')[1]:emit_signal('widget:redraw_needed') + end + end +} + +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Gitlab Widget', + text = message} +end + +local popup = awful.popup{ + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + maximum_width = 400, + offset = { y = 5 }, + widget = {} +} + +--- Converts string representation of date (2020-06-02T11:25:27Z) to date +local function parse_date(date_str) + local pattern = "(%d+)%-(%d+)%-(%d+)T(%d+):(%d+):(%d+)%Z" + local y, m, d, h, min, sec, _ = date_str:match(pattern) + + return os.time{year = y, month = m, day = d, hour = h, min = min, sec = sec} +end + +--- Converts seconds to "time ago" represenation, like '1 hour ago' +local function to_time_ago(seconds) + local days = seconds / 86400 + if days > 1 then + days = math.floor(days + 0.5) + return days .. (days == 1 and ' day' or ' days') .. ' ago' + end + + local hours = (seconds % 86400) / 3600 + if hours > 1 then + hours = math.floor(hours + 0.5) + return hours .. (hours == 1 and ' hour' or ' hours') .. ' ago' + end + + local minutes = ((seconds % 86400) % 3600) / 60 + if minutes > 1 then + minutes = math.floor(minutes + 0.5) + return minutes .. (minutes == 1 and ' minute' or ' minutes') .. ' ago' + end +end + +local function ellipsize(text, length) + return (text:len() > length and length > 0) + and text:sub(0, length - 3) .. '...' + or text +end + +local warning_shown = false +local tooltip = awful.tooltip { + mode = 'outside', + preferred_positions = {'bottom'}, + } + +local function worker(user_args) + + local args = user_args or {} + + local icon = args.icon or WIDGET_DIR .. '/icons/gitlab-icon.svg' + local access_token = args.access_token or show_warning('API Token is not set') + local host = args.host or show_warning('Gitlab host is not set') + local timeout = args.timeout or 60 + + local current_number_of_prs + + local to_review_rows = {layout = wibox.layout.fixed.vertical} + local my_review_rows = {layout = wibox.layout.fixed.vertical} + local rows = {layout = wibox.layout.fixed.vertical} + + gitlab_widget:set_icon(icon) + + local update_widget = function(widget, stdout, stderr, _, _) + + if stderr ~= '' then + if not warning_shown then + show_warning(stderr) + warning_shown = true + widget:is_everything_ok(false) + tooltip:add_to_object(widget) + + widget:connect_signal('mouse::enter', function() + tooltip.text = stderr + end) + end + return + end + + warning_shown = false + tooltip:remove_from_object(widget) + widget:is_everything_ok(true) + + local result = json.decode(stdout) + + current_number_of_prs = rawlen(result) + + if current_number_of_prs == 0 then + widget:set_visible(false) + return + end + + widget:set_visible(true) + widget:set_text(current_number_of_prs) + + for i = 0, #rows do rows[i]=nil end + + for i = 0, #to_review_rows do to_review_rows[i]=nil end + table.insert(to_review_rows, { + { + markup = 'PRs to review', + align = 'center', + forced_height = 20, + widget = wibox.widget.textbox + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + }) + + for i = 0, #my_review_rows do my_review_rows[i]=nil end + table.insert(my_review_rows, { + { + markup = 'My PRs', + align = 'center', + forced_height = 20, + widget = wibox.widget.textbox + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + }) + local current_time = os.time(os.date("!*t")) + + for _, pr in ipairs(result) do + local path_to_avatar = os.getenv("HOME") ..'/.cache/awmw/gitlab-widget/avatars/' .. pr.author.id + + local row = wibox.widget { + { + { + { + { + resize = true, + image = path_to_avatar, + forced_width = 40, + forced_height = 40, + widget = wibox.widget.imagebox + }, + id = 'avatar', + margins = 8, + layout = wibox.container.margin + }, + { + { + id = 'title', + markup = '' .. ellipsize(pr.title, 50) .. '', + widget = wibox.widget.textbox, + forced_width = 400 + }, + { + { + { + { + text = pr.source_branch, + widget = wibox.widget.textbox + }, + { + text = '->', + widget = wibox.widget.textbox + }, + { + text = pr.target_branch, + widget = wibox.widget.textbox + }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + { + { + text = pr.author.name, + widget = wibox.widget.textbox + }, + { + text = to_time_ago(os.difftime(current_time, parse_date(pr.created_at))), + widget = wibox.widget.textbox + }, + spacing = 8, + expand = 'none', + layout = wibox.layout.fixed.horizontal + }, + forced_width = 285, + layout = wibox.layout.fixed.vertical + }, + { + { + { + -- image = number_of_approves > 0 and WIDGET_DIR .. '/check.svg' or '', + image = WIDGET_DIR .. '/icons/check.svg', + resize = false, + widget = wibox.widget.imagebox + }, + { + text = pr.upvotes, + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal + }, + { + { + image = WIDGET_DIR .. '/icons/message-circle.svg', + resize = false, + widget = wibox.widget.imagebox + }, + { + text = pr.user_notes_count, + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal + }, + layout = wibox.layout.fixed.vertical + }, + layout = wibox.layout.fixed.horizontal + }, + + spacing = 8, + layout = wibox.layout.fixed.vertical + }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + margins = 8, + layout = wibox.container.margin + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + } + + if not gfs.file_readable(path_to_avatar) then + spawn.easy_async(string.format( + DOWNLOAD_AVATAR_CMD, + HOME_DIR, + pr.author.id, + pr.author.avatar_url), function() + row:get_children_by_id('avatar')[1]:set_image(path_to_avatar) + end) + end + + 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) + + row:get_children_by_id('title')[1]:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell("xdg-open " .. pr.web_url) + popup.visible = false + end) + ) + ) + row:get_children_by_id('avatar')[1]:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell("xdg-open " .. pr.author.web_url) + popup.visible = false + end) + ) + ) + + local old_cursor, old_wibox + row:get_children_by_id('title')[1]:connect_signal("mouse::enter", function() + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + row:get_children_by_id('title')[1]:connect_signal("mouse::leave", function() + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + row:get_children_by_id('avatar')[1]:connect_signal("mouse::enter", function() + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + row:get_children_by_id('avatar')[1]:connect_signal("mouse::leave", function() + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + table.insert(to_review_rows, row) + end + + table.insert(rows, to_review_rows) + if (#my_review_rows > 1) then + table.insert(rows, my_review_rows) + end + popup:setup(rows) + end + + gitlab_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if popup.visible then + popup.visible = not popup.visible + else + popup:move_next_to(mouse.current_widget_geometry) + end + end) + ) + ) + + watch(string.format(GET_PRS_CMD, access_token, host), + -- string.format(GET_PRS_CMD, host, workspace, repo_slug, uuid, uuid), + timeout, update_widget, gitlab_widget) + return gitlab_widget +end + +return setmetatable(gitlab_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/gitlab-widget/icons/check.svg b/dot_config/awesome/awesome-wm-widgets/gitlab-widget/icons/check.svg new file mode 100644 index 0000000..e9e44ac --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/gitlab-widget/icons/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/gitlab-widget/icons/gitlab-icon.svg b/dot_config/awesome/awesome-wm-widgets/gitlab-widget/icons/gitlab-icon.svg new file mode 100644 index 0000000..abe3f37 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/gitlab-widget/icons/gitlab-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/gitlab-widget/icons/message-circle.svg b/dot_config/awesome/awesome-wm-widgets/gitlab-widget/icons/message-circle.svg new file mode 100644 index 0000000..43eacbb --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/gitlab-widget/icons/message-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/gitlab-widget/screenshot.png b/dot_config/awesome/awesome-wm-widgets/gitlab-widget/screenshot.png new file mode 100644 index 0000000..8ab6590 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/gitlab-widget/screenshot.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/jira-widget/README.md b/dot_config/awesome/awesome-wm-widgets/jira-widget/README.md new file mode 100644 index 0000000..85abaf2 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/jira-widget/README.md @@ -0,0 +1,59 @@ +# Jira widget + +The widget shows the number of tickets assigned to the user (or any other result of a JQL query, see customization section) and when clicked shows them in the list, grouped by the ticket status. Left-click on the item opens the issue in the default browser: + +

+screenshot +

+ +## How it works + +Widget uses cURL to query Jira's [REST API](https://developer.atlassian.com/server/jira/platform/rest-apis/). In order to be authenticated, widget uses a [netrc](https://ec.haxx.se/usingcurl/usingcurl-netrc) feature of the cURL, which is basically to store basic auth credentials in a .netrc file in home folder. + +If you are on Atlassian Cloud, then instead of providing a password in netrc file you can set an [API token](https://confluence.atlassian.com/cloud/api-tokens-938839638.html) which is a safer option, as you can revoke/change the token at any time. + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `host` | Required | Ex: _http://jira.tmnt.com_ | +| `query` | `jql=assignee=currentuser() AND resolution=Unresolved` | JQL query | +| `icon` | `~/.config/awesome/awesome-wm-widgets/jira-widget/jira-mark-gradient-blue.svg` | Path to the icon | +| `timeout` | 600 | How often in seconds the widget refreshes | + +## Installation + +Create a .netrc file in your home directory with following content: + +```bash +machine turtlejira.com +login mikey@tmnt.com +password cowabunga +``` + +Then change file's permissions to 600 (so only you can read/write it): + +```bash +chmod 600 ~/.netrc +``` +And test if it works by calling the API (`-n` option is to use the .netrc file for authentication): + +```bash +curl -n 'https://turtleninja.com/rest/api/2/search?jql=assignee=currentuser()+AND+resolution=Unresolved' +``` + +Clone/download repo and use the widget in **rc.lua**: + +```lua +local jira_widget = require("awesome-wm-widgets.jira-widget.jira") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + jira_widget({host = 'http://jira.tmnt.com'}), + ... +``` diff --git a/dot_config/awesome/awesome-wm-widgets/jira-widget/icon/jira-mark-gradient-blue.svg b/dot_config/awesome/awesome-wm-widgets/jira-widget/icon/jira-mark-gradient-blue.svg new file mode 100644 index 0000000..74ea729 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/jira-widget/icon/jira-mark-gradient-blue.svg @@ -0,0 +1 @@ +jira-icon-gradient-blue \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/jira-widget/jira.lua b/dot_config/awesome/awesome-wm-widgets/jira-widget/jira.lua new file mode 100644 index 0000000..f34e951 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/jira-widget/jira.lua @@ -0,0 +1,305 @@ +------------------------------------------------- +-- Jira Widget for Awesome Window Manager +-- Shows the number of currently assigned issues +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/jira-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") +local json = require("json") +local spawn = require("awful.spawn") +local naughty = require("naughty") +local gears = require("gears") +local beautiful = require("beautiful") +local gfs = require("gears.filesystem") +local color = require("gears.color") + +local HOME_DIR = os.getenv("HOME") + +local GET_ISSUES_CMD = + [[bash -c "curl -s --show-error -X GET -n '%s/rest/api/2/search?%s&fields=id,assignee,summary,status'"]] +local DOWNLOAD_AVATAR_CMD = [[bash -c "curl -n --create-dirs -o %s/.cache/awmw/jira-widget/avatars/%s %s"]] + +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Jira Widget', + text = message} +end + +local jira_widget = wibox.widget { + { + { + { + { + id = 'c', + widget = wibox.widget.imagebox + }, + { + id = 'd', + draw = function(_, _, cr, width, height) + cr:set_source(color(beautiful.fg_urgent)) + cr:arc(width - height / 6, height / 6, height / 6, 0, math.pi * 2) + cr:fill() + end, + visible = false, + layout = wibox.widget.base.make_widget, + }, + id = 'b', + layout = wibox.layout.stack + }, + { + id = "txt", + widget = wibox.widget.textbox + }, + spacing = 4, + layout = wibox.layout.fixed.horizontal, + }, + margins = 4, + layout = wibox.container.margin + }, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + widget = wibox.container.background, + set_text = function(self, new_value) + self:get_children_by_id('txt')[1]:set_text(new_value) + --self.txt.text = new_value + end, + set_icon = function(self, path) + self:get_children_by_id('c')[1]:set_image(path) + end, + is_everything_ok = function(self, is_ok) + if is_ok then + self:get_children_by_id('d')[1]:set_visible(false) + self:get_children_by_id('c')[1]:set_opacity(1) + self:get_children_by_id('c')[1]:emit_signal('widget:redraw_needed') + else + --self.txt:set_text('') + self:get_children_by_id('txt')[1]:set_text('') + self:get_children_by_id('d')[1]:set_visible(true) + self:get_children_by_id('c')[1]:set_opacity(0.2) + self:get_children_by_id('c')[1]:emit_signal('widget:redraw_needed') + end + end +} + +local popup = awful.popup{ + 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 number_of_issues + +local warning_shown = false +local tooltip = awful.tooltip { + mode = 'outside', + preferred_positions = {'bottom'}, + } + +local function worker(user_args) + + local args = user_args or {} + + local icon = args.icon or + HOME_DIR .. '/.config/awesome/awesome-wm-widgets/jira-widget/icon/jira-mark-gradient-blue.svg' + local host = args.host or show_warning('Jira host is unknown') + local query = args.query or 'jql=assignee=currentuser() AND resolution=Unresolved' + local timeout = args.timeout or 600 + + jira_widget:set_icon(icon) + + local separator_widget = { + orientation = 'horizontal', + forced_height = 1, + color = beautiful.bg_focus, + widget = wibox.widget.separator + } + + local update_widget = function(widget, stdout, stderr, _, _) + if stderr ~= '' then + if not warning_shown then + show_warning(stderr) + warning_shown = true + widget:is_everything_ok(false) + tooltip:add_to_object(widget) + + widget:connect_signal('mouse::enter', function() + tooltip.text = stderr + end) + end + return + end + + warning_shown = false + tooltip:remove_from_object(widget) + widget:is_everything_ok(true) + + local result = json.decode(stdout) + + number_of_issues = rawlen(result.issues) + + if number_of_issues == 0 then + widget:set_visible(false) + return + end + + widget:set_visible(true) + widget:set_text(number_of_issues) + + local rows = { layout = wibox.layout.fixed.vertical } + + for i = 0, #rows do rows[i]=nil end + + -- sort issues based on the status + table.sort(result.issues, function(a,b) return a.fields.status.name > b.fields.status.name end) + + local cur_status = '' + for _, issue in ipairs(result.issues) do + + local name + if issue.fields.assignee.name == nil then + name = issue.fields.assignee.displayName + else + name = issue.fields.assignee.name + end + + local path_to_avatar = HOME_DIR ..'/.cache/awmw/jira-widget/avatars/' .. name + + if not gfs.file_readable(path_to_avatar) then + spawn.easy_async(string.format( + DOWNLOAD_AVATAR_CMD, + HOME_DIR, + name, + issue.fields.assignee.avatarUrls['48x48'])) + end + + if (cur_status ~= issue.fields.status.name) then + -- do not insert separator before first item + if (cur_status ~= '') then + table.insert(rows, separator_widget) + end + + table.insert(rows, wibox.widget { + { + { + markup = "" .. issue.fields.status.name .. "", + widget = wibox.widget.textbox, + }, + left = 8, + layout = wibox.container.margin + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + }) + cur_status = issue.fields.status.name + end + + local row = wibox.widget { + { + { + { + { + resize = true, + image = path_to_avatar, + forced_width = 40, + forced_height = 40, + widget = wibox.widget.imagebox + }, + left = 4, + layout = wibox.container.margin + }, + { + { + markup = '' .. issue.fields.summary .. '', + widget = wibox.widget.textbox + }, + { + { + markup = "" .. issue.key .. "", + widget = wibox.widget.textbox + }, + { + markup = "" + .. issue.fields.assignee.displayName .. "", + widget = wibox.widget.textbox + }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + layout = wibox.layout.align.vertical + }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + margins = 4, + layout = wibox.container.margin + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + } + + local old_cursor, old_wibox + row:connect_signal("mouse::enter", function(c) + c:set_bg(beautiful.bg_focus) + c:set_shape(function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end) + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + row:connect_signal("mouse::leave", function(c) + c:set_bg(beautiful.bg_normal) + c:set_shape(nil) + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + row:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell("xdg-open " .. host .. '/browse/' .. issue.key) + popup.visible = false + jira_widget:set_bg('#00000000') + end) + ) + ) + + table.insert(rows, row) + end + + popup:setup(rows) + end + + jira_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if popup.visible then + jira_widget:set_bg('#00000000') + popup.visible = not popup.visible + else + jira_widget:set_bg(beautiful.bg_focus) + popup:move_next_to(mouse.current_widget_geometry) + end + end) + ) + ) + watch(string.format(GET_ISSUES_CMD, host, query:gsub(' ', '+')), timeout, update_widget, jira_widget) + return jira_widget +end + +return setmetatable(jira_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/jira-widget/screenshot/screenshot.png b/dot_config/awesome/awesome-wm-widgets/jira-widget/screenshot/screenshot.png new file mode 100644 index 0000000..7bfe9b6 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/jira-widget/screenshot/screenshot.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/README.md b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/README.md new file mode 100644 index 0000000..6f8ca27 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/README.md @@ -0,0 +1,45 @@ +# Logout Menu Widget + +This widget shows a menu with options to log out from the current session, lock, reboot, suspend and power off the computer, similar to [logout-popup-widget](https://github.com/streetturtle/awesome-wm-widgets/tree/master/logout-popup-widget): + +![demo](./logout-menu.gif) + +## Installation + +Clone this repo (if not cloned yet) under **./.config/awesome/** + +```bash +cd ./.config/awesome/ +git clone https://github.com/streetturtle/awesome-wm-widgets +``` +Then add the widget to the wibar: + +```lua +local logout_menu_widget = require("awesome-wm-widgets.logout-menu-widget.logout-menu") + +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + logout_menu_widget(), + -- custom + logout_menu_widget{ + font = 'Play 14', + onlock = function() awful.spawn.with_shell('i3lock-fancy') end + } + ... +``` + +## Customization + +It is possible to customize the widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `font` | `beautiful.font` | Font of the menu items | +| `onlogout` | `function() awesome.quit() end` | Function which is called when the logout item is clicked | +| `onlock` | `function() awful.spawn.with_shell("i3lock") end` | Function which is called when the lock item is clicked | +| `onreboot` | `function() awful.spawn.with_shell("reboot") end` | Function which is called when the reboot item is clicked | +| `onsuspend` | `function() awful.spawn.with_shell("systemctl suspend") end` | Function which is called when the suspend item is clicked | +| `onpoweroff` | `function() awful.spawn.with_shell("shutdown now") end` | Function which is called when the poweroff item is clicked | diff --git a/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/lock.svg b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/lock.svg new file mode 100644 index 0000000..3cfa528 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/log-out.svg b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/log-out.svg new file mode 100644 index 0000000..77afebb --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/log-out.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/moon.svg b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/moon.svg new file mode 100644 index 0000000..60e6ce8 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/power.svg b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/power.svg new file mode 100644 index 0000000..68b1be8 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/power.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/power_w.svg b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/power_w.svg new file mode 100644 index 0000000..1f9c4e3 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/power_w.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/refresh-cw.svg b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/refresh-cw.svg new file mode 100644 index 0000000..39f52a5 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/icons/refresh-cw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/logout-menu.gif b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/logout-menu.gif new file mode 100644 index 0000000..9f17b51 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/logout-menu.gif differ diff --git a/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/logout-menu.lua b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/logout-menu.lua new file mode 100644 index 0000000..9a634f1 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/logout-menu-widget/logout-menu.lua @@ -0,0 +1,136 @@ +------------------------------------------------- +-- Logout Menu Widget for Awesome Window Manager +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/logout-menu-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local gears = require("gears") +local beautiful = require("beautiful") + +local HOME = os.getenv('HOME') +local ICON_DIR = HOME .. '/.config/awesome/awesome-wm-widgets/logout-menu-widget/icons/' + +local logout_menu_widget = wibox.widget { + { + { + image = ICON_DIR .. 'power_w.svg', + resize = true, + widget = wibox.widget.imagebox, + }, + margins = 4, + layout = wibox.container.margin + }, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + widget = wibox.container.background, +} + +local popup = awful.popup { + ontop = true, + visible = false, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + border_width = 1, + border_color = beautiful.bg_focus, + maximum_width = 400, + offset = { y = 5 }, + widget = {} +} + +local function worker(user_args) + local rows = { layout = wibox.layout.fixed.vertical } + + local args = user_args or {} + + local font = args.font or beautiful.font + + local onlogout = args.onlogout or function () awesome.quit() end + local onlock = args.onlock or function() awful.spawn.with_shell("i3lock") end + local onreboot = args.onreboot or function() awful.spawn.with_shell("reboot") end + local onsuspend = args.onsuspend or function() awful.spawn.with_shell("systemctl suspend") end + local onpoweroff = args.onpoweroff or function() awful.spawn.with_shell("shutdown now") end + + local menu_items = { + { name = 'Log out', icon_name = 'log-out.svg', command = onlogout }, + { name = 'Lock', icon_name = 'lock.svg', command = onlock }, + { name = 'Reboot', icon_name = 'refresh-cw.svg', command = onreboot }, + { name = 'Suspend', icon_name = 'moon.svg', command = onsuspend }, + { name = 'Power off', icon_name = 'power.svg', command = onpoweroff }, + } + + for _, item in ipairs(menu_items) do + + local row = wibox.widget { + { + { + { + image = ICON_DIR .. item.icon_name, + resize = false, + widget = wibox.widget.imagebox + }, + { + text = item.name, + font = font, + widget = wibox.widget.textbox + }, + spacing = 12, + layout = wibox.layout.fixed.horizontal + }, + margins = 8, + 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:buttons(awful.util.table.join(awful.button({}, 1, function() + popup.visible = not popup.visible + item.command() + end))) + + table.insert(rows, row) + end + popup:setup(rows) + + logout_menu_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if popup.visible then + popup.visible = not popup.visible + logout_menu_widget:set_bg('#00000000') + else + popup:move_next_to(mouse.current_widget_geometry) + logout_menu_widget:set_bg(beautiful.bg_focus) + end + end) + ) + ) + + return logout_menu_widget + +end + +return worker diff --git a/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/README.md b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/README.md new file mode 100644 index 0000000..d95b692 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/README.md @@ -0,0 +1,84 @@ +# Logout Popup Widget + +Widget which allows performing lock, reboot, log out, power off and sleep actions. It can be called either by a shortcut, or by clicking on a widget in wibar. + +

+ screenshot +

+ +When the widget is shown, following shortcuts can be used: + - Escape - hide widget + - s - shutdown + - r - reboot + - u - suspend + - k - lock + - l - log out + +# Installation + +Clone this (if not cloned yet) and the [awesome-buttons](https://github.com/streetturtle/awesome-buttons) repos under **./.config/awesome/** + +```bash +cd ./.config/awesome/ +git clone https://github.com/streetturtle/awesome-wm-widgets +git clone https://github.com/streetturtle/awesome-buttons +``` +Then + +- to show by a shortcut - define a shortcut in `globalkeys`: + + ```lua + local logout_popup = require("awesome-wm-widgets.logout-popup-widget.logout-popup") + ... + globalkeys = gears.table.join( + ... + awful.key({ modkey }, "l", function() logout_popup.launch() end, {description = "Show logout screen", group = "custom"}), + ``` + +- to show by clicking on a widget in wibar - add widget to the wibar: + + ```lua + local logout_popup = require("awesome-wm-widgets.logout-popup-widget.logout-popup") + + s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + logout_popup.widget{}, + ... + ``` + +# Customisation + +| Name | Default | Description | +|---|---|---| +| `icon` | `power.svg` | If used as widget - the path to the widget's icon | +| `icon_size` | `40` | Size of the icon | +| `icon_margin` | `16` | Margin around the icon | +| `bg_color` | `beautiful.bg_normal` | The color the background of the | +| `accent_color` | `beautiful.bg_focus` | The color of the buttons | +| `text_color` | `beautiful.fg_normal` | The color of text | +| `label_color` | `beautiful.fg_normal` | The color of the button's label | +| `phrases` | `{'Goodbye!'}` | The table with phrase(s) to show, if more than one provided, the phrase is chosen randomly. Leave empty (`{}`) to hide the phrase | +| `onlogout` | `function() awesome.quit() end` | Function which is called when the logout button is pressed | +| `onlock` | `function() awful.spawn.with_shell("systemctl suspend") end` | Function which is called when the lock button is pressed | +| `onreboot` | `function() awful.spawn.with_shell("reboot") end` | Function which is called when the reboot button is pressed | +| `onsuspend` | `function() awful.spawn.with_shell("systemctl suspend") end` | Function which is called when the suspend button is pressed | +| `onpoweroff` | `function() awful.spawn.with_shell("shutdown now") end` | Function which is called when the poweroff button is pressed | + +Some color themes for inspiration: + +![nord](./logout-nord.png) +![outrun](./logout-outrun.png) +![dark](./logout-dark.png) +![dracula](./logout-dracula.png) + +```lua +logout.launch{ + bg_color = "#261447", accent_color = "#ff4365", text_color = '#f706cf', icon_size = 40, icon_margin = 16, -- outrun + -- bg_color = "#0b0c10", accent_color = "#1f2833", text_color = '#66fce1', -- dark + -- bg_color = "#3B4252", accent_color = "#88C0D0", text_color = '#D8DEE9', -- nord + -- bg_color = "#282a36", accent_color = "#ff79c6", phrases = {}, -- dracula, no phrase + phrases = {"exit(0)", "Don't forget to be awesome.", "Yippee ki yay!"}, +} +``` diff --git a/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-dark.png b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-dark.png new file mode 100644 index 0000000..06e7c9c Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-dark.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-dracula.png b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-dracula.png new file mode 100644 index 0000000..3c61c46 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-dracula.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-nord.png b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-nord.png new file mode 100644 index 0000000..9ab4b55 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-nord.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-outrun.png b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-outrun.png new file mode 100644 index 0000000..9be68b5 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-outrun.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-popup.lua b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-popup.lua new file mode 100644 index 0000000..efe6882 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/logout-popup.lua @@ -0,0 +1,187 @@ +------------------------------------------------- +-- Logout widget for Awesome Window Manager +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/logout-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local capi = {keygrabber = keygrabber } +local wibox = require("wibox") +local gears = require("gears") +local beautiful = require("beautiful") +local awesomebuttons = require("awesome-buttons.awesome-buttons") + + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/logout-popup-widget' + + +local w = wibox { + bg = beautiful.fg_normal, + max_widget_size = 500, + ontop = true, + height = 200, + width = 400, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 8) + end +} + +local action = wibox.widget { + text = ' ', + widget = wibox.widget.textbox +} + +local phrase_widget = wibox.widget{ + align = 'center', + widget = wibox.widget.textbox +} + +local function create_button(icon_name, action_name, accent_color, label_color, onclick, icon_size, icon_margin) + + local button = awesomebuttons.with_icon { + type = 'basic', + icon = icon_name, + color = accent_color, + icon_size = icon_size, + icon_margin = icon_margin, + onclick = function() + onclick() + w.visible = false + capi.keygrabber.stop() + end + } + button:connect_signal("mouse::enter", function() + action:set_markup('' .. action_name .. '') + end) + + button:connect_signal("mouse::leave", function() action:set_markup(' ') end) + + return button +end + +local function launch(args) + args = args or {} + + local bg_color = args.bg_color or beautiful.bg_normal + local accent_color = args.accent_color or beautiful.bg_focus + local text_color = args.text_color or beautiful.fg_normal + local label_color = args.label_color or beautiful.fg_focus + local phrases = args.phrases or {'Goodbye!'} + local icon_size = args.icon_size or 40 + local icon_margin = args.icon_margin or 16 + + local onlogout = args.onlogout or function () awesome.quit() end + local onlock = args.onlock or function() awful.spawn.with_shell("i3lock") end + local onreboot = args.onreboot or function() awful.spawn.with_shell("reboot") end + local onsuspend = args.onsuspend or function() awful.spawn.with_shell("systemctl suspend") end + local onpoweroff = args.onpoweroff or function() awful.spawn.with_shell("shutdown now") end + + w:set_bg(bg_color) + if #phrases > 0 then + phrase_widget:set_markup( + '' .. phrases[ math.random( #phrases ) ] .. '') + end + + w:setup { + { + phrase_widget, + { + { + create_button('log-out', 'Log Out (l)', + accent_color, label_color, onlogout, icon_size, icon_margin), + create_button('lock', 'Lock (k)', + accent_color, label_color, onlock, icon_size, icon_margin), + create_button('refresh-cw', 'Reboot (r)', + accent_color, label_color, onreboot, icon_size, icon_margin), + create_button('moon', 'Suspend (u)', + accent_color, label_color, onsuspend, icon_size, icon_margin), + create_button('power', 'Power Off (s)', + accent_color, label_color, onpoweroff, icon_size, icon_margin), + id = 'buttons', + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + valign = 'center', + layout = wibox.container.place + }, + { + action, + haligh = 'center', + layout = wibox.container.place + }, + spacing = 32, + layout = wibox.layout.fixed.vertical + }, + id = 'a', + shape_border_width = 1, + valign = 'center', + layout = wibox.container.place + } + + w.screen = mouse.screen + w.visible = true + + awful.placement.centered(w) + capi.keygrabber.run(function(_, key, event) + if event == "release" then return end + if key then + if key == 'Escape' then + phrase_widget:set_text('') + capi.keygrabber.stop() + w.visible = false + elseif key == 's' then onpoweroff() + elseif key == 'r' then onreboot() + elseif key == 'u' then onsuspend() + elseif key == 'k' then onlock() + elseif key == 'l' then onlogout() + end + + if key == 'Escape' or string.match("srukl", key) then + phrase_widget:set_text('') + capi.keygrabber.stop() + w.visible = false + end + end + end) +end + +local function widget(args) + local icon = args.icon or WIDGET_DIR .. '/power.svg' + + local res = wibox.widget { + { + { + image = icon, + widget = wibox.widget.imagebox + }, + margins = 4, + layout = wibox.container.margin + }, + layout = wibox.layout.fixed.horizontal, + } + + res:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if w.visible then + phrase_widget:set_text('') + capi.keygrabber.stop() + w.visible = false + else + launch(args) + end + end) + )) + + return res + +end + +return { + launch = launch, + widget = widget +} diff --git a/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/power.svg b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/power.svg new file mode 100644 index 0000000..1f9c4e3 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/power.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/screenshot.gif b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/screenshot.gif new file mode 100644 index 0000000..4975c19 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/screenshot.gif differ diff --git a/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/screenshot.png b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/screenshot.png new file mode 100644 index 0000000..74ed7f0 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/logout-popup-widget/screenshot.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/mpdarc-widget/README.md b/dot_config/awesome/awesome-wm-widgets/mpdarc-widget/README.md new file mode 100644 index 0000000..2192410 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/mpdarc-widget/README.md @@ -0,0 +1,26 @@ +# MPD Widget + +Music Player Daemon widget by @raphaelfournier. + +# Prerequisite + +Install `mpd` (Music Player Daemon itself) and `mpc` (Music Player Client - program for controlling mpd), both should be available in repo, e.g for Ubuntu: + +```bash +sudo apt-get install mpd mpc +``` + +## Installation + +To use this widget clone repo under **~/.config/awesome/** and then add it in **rc.lua**: + +```lua +local mpdarc_widget = require("awesome-wm-widgets.mpdarc-widget.mpdarc") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + mpdarc_widget, + ... +``` diff --git a/dot_config/awesome/awesome-wm-widgets/mpdarc-widget/mpdarc.lua b/dot_config/awesome/awesome-wm-widgets/mpdarc-widget/mpdarc.lua new file mode 100644 index 0000000..f1d6930 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/mpdarc-widget/mpdarc.lua @@ -0,0 +1,119 @@ +------------------------------------------------- +-- mpd Arc Widget for Awesome Window Manager +-- Modelled after Pavel Makhov's work + +-- @author Raphaël Fournier-S'niehotta +-- @copyright 2018 Raphaël Fournier-S'niehotta +------------------------------------------------- + +local awful = require("awful") +local beautiful = require("beautiful") +local spawn = require("awful.spawn") +local watch = require("awful.widget.watch") +local wibox = require("wibox") +local naughty = require("naughty") + +local GET_MPD_CMD = "mpc status" +local TOGGLE_MPD_CMD = "mpc toggle" +local PAUSE_MPD_CMD = "mpc pause" +local STOP_MPD_CMD = "mpc stop" +local NEXT_MPD_CMD = "mpc next" +local PREV_MPD_CMD = "mpc prev" + +local PATH_TO_ICONS = "/usr/share/icons/Arc" +local PAUSE_ICON_NAME = PATH_TO_ICONS .. "/actions/24/player_pause.png" +local PLAY_ICON_NAME = PATH_TO_ICONS .. "/actions/24/player_play.png" +local STOP_ICON_NAME = PATH_TO_ICONS .. "/actions/24/player_stop.png" + +local icon = wibox.widget { + id = "icon", + widget = wibox.widget.imagebox, + image = PLAY_ICON_NAME + } +local mirrored_icon = wibox.container.mirror(icon, { horizontal = true }) + +local mpdarc = wibox.widget { + mirrored_icon, + max_value = 1, + value = 0.75, + thickness = 2, + start_angle = 4.71238898, -- 2pi*3/4 + forced_height = 32, + forced_width = 32, + rounded_edge = true, + bg = "#ffffff11", + paddings = 0, + widget = wibox.container.arcchart +} + +local mpdarc_icon_widget = wibox.container.mirror(mpdarc, { horizontal = true }) +local mpdarc_current_song_widget = wibox.widget { + id = 'current_song', + widget = wibox.widget.textbox, + font = 'Play 9' +} + +local update_graphic = function(widget, stdout, _, _, _) + local current_song = string.gmatch(stdout, "[^\r\n]+")() + stdout = string.gsub(stdout, "\n", "") + local mpdpercent = string.match(stdout, "(%d%d)%%") + local mpdstatus = string.match(stdout, "%[(%a+)%]") + if mpdstatus == "playing" then + icon.image = PLAY_ICON_NAME + widget.colors = { beautiful.widget_main_color } + widget.value = tonumber((100-mpdpercent)/100) + mpdarc_current_song_widget.markup = current_song + elseif mpdstatus == "paused" then + icon.image = PAUSE_ICON_NAME + widget.colors = { beautiful.widget_main_color } + widget.value = tonumber(mpdpercent/100) + mpdarc_current_song_widget.markup = current_song + else + icon.image = STOP_ICON_NAME + if string.len(stdout) == 0 then -- MPD is not running + mpdarc_current_song_widget.markup = "MPD is not running" + else + widget.colors = { beautiful.widget_red } + mpdarc_current_song_widget.markup = "" + end + end +end + +mpdarc:connect_signal("button::press", function(_, _, _, button) + if (button == 1) then awful.spawn(TOGGLE_MPD_CMD, false) -- left click + elseif (button == 2) then awful.spawn(STOP_MPD_CMD, false) + elseif (button == 3) then awful.spawn(PAUSE_MPD_CMD, false) + elseif (button == 4) then awful.spawn(NEXT_MPD_CMD, false) -- scroll up + elseif (button == 5) then awful.spawn(PREV_MPD_CMD, false) -- scroll down + end + + spawn.easy_async(GET_MPD_CMD, function(stdout, stderr, exitreason, exitcode) + update_graphic(mpdarc, stdout, stderr, exitreason, exitcode) + end) +end) + +local notification +local function show_MPD_status() + spawn.easy_async(GET_MPD_CMD, + function(stdout, _, _, _) + notification = naughty.notify { + text = stdout, + title = "MPD", + timeout = 5, + hover_timeout = 0.5, + width = 600, + } + end) +end + +mpdarc:connect_signal("mouse::enter", function() show_MPD_status() end) +mpdarc:connect_signal("mouse::leave", function() naughty.destroy(notification) end) + +watch(GET_MPD_CMD, 1, update_graphic, mpdarc) + +local mpdarc_widget = wibox.widget{ + mpdarc_icon_widget, + mpdarc_current_song_widget, + layout = wibox.layout.align.horizontal, + } +return mpdarc_widget diff --git a/dot_config/awesome/awesome-wm-widgets/mpris-widget/README.md b/dot_config/awesome/awesome-wm-widgets/mpris-widget/README.md new file mode 100644 index 0000000..7efad78 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/mpris-widget/README.md @@ -0,0 +1,26 @@ +# MPRIS Widget (In progress) + +Music Player Info widget cy @mgabs + +# Prerequisite + +Install `playerctl` (mpris implementation), should be available in repo, e.g for Ubuntu: + +```bash +sudo apt-get install playerctl +``` + +## Installation + +To use this widget clone repo under **~/.config/awesome/** and then add it in **rc.lua**: + +```lua +local mpris_widget = require("awesome-wm-widgets.mpris-widget") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + mpris_widget(), + ... +``` diff --git a/dot_config/awesome/awesome-wm-widgets/mpris-widget/init.lua b/dot_config/awesome/awesome-wm-widgets/mpris-widget/init.lua new file mode 100644 index 0000000..5e45ffa --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/mpris-widget/init.lua @@ -0,0 +1,187 @@ +------------------------------------------------- +-- mpris based Arc Widget for Awesome Window Manager +-- Modelled after Pavel Makhov's work +-- @author Mohammed Gaber +-- requires - playerctl +-- @copyright 2020 +------------------------------------------------- +local awful = require("awful") +local beautiful = require("beautiful") +local watch = require("awful.widget.watch") +local wibox = require("wibox") +local gears = require("gears") + +local GET_MPD_CMD = "playerctl -p %s -f '{{status}};{{xesam:artist}};{{xesam:title}}' metadata" + +local TOGGLE_MPD_CMD = "playerctl play-pause" +local NEXT_MPD_CMD = "playerctl next" +local PREV_MPD_CMD = "playerctl previous" +local LIST_PLAYERS_CMD = "playerctl -l" + +local PATH_TO_ICONS = "/usr/share/icons/Arc" +local PAUSE_ICON_NAME = PATH_TO_ICONS .. "/actions/24/player_pause.png" +local PLAY_ICON_NAME = PATH_TO_ICONS .. "/actions/24/player_play.png" +local STOP_ICON_NAME = PATH_TO_ICONS .. "/actions/24/player_stop.png" +local LIBRARY_ICON_NAME = PATH_TO_ICONS .. "/actions/24/music-library.png" + +local default_player = '' + +local icon = wibox.widget { + id = "icon", + widget = wibox.widget.imagebox, + image = PLAY_ICON_NAME +} + +local mpris_widget = wibox.widget{ + { + id = 'artist', + widget = wibox.widget.textbox + }, + { + icon, + max_value = 1, + value = 0, + thickness = 2, + start_angle = 4.71238898, -- 2pi*3/4 + forced_height = 24, + forced_width = 24, + rounded_edge = true, + bg = "#ffffff11", + paddings = 0, + widget = wibox.container.arcchart + }, + { + id = 'title', + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal, + set_text = function(self, artist, title) + self:get_children_by_id('artist')[1]:set_text(artist) + self:get_children_by_id('title')[1]:set_text(title) + end +} + +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 rebuild_popup() + awful.spawn.easy_async(LIST_PLAYERS_CMD, function(stdout, _, _, _) + for i = 0, #rows do rows[i]=nil end + for player_name in stdout:gmatch("[^\r\n]+") do + if player_name ~='' and player_name ~=nil then + + local checkbox = wibox.widget{ + { + checked = player_name == default_player, + 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 + }, + valign = 'center', + layout = wibox.container.place, + } + + checkbox:connect_signal("button::press", function() + default_player = player_name + rebuild_popup() + end) + + table.insert(rows, wibox.widget { + { + { + checkbox, + { + { + text = player_name, + 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 + }) + end + end + end) + + popup:setup(rows) +end + +local function worker() + + -- retrieve song info + local current_song, artist, player_status + + local update_graphic = function(widget, stdout, _, _, _) + local words = gears.string.split(stdout, ';') + player_status = words[1] + artist = words[2] + current_song = words[3] + if current_song ~= nil then + if string.len(current_song) > 18 then + current_song = string.sub(current_song, 0, 9) .. ".." + end + end + + if player_status == "Playing" then + icon.image = PLAY_ICON_NAME + widget.colors = {beautiful.widget_main_color} + widget:set_text(artist, current_song) + elseif player_status == "Paused" then + icon.image = PAUSE_ICON_NAME + widget.colors = {beautiful.widget_main_color} + widget:set_text(artist, current_song) + elseif player_status == "Stopped" then + icon.image = STOP_ICON_NAME + else -- no player is running + icon.image = LIBRARY_ICON_NAME + widget.colors = {beautiful.widget_red} + end + end + + mpris_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() awful.spawn(NEXT_MPD_CMD, false) end), + awful.button({}, 5, function() awful.spawn(PREV_MPD_CMD, false) end), + awful.button({}, 1, function() awful.spawn(TOGGLE_MPD_CMD, false) end) + ) + ) + + watch(string.format(GET_MPD_CMD, "'" .. default_player .. "'"), 1, update_graphic, mpris_widget) + + return mpris_widget + +end + +return setmetatable(mpris_widget, {__call = function(_, ...) return worker(...) end}) diff --git a/dot_config/awesome/awesome-wm-widgets/net-speed-widget/README.md b/dot_config/awesome/awesome-wm-widgets/net-speed-widget/README.md new file mode 100644 index 0000000..a09893e --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/net-speed-widget/README.md @@ -0,0 +1,22 @@ +# Net Speed Widget + +The widget and readme is in progress + +## Installation + +Please refer to the [installation](https://github.com/streetturtle/awesome-wm-widgets#installation) section of the repo. + +Clone repo, include widget and use it in **rc.lua**: + +```lua +local net_speed_widget = require("awesome-wm-widgets.net-speed-widget.net-speed") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + net_speed_widget(), + ... + } + ... +``` diff --git a/dot_config/awesome/awesome-wm-widgets/net-speed-widget/icons/down.svg b/dot_config/awesome/awesome-wm-widgets/net-speed-widget/icons/down.svg new file mode 100644 index 0000000..9a98f39 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/net-speed-widget/icons/down.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/net-speed-widget/icons/up.svg b/dot_config/awesome/awesome-wm-widgets/net-speed-widget/icons/up.svg new file mode 100644 index 0000000..e3c12a7 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/net-speed-widget/icons/up.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/net-speed-widget/net-speed.lua b/dot_config/awesome/awesome-wm-widgets/net-speed-widget/net-speed.lua new file mode 100644 index 0000000..6dd3b05 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/net-speed-widget/net-speed.lua @@ -0,0 +1,126 @@ +------------------------------------------------- +-- Net Speed Widget for Awesome Window Manager +-- Shows current upload/download speed +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/net-speed-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local watch = require("awful.widget.watch") +local wibox = require("wibox") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/net-speed-widget/' +local ICONS_DIR = WIDGET_DIR .. 'icons/' + +local net_speed_widget = {} + +local function convert_to_h(bytes) + local speed + local dim + local bits = bytes * 8 + if bits < 1000 then + speed = bits + dim = 'b/s' + elseif bits < 1000000 then + speed = bits/1000 + dim = 'kb/s' + elseif bits < 1000000000 then + speed = bits/1000000 + dim = 'mb/s' + elseif bits < 1000000000000 then + speed = bits/1000000000 + dim = 'gb/s' + else + speed = tonumber(bits) + dim = 'b/s' + end + return math.floor(speed + 0.5) .. dim +end + +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 + +local function worker(user_args) + + local args = user_args or {} + + local interface = args.interface or '*' + local timeout = args.timeout or 1 + local width = args.width or 55 + + net_speed_widget = wibox.widget { + { + id = 'rx_speed', + forced_width = width, + align = 'right', + widget = wibox.widget.textbox + }, + { + image = ICONS_DIR .. 'down.svg', + widget = wibox.widget.imagebox + }, + { + image = ICONS_DIR .. 'up.svg', + widget = wibox.widget.imagebox + }, + { + id = 'tx_speed', + forced_width = width, + align = 'left', + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal, + set_rx_text = function(self, new_rx_speed) + self:get_children_by_id('rx_speed')[1]:set_text(tostring(new_rx_speed)) + end, + set_tx_text = function(self, new_tx_speed) + self:get_children_by_id('tx_speed')[1]:set_text(tostring(new_tx_speed)) + end + } + + -- make sure these are not shared across different worker/widgets (e.g. two monitors) + -- otherwise the speed will be randomly split among the worker in each monitor + local prev_rx = 0 + local prev_tx = 0 + + local update_widget = function(widget, stdout) + + local cur_vals = split(stdout, '\r\n') + + local cur_rx = 0 + local cur_tx = 0 + + for i, v in ipairs(cur_vals) do + if i%2 == 1 then cur_rx = cur_rx + v end + if i%2 == 0 then cur_tx = cur_tx + v end + end + + local speed_rx = (cur_rx - prev_rx) / timeout + local speed_tx = (cur_tx - prev_tx) / timeout + + widget:set_rx_text(convert_to_h(speed_rx)) + widget:set_tx_text(convert_to_h(speed_tx)) + + prev_rx = cur_rx + prev_tx = cur_tx + end + + watch(string.format([[bash -c "cat /sys/class/net/%s/statistics/*_bytes"]], interface), + timeout, update_widget, net_speed_widget) + + return net_speed_widget + +end + +return setmetatable(net_speed_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/pomodoroarc-widget/README.md b/dot_config/awesome/awesome-wm-widgets/pomodoroarc-widget/README.md new file mode 100644 index 0000000..49b1b2c --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/pomodoroarc-widget/README.md @@ -0,0 +1,16 @@ +# Pomodoro Widget + +:construction: This widget is under construction :construction_worker: + +## Installation + +This widget is based on [@jsspencer](https://github.com/jsspencer)' [pomo](https://github.com/jsspencer/pomo) - a simple pomodoro timer. +So first install/clone it anywhere you like, then either + - in widget's code provide path to the pomo.sh, or + - add pomo.sh to the PATH, or + - make a soft link in /usr/local/bin/ to it: + ```bash + sudo ln -sf /opt/pomodoro/pomo.sh /usr/local/bin/pomo + ``` + +Note that by default widget's code expects third way and calls script by `pomo`. \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/pomodoroarc-widget/pomodoroarc.lua b/dot_config/awesome/awesome-wm-widgets/pomodoroarc-widget/pomodoroarc.lua new file mode 100644 index 0000000..497a208 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/pomodoroarc-widget/pomodoroarc.lua @@ -0,0 +1,135 @@ +------------------------------------------------- +-- Pomodoro Arc Widget for Awesome Window Manager +-- Modelled after Pavel Makhov's work + +-- @author Raphaël Fournier-S'niehotta +-- @copyright 2018 Raphaël Fournier-S'niehotta +------------------------------------------------- + +local awful = require("awful") +local beautiful = require("beautiful") +local spawn = require("awful.spawn") +local watch = require("awful.widget.watch") +local wibox = require("wibox") +local naughty = require("naughty") + +local GET_pomodoro_CMD = "pomo clock" +local PAUSE_pomodoro_CMD = "pomo pause" +local START_pomodoro_CMD = "pomo start" +local STOP_pomodoro_CMD = "pomo stop" + +local text = wibox.widget { + id = "txt", + --font = "Play 12", +font = "Inconsolata Medium 13", + widget = wibox.widget.textbox +} +-- mirror the text, because the whole widget will be mirrored after +local mirrored_text = wibox.container.margin(wibox.container.mirror(text, { horizontal = true })) +mirrored_text.right = 5 -- pour centrer le texte dans le rond +-- +--local mirrored_text = wibox.container.mirror(text, { horizontal = true }) + +-- mirrored text with background +local mirrored_text_with_background = wibox.container.background(mirrored_text) + +local pomodoroarc = wibox.widget { + mirrored_text_with_background, + max_value = 1, + thickness = 2, + start_angle = 4.71238898, -- 2pi*3/4 + forced_height = 32, + forced_width = 32, + rounded_edge = true, + bg = "#ffffff11", + paddings = 0, + widget = wibox.container.arcchart +} + +local pomodoroarc_widget = wibox.container.mirror(pomodoroarc, { horizontal = true }) + +local update_graphic = function(widget, stdout, _, _, _) + local pomostatus = string.match(stdout, " (%D?%D?):%D?%D?") + if pomostatus == "--" then +text.font = "Inconsolata Medium 13" + widget.colors = { beautiful.widget_main_color } + text.text = "25" + widget.value = 1 + else +text.font = "Inconsolata Medium 13" + local pomomin = string.match(stdout, "[ P]?[BW](%d?%d?):%d?%d?") + local pomosec = string.match(stdout, "[ P]?[BW]%d?%d?:(%d?%d?)") + local pomodoro = pomomin * 60 + pomosec + + local status = string.match(stdout, "([ P]?)[BW]%d?%d?:%d?%d?") + local workbreak = string.match(stdout, "[ P]?([BW])%d?%d?:%d?%d?") + text.text = pomomin + +-- Helps debugging + --naughty.notify { + --text = pomomin, + --title = "pomodoro debug", + --timeout = 5, + --hover_timeout = 0.5, + --width = 200, + --} + + if status == " " then -- clock ticking + if workbreak == "W" then + widget.value = tonumber(pomodoro/(25*60)) + if tonumber(pomomin) < 5 then -- last 5 min of pomo + widget.colors = { beautiful.widget_red } + else + widget.colors = { beautiful.widget_blue } + end + elseif workbreak == "B" then -- color during pause + widget.colors = { beautiful.widget_green } + widget.value = tonumber(pomodoro/(5*60)) + end + elseif status == "P" then -- paused + if workbreak == "W" then + widget.colors = { beautiful.widget_yellow } + widget.value = tonumber(pomodoro/(25*60)) +text.font = "Inconsolata Medium 13" + text.text = "PW" + elseif workbreak == "B" then + widget.colors = { beautiful.widget_yellow } + widget.value = tonumber(pomodoro/(5*60)) +text.font = "Inconsolata Medium 13" + text.text = "PB" + end + end + end +end + +pomodoroarc:connect_signal("button::press", function(_, _, _, button) + if (button == 2) then awful.spawn(PAUSE_pomodoro_CMD, false) + elseif (button == 1) then awful.spawn(START_pomodoro_CMD, false) + elseif (button == 3) then awful.spawn(STOP_pomodoro_CMD, false) + end + + spawn.easy_async(GET_pomodoro_CMD, function(stdout, stderr, exitreason, exitcode) + update_graphic(pomodoroarc, stdout, stderr, exitreason, exitcode) + end) +end) + +local notification +local function show_pomodoro_status() + spawn.easy_async(GET_pomodoro_CMD, + function(stdout, _, _, _) + notification = naughty.notify { + text = stdout, + title = "pomodoro status", + timeout = 5, + hover_timeout = 0.5, + width = 200, + } + end) +end + +pomodoroarc:connect_signal("mouse::enter", function() show_pomodoro_status() end) +pomodoroarc:connect_signal("mouse::leave", function() naughty.destroy(notification) end) + +watch(GET_pomodoro_CMD, 1, update_graphic, pomodoroarc) + +return pomodoroarc_widget diff --git a/dot_config/awesome/awesome-wm-widgets/ram-widget/README.md b/dot_config/awesome/awesome-wm-widgets/ram-widget/README.md new file mode 100644 index 0000000..568245b --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/ram-widget/README.md @@ -0,0 +1,41 @@ +# Ram widget + +This widget shows the RAM usage. When clicked another widget appears with more detailed information: + +![screenshot](./out.gif) + +Note: this widget is compatible with Awesome v4.3+, as it is using [awful.popup](https://awesomewm.org/doc/api/classes/awful.popup.html) + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `color_used` | `beautiful.bg_urgent` | Color for used RAM | +| `color_free` | `beautiful.fg_normal` | Color for free RAM | +| `color_buf` | `beautiful.border_color_active` | Color for buffers/cache | +| `widget_height` | `25` | Height of the widget | +| `widget_width` | `25` | Width of the widget | +| `widget_show_buf` | `false` | Whether to display buffers/cache separately in the tray widget. If `false`, buffers/cache are considered free RAM. | +| `timeout` | 1 | How often (in seconds) the widget refreshes | + +## Installation + +Please refer to the [installation](https://github.com/streetturtle/awesome-wm-widgets#installation) section of the repo. + +Clone repo, include widget and use it in **rc.lua**: + +```lua +local ram_widget = require("awesome-wm-widgets.ram-widget.ram-widget") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + ram_widget(), + ... + } + ... +``` + diff --git a/dot_config/awesome/awesome-wm-widgets/ram-widget/out.gif b/dot_config/awesome/awesome-wm-widgets/ram-widget/out.gif new file mode 100644 index 0000000..736f894 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/ram-widget/out.gif differ diff --git a/dot_config/awesome/awesome-wm-widgets/ram-widget/ram-widget.lua b/dot_config/awesome/awesome-wm-widgets/ram-widget/ram-widget.lua new file mode 100644 index 0000000..867d28e --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/ram-widget/ram-widget.lua @@ -0,0 +1,108 @@ +local awful = require("awful") +local beautiful = require("beautiful") +local gears = require("gears") +local watch = require("awful.widget.watch") +local wibox = require("wibox") + + +local ramgraph_widget = {} + + +local function worker(user_args) + local args = user_args or {} + local timeout = args.timeout or 1 + local color_used = args.color_used or beautiful.bg_urgent + local color_free = args.color_free or beautiful.fg_normal + local color_buf = args.color_buf or beautiful.border_color_active + local widget_show_buf = args.widget_show_buf or false + local widget_height = args.widget_height or 25 + local widget_width = args.widget_width or 25 + + --- Main ram widget shown on wibar + ramgraph_widget = wibox.widget { + border_width = 0, + colors = { + color_used, + color_free, + color_buf, + }, + display_labels = false, + forced_height = widget_height, + forced_width = widget_width, + widget = wibox.widget.piechart + } + + --- Widget which is shown when user clicks on the ram widget + local popup = awful.popup{ + ontop = true, + visible = false, + widget = { + widget = wibox.widget.piechart, + forced_height = 200, + forced_width = 400, + colors = { + color_used, + color_free, + color_buf, -- buf_cache + }, + }, + shape = gears.shape.rounded_rect, + border_color = beautiful.border_color_active, + border_width = 1, + offset = { y = 5 }, + } + + --luacheck:ignore 231 + local total, used, free, shared, buff_cache, available, total_swap, used_swap, free_swap + + local function getPercentage(value) + return math.floor(value / (total+total_swap) * 100 + 0.5) .. '%' + end + + watch('bash -c "LANGUAGE=en_US.UTF-8 free | grep -z Mem.*Swap.*"', timeout, + function(widget, stdout) + total, used, free, shared, buff_cache, available, total_swap, used_swap, free_swap = + stdout:match('(%d+)%s*(%d+)%s*(%d+)%s*(%d+)%s*(%d+)%s*(%d+)%s*Swap:%s*(%d+)%s*(%d+)%s*(%d+)') + + if widget_show_buf then + widget.data = { used, free, buff_cache } + else + widget.data = { used, total-used } + end + + if popup.visible then + popup:get_widget().data_list = { + {'used ' .. getPercentage(used + used_swap), used + used_swap}, + {'free ' .. getPercentage(free + free_swap), free + free_swap}, + {'buff_cache ' .. getPercentage(buff_cache), buff_cache} + } + end + end, + ramgraph_widget + ) + + ramgraph_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + popup:get_widget().data_list = { + {'used ' .. getPercentage(used + used_swap), used + used_swap}, + {'free ' .. getPercentage(free + free_swap), free + free_swap}, + {'buff_cache ' .. getPercentage(buff_cache), buff_cache} + } + + if popup.visible then + popup.visible = not popup.visible + else + popup:move_next_to(mouse.current_widget_geometry) + end + end) + ) + ) + + return ramgraph_widget +end + + +return setmetatable(ramgraph_widget, { __call = function(_, ...) + return worker(...) +end }) diff --git a/dot_config/awesome/awesome-wm-widgets/run-shell-2/run-shell-2.lua b/dot_config/awesome/awesome-wm-widgets/run-shell-2/run-shell-2.lua new file mode 100644 index 0000000..89491de --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/run-shell-2/run-shell-2.lua @@ -0,0 +1,102 @@ +------------------------------------------------- +-- Spotify Shell for Awesome Window Manager +-- Simplifies interaction with Spotify for Linux +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/spotify-shell + +-- @author Pavel Makhov +-- @copyright 2018 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local gfs = require("gears.filesystem") +local wibox = require("wibox") +local gears = require("gears") +local completion = require("awful.completion") + +local run = require("awesome-wm-widgets.run-shell-2.run") + +local run_shell = awful.widget.prompt() + +local w = wibox { + bg = '#2e3440', + border_width = 1, + border_color = '#3b4252', + max_widget_size = 500, + ontop = true, + height = 50, + width = 250, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 3) + -- ` gears.shape.infobubble(cr, width, height) + end +} + +local g = { + { + layout = wibox.container.margin, + left = 10, + run_shell, + }, + id = 'left', + layout = wibox.layout.fixed.horizontal +} + + +local function launch(type) + + if type == 'run' then + table.insert(g, 1, run.icon) + w:setup(g) + awful.placement.top(w, { margins = { top = 40 }, parent = awful.screen.focused() }) + w.visible = true + awful.prompt.run { + prompt = run.text, + bg_cursor = run.cursor_color, + textbox = run_shell.widget, + completion_callback = completion.shell, + exe_callback = function(...) + run_shell:spawn_and_handle_error(...) + end, + history_path = gfs.get_cache_dir() .. run.history, + done_callback = function() + w.visible = false + table.remove(g, 1) + end + } + elseif type == 'spotify' then + table.insert(g, 1, { + { + image = '/usr/share/icons/Papirus-Light/32x32/apps/spotify-linux-48x48.svg', + widget = wibox.widget.imagebox, + resize = false + }, + id = 'icon', + top = 9, + left = 10, + layout = wibox.container.margin + }) + w:setup(g) + awful.placement.top(w, { margins = { top = 40 }, parent = awful.screen.focused() }) + w.visible = true + + awful.prompt.run { + prompt = "Spotify Shell: ", + bg_cursor = '#84bd00', + textbox = run_shell.widget, + history_path = gfs.get_dir('cache') .. '/spotify_history', + exe_callback = function(input_text) + if not input_text or #input_text == 0 then return end + awful.spawn("sp " .. input_text) + end, + done_callback = function() + w.visible = false + table.remove(g, 1) + end + } + end +end + +return { + launch = launch +} diff --git a/dot_config/awesome/awesome-wm-widgets/run-shell-2/run.lua b/dot_config/awesome/awesome-wm-widgets/run-shell-2/run.lua new file mode 100644 index 0000000..12644ba --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/run-shell-2/run.lua @@ -0,0 +1,21 @@ +local wibox = require("wibox") + +local icon = { + { + markup = 'a', + widget = wibox.widget.textbox, + }, + id = 'icon', + top = 2, + left = 10, + layout = wibox.container.margin +} + +local text = 'Run: ' + +return { + icon = icon, + text = text, + cursor_color = '#74aeab', + history = '/history' +} \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/run-shell-3/README.md b/dot_config/awesome/awesome-wm-widgets/run-shell-3/README.md new file mode 100644 index 0000000..95749d1 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/run-shell-3/README.md @@ -0,0 +1,34 @@ +# Run Shell + +Blurs / pixelates background and shows widget with run prompt: + +![screenshot](./blur.png) + +![screenshot](./pixelate.png) + +## Installation + +1. To blur / pixelate the background this widget used [ffmpeg](https://www.ffmpeg.org/) and [frei0r](https://frei0r.dyne.org/) plugins (if you want to pixelate the background), which you need to install. Installation of those depends on your distribution, for ffmpeg just follow the installation section of the site, for frei0r I was able to install it by simply running + + ``` + sudo apt-get install frei0r-plugins + ``` + +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 widget at the beginning of **rc.lua**: + + ```lua + local run_shell = require("awesome-wm-widgets.run-shell-3.run-shell") + ``` + +1. Use it (don't forget to comment out the default prompt): + + ```lua + awful.key({modkey}, "r", function () run_shell.launch() end), + ``` +:warning: I am not 100% sure but it may (memory) leak. If awesome uses lots of RAM just reload config (Ctrl + Mod4 + r). diff --git a/dot_config/awesome/awesome-wm-widgets/run-shell-3/blur.png b/dot_config/awesome/awesome-wm-widgets/run-shell-3/blur.png new file mode 100644 index 0000000..4e8b54c Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/run-shell-3/blur.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/run-shell-3/pixelate.png b/dot_config/awesome/awesome-wm-widgets/run-shell-3/pixelate.png new file mode 100644 index 0000000..fedf320 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/run-shell-3/pixelate.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/run-shell-3/run-shell.lua b/dot_config/awesome/awesome-wm-widgets/run-shell-3/run-shell.lua new file mode 100644 index 0000000..0015232 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/run-shell-3/run-shell.lua @@ -0,0 +1,129 @@ +------------------------------------------------- +-- Run Shell for Awesome Window Manager +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/run-shell + +-- @author Pavel Makhov +-- @copyright 2018 Pavel Makhov +-- @copyright 2019 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local gfs = require("gears.filesystem") +local wibox = require("wibox") +local gears = require("gears") +local naughty = require("naughty") +local completion = require("awful.completion") + +local run_shell = awful.widget.prompt() + +local widget = {} + +function widget.new() + local widget_instance = { + _cached_wiboxes = {}, + _cmd_pixelate = [[sh -c 'ffmpeg -loglevel panic -f x11grab -video_size 1920x1060 -y -i :0.0+%s,20 -vf ]] + .. [[frei0r=pixeliz0r -vframes 1 /tmp/i3lock-%s.png ; echo done']], + _cmd_blur = [[sh -c 'ffmpeg -loglevel panic -f x11grab -video_size 1920x1060 -y -i :0.0+%s,20 ]] + .. [[-filter_complex "boxblur=9" -vframes 1 /tmp/i3lock-%s.png ; echo done']] + } + + function widget_instance:_create_wibox() + local w = wibox { + visible = false, + ontop = true, + height = mouse.screen.geometry.height, + width = mouse.screen.geometry.width, + } + + w:setup { + { + { + { + { + markup = 'a', + widget = wibox.widget.textbox, + }, + id = 'icon', + left = 10, + layout = wibox.container.margin + }, + { + run_shell, + left = 10, + layout = wibox.container.margin, + }, + id = 'left', + layout = wibox.layout.fixed.horizontal + }, + widget = wibox.container.background, + bg = '#333333', + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 3) + end, + shape_border_color = '#74aeab', + shape_border_width = 1, + forced_width = 200, + forced_height = 50 + }, + layout = wibox.container.place + } + + return w + end + + function widget_instance:launch() + local s = mouse.screen + if not self._cached_wiboxes[s] then + self._cached_wiboxes[s] = {} + end + if not self._cached_wiboxes[s][1] then + self._cached_wiboxes[s][1] = self:_create_wibox() + end + local w = self._cached_wiboxes[s][1] + local rnd = math.random() + awful.spawn.with_line_callback( + string.format(self._cmd_blur, tostring(awful.screen.focused().geometry.x), rnd), { + stdout = function() + w.visible = true + w.bgimage = '/tmp/i3lock-' .. rnd ..'.png' + awful.placement.top(w, { margins = { top = 20 }, parent = awful.screen.focused() }) + awful.prompt.run { + prompt = 'Run: ', + bg_cursor = '#74aeab', + textbox = run_shell.widget, + completion_callback = completion.shell, + exe_callback = function(...) + run_shell:spawn_and_handle_error(...) + end, + history_path = gfs.get_cache_dir() .. "/history", + done_callback = function() + w.visible = false + w.bgimage = '' + awful.spawn([[bash -c 'rm -f /tmp/i3lock*']]) + end + } + end, + stderr = function(line) + naughty.notify { text = "ERR:" .. line } + end, + }) + + end + + return widget_instance +end + +local function get_default_widget() + if not widget.default_widget then + widget.default_widget = widget.new() + end + return widget.default_widget +end + +function widget.launch(...) + return get_default_widget():launch(...) +end + +return widget + diff --git a/dot_config/awesome/awesome-wm-widgets/run-shell/README.md b/dot_config/awesome/awesome-wm-widgets/run-shell/README.md new file mode 100644 index 0000000..9ae7f5d --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/run-shell/README.md @@ -0,0 +1,25 @@ +# Run Shell + +Run prompt which is put inside a widget: + +[Demo](https://imgur.com/ohjAuCQ.mp4) + +## 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 widget at the beginning of **rc.lua**: + + ```lua + local run_shell = require("awesome-wm-widgets.run-shell.run-shell") + ``` + +1. Use it (don't forget to comment out the default prompt): + + ```lua + awful.key({modkey}, "r", function () run_shell.launch() end), + diff --git a/dot_config/awesome/awesome-wm-widgets/run-shell/out.mp4 b/dot_config/awesome/awesome-wm-widgets/run-shell/out.mp4 new file mode 100644 index 0000000..5db8172 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/run-shell/out.mp4 differ diff --git a/dot_config/awesome/awesome-wm-widgets/run-shell/run-shell.lua b/dot_config/awesome/awesome-wm-widgets/run-shell/run-shell.lua new file mode 100644 index 0000000..a43c4d5 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/run-shell/run-shell.lua @@ -0,0 +1,169 @@ +------------------------------------------------- +-- Run Shell for Awesome Window Manager +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/run-shell + +-- @author Pavel Makhov +-- @copyright 2019 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local gfs = require("gears.filesystem") +local wibox = require("wibox") +local gears = require("gears") +local completion = require("awful.completion") +local naughty = require("naughty") + +local HOME = os.getenv("HOME") + +local run_shell = awful.widget.prompt() + +local widget = {} + +function widget.new() + + local widget_instance = { + _cached_wiboxes = {} + } + + function widget_instance:_create_wibox() + local w = wibox { + visible = false, + ontop = true, + height = mouse.screen.geometry.height, + width = mouse.screen.geometry.width, + opacity = 0.9, + bg = 'radial:'.. mouse.screen.geometry.width/2 .. ',' + .. mouse.screen.geometry.height/2 .. ',20:' + .. mouse.screen.geometry.width/2 .. ',' + .. mouse.screen.geometry.height/2 + .. ',700:0,#2E344022:0.2,#4C566A88:1,#2E3440ff' + } + + local suspend_button = wibox.widget { + image = '/usr/share/icons/Arc/actions/symbolic/system-shutdown-symbolic.svg', + widget = wibox.widget.imagebox, + resize = false, + opacity = 0.2, + --luacheck:ignore 432 + set_hover = function(self, opacity) + self.opacity = opacity + self.image = '/usr/share/icons/Arc/actions/symbolic/system-shutdown-symbolic.svg' + end + } + + local turnoff_notification + + suspend_button:connect_signal("mouse::enter", function() + turnoff_notification = naughty.notify{ + icon = HOME .. "/.config/awesome/nichosi.png", + icon_size=100, + title = "Huston, we have a problem", + text = "You're about to turn off your computer", + timeout = 5, hover_timeout = 0.5, + position = "bottom_right", + bg = "#F06060", + fg = "#EEE9EF", + width = 300, + } + suspend_button:set_hover(1) + end) + + suspend_button:connect_signal("mouse::leave", function() + naughty.destroy(turnoff_notification) + suspend_button:set_hover(0.2) + end) + + suspend_button:connect_signal("button::press", function(_,_,_,button) + if (button == 1) then + awful.spawn("shutdown now") + end + end) + + w:setup { + { + { + { + { + { + markup = 'a', + widget = wibox.widget.textbox, + }, + id = 'icon', + left = 10, + layout = wibox.container.margin + }, + { + run_shell, + left = 10, + layout = wibox.container.margin, + }, + id = 'left', + layout = wibox.layout.fixed.horizontal + }, + bg = '#333333', + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 3) + end, + shape_border_color = '#74aeab', + shape_border_width = 1, + forced_width = 200, + forced_height = 50, + widget = wibox.container.background + }, + valign = 'center', + layout = wibox.container.place + }, + { + { + suspend_button, + layout = wibox.layout.fixed.horizontal + }, + valign = 'bottom', + layout = wibox.container.place, + }, + layout = wibox.layout.stack + } + + return w + end + + function widget_instance:launch() + local s = mouse.screen + if not self._cached_wiboxes[s] then + self._cached_wiboxes[s] = {} + end + if not self._cached_wiboxes[s][1] then + self._cached_wiboxes[s][1] = self:_create_wibox() + end + local w = self._cached_wiboxes[s][1] + w.visible = true + awful.placement.top(w, { margins = { top = 20 }, parent = awful.screen.focused() }) + awful.prompt.run { + prompt = 'Run: ', + bg_cursor = '#74aeab', + textbox = run_shell.widget, + completion_callback = completion.shell, + exe_callback = function(...) + run_shell:spawn_and_handle_error(...) + end, + history_path = gfs.get_cache_dir() .. "/history", + done_callback = function() w.visible = false end + } + end + + return widget_instance +end + +local function get_default_widget() + if not widget.default_widget then + widget.default_widget = widget.new() + end + return widget.default_widget +end + +function widget.launch(...) + return get_default_widget():launch(...) +end + +return widget diff --git a/dot_config/awesome/awesome-wm-widgets/screenshot.png b/dot_config/awesome/awesome-wm-widgets/screenshot.png new file mode 100644 index 0000000..9406ebf Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/screenshot.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/screenshot_with_sprtrs.png b/dot_config/awesome/awesome-wm-widgets/screenshot_with_sprtrs.png new file mode 100644 index 0000000..361d5a2 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/screenshot_with_sprtrs.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/scripts/executable_update_site.sh b/dot_config/awesome/awesome-wm-widgets/scripts/executable_update_site.sh new file mode 100644 index 0000000..35fc971 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/scripts/executable_update_site.sh @@ -0,0 +1,14 @@ +mkdir ./_widgets +for D in *; do + if [[ -d "${D}" ]] && [[ ${D} == *"-widget"* ]]; then + echo "${D}" + cp ${D}/README.md ./_widgets/${D}.md + sed -i '1s/^/---\nlayout: page\n---\n/' ./_widgets/${D}.md + + mkdir -p ./assets/img/widgets/screenshots/${D} + + find ${D}/ \( -name '*.jpg' -o -name '*.png' -o -name '*.gif' \) -exec cp '{}' ./assets/img/widgets/screenshots/${D} \; + + sed -i "s/](\.\(\/screenshots\)\{0,1\}/](..\/awesome-wm-widgets\/assets\/img\/widgets\/screenshots\/$D/g" ./_widgets/${D}.md + fi +done diff --git a/dot_config/awesome/awesome-wm-widgets/spotify-shell/README.md b/dot_config/awesome/awesome-wm-widgets/spotify-shell/README.md new file mode 100644 index 0000000..0f4981d --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/spotify-shell/README.md @@ -0,0 +1,73 @@ +# Spotify Shell + + +![demo](./demo.gif) + +## Features + +1. Supports following commands (same as `sp` client): + - `play`/`pause`/`next`; + - any other string will start a search and play the first result for a given search query; + - feh - shows the current artwork with `feh`; + +1. Stores history and allows navigating through it; + +1. Highly customizable + +## Controls + +Keyboard navigation (copied from [`awful.prompt`](https://awesomewm.org/doc/api/libraries/awful.prompt.html) API documentation page): + +| Name | Usage | +|---|---| +| CTRL+A | beginning-of-line | +| CTRL+B | backward-char | +| CTRL+C | cancel | +| CTRL+D | delete-char | +| CTRL+E | end-of-line | +| CTRL+J | accept-line | +| CTRL+M | accept-line | +| CTRL+F | move-cursor-right | +| CTRL+H | backward-delete-char | +| CTRL+K | kill-line | +| CTRL+U | unix-line-discard | +| CTRL+W | unix-word-rubout | +| CTRL+BACKSPACE | unix-word-rubout | +| SHIFT+INSERT | paste | +| HOME | beginning-of-line | +| END | end-of-line | +| CTRL+R | reverse history search, matches any history entry containing search term. | +| CTRL+S | forward history search, matches any history entry containing search term. | +| CTRL+UP | ZSH up line or search, matches any history entry starting with search term. | +| CTRL+DOWN | ZSH down line or search, matches any history entry starting with search term. | +| CTRL+DELETE | delete the currently visible history entry from history file. This does not delete new commands or history entries under user editing. | + + +## Installation + +1. Install [sp](https://gist.github.com/streetturtle/fa6258f3ff7b17747ee3) - CLI client for [Spotify for Linux](https://www.spotify.com/ca-en/download/linux/): + + ```bash + $ sudo git clone https://gist.github.com/fa6258f3ff7b17747ee3.git ~/dev/ + $ sudo ln -s ~/dev/sp /usr/local/bin/ + ``` + + Check if it works by running `sp help`. + +1. Get an 'id' and 'secret' from [developer.spotify.com](https://beta.developer.spotify.com/documentation/general/guides/app-settings/) and paste it in the header of the `sp` (`SP_ID` and `SP_SECRET`) - this enables search feature. + +1. Clone this repo under **~/.config/awesome/** + +1. Require spotify-shell at the beginning of **rc.lua**: + + ```lua + local spotify_shell = require("awesome-wm-widgets.spotify-shell.spotify-shell") + ``` + +1. Add a shortcut which will show Spotify Shell widget: + + ```lua + awful.key({ modkey, }, "d", function () spotify_shell.launch() end, {description = "spotify shell", group = "music"}), + ``` + +1. It uses icon from [Papirus Icon Theme](https://github.com/PapirusDevelopmentTeam/papirus-icon-theme). So you should either install this icon theme, or download an icon you want to use and provide path to it in **spotify-shell.lua**. diff --git a/dot_config/awesome/awesome-wm-widgets/spotify-shell/demo.gif b/dot_config/awesome/awesome-wm-widgets/spotify-shell/demo.gif new file mode 100644 index 0000000..6696b3a Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/spotify-shell/demo.gif differ diff --git a/dot_config/awesome/awesome-wm-widgets/spotify-shell/spotify-shell.lua b/dot_config/awesome/awesome-wm-widgets/spotify-shell/spotify-shell.lua new file mode 100644 index 0000000..0611e66 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/spotify-shell/spotify-shell.lua @@ -0,0 +1,75 @@ +------------------------------------------------- +-- Spotify Shell for Awesome Window Manager +-- Simplifies interaction with Spotify for Linux +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/spotify-shell + +-- @author Pavel Makhov +-- @copyright 2018 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local gfs = require("gears.filesystem") +local wibox = require("wibox") +local gears = require("gears") + +local ICON = '/usr/share/icons/Papirus-Light/32x32/apps/spotify-linux-48x48.svg' + +local spotify_shell = awful.widget.prompt() + +local w = wibox { + bg = '#1e252c', + border_width = 1, + border_color = '#84bd00', + max_widget_size = 500, + ontop = true, + height = 50, + width = 250, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 3) + end +} + +w:setup { + { + { + image = ICON, + widget = wibox.widget.imagebox, + resize = false + }, + id = 'icon', + top = 9, + left = 10, + layout = wibox.container.margin + }, + { + layout = wibox.container.margin, + left = 10, + spotify_shell, + }, + id = 'left', + layout = wibox.layout.fixed.horizontal +} + +local function launch() + w.visible = true + + awful.placement.top(w, { margins = {top = 40}, parent = awful.screen.focused()}) + awful.prompt.run{ + prompt = "Spotify Shell: ", + bg_cursor = '#84bd00', + textbox = spotify_shell.widget, + history_path = gfs.get_dir('cache') .. '/spotify_history', + exe_callback = function(input_text) + if not input_text or #input_text == 0 then return end + awful.spawn("sp " .. input_text) + end, + done_callback = function() + w.visible = false + end + } +end + +return { + launch = launch +} diff --git a/dot_config/awesome/awesome-wm-widgets/spotify-widget/README.md b/dot_config/awesome/awesome-wm-widgets/spotify-widget/README.md new file mode 100644 index 0000000..3a7b8d7 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/spotify-widget/README.md @@ -0,0 +1,95 @@ +# Spotify widget + +This widget displays currently playing song on [Spotify for Linux](https://www.spotify.com/download/linux/) client: ![screenshot](./spo-wid-1.png) + +Some features: + + - status icon which shows if music is currently playing + - artist and name of the current song + - dim widget if spotify is paused + - trim long artist/song names + - tooltip with more info about the song + +## Controls + + - left click - play/pause + - scroll up - play next song + - scroll down - play previous song + +## Dependencies + +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 | +|---|---|---| +| `play_icon` | `/usr/share/icons/Arc/actions/24/player_play.png` | Play icon | +| `pause_icon` | `/usr/share/icons/Arc/actions/24/player_pause.png` | Pause icon | +| `font` | `Play 9`| Font | +| `dim_when_paused` | `false` | Decrease the widget opacity if spotify is paused | +| `dim_opacity` | `0.2` | Widget's opacity when dimmed, `dim_when_paused` should be set to `true` | +| `max_length` | `15` | Maximum lentgh of artist and title names. Text will be ellipsized if longer. | +| `show_tooltip` | `true` | Show tooltip on hover with information about the playing song | +| `timeout` | 1 | How often in seconds the widget refreshes | +| `sp_bin` | `sp` | Path to the `sp` binary. Required if `sp` is not in environment PATH. | + + +### Example: + +```lua +spotify_widget({ + font = 'Ubuntu Mono 9', + play_icon = '/usr/share/icons/Papirus-Light/24x24/categories/spotify.svg', + pause_icon = '/usr/share/icons/Papirus-Dark/24x24/panel/spotify-indicator.svg', + dim_when_paused = true, + dim_opacity = 0.5, + max_length = -1, + show_tooltip = false, + sp_bin = gears.filesystem.get_configuration_dir() .. 'scripts/sp' +}) +``` + +Gives following widget + +Playing: +![screenshot](./spotify-widget-custom-playing.png) + +Paused: +![screenshot](./spotify-widget-custom-paused.png) + +## Installation + +First you need to have spotify CLI installed, it uses dbus to communicate with spotify-client: + +```bash +git clone https://gist.github.com/fa6258f3ff7b17747ee3.git +cd ./fa6258f3ff7b17747ee3 +chmod +x sp +# This widget will work by default if the binary is in the system PATH +sudo cp ./sp /usr/local/bin/ +# Alternatively, you may save the binary anywhere and supply the path via this widget's sp_bin argument: +# cp ./sp ~/.config/awesome/scripts/ +``` + +Then clone repo under **~/.config/awesome/** and add widget in **rc.lua**: + +```lua +local spotify_widget = require("awesome-wm-widgets.spotify-widget.spotify") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + spotify_widget(), + -- customized + spotify_widget({ + font = 'Ubuntu Mono 9', + play_icon = '/usr/share/icons/Papirus-Light/24x24/categories/spotify.svg', + pause_icon = '/usr/share/icons/Papirus-Dark/24x24/panel/spotify-indicator.svg' + }), + ... +``` diff --git a/dot_config/awesome/awesome-wm-widgets/spotify-widget/spo-wid-1.png b/dot_config/awesome/awesome-wm-widgets/spotify-widget/spo-wid-1.png new file mode 100644 index 0000000..5c7e403 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/spotify-widget/spo-wid-1.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/spotify-widget/spotify-widget-custom-paused.png b/dot_config/awesome/awesome-wm-widgets/spotify-widget/spotify-widget-custom-paused.png new file mode 100644 index 0000000..9ac9c4a Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/spotify-widget/spotify-widget-custom-paused.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/spotify-widget/spotify-widget-custom-playing.png b/dot_config/awesome/awesome-wm-widgets/spotify-widget/spotify-widget-custom-playing.png new file mode 100644 index 0000000..f9628f9 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/spotify-widget/spotify-widget-custom-playing.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/spotify-widget/spotify.lua b/dot_config/awesome/awesome-wm-widgets/spotify-widget/spotify.lua new file mode 100644 index 0000000..2c30685 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/spotify-widget/spotify.lua @@ -0,0 +1,171 @@ +------------------------------------------------- +-- Spotify Widget for Awesome Window Manager +-- Shows currently playing song on Spotify for Linux client +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/spotify-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") + +local function ellipsize(text, length) + -- utf8 only available in Lua 5.3+ + if utf8 == nil then + return text:sub(0, length) + end + return (utf8.len(text) > length and length > 0) + and text:sub(0, utf8.offset(text, length - 2) - 1) .. '...' + or text +end + +local spotify_widget = {} + +local function worker(user_args) + + local args = user_args or {} + + local play_icon = args.play_icon or '/usr/share/icons/Arc/actions/24/player_play.png' + local pause_icon = args.pause_icon or '/usr/share/icons/Arc/actions/24/player_pause.png' + local font = args.font or 'Play 9' + local dim_when_paused = args.dim_when_paused == nil and false or args.dim_when_paused + local dim_opacity = args.dim_opacity or 0.2 + local max_length = args.max_length or 15 + local show_tooltip = args.show_tooltip == nil and true or args.show_tooltip + local timeout = args.timeout or 1 + local sp_bin = args.sp_bin or 'sp' + + local GET_SPOTIFY_STATUS_CMD = sp_bin .. ' status' + local GET_CURRENT_SONG_CMD = sp_bin .. ' current' + + local cur_artist = '' + local cur_title = '' + local cur_album = '' + + spotify_widget = wibox.widget { + { + id = 'artistw', + font = font, + widget = wibox.widget.textbox, + }, + { + layout = wibox.layout.stack, + { + id = "icon", + widget = wibox.widget.imagebox, + }, + { + widget = wibox.widget.textbox, + font = font, + text = ' ', + forced_height = 1 + } + }, + { + layout = wibox.container.scroll.horizontal, + max_size = 100, + step_function = wibox.container.scroll.step_functions.waiting_nonlinear_back_and_forth, + speed = 40, + { + id = 'titlew', + font = font, + widget = wibox.widget.textbox + } + }, + layout = wibox.layout.align.horizontal, + set_status = function(self, is_playing) + self:get_children_by_id('icon')[1]:set_image(is_playing and play_icon or pause_icon) + if dim_when_paused then + self:get_children_by_id('icon')[1]:set_opacity(is_playing and 1 or dim_opacity) + + self:get_children_by_id('titlew')[1]:set_opacity(is_playing and 1 or dim_opacity) + self:get_children_by_id('titlew')[1]:emit_signal('widget::redraw_needed') + + self:get_children_by_id('artistw')[1]:set_opacity(is_playing and 1 or dim_opacity) + self:get_children_by_id('artistw')[1]:emit_signal('widget::redraw_needed') + end + end, + set_text = function(self, artist, song) + local artist_to_display = ellipsize(artist, max_length) + if self:get_children_by_id('artistw')[1]:get_markup() ~= artist_to_display then + self:get_children_by_id('artistw')[1]:set_markup(artist_to_display) + end + local title_to_display = ellipsize(song, max_length) + if self:get_children_by_id('titlew')[1]:get_markup() ~= title_to_display then + self:get_children_by_id('titlew')[1]:set_markup(title_to_display) + end + end + } + + local update_widget_icon = function(widget, stdout, _, _, _) + stdout = string.gsub(stdout, "\n", "") + widget:set_status(stdout == 'Playing' and true or false) + end + + local update_widget_text = function(widget, stdout, _, _, _) + if string.find(stdout, 'Error: Spotify is not running.') ~= nil then + widget:set_text('','') + widget:set_visible(false) + return + end + + local escaped = string.gsub(stdout, "&", '&') + local album, _, artist, title = + string.match(escaped, 'Album%s*(.*)\nAlbumArtist%s*(.*)\nArtist%s*(.*)\nTitle%s*(.*)\n') + + if album ~= nil and title ~=nil and artist ~= nil then + cur_artist = artist + cur_title = title + cur_album = album + + widget:set_text(artist, title) + widget:set_visible(true) + end + end + + watch(GET_SPOTIFY_STATUS_CMD, timeout, update_widget_icon, spotify_widget) + watch(GET_CURRENT_SONG_CMD, timeout, update_widget_text, spotify_widget) + + --- Adds mouse controls to the widget: + -- - left click - play/pause + -- - scroll up - play next song + -- - scroll down - play previous song + spotify_widget:connect_signal("button::press", function(_, _, _, button) + if (button == 1) then + awful.spawn("sp play", false) -- left click + elseif (button == 4) then + awful.spawn("sp next", false) -- scroll up + elseif (button == 5) then + awful.spawn("sp prev", false) -- scroll down + end + awful.spawn.easy_async(GET_SPOTIFY_STATUS_CMD, function(stdout, stderr, exitreason, exitcode) + update_widget_icon(spotify_widget, stdout, stderr, exitreason, exitcode) + end) + end) + + + if show_tooltip then + local spotify_tooltip = awful.tooltip { + mode = 'outside', + preferred_positions = {'bottom'}, + } + + spotify_tooltip:add_to_object(spotify_widget) + + spotify_widget:connect_signal('mouse::enter', function() + spotify_tooltip.markup = 'Album: ' .. cur_album + .. '\nArtist: ' .. cur_artist + .. '\nSong: ' .. cur_title + end) + end + + return spotify_widget + +end + +return setmetatable(spotify_widget, { __call = function(_, ...) + return worker(...) +end }) diff --git a/dot_config/awesome/awesome-wm-widgets/stackoverflow-widget/README.md b/dot_config/awesome/awesome-wm-widgets/stackoverflow-widget/README.md new file mode 100644 index 0000000..0098062 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/stackoverflow-widget/README.md @@ -0,0 +1,47 @@ +# Stackoverflow widget + +When clicked, widget shows latest questions from stackoverflow.com with a given tag(s). + +![screenshot](./screenshot.png) + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `icon`| `/.config/awesome/awesome-wm-widgets/stackoverflow-widget/so-icon.svg` | Path to the icon | +| `limit` | 5 | Number of items to show in the widget | +| `tagged` | awesome-wm | Tag, or comma-separated tags | +| `timeout` | 300 | How often in seconds the widget refreshes | + +## Installation + +1. Clone this repo (if not cloned yet) under **~/.config/awesome/**: + + ```bash + git clone https://github.com/streetturtle/awesome-wm-widgets.git ~/.config/awesome/ + ``` + +1. Require widget at the top of the **rc.lua**: + + ```lua + local stackoverflow_widget = require("awesome-wm-widgets.stackoverflow-widget.stackoverflow") + ``` + +1. Add widget to the tasklist: + + ```lua + s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + --default + stackoverflow_widget(), + --customized + stackoverflow_widget({ + limit = 10 + }) + ... + ``` + diff --git a/dot_config/awesome/awesome-wm-widgets/stackoverflow-widget/screenshot.png b/dot_config/awesome/awesome-wm-widgets/stackoverflow-widget/screenshot.png new file mode 100644 index 0000000..b0058ab Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/stackoverflow-widget/screenshot.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/stackoverflow-widget/so-icon.svg b/dot_config/awesome/awesome-wm-widgets/stackoverflow-widget/so-icon.svg new file mode 100644 index 0000000..5298d4c --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/stackoverflow-widget/so-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/stackoverflow-widget/stackoverflow.lua b/dot_config/awesome/awesome-wm-widgets/stackoverflow-widget/stackoverflow.lua new file mode 100644 index 0000000..15d2837 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/stackoverflow-widget/stackoverflow.lua @@ -0,0 +1,125 @@ +------------------------------------------------- +-- Stackoverflow Widget for Awesome Window Manager +-- Shows new questions by a given tag +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/stackoverflow-widget + +-- @author Pavel Makhov +-- @copyright 2019 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") +local json = require("json") +local spawn = require("awful.spawn") +local gears = require("gears") +local beautiful = require("beautiful") + +local HOME_DIR = os.getenv("HOME") + +local GET_QUESTIONS_CMD = [[bash -c "curl --compressed -s -X GET]] + .. [[ 'http://api.stackexchange.com/2.2/questions/no-answers]] + .. [[?page=1&pagesize=%s&order=desc&sort=activity&tagged=%s&site=stackoverflow'"]] + +local stackoverflow_widget = {} + +local function worker(user_args) + + local args = user_args or {} + + local icon = args.icon or HOME_DIR .. '/.config/awesome/awesome-wm-widgets/stackoverflow-widget/so-icon.svg' + local limit = args.limit or 5 + local tagged = args.tagged or 'awesome-wm' + local timeout = args.timeout or 300 + + local rows = { + { widget = wibox.widget.textbox }, + layout = wibox.layout.fixed.vertical, + } + + local popup = awful.popup{ + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + maximum_width = 400, + preferred_positions = 'top', + offset = { y = 5 }, + widget = {} + } + + stackoverflow_widget = wibox.widget { + { + image = icon, + widget = wibox.widget.imagebox + }, + { + id = "txt", + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal, + set_text = function(self, new_value) + self.txt.text = new_value + end, + } + + local update_widget = function(_, stdout, _, _, _) + + local result = json.decode(stdout) + + for i = 0, #rows do rows[i]=nil end + for _, item in ipairs(result.items) do + local tags = '' + for i = 1, #item.tags do tags = tags .. item.tags[i] .. ' ' end + local row = wibox.widget { + { + { + { + text = item.title, + widget = wibox.widget.textbox + }, + { + text = tags, + align = 'right', + widget = wibox.widget.textbox + }, + layout = wibox.layout.align.vertical + }, + margins = 8, + layout = wibox.container.margin + }, + widget = wibox.container.background + } + + row:connect_signal("button::release", function() + spawn.with_shell("xdg-open " .. item.link) + popup.visible = false + end) + + 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) + + table.insert(rows, row) + end + + popup:setup(rows) + end + + stackoverflow_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if popup.visible then + popup.visible = not popup.visible + else + popup:move_next_to(mouse.current_widget_geometry) + end + end) + ) + ) + watch(string.format(GET_QUESTIONS_CMD, limit, tagged), timeout, update_widget, stackoverflow_widget) + return stackoverflow_widget +end + +return setmetatable(stackoverflow_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/todo-widget/README.md b/dot_config/awesome/awesome-wm-widgets/todo-widget/README.md new file mode 100644 index 0000000..c97d845 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/todo-widget/README.md @@ -0,0 +1,28 @@ +# ToDo Widget + +This widget displays a list of todo items and allows marking item as done/undone, delete an item and create new ones: + +![screenshot](./todo.gif) + +# Installation + +Widget persists todo items as a JSON, so in order to simplify JSON serialisation/deserialisation download a **json.lua** from this repository: https://github.com/rxi/json.lua under `~/.config/awesone` folder. And don't forget to star a repo :) + +Then clone this repository under **~/.config/awesome/** and add the widget in **rc.lua**: + +```lua +local todo_widget = require("awesome-wm-widgets.todo-widget.todo") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + todo_widget(), + ... +``` +Also note that widget uses [Arc Icons](https://github.com/horst3180/arc-icon-theme) and expects them to be installed under `/usr/share/icons/Arc/`. + +# Theming + +Widget uses your theme's colors. In case you want to have different colors, without changing your theme, please create an issue for it. I'll extract them as widget parameters. diff --git a/dot_config/awesome/awesome-wm-widgets/todo-widget/checkbox-checked-symbolic.svg b/dot_config/awesome/awesome-wm-widgets/todo-widget/checkbox-checked-symbolic.svg new file mode 100644 index 0000000..afeca62 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/todo-widget/checkbox-checked-symbolic.svg @@ -0,0 +1,148 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/todo-widget/chevron-down.svg b/dot_config/awesome/awesome-wm-widgets/todo-widget/chevron-down.svg new file mode 100644 index 0000000..dceeb0f --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/todo-widget/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/todo-widget/chevron-up.svg b/dot_config/awesome/awesome-wm-widgets/todo-widget/chevron-up.svg new file mode 100644 index 0000000..88474ce --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/todo-widget/chevron-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/todo-widget/list-add-symbolic.svg b/dot_config/awesome/awesome-wm-widgets/todo-widget/list-add-symbolic.svg new file mode 100644 index 0000000..9cc2d3a --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/todo-widget/list-add-symbolic.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/todo-widget/todo.gif b/dot_config/awesome/awesome-wm-widgets/todo-widget/todo.gif new file mode 100644 index 0000000..7160e21 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/todo-widget/todo.gif differ diff --git a/dot_config/awesome/awesome-wm-widgets/todo-widget/todo.lua b/dot_config/awesome/awesome-wm-widgets/todo-widget/todo.lua new file mode 100644 index 0000000..78ca262 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/todo-widget/todo.lua @@ -0,0 +1,340 @@ +------------------------------------------------- +-- ToDo Widget for Awesome Window Manager +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/todo-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local json = require("json") +local spawn = require("awful.spawn") +local gears = require("gears") +local beautiful = require("beautiful") +local gfs = require("gears.filesystem") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/todo-widget' +local STORAGE = HOME_DIR .. '/.cache/awmw/todo-widget/todos.json' + +local GET_TODO_ITEMS = 'bash -c "cat ' .. STORAGE .. '"' + +local rows = { layout = wibox.layout.fixed.vertical } +local todo_widget = {} +local update_widget +todo_widget.widget = wibox.widget { + { + { + { + { + id = "icon", + forced_height = 16, + forced_width = 16, + widget = wibox.widget.imagebox + }, + valign = 'center', + layout = wibox.container.place + }, + { + id = "txt", + widget = wibox.widget.textbox + }, + spacing = 4, + layout = wibox.layout.fixed.horizontal, + }, + margins = 4, + layout = wibox.container.margin + }, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + widget = wibox.container.background, + set_text = function(self, new_value) + self:get_children_by_id("txt")[1].text = new_value + end, + set_icon = function(self, new_value) + self:get_children_by_id("icon")[1].image = new_value + end +} + +function todo_widget:update_counter(todos) + local todo_count = 0 + for _,p in ipairs(todos) do + if not p.status then + todo_count = todo_count + 1 + end + end + + todo_widget.widget:set_text(todo_count) +end + +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 add_button = wibox.widget { + { + { + image = WIDGET_DIR .. '/list-add-symbolic.svg', + resize = false, + widget = wibox.widget.imagebox + }, + top = 11, + left = 8, + right = 8, + layout = wibox.container.margin + }, + shape = function(cr, width, height) + gears.shape.circle(cr, width, height, 12) + end, + widget = wibox.container.background +} + +add_button:connect_signal("button::press", function() + local pr = awful.widget.prompt() + + table.insert(rows, wibox.widget { + { + { + pr.widget, + spacing = 8, + layout = wibox.layout.align.horizontal + }, + margins = 8, + layout = wibox.container.margin + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + }) + awful.prompt.run{ + prompt = "New item: ", + bg = beautiful.bg_normal, + bg_cursor = beautiful.fg_urgent, + textbox = pr.widget, + exe_callback = function(input_text) + if not input_text or #input_text == 0 then return end + spawn.easy_async(GET_TODO_ITEMS, function(stdout) + local res = json.decode(stdout) + table.insert(res.todo_items, {todo_item = input_text, status = false}) + spawn.easy_async_with_shell("echo '" .. json.encode(res) .. "' > " .. STORAGE, function() + spawn.easy_async(GET_TODO_ITEMS, function(items) update_widget(items) end) + end) + end) + end + } + popup:setup(rows) +end) +add_button:connect_signal("mouse::enter", function(c) c:set_bg(beautiful.bg_focus) end) +add_button:connect_signal("mouse::leave", function(c) c:set_bg(beautiful.bg_normal) end) + +local function worker(user_args) + + local args = user_args or {} + + local icon = args.icon or WIDGET_DIR .. '/checkbox-checked-symbolic.svg' + + todo_widget.widget:set_icon(icon) + + function update_widget(stdout) + local result = json.decode(stdout) + if result == nil or result == '' then result = {} end + todo_widget:update_counter(result.todo_items) + + for i = 0, #rows do rows[i]=nil end + + local first_row = wibox.widget { + { + {widget = wibox.widget.textbox}, + { + markup = 'ToDo', + align = 'center', + forced_width = 350, -- for horizontal alignment + forced_height = 40, + widget = wibox.widget.textbox + }, + add_button, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + } + + table.insert(rows, first_row) + + for i, todo_item in ipairs(result.todo_items) do + + local checkbox = wibox.widget { + checked = todo_item.status, + 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(c) + c:set_checked(not c.checked) + todo_item.status = not todo_item.status + result.todo_items[i] = todo_item + spawn.easy_async_with_shell("echo '" .. json.encode(result) .. "' > " .. STORAGE, function () + todo_widget:update_counter(result.todo_items) + end) + end) + + + local trash_button = wibox.widget { + { + { image = WIDGET_DIR .. '/window-close-symbolic.svg', + resize = false, + widget = wibox.widget.imagebox + }, + margins = 5, + layout = wibox.container.margin + }, + border_width = 1, + shape = function(cr, width, height) + gears.shape.circle(cr, width, height, 10) + end, + widget = wibox.container.background + } + + trash_button:connect_signal("button::press", function() + table.remove(result.todo_items, i) + spawn.easy_async_with_shell("printf '" .. json.encode(result) .. "' > " .. STORAGE, function () + spawn.easy_async(GET_TODO_ITEMS, function(items) update_widget(items) end) + end) + end) + + + local move_up = wibox.widget { + image = WIDGET_DIR .. '/chevron-up.svg', + resize = false, + widget = wibox.widget.imagebox + } + + move_up:connect_signal("button::press", function() + local temp = result.todo_items[i] + result.todo_items[i] = result.todo_items[i-1] + result.todo_items[i-1] = temp + spawn.easy_async_with_shell("printf '" .. json.encode(result) .. "' > " .. STORAGE, function () + spawn.easy_async(GET_TODO_ITEMS, function(items) update_widget(items) end) + end) + end) + + local move_down = wibox.widget { + image = WIDGET_DIR .. '/chevron-down.svg', + resize = false, + widget = wibox.widget.imagebox + } + + move_down:connect_signal("button::press", function() + local temp = result.todo_items[i] + result.todo_items[i] = result.todo_items[i+1] + result.todo_items[i+1] = temp + spawn.easy_async_with_shell("printf '" .. json.encode(result) .. "' > " .. STORAGE, function () + spawn.easy_async(GET_TODO_ITEMS, function(items) update_widget(items) end) + end) + end) + + + local move_buttons = { + layout = wibox.layout.fixed.vertical + } + + if i == 1 and #result.todo_items > 1 then + table.insert(move_buttons, move_down) + elseif i == #result.todo_items and #result.todo_items > 1 then + table.insert(move_buttons, move_up) + elseif #result.todo_items > 1 then + table.insert(move_buttons, move_up) + table.insert(move_buttons, move_down) + end + + local row = wibox.widget { + { + { + { + checkbox, + valign = 'center', + layout = wibox.container.place, + }, + { + { + text = todo_item.todo_item, + align = 'left', + widget = wibox.widget.textbox + }, + left = 10, + layout = wibox.container.margin + }, + { + { + move_buttons, + valign = 'center', + layout = wibox.container.place, + }, + { + trash_button, + valign = 'center', + layout = wibox.container.place, + }, + spacing = 8, + layout = wibox.layout.align.horizontal + }, + spacing = 8, + layout = wibox.layout.align.horizontal + }, + margins = 8, + 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) + + table.insert(rows, row) + end + + popup:setup(rows) + end + + todo_widget.widget:buttons( + gears.table.join( + awful.button({}, 1, function() + if popup.visible then + todo_widget.widget:set_bg('#00000000') + popup.visible = not popup.visible + else + todo_widget.widget:set_bg(beautiful.bg_focus) + popup:move_next_to(mouse.current_widget_geometry) + end + end) + ) + ) + + spawn.easy_async(GET_TODO_ITEMS, function(stdout) update_widget(stdout) end) + + return todo_widget.widget +end + +if not gfs.file_readable(STORAGE) then + spawn.easy_async(string.format([[bash -c "dirname %s | xargs mkdir -p && echo '{\"todo_items\":{}}' > %s"]], + STORAGE, STORAGE)) +end + +return setmetatable(todo_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/dot_config/awesome/awesome-wm-widgets/todo-widget/window-close-symbolic.svg b/dot_config/awesome/awesome-wm-widgets/todo-widget/window-close-symbolic.svg new file mode 100644 index 0000000..46ff888 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/todo-widget/window-close-symbolic.svg @@ -0,0 +1,95 @@ + + + + + + + + Gnome Symbolic Icon Theme + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/translate-widget/README.MD b/dot_config/awesome/awesome-wm-widgets/translate-widget/README.MD new file mode 100644 index 0000000..a82fda7 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/translate-widget/README.MD @@ -0,0 +1,38 @@ +# Translate Widget + +This widget allows quickly translate words or phrases without opening a browser - just using Awesome. To provide direction of the translation add the 2 letters code of the source and target languages at the end of the phrase, for example _hello enfr_ will translate _hello_ from English to French. This widget is based on [Watson Language Translator](https://www.ibm.com/watson/services/language-translator/) from IBM. + +![demo](./demo.gif) + +## Controls + + - Mod4 + c - opens a translate prompt; + - left click on the popup widget - copies the translation to the clipboard and closes widget; + - right click on the popup widget - copies text to translate to the clipboard and closes widget. + +## Installation + +1. Clone repo under **~/.config/awesome/** +1. Create an IBM Cloud API key at [cloud.ibm.com/iam/apikeys](https://cloud.ibm.com/iam/apikeys) +1. Copy a service URL by going to [resource list](https://cloud.ibm.com/resources), then under "Services" select "Language Translator" option, and then copy URL from the "Credentials" section +1. Require widget in **rc.lua**: + + ```lua + local translate = require("awesome-wm-widgets.translate-widget.translate") + ``` + +1. Add a shortcut to run translate prompt: + + ```lua + awful.key({ modkey }, "c", function() + translate.launch{api_key = '', url = 'url'} + end, { description = "run translate prompt", group = "launcher" }) + ``` + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/translate-widget/demo.gif b/dot_config/awesome/awesome-wm-widgets/translate-widget/demo.gif new file mode 100644 index 0000000..3645d47 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/translate-widget/demo.gif differ diff --git a/dot_config/awesome/awesome-wm-widgets/translate-widget/demo1.gif b/dot_config/awesome/awesome-wm-widgets/translate-widget/demo1.gif new file mode 100644 index 0000000..62cc9f4 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/translate-widget/demo1.gif differ diff --git a/dot_config/awesome/awesome-wm-widgets/translate-widget/gnome-translate.svg b/dot_config/awesome/awesome-wm-widgets/translate-widget/gnome-translate.svg new file mode 100644 index 0000000..ca02b1a --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/translate-widget/gnome-translate.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/translate-widget/translate.lua b/dot_config/awesome/awesome-wm-widgets/translate-widget/translate.lua new file mode 100644 index 0000000..d680c4e --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/translate-widget/translate.lua @@ -0,0 +1,201 @@ +------------------------------------------------- +-- Translate Widget based on the Yandex.Translate API +-- https://tech.yandex.com/translate/ + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local spawn = require("awful.spawn") +local capi = {keygrabber = keygrabber } +local beautiful = require("beautiful") +local json = require("json") +local naughty = require("naughty") +local wibox = require("wibox") +local gears = require("gears") +local gfs = require("gears.filesystem") + +local TRANSLATE_CMD = [[bash -c 'curl -s -u "apikey:%s" -H "Content-Type: application/json"]] + ..[[ -d '\''{"text": ["%s"], "model_id":"%s"}'\'' "%s/v3/translate?version=2018-05-01"']] +local ICON = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/translate-widget/gnome-translate.svg' + +--- Returns two values - string to translate and direction: +-- 'dog enfr' -> 'dog', 'en-fr' +-- @param input_string user's input which consists of +-- text to translate and direction, 'dog enfr' +local function extract(input_string) + local word, lang = input_string:match('^(.+)%s(%a%a%a%a)$') + + if word ~= nil and lang ~= nil then + lang = lang:sub(1, 2) .. '-' .. lang:sub(3) + end + return word, lang +end + +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Translate Shell', + text = message} +end + +local w = awful.popup { + widget = {}, + visible = false, + border_width = 1, + maximum_width = 400, + width = 400, + border_color = '#66ccff', + ontop = true, + bg = beautiful.bg_normal, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 3) + end, +} +awful.placement.top(w, { margins = {top = 40}}) + + +--- Main function - takes the user input and shows the widget with translation +-- @param request_string - user input (dog enfr) +local function translate(to_translate, lang, api_key, url) + + local cmd = string.format(TRANSLATE_CMD, api_key, to_translate, lang, url) + spawn.easy_async(cmd, function (stdout, stderr) + if stderr ~= '' then + show_warning(stderr) + end + + local resp = json.decode(stdout) + + w:setup { + { + { + { + { + image = ICON, + widget = wibox.widget.imagebox, + resize = false + }, + valign = 'center', + layout = wibox.container.place, + }, + { + { + id = 'src', + markup = '' .. lang:sub(1,2) .. ': ' + .. to_translate .. '', + widget = wibox.widget.textbox + }, + { + id = 'res', + markup = '' .. lang:sub(4) .. ': ' + .. resp.translations[1].translation .. '', + widget = wibox.widget.textbox + }, + id = 'text', + layout = wibox.layout.fixed.vertical, + }, + id = 'left', + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + bg = beautiful.bg_normal, + forced_width = 400, + widget = wibox.container.background + }, + color = beautiful.bg_normal, + margins = 8, + widget = wibox.container.margin + } + + w.visible = true + w:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell("echo '" .. resp.translations[1].translation .. "' | xclip -selection clipboard") + w.visible = false + end), + awful.button({}, 3, function() + spawn.with_shell("echo '" .. to_translate .."' | xclip -selection clipboard") + w.visible = false + end) + ) + ) + + capi.keygrabber.run(function(_, key, event) + if event == "release" then return end + if key then + capi.keygrabber.stop() + w.visible = false + end + end) + end) +end + +local prompt = awful.widget.prompt() +local input_widget = wibox { + visible = false, + width = 300, + height = 100, + maxmimum_width = 300, + maxmimum_height = 900, + ontop = true, + screen = mouse.screen, + expand = true, + bg = beautiful.bg_normal, + max_widget_size = 500, + border_width = 1, + border_color = '#66ccff', + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 3) + end, +} + +input_widget:setup{ + { + prompt, + bg = beautiful.bg_normal, + widget = wibox.container.background + }, + margins = 8, + widget = wibox.container.margin +} + +local function launch(user_args) + + local args = user_args or {} + + local api_key = args.api_key + local url = args.url + + awful.placement.top(input_widget, { margins = {top = 40}, parent = awful.screen.focused()}) + input_widget.visible = true + + awful.prompt.run { + prompt = "Translate: ", + textbox = prompt.widget, + history_path = gfs.get_dir('cache') .. '/translate_history', + bg_cursor = '#66ccff', + exe_callback = function(text) + if not text or #text == 0 then return end + local to_translate, lang = extract(text) + if not to_translate or #to_translate==0 or not lang or #lang == 0 then + naughty.notify({ + preset = naughty.config.presets.critical, + title = 'Translate Widget Error', + text = 'Language is not provided', + }) + return + end + translate(to_translate, lang, api_key, url) + end, + done_callback = function() + input_widget.visible = false + end + } +end + +return { + launch = launch +} diff --git a/dot_config/awesome/awesome-wm-widgets/volume-widget/README.md b/dot_config/awesome/awesome-wm-widgets/volume-widget/README.md new file mode 100644 index 0000000..4fc7f55 --- /dev/null +++ b/dot_config/awesome/awesome-wm-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/dot_config/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-high-symbolic.svg b/dot_config/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-high-symbolic.svg new file mode 100644 index 0000000..985c107 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-high-symbolic.svg @@ -0,0 +1,88 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-low-symbolic.svg b/dot_config/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-low-symbolic.svg new file mode 100644 index 0000000..7eb4531 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-low-symbolic.svg @@ -0,0 +1,88 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-medium-symbolic.svg b/dot_config/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-medium-symbolic.svg new file mode 100644 index 0000000..11e44fe --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-medium-symbolic.svg @@ -0,0 +1,88 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-muted-symbolic.svg b/dot_config/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-muted-symbolic.svg new file mode 100644 index 0000000..e577d05 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-muted-symbolic.svg @@ -0,0 +1,88 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/dot_config/awesome/awesome-wm-widgets/volume-widget/screenshots/variations.png b/dot_config/awesome/awesome-wm-widgets/volume-widget/screenshots/variations.png new file mode 100644 index 0000000..21d7ead Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/volume-widget/screenshots/variations.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/volume-widget/screenshots/volume-sink-sources.png b/dot_config/awesome/awesome-wm-widgets/volume-widget/screenshots/volume-sink-sources.png new file mode 100644 index 0000000..7d010bc Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/volume-widget/screenshots/volume-sink-sources.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/volume-widget/utils.lua b/dot_config/awesome/awesome-wm-widgets/volume-widget/utils.lua new file mode 100644 index 0000000..417a666 --- /dev/null +++ b/dot_config/awesome/awesome-wm-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/dot_config/awesome/awesome-wm-widgets/volume-widget/volume-2.svg b/dot_config/awesome/awesome-wm-widgets/volume-widget/volume-2.svg new file mode 100644 index 0000000..10f1c67 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/volume-widget/volume-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/volume-widget/volume.lua b/dot_config/awesome/awesome-wm-widgets/volume-widget/volume.lua new file mode 100644 index 0000000..4c44042 --- /dev/null +++ b/dot_config/awesome/awesome-wm-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/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.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("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 = {} +} + +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/dot_config/awesome/awesome-wm-widgets/volume-widget/widgets/arc-widget.lua b/dot_config/awesome/awesome-wm-widgets/volume-widget/widgets/arc-widget.lua new file mode 100644 index 0000000..b512f12 --- /dev/null +++ b/dot_config/awesome/awesome-wm-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/awesome-wm-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 \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/volume-widget/widgets/horizontal-bar-widget.lua b/dot_config/awesome/awesome-wm-widgets/volume-widget/widgets/horizontal-bar-widget.lua new file mode 100644 index 0000000..be1f38d --- /dev/null +++ b/dot_config/awesome/awesome-wm-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/awesome-wm-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/dot_config/awesome/awesome-wm-widgets/volume-widget/widgets/icon-and-text-widget.lua b/dot_config/awesome/awesome-wm-widgets/volume-widget/widgets/icon-and-text-widget.lua new file mode 100644 index 0000000..b1a2793 --- /dev/null +++ b/dot_config/awesome/awesome-wm-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/awesome-wm-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 \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/volume-widget/widgets/icon-widget.lua b/dot_config/awesome/awesome-wm-widgets/volume-widget/widgets/icon-widget.lua new file mode 100644 index 0000000..cc39a3d --- /dev/null +++ b/dot_config/awesome/awesome-wm-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/awesome-wm-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 \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/volume-widget/widgets/vertical-bar-widget.lua b/dot_config/awesome/awesome-wm-widgets/volume-widget/widgets/vertical-bar-widget.lua new file mode 100644 index 0000000..6f32b50 --- /dev/null +++ b/dot_config/awesome/awesome-wm-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/awesome-wm-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 diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/README.md b/dot_config/awesome/awesome-wm-widgets/weather-widget/README.md new file mode 100644 index 0000000..3bf6228 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/README.md @@ -0,0 +1,144 @@ +# Weather widget + +

+ GitHub issues by-label + + + Twitter URL + +

+ +The widget showing current, hourly and daily weather forecast: + +

+ screenshot +

+ +The widget consists of three sections: + - current weather, including humidity, wind speed, UV index + - hourly forecast for the next 24 hours + - daily forecast for the next five days + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| coordinates | Required | Table with two elements: latitude and longitude, e.g. `{46.204400, 6.143200}` | +| api_key | Required | Get it [here](https://openweathermap.org/appid) | +| font_name | `beautiful.font:gsub("%s%d+$", "")` | **Name** of the font to use e.g. 'Play' | +| both_units_widget | false | Show temperature in both units - '28°C (83°F) | +| units | metric | `metric` for celsius, `imperial` for fahrenheit | +| show_hourly_forecast | false | Show hourly forecase section | +| time_format_12h |false | 12 or 24 hour format (13:00 - default or 1pm) | +| show_daily_forecast | false | Show daily forecast section | +| icon_pack_name | weather-underground-icons | Name of the icon pack, could be `weather-underground-icon` or `VitalyGorbachev` or create your own, more details below | +| icons_extension | `.png` | File extension of icons in the pack | +| timeout | 120 | How often in seconds the widget refreshes | + +### Icons: + +The widget comes with two predefined icon packs: + + - weather-underground-icons taken from [here](https://github.com/manifestinteractive/weather-underground-icons) + - VitalyGorbachev taken from [here](https://www.flaticon.com/authors/vitaly-gorbachev) + +To add your custom icons, create a folder with the pack name under `/icons` and use the folder name in widget's config. There should be 18 icons, preferably 128x128 minimum. Icons should also respect the naming convention, please check widget's source. + +### Examples: + +#### Custom font, icons + +![example1](./example1.png) + +```lua +weather_curl_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, + time_format_12h = true, + units = 'imperial', + both_units_widget = true, + font_name = 'Carter One', + icons = 'VitalyGorbachev', + icons_extension = '.svg', + show_hourly_forecast = true, + show_daily_forecast = true, +}), +``` + +#### Only current weather + +![example2](./example2.png) + +```lua +weather_curl_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, +}), +``` + +## Installation + +1. Download json parser for lua from [github.com/rxi/json.lua](https://github.com/rxi/json.lua) and place it under **~/.config/awesome/** (don't forget to star a repo ): + + ```bash + wget -P ~/.config/awesome/ https://raw.githubusercontent.com/rxi/json.lua/master/json.lua + ``` + +1. Clone this repo under **~/.config/awesome/**: + + ```bash + git clone https://github.com/streetturtle/awesome-wm-widgets.git ~/.config/awesome/ + ``` + +1. Get Open Weather Map app id here: [openweathermap.org/appid](https://openweathermap.org/appid). + +1. Require weather widget at the beginning of **rc.lua**: + + ```lua + local weather_widget = require("awesome-wm-widgets.weather-widget.weather") + ``` + +1. Add widget to the tasklist: + + ```lua + s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + --default + weather_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, + }), + , + --customized + weather_curl_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, + time_format_12h = true, + units = 'imperial', + both_units_widget = true, + font_name = 'Carter One', + icons = 'VitalyGorbachev', + icons_extension = '.svg', + show_hourly_forecast = true, + show_daily_forecast = true, + }), + ... + ``` + +## More screenshots + +Only negative temperature: + +![negative](./negative.png) + +Both positive and negative tempertature: + +![both](./both.png) + +## How it works + +TBW diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/both.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/both.png new file mode 100644 index 0000000..0947a37 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/both.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/example1.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/example1.png new file mode 100644 index 0000000..7074faa Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/example1.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/example2.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/example2.png new file mode 100644 index 0000000..857274b Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/example2.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/example_response.json b/dot_config/awesome/awesome-wm-widgets/weather-widget/example_response.json new file mode 100644 index 0000000..2b90a6e --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/example_response.json @@ -0,0 +1,1419 @@ +{ + "lat": 45.5, + "lon": -73.57, + "timezone": "America/Toronto", + "timezone_offset": -14400, + "current": { + "dt": 1603155313, + "sunrise": 1603106181, + "sunset": 1603144896, + "temp": 8.91, + "feels_like": 7.97, + "pressure": 1025, + "humidity": 100, + "dew_point": 8.91, + "uvi": 2.37, + "clouds": 90, + "visibility": 4828, + "wind_speed": 1, + "wind_deg": 40, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + }, + { + "id": 701, + "main": "Mist", + "description": "mist", + "icon": "50n" + } + ], + "rain": { + "1h": 0.65 + } + }, + "hourly": [ + { + "dt": 1603152000, + "temp": -8.91, + "feels_like": 7.95, + "pressure": 1025, + "humidity": 100, + "dew_point": 8.91, + "clouds": 90, + "visibility": 10000, + "wind_speed": 1.03, + "wind_deg": 32, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 0.4 + } + }, + { + "dt": 1603155600, + "temp": -9.16, + "feels_like": 7.7, + "pressure": 1025, + "humidity": 91, + "dew_point": 7.77, + "clouds": 95, + "visibility": 10000, + "wind_speed": 1.34, + "wind_deg": 67, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 0.29, + "rain": { + "1h": 0.87 + } + }, + { + "dt": 1603159200, + "temp": -9.24, + "feels_like": 7.7, + "pressure": 1024, + "humidity": 88, + "dew_point": 7.36, + "clouds": 98, + "visibility": 10000, + "wind_speed": 1.32, + "wind_deg": 48, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 0.59, + "rain": { + "1h": 0.42 + } + }, + { + "dt": 1603162800, + "temp": -9.18, + "feels_like": 6.91, + "pressure": 1023, + "humidity": 86, + "dew_point": 6.96, + "clouds": 99, + "visibility": 10000, + "wind_speed": 2.23, + "wind_deg": 42, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0.86 + }, + { + "dt": 1603166400, + "temp": -9.09, + "feels_like": 6.46, + "pressure": 1023, + "humidity": 88, + "dew_point": 7.21, + "clouds": 100, + "visibility": 10000, + "wind_speed": 2.83, + "wind_deg": 46, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 0.94, + "rain": { + "1h": 0.6 + } + }, + { + "dt": 1603170000, + "temp": -8.96, + "feels_like": 6.43, + "pressure": 1022, + "humidity": 91, + "dew_point": 7.62, + "clouds": 100, + "visibility": 5405, + "wind_speed": 2.81, + "wind_deg": 18, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 1.89 + } + }, + { + "dt": 1603173600, + "temp": -8.84, + "feels_like": 6.29, + "pressure": 1021, + "humidity": 91, + "dew_point": 7.6, + "clouds": 100, + "visibility": 7599, + "wind_speed": 2.8, + "wind_deg": 35, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 2.07 + } + }, + { + "dt": 1603177200, + "temp": -8.92, + "feels_like": 6.34, + "pressure": 1021, + "humidity": 92, + "dew_point": 7.78, + "clouds": 100, + "visibility": 8594, + "wind_speed": 2.91, + "wind_deg": 44, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 1.19 + } + }, + { + "dt": 1603180800, + "temp": -9.08, + "feels_like": 7.18, + "pressure": 1020, + "humidity": 93, + "dew_point": 8.06, + "clouds": 100, + "visibility": 9347, + "wind_speed": 2.06, + "wind_deg": 37, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 1.02 + } + }, + { + "dt": 1603184400, + "temp": -8.98, + "feels_like": 6.28, + "pressure": 1019, + "humidity": 93, + "dew_point": 8, + "clouds": 100, + "visibility": 6164, + "wind_speed": 3.16, + "wind_deg": 354, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 1.89 + } + }, + { + "dt": 1603188000, + "temp": -8.69, + "feels_like": 5.78, + "pressure": 1019, + "humidity": 92, + "dew_point": 7.58, + "clouds": 100, + "visibility": 5143, + "wind_speed": 3.31, + "wind_deg": 29, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 2.6 + } + }, + { + "dt": 1603191600, + "temp": -8.6, + "feels_like": 6.08, + "pressure": 1019, + "humidity": 92, + "dew_point": 7.42, + "clouds": 100, + "visibility": 6072, + "wind_speed": 2.73, + "wind_deg": 29, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 2.3 + } + }, + { + "dt": 1603195200, + "temp": -8.56, + "feels_like": 6.68, + "pressure": 1019, + "humidity": 92, + "dew_point": 7.45, + "clouds": 100, + "visibility": 6697, + "wind_speed": 1.8, + "wind_deg": 16, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 2.58 + } + }, + { + "dt": 1603198800, + "temp": -8.74, + "feels_like": 6.88, + "pressure": 1020, + "humidity": 91, + "dew_point": 7.38, + "clouds": 100, + "visibility": 10000, + "wind_speed": 1.77, + "wind_deg": 319, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 1.06 + } + }, + { + "dt": 1603202400, + "temp": -8.93, + "feels_like": 7.32, + "pressure": 1020, + "humidity": 90, + "dew_point": 7.48, + "clouds": 100, + "visibility": 10000, + "wind_speed": 1.42, + "wind_deg": 291, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 0.95 + } + }, + { + "dt": 1603206000, + "temp": -9.07, + "feels_like": 6.75, + "pressure": 1021, + "humidity": 89, + "dew_point": 7.43, + "clouds": 100, + "visibility": 10000, + "wind_speed": 2.43, + "wind_deg": 276, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 0.31 + } + }, + { + "dt": 1603209600, + "temp": -9.31, + "feels_like": 6.58, + "pressure": 1022, + "humidity": 86, + "dew_point": 7.17, + "clouds": 100, + "visibility": 10000, + "wind_speed": 2.93, + "wind_deg": 262, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "pop": 0.8 + }, + { + "dt": 1603213200, + "temp": -10.07, + "feels_like": 6.68, + "pressure": 1023, + "humidity": 80, + "dew_point": 6.78, + "clouds": 100, + "visibility": 10000, + "wind_speed": 3.77, + "wind_deg": 269, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "pop": 0.8 + }, + { + "dt": 1603216800, + "temp": -11.87, + "feels_like": 7.99, + "pressure": 1023, + "humidity": 67, + "dew_point": 6.15, + "clouds": 99, + "visibility": 10000, + "wind_speed": 4.21, + "wind_deg": 265, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "pop": 0.8 + }, + { + "dt": 1603220400, + "temp": -12.05, + "feels_like": 7.95, + "pressure": 1024, + "humidity": 64, + "dew_point": 5.63, + "clouds": 100, + "visibility": 10000, + "wind_speed": 4.38, + "wind_deg": 270, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1603224000, + "temp": -11.74, + "feels_like": 7.54, + "pressure": 1025, + "humidity": 63, + "dew_point": 5.08, + "clouds": 100, + "visibility": 10000, + "wind_speed": 4.38, + "wind_deg": 276, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1603227600, + "temp": -11.09, + "feels_like": 7.13, + "pressure": 1026, + "humidity": 62, + "dew_point": 4.24, + "clouds": 100, + "visibility": 10000, + "wind_speed": 3.79, + "wind_deg": 293, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1603231200, + "temp": -10.13, + "feels_like": 6.51, + "pressure": 1027, + "humidity": 63, + "dew_point": 3.43, + "clouds": 100, + "visibility": 10000, + "wind_speed": 3.13, + "wind_deg": 318, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603234800, + "temp": -9.53, + "feels_like": 7.02, + "pressure": 1028, + "humidity": 63, + "dew_point": 3.02, + "clouds": 100, + "visibility": 10000, + "wind_speed": 1.4, + "wind_deg": 329, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603238400, + "temp": -9.2, + "feels_like": 7.3, + "pressure": 1028, + "humidity": 65, + "dew_point": 3.03, + "clouds": 100, + "visibility": 10000, + "wind_speed": 0.56, + "wind_deg": 52, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603242000, + "temp": -8.73, + "feels_like": 6.57, + "pressure": 1029, + "humidity": 68, + "dew_point": 3.28, + "clouds": 100, + "visibility": 10000, + "wind_speed": 0.98, + "wind_deg": 75, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603245600, + "temp": -8.12, + "feels_like": 5.55, + "pressure": 1029, + "humidity": 71, + "dew_point": 3.27, + "clouds": 89, + "visibility": 10000, + "wind_speed": 1.57, + "wind_deg": 68, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603249200, + "temp": -7.83, + "feels_like": 4.86, + "pressure": 1029, + "humidity": 71, + "dew_point": 3.05, + "clouds": 93, + "visibility": 10000, + "wind_speed": 2.07, + "wind_deg": 68, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603252800, + "temp": -7.49, + "feels_like": 4.21, + "pressure": 1029, + "humidity": 72, + "dew_point": 2.8, + "clouds": 94, + "visibility": 10000, + "wind_speed": 2.48, + "wind_deg": 66, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603256400, + "temp": -6.92, + "feels_like": 3.31, + "pressure": 1029, + "humidity": 73, + "dew_point": 2.47, + "clouds": 96, + "visibility": 10000, + "wind_speed": 2.87, + "wind_deg": 81, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603260000, + "temp": -6.49, + "feels_like": 2.48, + "pressure": 1029, + "humidity": 74, + "dew_point": 2.22, + "clouds": 96, + "visibility": 10000, + "wind_speed": 3.38, + "wind_deg": 78, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603263600, + "temp": -6.3, + "feels_like": 1.81, + "pressure": 1028, + "humidity": 71, + "dew_point": 1.55, + "clouds": 100, + "visibility": 10000, + "wind_speed": 3.89, + "wind_deg": 84, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603267200, + "temp": -6.22, + "feels_like": 1.39, + "pressure": 1027, + "humidity": 69, + "dew_point": 0.99, + "clouds": 98, + "visibility": 10000, + "wind_speed": 4.27, + "wind_deg": 74, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603270800, + "temp": -6.69, + "feels_like": 1.66, + "pressure": 1026, + "humidity": 65, + "dew_point": 0.79, + "clouds": 96, + "visibility": 10000, + "wind_speed": 4.47, + "wind_deg": 69, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0.05 + }, + { + "dt": 1603274400, + "temp": -6.53, + "feels_like": 1.74, + "pressure": 1024, + "humidity": 68, + "dew_point": 1.07, + "clouds": 97, + "visibility": 10000, + "wind_speed": 4.23, + "wind_deg": 65, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0.28 + }, + { + "dt": 1603278000, + "temp": -6.41, + "feels_like": 1.87, + "pressure": 1023, + "humidity": 73, + "dew_point": 1.96, + "clouds": 97, + "visibility": 10000, + "wind_speed": 4.08, + "wind_deg": 73, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 0.35, + "rain": { + "1h": 0.17 + } + }, + { + "dt": 1603281600, + "temp": -6.42, + "feels_like": 2.71, + "pressure": 1022, + "humidity": 79, + "dew_point": 3.21, + "clouds": 98, + "visibility": 9620, + "wind_speed": 3.16, + "wind_deg": 71, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 0.62, + "rain": { + "1h": 0.37 + } + }, + { + "dt": 1603285200, + "temp": -6.67, + "feels_like": 3.38, + "pressure": 1021, + "humidity": 86, + "dew_point": 4.59, + "clouds": 100, + "visibility": 10000, + "wind_speed": 2.95, + "wind_deg": 84, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "pop": 0.9, + "rain": { + "1h": 1.08 + } + }, + { + "dt": 1603288800, + "temp": -8.55, + "feels_like": 5.61, + "pressure": 1019, + "humidity": 87, + "dew_point": 6.64, + "clouds": 100, + "visibility": 10000, + "wind_speed": 3.05, + "wind_deg": 135, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 0.23 + } + }, + { + "dt": 1603292400, + "temp": -10.85, + "feels_like": 8.09, + "pressure": 1018, + "humidity": 95, + "dew_point": 10.13, + "clouds": 100, + "visibility": 10000, + "wind_speed": 4.04, + "wind_deg": 150, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 0.31 + } + }, + { + "dt": 1603296000, + "temp": -13.37, + "feels_like": 10.29, + "pressure": 1017, + "humidity": 90, + "dew_point": 11.93, + "clouds": 100, + "visibility": 10000, + "wind_speed": 5.19, + "wind_deg": 170, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 0.32 + } + }, + { + "dt": 1603299600, + "temp": -13.92, + "feels_like": 11.34, + "pressure": 1015, + "humidity": 94, + "dew_point": 13.07, + "clouds": 100, + "visibility": 6450, + "wind_speed": 5.01, + "wind_deg": 177, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 3.08 + } + }, + { + "dt": 1603303200, + "temp": -14.85, + "feels_like": 12.78, + "pressure": 1014, + "humidity": 95, + "dew_point": 14.1, + "clouds": 100, + "visibility": 10000, + "wind_speed": 4.79, + "wind_deg": 183, + "weather": [ + { + "id": 502, + "main": "Rain", + "description": "heavy intensity rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 4.94 + } + }, + { + "dt": 1603306800, + "temp": -15.94, + "feels_like": 13.56, + "pressure": 1014, + "humidity": 93, + "dew_point": 14.96, + "clouds": 100, + "visibility": 7138, + "wind_speed": 5.61, + "wind_deg": 207, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 2.71 + } + }, + { + "dt": 1603310400, + "temp": -16.72, + "feels_like": 14.6, + "pressure": 1014, + "humidity": 93, + "dew_point": 15.66, + "clouds": 100, + "visibility": 10000, + "wind_speed": 5.64, + "wind_deg": 208, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 1.33 + } + }, + { + "dt": 1603314000, + "temp": -16.74, + "feels_like": 14.61, + "pressure": 1014, + "humidity": 94, + "dew_point": 15.81, + "clouds": 100, + "visibility": 10000, + "wind_speed": 5.75, + "wind_deg": 216, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 0.89 + } + }, + { + "dt": 1603317600, + "temp": -17.05, + "feels_like": 14.53, + "pressure": 1015, + "humidity": 92, + "dew_point": 15.83, + "clouds": 100, + "visibility": 10000, + "wind_speed": 6.3, + "wind_deg": 234, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 0.51 + } + }, + { + "dt": 1603321200, + "temp": -16.74, + "feels_like": 14.18, + "pressure": 1016, + "humidity": 87, + "dew_point": 14.65, + "clouds": 100, + "visibility": 10000, + "wind_speed": 5.74, + "wind_deg": 257, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 0.22 + } + } + ], + "daily": [ + { + "dt": 1603123200, + "sunrise": 1603106181, + "sunset": 1603144896, + "temp": { + "day": 12, + "min": 8.91, + "max": 12.73, + "night": 9.05, + "eve": 9.72, + "morn": 12.73 + }, + "feels_like": { + "day": 9.92, + "night": 7.02, + "eve": 7.88, + "morn": 8.02 + }, + "pressure": 1025, + "humidity": 78, + "dew_point": 8.34, + "wind_speed": 2.41, + "wind_deg": 242, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "clouds": 100, + "pop": 1, + "rain": 8.77, + "uvi": 2.37 + }, + { + "dt": 1603209600, + "sunrise": 1603192663, + "sunset": 1603231195, + "temp": { + "day": 9.07, + "min": 7.83, + "max": 11.87, + "night": 7.83, + "eve": 11.09, + "morn": 8.98 + }, + "feels_like": { + "day": 6.75, + "night": 4.86, + "eve": 7.13, + "morn": 6.28 + }, + "pressure": 1021, + "humidity": 89, + "dew_point": 7.43, + "wind_speed": 2.43, + "wind_deg": 276, + "weather": [ + { + "id": 502, + "main": "Rain", + "description": "heavy intensity rain", + "icon": "10d" + } + ], + "clouds": 100, + "pop": 1, + "rain": 18.46, + "uvi": 2.3 + }, + { + "dt": 1603296000, + "sunrise": 1603279145, + "sunset": 1603317495, + "temp": { + "day": 10.85, + "min": 6.42, + "max": 16.74, + "night": 12.25, + "eve": 16.74, + "morn": 6.69 + }, + "feels_like": { + "day": 8.09, + "night": 8.59, + "eve": 14.61, + "morn": 1.66 + }, + "pressure": 1018, + "humidity": 95, + "dew_point": 10.13, + "wind_speed": 4.04, + "wind_deg": 150, + "weather": [ + { + "id": 502, + "main": "Rain", + "description": "heavy intensity rain", + "icon": "10d" + } + ], + "clouds": 100, + "pop": 1, + "rain": 16.19, + "uvi": 2.32 + }, + { + "dt": 1603382400, + "sunrise": 1603365627, + "sunset": 1603403795, + "temp": { + "day": 9.95, + "min": 7.29, + "max": 11.27, + "night": 7.29, + "eve": 11.01, + "morn": 9.44 + }, + "feels_like": { + "day": 5.45, + "night": 2.63, + "eve": 8.07, + "morn": 5.65 + }, + "pressure": 1027, + "humidity": 57, + "dew_point": 2.03, + "wind_speed": 4, + "wind_deg": 283, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "scattered clouds", + "icon": "03d" + } + ], + "clouds": 27, + "pop": 0, + "uvi": 2.36 + }, + { + "dt": 1603468800, + "sunrise": 1603452109, + "sunset": 1603490097, + "temp": { + "day": 12.02, + "min": 6.62, + "max": 17.04, + "night": 15.91, + "eve": 17.04, + "morn": 7.09 + }, + "feels_like": { + "day": 8.48, + "night": 11.82, + "eve": 12.58, + "morn": 3.07 + }, + "pressure": 1022, + "humidity": 72, + "dew_point": 7.29, + "wind_speed": 4.1, + "wind_deg": 147, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "clouds": 99, + "pop": 0.29, + "rain": 0.22, + "uvi": 2.2 + }, + { + "dt": 1603555200, + "sunrise": 1603538592, + "sunset": 1603576400, + "temp": { + "day": 8.39, + "min": 6.83, + "max": 15.86, + "night": 6.83, + "eve": 9.56, + "morn": 12.99 + }, + "feels_like": { + "day": 3.79, + "night": 3.04, + "eve": 6.58, + "morn": 10.39 + }, + "pressure": 1022, + "humidity": 58, + "dew_point": 0.71, + "wind_speed": 3.87, + "wind_deg": 10, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "clouds": 72, + "pop": 0.9, + "rain": 2.54, + "uvi": 2.07 + }, + { + "dt": 1603641600, + "sunrise": 1603625075, + "sunset": 1603662705, + "temp": { + "day": 5.33, + "min": 3.23, + "max": 7.24, + "night": 4.97, + "eve": 6.59, + "morn": 3.97 + }, + "feels_like": { + "day": 1.26, + "night": 0.02, + "eve": 2.58, + "morn": -0.34 + }, + "pressure": 1025, + "humidity": 61, + "dew_point": -5.56, + "wind_speed": 2.67, + "wind_deg": 37, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04d" + } + ], + "clouds": 74, + "pop": 0.08, + "uvi": 2.25 + }, + { + "dt": 1603728000, + "sunrise": 1603711558, + "sunset": 1603749010, + "temp": { + "day": 3.7, + "min": 2.09, + "max": 3.88, + "night": 3.54, + "eve": 3.54, + "morn": 2.09 + }, + "feels_like": { + "day": -0.28, + "night": -0.76, + "eve": -0.86, + "morn": -2.81 + }, + "pressure": 1021, + "humidity": 90, + "dew_point": 2.33, + "wind_speed": 3.35, + "wind_deg": 32, + "weather": [ + { + "id": 502, + "main": "Rain", + "description": "heavy intensity rain", + "icon": "10d" + } + ], + "clouds": 100, + "pop": 1, + "rain": 12.43 + } + ] +} \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/broken-clouds-night.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/broken-clouds-night.svg new file mode 100644 index 0000000..8b7fc48 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/broken-clouds-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/broken-clouds.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/broken-clouds.svg new file mode 100644 index 0000000..d42ea59 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/broken-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/clear-sky-night.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/clear-sky-night.svg new file mode 100644 index 0000000..44f096c --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/clear-sky-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/clear-sky.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/clear-sky.svg new file mode 100644 index 0000000..dc82163 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/clear-sky.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/few-clouds-night.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/few-clouds-night.svg new file mode 100644 index 0000000..8b7fc48 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/few-clouds-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/few-clouds.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/few-clouds.svg new file mode 100644 index 0000000..d42ea59 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/few-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/mist-night.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/mist-night.svg new file mode 100644 index 0000000..960b07d --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/mist-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/mist.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/mist.svg new file mode 100644 index 0000000..770f8d7 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/mist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/rain-night.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/rain-night.svg new file mode 100644 index 0000000..11ecf00 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/rain-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/rain.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/rain.svg new file mode 100644 index 0000000..11ecf00 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/scattered-clouds-night.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/scattered-clouds-night.svg new file mode 100644 index 0000000..8b7fc48 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/scattered-clouds-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/scattered-clouds.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/scattered-clouds.svg new file mode 100644 index 0000000..d42ea59 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/scattered-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/shower-rain-night.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/shower-rain-night.svg new file mode 100644 index 0000000..4d1897c --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/shower-rain-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/shower-rain.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/shower-rain.svg new file mode 100644 index 0000000..4d1897c --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/shower-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/snow-night.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/snow-night.svg new file mode 100644 index 0000000..bee891e --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/snow-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/snow.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/snow.svg new file mode 100644 index 0000000..e2ea140 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/thunderstorm-night.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/thunderstorm-night.svg new file mode 100644 index 0000000..1813197 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/thunderstorm-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/thunderstorm.svg b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/thunderstorm.svg new file mode 100644 index 0000000..44a733c --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/thunderstorm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/broken-clouds-night.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/broken-clouds-night.png new file mode 100644 index 0000000..061d1cd Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/broken-clouds-night.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/clear-sky-night.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/clear-sky-night.png new file mode 100644 index 0000000..cc40d0f Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/clear-sky-night.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_broken-clouds.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_broken-clouds.png new file mode 100644 index 0000000..5967d92 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_broken-clouds.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_clear-sky.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_clear-sky.png new file mode 100644 index 0000000..acf8e5c Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_clear-sky.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_few-clouds.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_few-clouds.png new file mode 100644 index 0000000..7580fc5 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_few-clouds.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_mist-night.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_mist-night.png new file mode 100644 index 0000000..102142a Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_mist-night.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_mist.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_mist.png new file mode 100644 index 0000000..102142a Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_mist.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_rain-night.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_rain-night.png new file mode 100644 index 0000000..49f0903 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_rain-night.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_rain.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_rain.png new file mode 100644 index 0000000..49f0903 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_rain.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_scattered-clouds-night.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_scattered-clouds-night.png new file mode 100644 index 0000000..63cb1b2 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_scattered-clouds-night.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_scattered-clouds.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_scattered-clouds.png new file mode 100644 index 0000000..63cb1b2 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_scattered-clouds.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_shower-rain-night.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_shower-rain-night.png new file mode 100644 index 0000000..49f0903 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_shower-rain-night.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_shower-rain.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_shower-rain.png new file mode 100644 index 0000000..49f0903 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_shower-rain.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_snow-night.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_snow-night.png new file mode 100644 index 0000000..0a7f006 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_snow-night.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_snow.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_snow.png new file mode 100644 index 0000000..0a7f006 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_snow.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_thunderstorm-night.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_thunderstorm-night.png new file mode 100644 index 0000000..2102104 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_thunderstorm-night.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_thunderstorm.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_thunderstorm.png new file mode 100644 index 0000000..2102104 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/executable_thunderstorm.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/few-clouds-night.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/few-clouds-night.png new file mode 100644 index 0000000..9c34fab Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/few-clouds-night.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/locale/de.lua b/dot_config/awesome/awesome-wm-widgets/weather-widget/locale/de.lua new file mode 100644 index 0000000..2a9236a --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/locale/de.lua @@ -0,0 +1,13 @@ +local de = { + warning_title = "Wetter Widget", + parameter_warning = "Folgende benötigte Parameter fehlen: ", + directions = { + "N", "NNO", "NO", "ONO", "O", "OSO", "SO", "SSO", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N" + }, + feels_like = "Gefühlt: ", + wind = "Wind: ", + humidity = "Luftfeuchtigkeit: ", + uv = "UV-Index: " +} + +return de diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/locale/en.lua b/dot_config/awesome/awesome-wm-widgets/weather-widget/locale/en.lua new file mode 100644 index 0000000..377dc6f --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/locale/en.lua @@ -0,0 +1,14 @@ +local en = { + warning_title = "Weather Widget", + parameter_warning = "Required parameters are not set: ", + directions = { + "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", + "WSW", "W", "WNW", "NW", "NNW", "N" + }, + feels_like = "Feels like ", + wind = "Wind: ", + humidity = "Humidity: ", + uv = "UV: " +} + +return en diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/locale/fr.lua b/dot_config/awesome/awesome-wm-widgets/weather-widget/locale/fr.lua new file mode 100644 index 0000000..de50814 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/locale/fr.lua @@ -0,0 +1,14 @@ +local fr = { + warning_title = "Widget Météo", + parameter_warning = "Les paramètres suivants sont obligatoires : ", + directions = { + "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSO", "SO", + "OSO", "O", "ONO", "NO", "NNO", "N" + }, + feels_like = "ressentie à ", + wind = "Vent : ", + humidity = "Humidité : ", + uv = "Indice UV : " +} + +return fr diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/locale/pt.lua b/dot_config/awesome/awesome-wm-widgets/weather-widget/locale/pt.lua new file mode 100644 index 0000000..e7f4012 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/locale/pt.lua @@ -0,0 +1,14 @@ +local pt = { + warning_title = "Widget do tempo", + parameter_warning = "Parâmetros necessários não definidos: ", + directions = { + "N", "NNE", "NE", "ENE", "L", "ESE", "SE", "SSE", "S", "SSO", "SO", + "OSO", "O", "ONO", "NO", "NNO", "N" + }, + feels_like = "Sensação de ", + wind = "Vento: ", + humidity = "Umidade: ", + uv = "UV: " +} + +return pt diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/negative.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/negative.png new file mode 100644 index 0000000..afcf567 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/negative.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/weather-widget.png b/dot_config/awesome/awesome-wm-widgets/weather-widget/weather-widget.png new file mode 100644 index 0000000..c7fc37e Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/weather-widget/weather-widget.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/weather-widget/weather.lua b/dot_config/awesome/awesome-wm-widgets/weather-widget/weather.lua new file mode 100644 index 0000000..3ec1c3f --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/weather-widget/weather.lua @@ -0,0 +1,575 @@ +------------------------------------------------- +-- Weather Widget based on the OpenWeatherMap +-- https://openweathermap.org/ +-- +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- +local awful = require("awful") +local watch = require("awful.widget.watch") +local json = require("json") +local naughty = require("naughty") +local wibox = require("wibox") +local gears = require("gears") +local beautiful = require("beautiful") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/weather-widget' +local GET_FORECAST_CMD = [[bash -c "curl -s --show-error -X GET '%s'"]] + +local SYS_LANG = os.getenv("LANG"):sub(1, 2) +if SYS_LANG == "C" or SYS_LANG == "C." then + -- C-locale is a common fallback for simple English + SYS_LANG = "en" +end +-- default language is ENglish +local LANG = gears.filesystem.file_readable(WIDGET_DIR .. "/" .. "locale/" .. + SYS_LANG .. ".lua") and SYS_LANG or "en" +local LCLE = require("awesome-wm-widgets.weather-widget.locale." .. LANG) + + +local function show_warning(message) + naughty.notify { + preset = naughty.config.presets.critical, + title = LCLE.warning_title, + text = message + } +end + +if SYS_LANG ~= LANG then + show_warning("Your language is not supported yet. Language set to English") +end + +local weather_widget = {} +local warning_shown = false +local tooltip = awful.tooltip { + mode = 'outside', + preferred_positions = {'bottom'} +} + +local weather_popup = awful.popup { + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + maximum_width = 400, + offset = {y = 5}, + hide_on_right_click = true, + widget = {} +} + +--- Maps openWeatherMap icon name to file name w/o extension +local icon_map = { + ["01d"] = "clear-sky", + ["02d"] = "few-clouds", + ["03d"] = "scattered-clouds", + ["04d"] = "broken-clouds", + ["09d"] = "shower-rain", + ["10d"] = "rain", + ["11d"] = "thunderstorm", + ["13d"] = "snow", + ["50d"] = "mist", + ["01n"] = "clear-sky-night", + ["02n"] = "few-clouds-night", + ["03n"] = "scattered-clouds-night", + ["04n"] = "broken-clouds-night", + ["09n"] = "shower-rain-night", + ["10n"] = "rain-night", + ["11n"] = "thunderstorm-night", + ["13n"] = "snow-night", + ["50n"] = "mist-night" +} + +--- Return wind direction as a string +local function to_direction(degrees) + -- Ref: https://www.campbellsci.eu/blog/convert-wind-directions + if degrees == nil then return "Unknown dir" end + local directions = LCLE.directions + return directions[math.floor((degrees % 360) / 22.5) + 1] +end + +--- Convert degrees Celsius to Fahrenheit +local function celsius_to_fahrenheit(c) return c * 9 / 5 + 32 end + +-- Convert degrees Fahrenheit to Celsius +local function fahrenheit_to_celsius(f) return (f - 32) * 5 / 9 end + +local function gen_temperature_str(temp, fmt_str, show_other_units, units) + local temp_str = string.format(fmt_str, temp) + local s = temp_str .. '°' .. (units == 'metric' and 'C' or 'F') + + if (show_other_units) then + local temp_conv, units_conv + if (units == 'metric') then + temp_conv = celsius_to_fahrenheit(temp) + units_conv = 'F' + else + temp_conv = fahrenheit_to_celsius(temp) + units_conv = 'C' + end + + local temp_conv_str = string.format(fmt_str, temp_conv) + s = s .. ' ' .. '(' .. temp_conv_str .. '°' .. units_conv .. ')' + end + return s +end + +local function uvi_index_color(uvi) + local color + if uvi >= 0 and uvi < 3 then color = '#A3BE8C' + elseif uvi >= 3 and uvi < 6 then color = '#EBCB8B' + elseif uvi >= 6 and uvi < 8 then color = '#D08770' + elseif uvi >= 8 and uvi < 11 then color = '#BF616A' + elseif uvi >= 11 then color = '#B48EAD' + end + + return '' .. uvi .. '' +end + +local function worker(user_args) + + local args = user_args or {} + + --- Validate required parameters + if args.coordinates == nil or args.api_key == nil then + show_warning(LCLE.parameter_warning .. + (args.coordinates == nil and 'coordinates' or '') .. + (args.api_key == nil and ', api_key ' or '')) + return + end + + local coordinates = args.coordinates + local api_key = args.api_key + local font_name = args.font_name or beautiful.font:gsub("%s%d+$", "") + local units = args.units or 'metric' + local time_format_12h = args.time_format_12h + local both_units_widget = args.both_units_widget or false + local show_hourly_forecast = args.show_hourly_forecast + local show_daily_forecast = args.show_daily_forecast + local icon_pack_name = args.icons or 'weather-underground-icons' + local icons_extension = args.icons_extension or '.png' + local timeout = args.timeout or 120 + + local ICONS_DIR = WIDGET_DIR .. '/icons/' .. icon_pack_name .. '/' + local owm_one_cal_api = + ('https://api.openweathermap.org/data/2.5/onecall' .. + '?lat=' .. coordinates[1] .. '&lon=' .. coordinates[2] .. '&appid=' .. api_key .. + '&units=' .. units .. '&exclude=minutely' .. + (show_hourly_forecast == false and ',hourly' or '') .. + (show_daily_forecast == false and ',daily' or '') .. + '&lang=' .. LANG) + + weather_widget = wibox.widget { + { + { + { + { + id = 'icon', + resize = true, + widget = wibox.widget.imagebox + }, + valign = 'center', + widget = wibox.container.place, + }, + { + id = 'txt', + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal, + }, + left = 4, + right = 4, + layout = wibox.container.margin + }, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + widget = wibox.container.background, + set_image = function(self, path) + self:get_children_by_id('icon')[1].image = path + end, + set_text = function(self, text) + self:get_children_by_id('txt')[1].text = text + end, + is_ok = function(self, is_ok) + if is_ok then + self:get_children_by_id('icon')[1]:set_opacity(1) + self:get_children_by_id('icon')[1]:emit_signal('widget:redraw_needed') + else + self:get_children_by_id('icon')[1]:set_opacity(0.2) + self:get_children_by_id('icon')[1]:emit_signal('widget:redraw_needed') + end + end + } + + local current_weather_widget = wibox.widget { + { + { + { + id = 'icon', + resize = true, + forced_width = 128, + forced_height = 128, + widget = wibox.widget.imagebox + }, + align = 'center', + widget = wibox.container.place + }, + { + id = 'description', + font = font_name .. ' 10', + align = 'center', + widget = wibox.widget.textbox + }, + forced_width = 128, + layout = wibox.layout.align.vertical + }, + { + { + { + id = 'temp', + font = font_name .. ' 36', + widget = wibox.widget.textbox + }, + { + id = 'feels_like_temp', + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.vertical + }, + { + { + id = 'wind', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + { + id = 'humidity', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + { + id = 'uv', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + expand = 'inside', + layout = wibox.layout.align.vertical + }, + spacing = 16, + forced_width = 150, + layout = wibox.layout.fixed.vertical + }, + forced_width = 300, + layout = wibox.layout.flex.horizontal, + update = function(self, weather) + self:get_children_by_id('icon')[1]:set_image( + ICONS_DIR .. icon_map[weather.weather[1].icon] .. icons_extension) + self:get_children_by_id('temp')[1]:set_text(gen_temperature_str(weather.temp, '%.0f', false, units)) + self:get_children_by_id('feels_like_temp')[1]:set_text( + LCLE.feels_like .. gen_temperature_str(weather.feels_like, '%.0f', false, units)) + self:get_children_by_id('description')[1]:set_text(weather.weather[1].description) + self:get_children_by_id('wind')[1]:set_markup( + LCLE.wind .. '' .. weather.wind_speed .. 'm/s (' .. to_direction(weather.wind_deg) .. ')') + self:get_children_by_id('humidity')[1]:set_markup(LCLE.humidity .. '' .. weather.humidity .. '%') + self:get_children_by_id('uv')[1]:set_markup(LCLE.uv .. uvi_index_color(weather.uvi)) + end + } + + + local daily_forecast_widget = { + forced_width = 300, + layout = wibox.layout.flex.horizontal, + update = function(self, forecast, timezone_offset) + local count = #self + for i = 0, count do self[i]=nil end + for i, day in ipairs(forecast) do + if i > 5 then break end + local day_forecast = wibox.widget { + { + text = os.date('%a', tonumber(day.dt) + tonumber(timezone_offset)), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + { + { + { + image = ICONS_DIR .. icon_map[day.weather[1].icon] .. icons_extension, + resize = true, + forced_width = 48, + forced_height = 48, + widget = wibox.widget.imagebox + }, + align = 'center', + layout = wibox.container.place + }, + { + text = day.weather[1].description, + font = font_name .. ' 8', + align = 'center', + forced_height = 50, + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.vertical + }, + { + { + text = gen_temperature_str(day.temp.day, '%.0f', false, units), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + { + text = gen_temperature_str(day.temp.night, '%.0f', false, units), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.vertical + }, + spacing = 8, + layout = wibox.layout.fixed.vertical + } + table.insert(self, day_forecast) + end + end + } + + local hourly_forecast_graph = wibox.widget { + step_width = 12, + color = '#EBCB8B', + background_color = beautiful.bg_normal, + forced_height = 100, + forced_width = 300, + widget = wibox.widget.graph, + set_max_value = function(self, new_max_value) + self.max_value = new_max_value + end, + set_min_value = function(self, new_min_value) + self.min_value = new_min_value + end + } + local hourly_forecast_negative_graph = wibox.widget { + step_width = 12, + color = '#5E81AC', + background_color = beautiful.bg_normal, + forced_height = 100, + forced_width = 300, + widget = wibox.widget.graph, + set_max_value = function(self, new_max_value) + self.max_value = new_max_value + end, + set_min_value = function(self, new_min_value) + self.min_value = new_min_value + end + } + + local hourly_forecast_widget = { + layout = wibox.layout.fixed.vertical, + update = function(self, hourly) + local hours_below = { + id = 'hours', + forced_width = 300, + layout = wibox.layout.flex.horizontal + } + local temp_below = { + id = 'temp', + forced_width = 300, + layout = wibox.layout.flex.horizontal + } + + local max_temp = -1000 + local min_temp = 1000 + local values = {} + for i, hour in ipairs(hourly) do + if i > 25 then break end + values[i] = hour.temp + if max_temp < hour.temp then max_temp = hour.temp end + if min_temp > hour.temp then min_temp = hour.temp end + if (i - 1) % 5 == 0 then + table.insert(hours_below, wibox.widget { + text = os.date(time_format_12h and '%I%p' or '%H:00', tonumber(hour.dt)), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }) + table.insert(temp_below, wibox.widget { + markup = '' + .. string.format('%.0f', hour.temp) .. '°' .. '', + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }) + end + end + + hourly_forecast_graph:set_max_value(math.max(max_temp, math.abs(min_temp))) + hourly_forecast_graph:set_min_value(min_temp > 0 and min_temp * 0.7 or 0) -- move graph a bit up + + hourly_forecast_negative_graph:set_max_value(math.abs(min_temp)) + hourly_forecast_negative_graph:set_min_value(max_temp < 0 and math.abs(max_temp) * 0.7 or 0) + + for _, value in ipairs(values) do + if value >= 0 then + hourly_forecast_graph:add_value(value) + hourly_forecast_negative_graph:add_value(0) + else + hourly_forecast_graph:add_value(0) + hourly_forecast_negative_graph:add_value(math.abs(value)) + end + end + + local count = #self + for i = 0, count do self[i]=nil end + + -- all temperatures are positive + if min_temp > 0 then + table.insert(self, wibox.widget{ + { + hourly_forecast_graph, + reflection = {horizontal = true}, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'bottom', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + table.insert(self, hours_below) + + -- all temperatures are negative + elseif max_temp < 0 then + table.insert(self, hours_below) + table.insert(self, wibox.widget{ + { + hourly_forecast_negative_graph, + reflection = {horizontal = true, vertical = true}, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'top', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + + -- there are both negative and positive temperatures + else + table.insert(self, wibox.widget{ + { + hourly_forecast_graph, + reflection = {horizontal = true}, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'bottom', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + table.insert(self, wibox.widget{ + { + hourly_forecast_negative_graph, + reflection = {horizontal = true, vertical = true}, + widget = wibox.container.mirror + }, + { + hours_below, + valign = 'top', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + end + end + } + + local function update_widget(widget, stdout, stderr) + if stderr ~= '' then + if not warning_shown then + if (stderr ~= 'curl: (52) Empty reply from server' + and stderr ~= 'curl: (28) Failed to connect to api.openweathermap.org port 443: Connection timed out' + and stderr:find('^curl: %(18%) transfer closed with %d+ bytes remaining to read$') ~= nil + ) then + show_warning(stderr) + end + warning_shown = true + widget:is_ok(false) + tooltip:add_to_object(widget) + + widget:connect_signal('mouse::enter', function() tooltip.text = stderr end) + end + return + end + + warning_shown = false + tooltip:remove_from_object(widget) + widget:is_ok(true) + + local result = json.decode(stdout) + + widget:set_image(ICONS_DIR .. icon_map[result.current.weather[1].icon] .. icons_extension) + widget:set_text(gen_temperature_str(result.current.temp, '%.0f', both_units_widget, units)) + + current_weather_widget:update(result.current) + + local final_widget = { + current_weather_widget, + spacing = 16, + layout = wibox.layout.fixed.vertical + } + + if show_hourly_forecast then + hourly_forecast_widget:update(result.hourly) + table.insert(final_widget, hourly_forecast_widget) + end + + if show_daily_forecast then + daily_forecast_widget:update(result.daily, result.timezone_offset) + table.insert(final_widget, daily_forecast_widget) + end + + weather_popup:setup({ + { + final_widget, + margins = 10, + widget = wibox.container.margin + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + }) + end + + weather_widget:buttons(gears.table.join(awful.button({}, 1, function() + if weather_popup.visible then + weather_widget:set_bg('#00000000') + weather_popup.visible = not weather_popup.visible + else + weather_widget:set_bg(beautiful.bg_focus) + weather_popup:move_next_to(mouse.current_widget_geometry) + end + end))) + + watch( + string.format(GET_FORECAST_CMD, owm_one_cal_api), + timeout, -- API limit is 1k req/day; day has 1440 min; every 2 min is good + update_widget, weather_widget + ) + + return weather_widget +end + +return setmetatable(weather_widget, {__call = function(_, ...) return worker(...) end}) diff --git a/dot_config/awesome/awesome-wm-widgets/widgets-icons.png b/dot_config/awesome/awesome-wm-widgets/widgets-icons.png new file mode 100644 index 0000000..ba9c551 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/widgets-icons.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/word-clock-widget/README.md b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/README.md new file mode 100644 index 0000000..9bf1032 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/README.md @@ -0,0 +1,74 @@ +# word clock widget + +Widget displaying current time using words: + +![screenshot](./screenshots/halfpastthree.png) + +## 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 word on odd position | +| accent_color | `beautiful.fg_urgent` | Color of the word on even position | +| font | `beautiful.font` | Font (`Play 20`) | +| is_human_readable | `false` | _nine fifteen_ or _fifteen past nine_ | +| military_time | `false` | 12 or 24 time format | +| with_spaces | `false` | Separate words with spaces | + +## Installation + +Clone repo, include widget and use it in **rc.lua**: + +```lua +local word_clock = require("awesome-wm-widgets.word-clock-widget.word-clock") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + word_clock(), + ... +``` + +# Screenshots + +```lua + word_clock{ + font = 'Carter One 12', + accent_color = '#ff79c6', + main_color = '#8be9fd', + is_human_readable = true, +} +``` +![](./screenshots/halfpastthree_color.png) + + +```lua +word_clock{ + font = 'Carter One 12', + is_human_readable = true, +} +``` +![](./screenshots/twentythreepastnine.png) + + +```lua +word_clock{ + font = 'Carter One 12', + is_human_readable = true, + military_time = true +} +``` +![](./screenshots/twentythreepasttwentyone.png) + + +```lua +word_clock{ + font = 'Carter One 12', + accent_color = '#f00', + main_color = '#0f0', +} +``` +![](./screenshots/onetwentyseven.png) diff --git a/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/halfpastthree.png b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/halfpastthree.png new file mode 100644 index 0000000..af9e0d9 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/halfpastthree.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/halfpastthree_color.png b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/halfpastthree_color.png new file mode 100644 index 0000000..3c2cdd7 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/halfpastthree_color.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/onetwentyseven.png b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/onetwentyseven.png new file mode 100644 index 0000000..08e852c Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/onetwentyseven.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/testpasttwentyone.png b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/testpasttwentyone.png new file mode 100644 index 0000000..41d266f Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/testpasttwentyone.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/twentythreepastnine.png b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/twentythreepastnine.png new file mode 100644 index 0000000..7d18e22 Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/twentythreepastnine.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/twentythreepasttwentyone.png b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/twentythreepasttwentyone.png new file mode 100644 index 0000000..8a8218f Binary files /dev/null and b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/screenshots/twentythreepasttwentyone.png differ diff --git a/dot_config/awesome/awesome-wm-widgets/word-clock-widget/word-clock.lua b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/word-clock.lua new file mode 100644 index 0000000..12d5d83 --- /dev/null +++ b/dot_config/awesome/awesome-wm-widgets/word-clock-widget/word-clock.lua @@ -0,0 +1,140 @@ +------------------------------------------------- +-- Text Clock Widget for Awesome Window Manager +-- Shows current time in words, e.g. 11.54 -> eleven fifty four +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/text-clock-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local wibox = require("wibox") +local beautiful = require("beautiful") +local gears = require("gears") + +local function tablelength(T) + local count = 0 + for _ in pairs(T) do count = count + 1 end + return count +end + +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 + +local function convertNumberToName(num) + local lowNames = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", + "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", + "eighteen", "nineteen"}; + local tensNames = {"twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"} + local tens, ones, result + + if num < tablelength(lowNames) then + result = lowNames[num + 1]; + else + tens = math.floor(num / 10); + ones = num % 10; + if (tens <= 9) then + result = tensNames[tens - 2 + 1]; + if (ones > 0) then + result = result .. " " .. lowNames[ones + 1]; + end + else + result = "unknown" + end + end + return result; +end + +local text_clock = {} + +local function worker(user_args) + + local args = user_args or {} + + local main_color = args.main_color or beautiful.fg_normal + local accent_color = args.accent_color or beautiful.fg_urgent + local font = args.font or beautiful.font + local is_human_readable = args.is_human_readable + local military_time = args.military_time + local with_spaces = args.with_spaces + + if military_time == nil then military_time = false end + if with_spaces == nil then with_spaces = false end + if is_human_readable == nil then is_human_readable = false end + + text_clock = wibox.widget { + { + id = 'clock', + font = font, + widget = wibox.widget.textbox, + }, + layout = wibox.layout.align.horizontal, + set_text = function(self, time) + local t = split(time) + local res = '' + for i, v in ipairs(t) do + res = res .. '' .. v .. '' + .. (with_spaces and ' ' or '') + end + self:get_children_by_id('clock')[1]:set_markup(res) + end + } + + gears.timer { + timeout = 1, + call_now = true, + autostart = true, + callback = function() + local time = os.date((military_time and '%H' or '%I') .. ':%M') + local h,m = time:match('(%d+):(%d+)') + local min = tonumber(m) + local hour = tonumber(h) + + if is_human_readable then + + if min == 0 then + text_clock:set_text(convertNumberToName(hour) .. " o'clock") + else + local mm + if min == 15 or min == 45 then + mm = 'quater' + elseif min == 30 then + mm = 'half' + else + mm = convertNumberToName((min < 31) and min or 60 - min) + end + + local to_past + + if min < 31 then + to_past = 'past' + else + to_past = 'to' + hour = hour + 1 + end + + text_clock:set_text(mm .. ' ' .. to_past .. ' ' .. convertNumberToName(hour)) + end + else + text_clock:set_text(convertNumberToName(hour) .. ' ' .. convertNumberToName(min)) + end + end + } + + return text_clock + +end + +return setmetatable(text_clock, { __call = function(_, ...) + return worker(...) +end }) \ No newline at end of file diff --git a/dot_config/awesome/mapping/client.lua b/dot_config/awesome/mapping/client.lua new file mode 100644 index 0000000..afe7fa1 --- /dev/null +++ b/dot_config/awesome/mapping/client.lua @@ -0,0 +1,83 @@ +local awful = require("awful") +local gears = require("gears") + +local function bind(globalkeys, modkey) + local clientbuttons = gears.table.join(awful.button({}, 1, function(c) + c:emit_signal("request::activate", "mouse_click", { + raise = true + }) + end), awful.button({modkey}, 1, function(c) + c:emit_signal("request::activate", "mouse_click", { + raise = true + }) + awful.mouse.client.move(c) + end), awful.button({modkey}, 3, function(c) + c:emit_signal("request::activate", "mouse_click", { + raise = true + }) + awful.mouse.client.resize(c) + end)) + + local clientkeys = gears.table.join(awful.key({modkey}, "f", function(c) + c.fullscreen = not c.fullscreen + c:raise() + end, { + description = "toggle fullscreen", + group = "client" + }), awful.key({modkey, "Shift"}, "c", function(c) + c:kill() + end, { + description = "close", + group = "client" + }), awful.key({modkey, "Control"}, "space", awful.client.floating.toggle, { + description = "toggle floating", + group = "client" + }), awful.key({modkey, "Control"}, "Return", function(c) + c:swap(awful.client.getmaster()) + end, { + description = "move to master", + group = "client" + }), awful.key({modkey}, "o", function(c) + c:move_to_screen() + end, { + description = "move to screen", + group = "client" + }), awful.key({modkey}, "t", function(c) + c.ontop = not c.ontop + end, { + description = "toggle keep on top", + group = "client" + }), awful.key({modkey}, "n", function(c) + -- The client currently has the input focus, so it cannot be + -- minimized, since minimized clients can't have the focus. + c.minimized = true + end, { + description = "minimize", + group = "client" + }), awful.key({modkey}, "m", function(c) + c.maximized = not c.maximized + c:raise() + end, { + description = "(un)maximize", + group = "client" + }), awful.key({modkey, "Control"}, "m", function(c) + c.maximized_vertical = not c.maximized_vertical + c:raise() + end, { + description = "(un)maximize vertically", + group = "client" + }), awful.key({modkey, "Shift"}, "m", function(c) + c.maximized_horizontal = not c.maximized_horizontal + c:raise() + end, { + description = "(un)maximize horizontally", + group = "client" + })) + + + return globalkeys, clientbuttons, clientkeys +end + +return { + bind = bind +} \ No newline at end of file diff --git a/dot_config/awesome/mapping/global.lua b/dot_config/awesome/mapping/global.lua new file mode 100644 index 0000000..0fdbe20 --- /dev/null +++ b/dot_config/awesome/mapping/global.lua @@ -0,0 +1,158 @@ +local awful = require("awful") +local gears = require("gears") + +local function bind(modkey, hotkeys_popup, mymainmenu, terminal) + -- {{{ Key bindings + return gears.table.join( + awful.key({modkey}, "s", hotkeys_popup.show_help, { + escription = "show help", + group = "awesome" + }), awful.key({modkey}, "Left", awful.tag.viewprev, { + description = "view previous", + group = "tag" + }), awful.key({modkey}, "Right", awful.tag.viewnext, { + description = "view next", + group = "tag" + }), awful.key({modkey}, "Escape", awful.tag.history.restore, { + description = "go back", + group = "tag" + }), awful.key({modkey}, "j", function() + awful.client.focus.byidx(1) + end, { + description = "focus next by index", + group = "client" + }), awful.key({modkey}, "k", function() + awful.client.focus.byidx(-1) + end, { + description = "focus previous by index", + group = "client" + }), awful.key({modkey}, "w", function() + mymainmenu:show() + end, { + description = "show main menu", + group = "awesome" + }), -- Layout manipulation + awful.key({modkey, "Shift"}, "j", function() + awful.client.swap.byidx(1) + end, { + description = "swap with next client by index", + group = "client" + }), awful.key({modkey, "Shift"}, "k", function() + awful.client.swap.byidx(-1) + end, { + description = "swap with previous client by index", + group = "client" + }), awful.key({modkey, "Control"}, "j", function() + awful.screen.focus_relative(1) + end, { + description = "focus the next screen", + group = "screen" + }), awful.key({modkey, "Control"}, "k", function() + awful.screen.focus_relative(-1) + end, { + description = "focus the previous screen", + group = "screen" + }), awful.key({modkey}, "u", awful.client.urgent.jumpto, { + description = "jump to urgent client", + group = "client" + }), awful.key({modkey}, "Tab", function() + awful.client.focus.history.previous() + if client.focus then + client.focus:raise() + end + end, { + description = "go back", + group = "client" + }), -- Standard program + awful.key({modkey}, "Return", function() + awful.spawn(terminal) + end, { + description = "open a terminal", + group = "launcher" + }), awful.key({modkey, "Control"}, "r", awesome.restart, { + description = "reload awesome", + group = "awesome" + }), awful.key({modkey, "Shift"}, "q", awesome.quit, { + description = "quit awesome", + group = "awesome" + }), awful.key({modkey}, "l", function() + awful.tag.incmwfact(0.05) + end, { + description = "increase master width factor", + group = "layout" + }), awful.key({modkey}, "h", function() + awful.tag.incmwfact(-0.05) + end, { + description = "decrease master width factor", + group = "layout" + }), awful.key({modkey, "Shift"}, "h", function() + awful.tag.incnmaster(1, nil, true) + end, { + description = "increase the number of master clients", + group = "layout" + }), awful.key({modkey, "Shift"}, "l", function() + awful.tag.incnmaster(-1, nil, true) + end, { + description = "decrease the number of master clients", + group = "layout" + }), awful.key({modkey, "Control"}, "h", function() + awful.tag.incncol(1, nil, true) + end, { + description = "increase the number of columns", + group = "layout" + }), awful.key({modkey, "Control"}, "l", function() + awful.tag.incncol(-1, nil, true) + end, { + description = "decrease the number of columns", + group = "layout" + }), awful.key({modkey}, "space", function() + awful.layout.inc(1) + end, { + description = "select next", + group = "layout" + }), awful.key({modkey, "Shift"}, "space", function() + awful.layout.inc(-1) + end, { + description = "select previous", + group = "layout" + }), awful.key({modkey, "Control"}, "n", function() + local c = awful.client.restore() + -- Focus restored client + if c then + c:emit_signal("request::activate", "key.unminimize", { + raise = true + }) + end + end, { + description = "restore minimized", + group = "client" + }), -- Prompt + awful.key({modkey}, "r", function() + awful.screen.focused().mypromptbox:run() + end, { + description = "run prompt", + group = "launcher" + }), awful.key({modkey}, "x", function() + awful.prompt.run { + prompt = "Run Lua code: ", + textbox = awful.screen.focused().mypromptbox.widget, + exe_callback = awful.util.eval, + history_path = awful.util.get_cache_dir() .. "/history_eval" + } + end, { + description = "lua execute prompt", + group = "awesome" + }), -- Menubar + awful.key({modkey}, "p", function() + awful.spawn.with_shell("rofi -show drun &>> /tmp/rofi.log") + -- menubar.show() + end, { + description = "show the menubar", + group = "launcher" + }) + ) +end + +return { + bind = bind +} \ No newline at end of file diff --git a/dot_config/awesome/mapping/init.lua b/dot_config/awesome/mapping/init.lua new file mode 100644 index 0000000..30f27fc --- /dev/null +++ b/dot_config/awesome/mapping/init.lua @@ -0,0 +1,17 @@ +local modkey = 'Mod4' + +local function bind(hotkeys_popup, mymainmenu, terminal) + -- Media Control + local globalkeys = require('mapping.global').bind(modkey, hotkeys_popup, mymainmenu, terminal) + local globalkeys, clientbuttons, clientkeys = require('mapping.client').bind(globalkeys, modkey) + require('mapping.mouse').bind() + local globalkeys = require('mapping.tags').bind(globalkeys) + + root.keys(globalkeys) + + return clientkeys, clientbuttons +end + +return { + bind = bind +} \ No newline at end of file diff --git a/dot_config/awesome/mapping/mediacontrol.lua b/dot_config/awesome/mapping/mediacontrol.lua new file mode 100644 index 0000000..64c30ce --- /dev/null +++ b/dot_config/awesome/mapping/mediacontrol.lua @@ -0,0 +1,19 @@ +local awful = require("awful") +local gears = require("gears") + +local function bind(globalkeys) + globalkeys = gears.table.join(globalkeys, + awful.key({ }, "XF86AudioPlay", function () awful.util.spawn_with_shell("playerctl play-pause") end), + awful.key({ }, "XF86AudioNext", function () awful.util.spawn_with_shell("playerctl next") end), + awful.key({ }, "XF86AudioPrev", function () awful.util.spawn_with_shell("playerctl previous") end), + awful.key({ }, "XF86AudioRaiseVolume", function () awful.util.spawn_with_shell("amixer -c 0 set Master 5dB+") end), + awful.key({ }, "XF86AudioLowerVolume", function () awful.util.spawn_with_shell("amixer -c 0 set Master 5dB-") end), + awful.key({ }, "XF86AudioMute", function () awful.util.spawn_with_shell("amixer -c 0 set Master toggle") end) + ) + + return globalkeys +end + +return { + bind = bind +} \ No newline at end of file diff --git a/dot_config/awesome/mapping/mouse.lua b/dot_config/awesome/mapping/mouse.lua new file mode 100644 index 0000000..4945c2a --- /dev/null +++ b/dot_config/awesome/mapping/mouse.lua @@ -0,0 +1,14 @@ +local awful = require("awful") +local gears = require("gears") + +local function bind() + -- {{{ Mouse bindings + root.buttons(gears.table.join(awful.button({}, 3, function() + mymainmenu:toggle() + end), awful.button({}, 4, awful.tag.viewnext), awful.button({}, 5, awful.tag.viewprev))) + -- }}} +end + +return { + bind = bind +} \ No newline at end of file diff --git a/dot_config/awesome/mapping/tags.lua b/dot_config/awesome/mapping/tags.lua new file mode 100644 index 0000000..c8fa0a8 --- /dev/null +++ b/dot_config/awesome/mapping/tags.lua @@ -0,0 +1,60 @@ +local awful = require("awful") +local gears = require("gears") + +local function bind(globalkeys, modkey) + -- Bind all key numbers to tags. + -- Be careful: we use keycodes to make it work on any keyboard layout. + -- This should map on the top row of your keyboard, usually 1 to 9. + for i = 1, 9 do + globalkeys = gears.table.join(globalkeys, -- View tag only. + awful.key({modkey}, "#" .. i + 9, function() + local screen = awful.screen.focused() + local tag = screen.tags[i] + if tag then + tag:view_only() + end + end, { + description = "view tag #" .. i, + group = "tag" + }), -- Toggle tag display. + awful.key({modkey, "Control"}, "#" .. i + 9, function() + local screen = awful.screen.focused() + local tag = screen.tags[i] + if tag then + awful.tag.viewtoggle(tag) + end + end, { + description = "toggle tag #" .. i, + group = "tag" + }), -- Move client to tag. + awful.key({modkey, "Shift"}, "#" .. i + 9, function() + if client.focus then + local tag = client.focus.screen.tags[i] + if tag then + client.focus:move_to_tag(tag) + end + end + end, { + description = "move focused client to tag #" .. i, + group = "tag" + }), -- Toggle tag on focused client. + awful.key({modkey, "Control", "Shift"}, "#" .. i + 9, function() + if client.focus then + local tag = client.focus.screen.tags[i] + if tag then + client.focus:toggle_tag(tag) + end + end + end, { + description = "toggle focused client on tag #" .. i, + group = "tag" + }) + ) + end + + return globalkeys +end + +return { + bind = bind +} \ No newline at end of file diff --git a/dot_config/awesome/rc.lua b/dot_config/awesome/rc.lua index 3122fdb..3d7742e 100644 --- a/dot_config/awesome/rc.lua +++ b/dot_config/awesome/rc.lua @@ -22,16 +22,16 @@ require("error_handling").init(awesome) beautiful.init(string.format("%s/.config/awesome/theme/theme.lua", os.getenv("HOME"))) -- This is used later as the default terminal and editor to run. -terminal = "alacritty" -editor = os.getenv("EDITOR") or "vim" -editor_cmd = terminal .. " -e " .. editor +local terminal = "kitty" +local editor = os.getenv("EDITOR") or "vim" +local editor_cmd = terminal .. " -e " .. editor -- Default modkey. -- Usually, Mod4 is the key with a logo between Control and Alt. -- If you do not like this or do not have such a key, -- I suggest you to remap Mod4 to another key using xmodmap or other tools. -- However, you can use another modifier like Mod1, but it may interact with others. -modkey = "Mod4" +local modkey = "Mod4" -- Table of layouts to cover with awful.layout.inc, order matters. awful.layout.layouts = {awful.layout.suit.floating, awful.layout.suit.tile, awful.layout.suit.tile.left, @@ -46,18 +46,18 @@ awful.layout.layouts = {awful.layout.suit.floating, awful.layout.suit.tile, awfu -- {{{ Menu -- Create a launcher widget and a main menu -myawesomemenu = {{"hotkeys", function() +local myawesomemenu = {{"hotkeys", function() hotkeys_popup.show_help(nil, awful.screen.focused()) end}, {"manual", terminal .. " -e man awesome"}, {"edit config", editor_cmd .. " " .. awesome.conffile}, {"restart", awesome.restart}, {"quit", function() awesome.quit() end}} -mymainmenu = awful.menu({ +local mymainmenu = awful.menu({ items = {{"awesome", myawesomemenu, beautiful.awesome_icon}, {"open terminal", terminal}} }) -mylauncher = awful.widget.launcher({ +local mylauncher = awful.widget.launcher({ image = beautiful.awesome_icon, menu = mymainmenu }) @@ -67,11 +67,11 @@ menubar.utils.terminal = terminal -- Set the terminal for applications that requ -- }}} -- Keyboard map indicator and switcher -mykeyboardlayout = awful.widget.keyboardlayout() +local mykeyboardlayout = awful.widget.keyboardlayout() -- {{{ Wibar -- Create a textclock widget -mytextclock = wibox.widget.textclock() +local mytextclock = wibox.widget.textclock() local volume_widget = require('awesome-wm-widgets.volume-widget.volume') local cpu_widget = require("awesome-wm-widgets.cpu-widget.cpu-widget") @@ -198,286 +198,7 @@ awful.screen.connect_for_each_screen(function(s) end) -- }}} --- {{{ Mouse bindings -root.buttons(gears.table.join(awful.button({}, 3, function() - mymainmenu:toggle() -end), awful.button({}, 4, awful.tag.viewnext), awful.button({}, 5, awful.tag.viewprev))) --- }}} - --- {{{ Key bindings -globalkeys = gears.table.join(awful.key({modkey}, "s", hotkeys_popup.show_help, { - escription = "show help", - group = "awesome" -}), awful.key({modkey}, "Left", awful.tag.viewprev, { - description = "view previous", - group = "tag" -}), awful.key({modkey}, "Right", awful.tag.viewnext, { - description = "view next", - group = "tag" -}), awful.key({modkey}, "Escape", awful.tag.history.restore, { - description = "go back", - group = "tag" -}), awful.key({modkey}, "j", function() - awful.client.focus.byidx(1) -end, { - description = "focus next by index", - group = "client" -}), awful.key({modkey}, "k", function() - awful.client.focus.byidx(-1) -end, { - description = "focus previous by index", - group = "client" -}), awful.key({modkey}, "w", function() - mymainmenu:show() -end, { - description = "show main menu", - group = "awesome" -}), -- Layout manipulation -awful.key({modkey, "Shift"}, "j", function() - awful.client.swap.byidx(1) -end, { - description = "swap with next client by index", - group = "client" -}), awful.key({modkey, "Shift"}, "k", function() - awful.client.swap.byidx(-1) -end, { - description = "swap with previous client by index", - group = "client" -}), awful.key({modkey, "Control"}, "j", function() - awful.screen.focus_relative(1) -end, { - description = "focus the next screen", - group = "screen" -}), awful.key({modkey, "Control"}, "k", function() - awful.screen.focus_relative(-1) -end, { - description = "focus the previous screen", - group = "screen" -}), awful.key({modkey}, "u", awful.client.urgent.jumpto, { - description = "jump to urgent client", - group = "client" -}), awful.key({modkey}, "Tab", function() - awful.client.focus.history.previous() - if client.focus then - client.focus:raise() - end -end, { - description = "go back", - group = "client" -}), -- Standard program -awful.key({modkey}, "Return", function() - awful.spawn(terminal) -end, { - description = "open a terminal", - group = "launcher" -}), awful.key({modkey, "Control"}, "r", awesome.restart, { - description = "reload awesome", - group = "awesome" -}), awful.key({modkey, "Shift"}, "q", awesome.quit, { - description = "quit awesome", - group = "awesome" -}), awful.key({modkey}, "l", function() - awful.tag.incmwfact(0.05) -end, { - description = "increase master width factor", - group = "layout" -}), awful.key({modkey}, "h", function() - awful.tag.incmwfact(-0.05) -end, { - description = "decrease master width factor", - group = "layout" -}), awful.key({modkey, "Shift"}, "h", function() - awful.tag.incnmaster(1, nil, true) -end, { - description = "increase the number of master clients", - group = "layout" -}), awful.key({modkey, "Shift"}, "l", function() - awful.tag.incnmaster(-1, nil, true) -end, { - description = "decrease the number of master clients", - group = "layout" -}), awful.key({modkey, "Control"}, "h", function() - awful.tag.incncol(1, nil, true) -end, { - description = "increase the number of columns", - group = "layout" -}), awful.key({modkey, "Control"}, "l", function() - awful.tag.incncol(-1, nil, true) -end, { - description = "decrease the number of columns", - group = "layout" -}), awful.key({modkey}, "space", function() - awful.layout.inc(1) -end, { - description = "select next", - group = "layout" -}), awful.key({modkey, "Shift"}, "space", function() - awful.layout.inc(-1) -end, { - description = "select previous", - group = "layout" -}), awful.key({modkey, "Control"}, "n", function() - local c = awful.client.restore() - -- Focus restored client - if c then - c:emit_signal("request::activate", "key.unminimize", { - raise = true - }) - end -end, { - description = "restore minimized", - group = "client" -}), -- Prompt -awful.key({modkey}, "r", function() - awful.screen.focused().mypromptbox:run() -end, { - description = "run prompt", - group = "launcher" -}), awful.key({modkey}, "x", function() - awful.prompt.run { - prompt = "Run Lua code: ", - textbox = awful.screen.focused().mypromptbox.widget, - exe_callback = awful.util.eval, - history_path = awful.util.get_cache_dir() .. "/history_eval" - } -end, { - description = "lua execute prompt", - group = "awesome" -}), -- Menubar -awful.key({modkey}, "p", function() - awful.spawn.with_shell("rofi -show drun &>> /tmp/rofi.log") - -- menubar.show() -end, { - description = "show the menubar", - group = "launcher" -})) - -clientkeys = gears.table.join(awful.key({modkey}, "f", function(c) - c.fullscreen = not c.fullscreen - c:raise() -end, { - description = "toggle fullscreen", - group = "client" -}), awful.key({modkey, "Shift"}, "c", function(c) - c:kill() -end, { - description = "close", - group = "client" -}), awful.key({modkey, "Control"}, "space", awful.client.floating.toggle, { - description = "toggle floating", - group = "client" -}), awful.key({modkey, "Control"}, "Return", function(c) - c:swap(awful.client.getmaster()) -end, { - description = "move to master", - group = "client" -}), awful.key({modkey}, "o", function(c) - c:move_to_screen() -end, { - description = "move to screen", - group = "client" -}), awful.key({modkey}, "t", function(c) - c.ontop = not c.ontop -end, { - description = "toggle keep on top", - group = "client" -}), awful.key({modkey}, "n", function(c) - -- The client currently has the input focus, so it cannot be - -- minimized, since minimized clients can't have the focus. - c.minimized = true -end, { - description = "minimize", - group = "client" -}), awful.key({modkey}, "m", function(c) - c.maximized = not c.maximized - c:raise() -end, { - description = "(un)maximize", - group = "client" -}), awful.key({modkey, "Control"}, "m", function(c) - c.maximized_vertical = not c.maximized_vertical - c:raise() -end, { - description = "(un)maximize vertically", - group = "client" -}), awful.key({modkey, "Shift"}, "m", function(c) - c.maximized_horizontal = not c.maximized_horizontal - c:raise() -end, { - description = "(un)maximize horizontally", - group = "client" -})) - --- Bind all key numbers to tags. --- Be careful: we use keycodes to make it work on any keyboard layout. --- This should map on the top row of your keyboard, usually 1 to 9. -for i = 1, 9 do - globalkeys = gears.table.join(globalkeys, -- View tag only. - awful.key({modkey}, "#" .. i + 9, function() - local screen = awful.screen.focused() - local tag = screen.tags[i] - if tag then - tag:view_only() - end - end, { - description = "view tag #" .. i, - group = "tag" - }), -- Toggle tag display. - awful.key({modkey, "Control"}, "#" .. i + 9, function() - local screen = awful.screen.focused() - local tag = screen.tags[i] - if tag then - awful.tag.viewtoggle(tag) - end - end, { - description = "toggle tag #" .. i, - group = "tag" - }), -- Move client to tag. - awful.key({modkey, "Shift"}, "#" .. i + 9, function() - if client.focus then - local tag = client.focus.screen.tags[i] - if tag then - client.focus:move_to_tag(tag) - end - end - end, { - description = "move focused client to tag #" .. i, - group = "tag" - }), -- Toggle tag on focused client. - awful.key({modkey, "Control", "Shift"}, "#" .. i + 9, function() - if client.focus then - local tag = client.focus.screen.tags[i] - if tag then - client.focus:toggle_tag(tag) - end - end - end, { - description = "toggle focused client on tag #" .. i, - group = "tag" - })) -end - -clientbuttons = gears.table.join(awful.button({}, 1, function(c) - c:emit_signal("request::activate", "mouse_click", { - raise = true - }) -end), awful.button({modkey}, 1, function(c) - c:emit_signal("request::activate", "mouse_click", { - raise = true - }) - awful.mouse.client.move(c) -end), awful.button({modkey}, 3, function(c) - c:emit_signal("request::activate", "mouse_click", { - raise = true - }) - awful.mouse.client.resize(c) -end)) - -globalkeys, clientkeys, clientbuttons = require('keymapping').bind(globalkeys, clientkeys, clientbuttons) - --- Set keys -root.keys(globalkeys) --- }}} +local clientkeys, clientbuttons = require('mapping').bind(hotkeys_popup, mymainmenu, terminal) -- {{{ Rules -- Rules to apply to new clients (through the "manage" signal).