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 @@ +
+ +
+ ++ + + + + + + + +
+ +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 @@ + \ 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' --+ +
+ +## 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 @@ + 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 $/;+ +
+ +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 = '+ +
+ +## 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 + + + +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: + ++ +
+ +## 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 @@ + \ 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. + ++ +
+ +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 @@ + + + + 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 @@ + + + + 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 @@ + + + + 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 = '+ + + + + +
+ +The widget showing current, hourly and daily weather forecast: + ++ +
+ +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='