commit 5fa4c76c242a9f3c26fb206a1a2eb43ad78e6dd1 Author: jmug Date: Sat May 3 23:42:03 2025 -0700 Dump nixos config after scrubing diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..2fea209 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,10 @@ +keys: + - &jmug age1psyctjy329r9v07uqu72vkjl06f26f0epvh6zxejdkwp3m0tnyvq88rnr4 # This key is in cold storage. + - &hosts: + - &nixlap age1cfcfye2unv89fgyuwpvy9sas40jd87kksw7rlgy4cwmcfjqntv2st2jcnp +creation_rules: + - path_regex: secrets.yaml$ + key_groups: + - age: + - *jmug + - *nixlap diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..23bba14 --- /dev/null +++ b/flake.lock @@ -0,0 +1,356 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1733328505, + "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "locked": { + "lastModified": 1659877975, + "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "ghostty": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs-stable": "nixpkgs-stable", + "nixpkgs-unstable": "nixpkgs-unstable", + "zig": "zig", + "zig2nix": "zig2nix" + }, + "locked": { + "lastModified": 1740522536, + "narHash": "sha256-PdtWmyCBKEUXooecUTKtZSALRQ0jqh5e9MyyIegX5NQ=", + "owner": "ghostty-org", + "repo": "ghostty", + "rev": "4e5e4a7c2fe7ba148566dfb84cf546dfc26980d2", + "type": "github" + }, + "original": { + "owner": "ghostty-org", + "repo": "ghostty", + "type": "github" + } + }, + "home-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1739757849, + "narHash": "sha256-Gs076ot1YuAAsYVcyidLKUMIc4ooOaRGO0PqTY7sBzA=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "9d3d080aec2a35e05a15cedd281c2384767c2cfe", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "release-24.11", + "repo": "home-manager", + "type": "github" + } + }, + "nix-darwin": { + "inputs": { + "nixpkgs": [ + "nixpkgs-darwin" + ] + }, + "locked": { + "lastModified": 1743127615, + "narHash": "sha256-+sMGqywrSr50BGMLMeY789mSrzjkoxZiu61eWjYS/8o=", + "owner": "LnL7", + "repo": "nix-darwin", + "rev": "fc843893cecc1838a59713ee3e50e9e7edc6207c", + "type": "github" + }, + "original": { + "owner": "LnL7", + "ref": "nix-darwin-24.11", + "repo": "nix-darwin", + "type": "github" + } + }, + "nixgl": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1713543440, + "narHash": "sha256-lnzZQYG0+EXl/6NkGpyIz+FEOc/DSEG57AP1VsdeNrM=", + "owner": "nix-community", + "repo": "nixGL", + "rev": "310f8e49a149e4c9ea52f1adf70cdc768ec53f8a", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixGL", + "type": "github" + } + }, + "nixos-hardware": { + "locked": { + "lastModified": 1740387674, + "narHash": "sha256-pGk/aA0EBvI6o4DeuZsr05Ig/r4uMlSaf5EWUZEWM10=", + "owner": "NixOS", + "repo": "nixos-hardware", + "rev": "d58f642ddb23320965b27beb0beba7236e9117b5", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "master", + "repo": "nixos-hardware", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1740339700, + "narHash": "sha256-cbrw7EgQhcdFnu6iS3vane53bEagZQy/xyIkDWpCgVE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "04ef94c4c1582fd485bbfdb8c4a8ba250e359195", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-darwin": { + "locked": { + "lastModified": 1740357648, + "narHash": "sha256-CaawdjLmSny3UV97my2Hg4h867p4lhd+EpRhFQGaHK4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "060b03c5d950ee0592d16e97c63860640bd31f50", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-24.11-darwin", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-msft-go": { + "locked": { + "lastModified": 1725980463, + "narHash": "sha256-3ruEhwpLlccjwYTvLCyFAtPndcRxAs3XazL2lhOXucI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5ed627539ac84809c78b2dd6d26a5cebeb5ae269", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5ed627539ac84809c78b2dd6d26a5cebeb5ae269", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1738255539, + "narHash": "sha256-hP2eOqhIO/OILW+3moNWO4GtdJFYCqAe9yJZgvlCoDQ=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "c3511a3b53b482aa7547c9d1626fd7310c1de1c5", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "release-24.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-unstable": { + "locked": { + "lastModified": 1738136902, + "narHash": "sha256-pUvLijVGARw4u793APze3j6mU1Zwdtz7hGkGGkD87qw=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "9a5db3142ce450045840cc8d832b13b8a2018e0c", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-unstable_2": { + "locked": { + "lastModified": 1743315132, + "narHash": "sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om+D4UnDhlDW9BE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "52faf482a3889b7619003c0daec593a1912fddc1", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "ghostty": "ghostty", + "home-manager": "home-manager", + "nix-darwin": "nix-darwin", + "nixgl": "nixgl", + "nixos-hardware": "nixos-hardware", + "nixpkgs": "nixpkgs", + "nixpkgs-darwin": "nixpkgs-darwin", + "nixpkgs-msft-go": "nixpkgs-msft-go", + "nixpkgs-unstable": "nixpkgs-unstable_2", + "sops-nix": "sops-nix" + } + }, + "sops-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1745310711, + "narHash": "sha256-ePyTpKEJTgX0gvgNQWd7tQYQ3glIkbqcW778RpHlqgA=", + "owner": "mic92", + "repo": "sops-nix", + "rev": "5e3e92b16d6fdf9923425a8d4df7496b2434f39c", + "type": "github" + }, + "original": { + "owner": "mic92", + "repo": "sops-nix", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "zig": { + "inputs": { + "flake-compat": [ + "ghostty" + ], + "flake-utils": [ + "ghostty", + "flake-utils" + ], + "nixpkgs": [ + "ghostty", + "nixpkgs-stable" + ] + }, + "locked": { + "lastModified": 1738239110, + "narHash": "sha256-Y5i9mQ++dyIQr+zEPNy+KIbc5wjPmfllBrag3cHZgcE=", + "owner": "mitchellh", + "repo": "zig-overlay", + "rev": "1a8fb6f3a04724519436355564b95fce5e272504", + "type": "github" + }, + "original": { + "owner": "mitchellh", + "repo": "zig-overlay", + "type": "github" + } + }, + "zig2nix": { + "inputs": { + "flake-utils": [ + "ghostty", + "flake-utils" + ], + "nixpkgs": [ + "ghostty", + "nixpkgs-stable" + ] + }, + "locked": { + "lastModified": 1738263917, + "narHash": "sha256-j/3fwe2pEOquHabP/puljOKwAZFjIE9gXZqA91sC48M=", + "owner": "jcollie", + "repo": "zig2nix", + "rev": "c311d8e77a6ee0d995f40a6e10a89a3a4ab04f9a", + "type": "github" + }, + "original": { + "owner": "jcollie", + "ref": "c311d8e77a6ee0d995f40a6e10a89a3a4ab04f9a", + "repo": "zig2nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..fd9f179 --- /dev/null +++ b/flake.nix @@ -0,0 +1,131 @@ +{ + description = "NixOS config flake"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; + nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; + nixpkgs-darwin.url = "github:NixOS/nixpkgs/nixpkgs-24.11-darwin"; + nix-darwin.url = "github:LnL7/nix-darwin/nix-darwin-24.11"; + nix-darwin.inputs.nixpkgs.follows = "nixpkgs-darwin"; + + # required to match on a specific version of Go for AKS dev setup. + nixpkgs-msft-go.url = "github:NixOS/nixpkgs/5ed627539ac84809c78b2dd6d26a5cebeb5ae269"; + + nixgl.url = "github:nix-community/nixGL"; + nixgl.inputs.nixpkgs.follows = "nixpkgs"; + + # Secrets management. + sops-nix = { + url = "github:mic92/sops-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + nixos-hardware.url = "github:NixOS/nixos-hardware/master"; + home-manager = { + url = "github:nix-community/home-manager/release-24.11"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + ghostty = { + url = "github:ghostty-org/ghostty"; + }; + }; + + outputs = { + self, + nixpkgs, + nixpkgs-unstable, + nixpkgs-msft-go, + nix-darwin, + nixos-hardware, + home-manager, + ghostty, + nixgl, ... + }@inputs: { + nixosConfigurations = { + nixlap = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + specialArgs = { + inherit inputs ghostty; + }; + modules = [ + ./hosts/nixlap/configuration.nix + nixos-hardware.nixosModules.framework-13-7040-amd + home-manager.nixosModules.home-manager + { + home-manager.extraSpecialArgs = { inherit inputs; }; + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + home-manager.users.jmug = import ./hosts/nixlap/home.nix; + home-manager.users.root = import ./hosts/nixlap/home-root.nix; + } + ]; + }; + devbox = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + specialArgs = { + inherit inputs; + }; + modules = [ + ./hosts/devbox/configuration.nix + home-manager.nixosModules.home-manager + { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + home-manager.users.jmug = import ./hosts/devbox/home.nix; + home-manager.users.root = import ./hosts/devbox/home-root.nix; + } + ]; + }; + }; + + # Build darwin flake using: + # $ darwin-rebuild build --flake .#macbook + darwinConfigurations = { + "macbook" = nix-darwin.lib.darwinSystem { + system = "aarch64-darwin"; + specialArgs = { + inherit inputs ghostty self; + }; + modules = [ + ./hosts/macbook/configuration.nix + home-manager.darwinModules.home-manager + { + home-manager.useGlobalPkgs = true; + home-manager.useUserPackages = true; + home-manager.users.uagm.imports = [ ./hosts/macbook/home.nix ]; + } + ]; + }; + }; + + homeConfigurations = { + alarm = home-manager.lib.homeManagerConfiguration { + pkgs = nixpkgs.legacyPackages."aarch64-linux"; + modules = [ + ./users/alarm/home.nix + ]; + }; + msftdevbox = home-manager.lib.homeManagerConfiguration { + pkgs = nixpkgs.legacyPackages."x86_64-linux"; + extraSpecialArgs = { + pkgs-msft-go = nixpkgs-msft-go.legacyPackages."x86_64-linux"; + pkgs-unstable = nixpkgs-unstable.legacyPackages."x86_64-linux"; + }; + modules = [ + ./users/msftdevbox/home.nix + ]; + }; + nixlapmsft = home-manager.lib.homeManagerConfiguration { + pkgs = nixpkgs.legacyPackages."x86_64-linux"; + extraSpecialArgs = { + inherit ghostty nixgl; + pkgs-msft-go = nixpkgs-msft-go.legacyPackages."x86_64-linux"; + pkgs-unstable = nixpkgs-unstable.legacyPackages."x86_64-linux"; + }; + modules = [ + ./users/nixlapmsft/home.nix + ]; + }; + }; + }; +} diff --git a/home-modules/books.nix b/home-modules/books.nix new file mode 100644 index 0000000..8223756 --- /dev/null +++ b/home-modules/books.nix @@ -0,0 +1,5 @@ +{ pkgs, ... }: { + home.packages = with pkgs; [ + calibre + ]; +} diff --git a/home-modules/default.nix b/home-modules/default.nix new file mode 100644 index 0000000..032c2ce --- /dev/null +++ b/home-modules/default.nix @@ -0,0 +1,18 @@ +{ pkgs, ... }: { + home = { + packages = with pkgs; [ + lua-language-server + neovim + zip + unzip + eza + ]; + }; + + programs.zsh = { + shellAliases = { + ls = "eza"; + n = "nvim"; + }; + }; +} diff --git a/home-modules/direnv.nix b/home-modules/direnv.nix new file mode 100644 index 0000000..355dcf7 --- /dev/null +++ b/home-modules/direnv.nix @@ -0,0 +1,7 @@ +{ ... }: { + programs.direnv = { + enable = true; + enableZshIntegration = true; + nix-direnv.enable = true; + }; +} diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/.luacheckrc b/home-modules/explicit-configs/awesome/awesome-buttons/.luacheckrc new file mode 100644 index 0000000..e4f47fa --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/.luacheckrc @@ -0,0 +1,24 @@ +self = false + +globals = { + "screen", + "mouse", + "root", + "client" +} + +read_globals = { + "awesome", + "button", + "dbus", + "drawable", + "drawin", + "key", + "keygrabber", + "mousegrabber", + "selection", + "tag", + "window", + "table.unpack", + "math.atan2", +} \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/README.md b/home-modules/explicit-configs/awesome/awesome-buttons/README.md new file mode 100644 index 0000000..f84fc40 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/README.md @@ -0,0 +1,132 @@ +# awesome-buttons + +Button widget library for Awesome Window Manager + +

+ logo +

+ +

+ + + GitHub Workflow Status +

+ +# Customizations + +## Text button + +| Name | Default | Description | +|---|---|---| +| `type` | `basic` | Button type, could be 'outline', 'flat' or 'basic' | +| `text` | `''` | Button text | +| `color` | `#D8DEE9` | Button color | +| `text_size` | `10` | Size of the text | +| `onclick` | `function() end` | Function which is called when button is clicked | + + +## Icon button + +| Name | Default | Description | +|---|---|---| +| `type` | `basic` | Button type, could be 'outline', 'flat' or 'basic' | +| `color` | `#D8DEE9` | Button color | +| `icon` | `help-circle` | Icon name from feathericons, or path to the icon, read below for more details | +| `icon_size` | `20` | Size of the icon | +| `icon_margin` | `4` | Margins around the icon | +| `shape` | `circle` | Shape of the button, could be 'circle', 'rounded_rect' or 'rectangle' | +| `onclick` | `function() end` | Function which is called when button is clicked | + +## Icon with text button + +| Name | Default | Description | +|---|---|---| +| `type` | `basic` | Button type, could be 'outline', 'flat' or 'basic' | +| `icon` | `help-circle` | Icon name from feathericons, or path to the icon, read below for more details | +| `text` | `''` | Button text | +| `text_size` | `10` | Size of the text | +| `onclick` | `function() end` | Function which is called when button is clicked | + +Buttons come with preinstalled icons (taken from https://feathericons.com/). So you can either specify the name of the icon to use (to check available icons check the https://feathericons.com/ or under the icons folder): + +```lua +awesomebuttons.with_icon{ icon = 'moon', color = '#f88' } +``` + +or provide a path to the icon: + +```lua +awesomebuttons.with_icon{ icon = '/home/something/something.svg', color = '#f88' } +``` + +# Demo + +To check buttons in action, simply add the section below to your rc.lua. It will create a wibox with buttons (same as in screenshot.) + +```lua + +local awesomebuttons = require("awesome-buttons.awesome-buttons") + +local buttons_example = wibox { + visible = true, + bg = '#2E3440', + max_widget_size = 500, + ontop = true, + height = 200, + width = 500, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 3) + end +} + +buttons_example:setup { + { + { + { + markup = 'Awesome Buttons Demo', + align = 'center', + widget = wibox.widget.textbox + }, + { + wibox.widget.textbox('Only text:'), + awesomebuttons.with_text{ text = 'Hello', color = '#ff8' }, + awesomebuttons.with_text{ type = 'outline', text = 'Bonjour', color = '#8ff', text_size = 8 }, + awesomebuttons.with_text{ type = 'flat', text = 'Ola', color = '#f8f', text_size = 12 }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + { + wibox.widget.textbox('Only icon:'), + awesomebuttons.with_icon{ icon = 'moon', color = '#f88' }, + awesomebuttons.with_icon{ icon = 'meh', color = '#a38', shape = 'circle' }, + awesomebuttons.with_icon{ icon = 'map', color = '#f8c', shape = 'rounded_rect' }, + awesomebuttons.with_icon{ type = 'outline', icon = 'sun', color = '#218' }, + awesomebuttons.with_icon{ type = 'outline', icon = 'shield', color = '#188', shape = 'circle' }, + awesomebuttons.with_icon{ type = 'outline', icon = 'zoom-in', color = '#f8f', shape = 'rounded_rect' }, + awesomebuttons.with_icon{ type = 'flat', icon = 'truck', color = '#fc8' }, + awesomebuttons.with_icon{ type = 'flat', icon = 'wifi', color = '#488', shape = 'circle' }, + awesomebuttons.with_icon{ type = 'flat', icon = 'watch', color = '#788', shape = 'rounded_rect' }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + { + wibox.widget.textbox('Icon and text:'), + awesomebuttons.with_icon_and_text{ icon = 'check-circle', text = 'With Icon!', color = '#f48' }, + awesomebuttons.with_icon_and_text{ type = 'outline', icon = 'battery-charging', text = 'Charging', color = '#8d1' }, + awesomebuttons.with_icon_and_text{ type = 'flat', icon = 'star', text = 'Awesome!!!', color = '#EBCB8B' }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + spacing = 16, + layout = wibox.layout.fixed.vertical, + }, + shape_border_width = 1, + valigh = 'center', + layout = wibox.container.place + }, + margins = 16, + widget = wibox.container.margin +} + +awful.placement.top(buttons_example, { margins = {top = 40}, parent = awful.screen.focused()}) +``` diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/awesome-buttons.lua b/home-modules/explicit-configs/awesome/awesome-buttons/awesome-buttons.lua new file mode 100644 index 0000000..5414d7c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/awesome-buttons.lua @@ -0,0 +1,209 @@ +local wibox = require("wibox") +local gears = require("gears") + +local buttons = {} + +buttons.with_icon = function(args) + local type = args.type or 'basic' + local color = args.color or '#D8DEE9' + local icon = args.icon or 'help-circle' + local shape = args.shape or 'circle' + local icon_size = args.icon_size or 20 + local icon_margin = args.icon_margin or 4 + local onclick = args.onclick or function () end + + if icon:sub(1, 1) ~= '/' then + icon = os.getenv("HOME") .. '/.config/awesome/awesome-buttons/icons/' .. icon .. '.svg' + end + + local result = wibox.widget{ + { + { + image = icon, + resize = true, + forced_height = icon_size, + forced_width = icon_size, + widget = wibox.widget.imagebox + }, + margins = icon_margin, + widget = wibox.container.margin + }, + bg = '#00000000', + widget = wibox.container.background + } + + if type == 'outline' then + result:set_shape_border_color(color) + result:set_shape_border_width(1) + elseif type == 'flat' then + result:set_bg(color) + end + + if shape == 'circle' then + result:set_shape(gears.shape.circle) + elseif shape == 'rounded_rect' then + result:set_shape(function(cr, width, height) gears.shape.rounded_rect(cr, width, height, 4) end) + else + result:set_shape(gears.shape.rectangle) + end + + local old_cursor, old_wibox + result:connect_signal("mouse::enter", function(c) + if type ~= 'flat' then + c:set_bg(color) + end + pcall(function() + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + end) + result:connect_signal("mouse::leave", function(c) + if type ~= 'flat' then + c:set_bg('#00000000') + end + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + result:connect_signal("button::press", function() onclick() end) + + return result +end + + +buttons.with_text = function(args) + local type = args.type or 'basic' + local text = args.text + local onclick = args.onclick or function() end + local color = args.color or '#D8DEE9' + local text_size = args.text_size or 10 + + local result = wibox.widget{ + { + { + markup = '' .. text ..'', + widget = wibox.widget.textbox + }, + top = 4, bottom = 4, left = 8, right = 8, + widget = wibox.container.margin + }, + bg = '#00000000', + shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, 4) end, + widget = wibox.container.background + } + + if type == 'outline' then + result:set_shape_border_color(color) + result:set_shape_border_width(1) + elseif type == 'flat' then + result:set_bg(color) + end + + local old_cursor, old_wibox + result:connect_signal("mouse::enter", function(c) + if type ~= 'flat' then + c:set_bg("#00000066") + end + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + result:connect_signal("mouse::leave", function(c) + if type ~= 'flat' then + c:set_bg('#00000000') + end + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + result:connect_signal("button::press", function() onclick() end) + + return result +end + + +buttons.with_icon_and_text = function(args) + + local type = args.type or 'basic' + local text = args.text + local icon = args.icon + local onclick = args.onclick or function() end + local color = args.color or '#D8DEE9' + local text_size = args.text_size or 10 + + if icon:sub(1, 1) ~= '/' then + icon = os.getenv("HOME") .. '/.config/awesome/awesome-buttons/icons/' .. icon .. '.svg' + end + + + local result = wibox.widget{ + { + { + { + image = icon, + resize = true, + forced_height = 20, + widget = wibox.widget.imagebox + }, + margins = 4, + widget = wibox.container.margin + }, + { + { + markup = '' .. text ..'', + widget = wibox.widget.textbox + }, + top = 4, bottom = 4, right = 8, + widget = wibox.container.margin + }, + layout = wibox.layout.fixed.horizontal + }, + bg = '#00000000', + shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, 4) end, + widget = wibox.container.background + } + + if type == 'outline' then + result:set_shape_border_color(color) + result:set_shape_border_width(1) + end + + if type == 'flat' then + result:set_bg(color) + end + + local old_cursor, old_wibox + result:connect_signal("mouse::enter", function(c) + if type ~= 'flat' then + c:set_bg('#00000044') + end + pcall(function () + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + end) + result:connect_signal("mouse::leave", function(c) + if type ~= 'flat' then + c:set_bg('#00000000') + end + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + result:connect_signal("button::press", function() onclick() end) + + return result +end + + +return buttons diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/activity.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/activity.svg new file mode 100644 index 0000000..9408b98 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/activity.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/airplay.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/airplay.svg new file mode 100644 index 0000000..8640fc9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/airplay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/alert-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/alert-circle.svg new file mode 100644 index 0000000..fbe1994 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/alert-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/alert-octagon.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/alert-octagon.svg new file mode 100644 index 0000000..652a164 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/alert-octagon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/alert-triangle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/alert-triangle.svg new file mode 100644 index 0000000..1176793 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/alert-triangle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/align-center.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/align-center.svg new file mode 100644 index 0000000..07eed99 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/align-center.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/align-justify.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/align-justify.svg new file mode 100644 index 0000000..b6ca01b --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/align-justify.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/align-left.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/align-left.svg new file mode 100644 index 0000000..5ad658d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/align-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/align-right.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/align-right.svg new file mode 100644 index 0000000..5c74da5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/align-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/anchor.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/anchor.svg new file mode 100644 index 0000000..5c89a5a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/anchor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/aperture.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/aperture.svg new file mode 100644 index 0000000..e62e11d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/aperture.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/archive.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/archive.svg new file mode 100644 index 0000000..2bf0159 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/archive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-down-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-down-circle.svg new file mode 100644 index 0000000..b33e632 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-down-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-down-left.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-down-left.svg new file mode 100644 index 0000000..11b3d19 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-down-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-down-right.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-down-right.svg new file mode 100644 index 0000000..c8a41fd --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-down-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-down.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-down.svg new file mode 100644 index 0000000..5f9ab74 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-left-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-left-circle.svg new file mode 100644 index 0000000..3cfbfd3 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-left-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-left.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-left.svg new file mode 100644 index 0000000..cd19e42 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-right-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-right-circle.svg new file mode 100644 index 0000000..596da98 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-right-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-right.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-right.svg new file mode 100644 index 0000000..abc2125 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-up-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-up-circle.svg new file mode 100644 index 0000000..0c03f7b --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-up-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-up-left.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-up-left.svg new file mode 100644 index 0000000..443577c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-up-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-up-right.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-up-right.svg new file mode 100644 index 0000000..2fe39e1 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-up-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-up.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-up.svg new file mode 100644 index 0000000..6f692fc --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/arrow-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/at-sign.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/at-sign.svg new file mode 100644 index 0000000..b2cd6c1 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/at-sign.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/award.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/award.svg new file mode 100644 index 0000000..e0108aa --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/award.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/bar-chart-2.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bar-chart-2.svg new file mode 100644 index 0000000..8e6976f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bar-chart-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/bar-chart.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bar-chart.svg new file mode 100644 index 0000000..e246c33 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bar-chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/battery-charging.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/battery-charging.svg new file mode 100644 index 0000000..6a5ce67 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/battery-charging.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/battery.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/battery.svg new file mode 100644 index 0000000..b33bfc9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/battery.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/bell-off.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bell-off.svg new file mode 100644 index 0000000..4814bda --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bell-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/bell.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bell.svg new file mode 100644 index 0000000..c2d254a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bell.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/bluetooth.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bluetooth.svg new file mode 100644 index 0000000..4758ab8 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bluetooth.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/bold.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bold.svg new file mode 100644 index 0000000..64c44c6 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/book-open.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/book-open.svg new file mode 100644 index 0000000..fb2b4c1 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/book-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/book.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/book.svg new file mode 100644 index 0000000..cae4b3c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/book.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/bookmark.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bookmark.svg new file mode 100644 index 0000000..4bf4782 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/bookmark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/box.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/box.svg new file mode 100644 index 0000000..b4b48dc --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/briefcase.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/briefcase.svg new file mode 100644 index 0000000..27e8892 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/briefcase.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/calendar.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/calendar.svg new file mode 100644 index 0000000..31cd864 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/camera-off.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/camera-off.svg new file mode 100644 index 0000000..bc83f52 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/camera-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/camera.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/camera.svg new file mode 100644 index 0000000..9f7d7ca --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/camera.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/cast.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cast.svg new file mode 100644 index 0000000..f2fc1d9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cast.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/check-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/check-circle.svg new file mode 100644 index 0000000..1ec567c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/check-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/check-square.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/check-square.svg new file mode 100644 index 0000000..39691c7 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/check-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/check.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/check.svg new file mode 100644 index 0000000..c4cf28e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevron-down.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevron-down.svg new file mode 100644 index 0000000..6e875fc --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevron-left.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevron-left.svg new file mode 100644 index 0000000..adc2d19 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevron-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevron-right.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevron-right.svg new file mode 100644 index 0000000..6c45ab6 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevron-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevron-up.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevron-up.svg new file mode 100644 index 0000000..c177fa4 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevron-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevrons-down.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevrons-down.svg new file mode 100644 index 0000000..465b89c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevrons-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevrons-left.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevrons-left.svg new file mode 100644 index 0000000..e24a7c6 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevrons-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevrons-right.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevrons-right.svg new file mode 100644 index 0000000..2a34344 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevrons-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevrons-up.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevrons-up.svg new file mode 100644 index 0000000..f387bcb --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chevrons-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/chrome.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chrome.svg new file mode 100644 index 0000000..e50d84f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/chrome.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/circle.svg new file mode 100644 index 0000000..10037ad --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/clipboard.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/clipboard.svg new file mode 100644 index 0000000..0b05e9e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/clipboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/clock.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/clock.svg new file mode 100644 index 0000000..779d5da --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/clock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-drizzle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-drizzle.svg new file mode 100644 index 0000000..3e3d812 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-drizzle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-lightning.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-lightning.svg new file mode 100644 index 0000000..7ff3107 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-lightning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-off.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-off.svg new file mode 100644 index 0000000..39c7cf4 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-rain.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-rain.svg new file mode 100644 index 0000000..3e9238f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-snow.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-snow.svg new file mode 100644 index 0000000..753deea --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud-snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud.svg new file mode 100644 index 0000000..591304d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/code.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/code.svg new file mode 100644 index 0000000..3ecf1c1 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/codepen.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/codepen.svg new file mode 100644 index 0000000..3a25028 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/codepen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/codesandbox.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/codesandbox.svg new file mode 100644 index 0000000..33ea099 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/codesandbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/coffee.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/coffee.svg new file mode 100644 index 0000000..181f9bd --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/coffee.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/columns.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/columns.svg new file mode 100644 index 0000000..27a29ea --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/columns.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/command.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/command.svg new file mode 100644 index 0000000..3115a15 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/command.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/compass.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/compass.svg new file mode 100644 index 0000000..7417099 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/compass.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/copy.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/copy.svg new file mode 100644 index 0000000..7fbc14a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-down-left.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-down-left.svg new file mode 100644 index 0000000..6e1a00d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-down-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-down-right.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-down-right.svg new file mode 100644 index 0000000..5bad382 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-down-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-left-down.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-left-down.svg new file mode 100644 index 0000000..594dbd6 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-left-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-left-up.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-left-up.svg new file mode 100644 index 0000000..f6cc298 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-left-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-right-down.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-right-down.svg new file mode 100644 index 0000000..ce99574 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-right-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-right-up.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-right-up.svg new file mode 100644 index 0000000..5d99e41 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-right-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-up-left.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-up-left.svg new file mode 100644 index 0000000..ff7a9a2 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-up-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-up-right.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-up-right.svg new file mode 100644 index 0000000..c040808 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/corner-up-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/cpu.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cpu.svg new file mode 100644 index 0000000..f6f7e81 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/cpu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/credit-card.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/credit-card.svg new file mode 100644 index 0000000..ad33256 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/credit-card.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/crop.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/crop.svg new file mode 100644 index 0000000..7661338 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/crop.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/crosshair.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/crosshair.svg new file mode 100644 index 0000000..79c8c8c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/crosshair.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/database.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/database.svg new file mode 100644 index 0000000..f0291b5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/database.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/delete.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/delete.svg new file mode 100644 index 0000000..971231c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/delete.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/disc.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/disc.svg new file mode 100644 index 0000000..ed75844 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/disc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/dollar-sign.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/dollar-sign.svg new file mode 100644 index 0000000..1c98a94 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/dollar-sign.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/download-cloud.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/download-cloud.svg new file mode 100644 index 0000000..1786988 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/download-cloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/download.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/download.svg new file mode 100644 index 0000000..85e2294 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/droplet.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/droplet.svg new file mode 100644 index 0000000..c09687a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/droplet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/edit-2.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/edit-2.svg new file mode 100644 index 0000000..eee1418 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/edit-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/edit-3.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/edit-3.svg new file mode 100644 index 0000000..7e365a9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/edit-3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/edit.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/edit.svg new file mode 100644 index 0000000..15662ba --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/external-link.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/external-link.svg new file mode 100644 index 0000000..34085ea --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/external-link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/eye-off.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/eye-off.svg new file mode 100644 index 0000000..31d949c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/eye-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/eye.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/eye.svg new file mode 100644 index 0000000..52a6aaa --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/facebook.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/facebook.svg new file mode 100644 index 0000000..6d9b581 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/facebook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/fast-forward.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/fast-forward.svg new file mode 100644 index 0000000..2e2db71 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/fast-forward.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/feather.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/feather.svg new file mode 100644 index 0000000..f8c4445 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/feather.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/figma.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/figma.svg new file mode 100644 index 0000000..bc6f43d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/figma.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/file-minus.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/file-minus.svg new file mode 100644 index 0000000..f613490 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/file-minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/file-plus.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/file-plus.svg new file mode 100644 index 0000000..07cdc86 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/file-plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/file-text.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/file-text.svg new file mode 100644 index 0000000..c9bb283 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/file-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/file.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/file.svg new file mode 100644 index 0000000..7114e2e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/film.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/film.svg new file mode 100644 index 0000000..026d7ee --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/film.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/filter.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/filter.svg new file mode 100644 index 0000000..0617a11 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/filter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/flag.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/flag.svg new file mode 100644 index 0000000..3a3ecee --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/flag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/folder-minus.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/folder-minus.svg new file mode 100644 index 0000000..4bc721e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/folder-minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/folder-plus.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/folder-plus.svg new file mode 100644 index 0000000..fdacf20 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/folder-plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/folder.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/folder.svg new file mode 100644 index 0000000..9877a7e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/folder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/framer.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/framer.svg new file mode 100644 index 0000000..92ce7d1 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/framer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/frown.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/frown.svg new file mode 100644 index 0000000..214d882 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/frown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/gift.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/gift.svg new file mode 100644 index 0000000..f5b50b3 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/gift.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/git-branch.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/git-branch.svg new file mode 100644 index 0000000..e4b8060 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/git-branch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/git-commit.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/git-commit.svg new file mode 100644 index 0000000..1349be4 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/git-commit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/git-merge.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/git-merge.svg new file mode 100644 index 0000000..f30bb9a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/git-merge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/git-pull-request.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/git-pull-request.svg new file mode 100644 index 0000000..e395e32 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/git-pull-request.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/github.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/github.svg new file mode 100644 index 0000000..8720b3c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/gitlab.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/gitlab.svg new file mode 100644 index 0000000..778e2da --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/gitlab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/globe.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/globe.svg new file mode 100644 index 0000000..c4c97ba --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/grid.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/grid.svg new file mode 100644 index 0000000..041349a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/grid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/hard-drive.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/hard-drive.svg new file mode 100644 index 0000000..e83055d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/hard-drive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/hash.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/hash.svg new file mode 100644 index 0000000..dd05f71 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/hash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/headphones.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/headphones.svg new file mode 100644 index 0000000..421adb9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/headphones.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/heart.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/heart.svg new file mode 100644 index 0000000..645e263 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/heart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/help-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/help-circle.svg new file mode 100644 index 0000000..d8daf80 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/help-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/hexagon.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/hexagon.svg new file mode 100644 index 0000000..42386ce --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/hexagon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/home.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/home.svg new file mode 100644 index 0000000..f74cb68 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/image.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/image.svg new file mode 100644 index 0000000..5818658 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/inbox.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/inbox.svg new file mode 100644 index 0000000..6610af8 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/inbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/info.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/info.svg new file mode 100644 index 0000000..dc9dff6 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/instagram.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/instagram.svg new file mode 100644 index 0000000..a8f5bc6 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/instagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/italic.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/italic.svg new file mode 100644 index 0000000..4f04b76 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/italic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/key.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/key.svg new file mode 100644 index 0000000..23e3035 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/key.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/layers.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/layers.svg new file mode 100644 index 0000000..8df4d8e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/layers.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/layout.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/layout.svg new file mode 100644 index 0000000..8b4cddb --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/layout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/life-buoy.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/life-buoy.svg new file mode 100644 index 0000000..1ab06c2 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/life-buoy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/link-2.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/link-2.svg new file mode 100644 index 0000000..c06b3c6 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/link-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/link.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/link.svg new file mode 100644 index 0000000..77307b1 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/linkedin.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/linkedin.svg new file mode 100644 index 0000000..b4b2d06 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/linkedin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/list.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/list.svg new file mode 100644 index 0000000..ec008f2 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/list.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/loader.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/loader.svg new file mode 100644 index 0000000..2229f66 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/loader.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/lock.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/lock.svg new file mode 100644 index 0000000..b0f7e8b --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/log-in.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/log-in.svg new file mode 100644 index 0000000..db56689 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/log-in.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/log-out.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/log-out.svg new file mode 100644 index 0000000..01f39a0 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/log-out.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/mail.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/mail.svg new file mode 100644 index 0000000..d15f5a2 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/mail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/map-pin.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/map-pin.svg new file mode 100644 index 0000000..93e802e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/map-pin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/map.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/map.svg new file mode 100644 index 0000000..266c5b5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/map.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/maximize-2.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/maximize-2.svg new file mode 100644 index 0000000..4ba4bff --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/maximize-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/maximize.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/maximize.svg new file mode 100644 index 0000000..e1bcd28 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/maximize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/meh.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/meh.svg new file mode 100644 index 0000000..fd4596f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/meh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/menu.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/menu.svg new file mode 100644 index 0000000..f30cca1 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/message-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/message-circle.svg new file mode 100644 index 0000000..6cca05a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/message-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/message-square.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/message-square.svg new file mode 100644 index 0000000..2c0bc22 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/message-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/mic-off.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/mic-off.svg new file mode 100644 index 0000000..bdcc709 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/mic-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/mic.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/mic.svg new file mode 100644 index 0000000..fc23acd --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/mic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/minimize-2.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/minimize-2.svg new file mode 100644 index 0000000..0700969 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/minimize-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/minimize.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/minimize.svg new file mode 100644 index 0000000..3458f23 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/minimize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/minus-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/minus-circle.svg new file mode 100644 index 0000000..2f4e877 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/minus-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/minus-square.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/minus-square.svg new file mode 100644 index 0000000..ae43378 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/minus-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/minus.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/minus.svg new file mode 100644 index 0000000..04bfd17 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/monitor.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/monitor.svg new file mode 100644 index 0000000..74ac518 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/monitor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/moon.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/moon.svg new file mode 100644 index 0000000..ec4498d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/more-horizontal.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/more-horizontal.svg new file mode 100644 index 0000000..eae67e4 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/more-horizontal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/more-vertical.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/more-vertical.svg new file mode 100644 index 0000000..a6e7174 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/more-vertical.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/mouse-pointer.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/mouse-pointer.svg new file mode 100644 index 0000000..f2c3a90 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/mouse-pointer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/move.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/move.svg new file mode 100644 index 0000000..babf953 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/move.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/music.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/music.svg new file mode 100644 index 0000000..d8a67ed --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/music.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/navigation-2.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/navigation-2.svg new file mode 100644 index 0000000..22e7fde --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/navigation-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/navigation.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/navigation.svg new file mode 100644 index 0000000..79bae77 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/navigation.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/octagon.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/octagon.svg new file mode 100644 index 0000000..d732e3c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/octagon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/package.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/package.svg new file mode 100644 index 0000000..a2b3869 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/package.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/paperclip.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/paperclip.svg new file mode 100644 index 0000000..af72d5b --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/paperclip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/pause-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/pause-circle.svg new file mode 100644 index 0000000..ca70383 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/pause-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/pause.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/pause.svg new file mode 100644 index 0000000..1c74f1a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/pause.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/pen-tool.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/pen-tool.svg new file mode 100644 index 0000000..8b0f6ef --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/pen-tool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/percent.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/percent.svg new file mode 100644 index 0000000..bdc3417 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/percent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-call.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-call.svg new file mode 100644 index 0000000..cb6f773 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-call.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-forwarded.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-forwarded.svg new file mode 100644 index 0000000..addbe0a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-forwarded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-incoming.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-incoming.svg new file mode 100644 index 0000000..1e5022e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-incoming.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-missed.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-missed.svg new file mode 100644 index 0000000..7db8a98 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-missed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-off.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-off.svg new file mode 100644 index 0000000..4eeac74 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-outgoing.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-outgoing.svg new file mode 100644 index 0000000..d34991a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone-outgoing.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone.svg new file mode 100644 index 0000000..ff389bf --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/phone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/pie-chart.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/pie-chart.svg new file mode 100644 index 0000000..05bf78f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/pie-chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/play-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/play-circle.svg new file mode 100644 index 0000000..7b793ac --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/play-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/play.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/play.svg new file mode 100644 index 0000000..7ff6ec3 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/play.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/plus-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/plus-circle.svg new file mode 100644 index 0000000..3db57c1 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/plus-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/plus-square.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/plus-square.svg new file mode 100644 index 0000000..12b0ba5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/plus-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/plus.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/plus.svg new file mode 100644 index 0000000..ce0aa2a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/pocket.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/pocket.svg new file mode 100644 index 0000000..7b15ba7 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/pocket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/power.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/power.svg new file mode 100644 index 0000000..c929bd4 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/power.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/printer.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/printer.svg new file mode 100644 index 0000000..aa5132c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/printer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/radio.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/radio.svg new file mode 100644 index 0000000..d43a3a0 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/radio.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/refresh-ccw.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/refresh-ccw.svg new file mode 100644 index 0000000..624867a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/refresh-ccw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/refresh-cw.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/refresh-cw.svg new file mode 100644 index 0000000..08f2410 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/refresh-cw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/repeat.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/repeat.svg new file mode 100644 index 0000000..0b5b4df --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/repeat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/rewind.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/rewind.svg new file mode 100644 index 0000000..205bba9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/rewind.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/rotate-ccw.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/rotate-ccw.svg new file mode 100644 index 0000000..321b313 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/rotate-ccw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/rotate-cw.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/rotate-cw.svg new file mode 100644 index 0000000..bd7b81f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/rotate-cw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/rss.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/rss.svg new file mode 100644 index 0000000..9edd12f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/rss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/save.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/save.svg new file mode 100644 index 0000000..07602b3 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/scissors.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/scissors.svg new file mode 100644 index 0000000..beaccd7 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/scissors.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/search.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/search.svg new file mode 100644 index 0000000..e140866 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/send.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/send.svg new file mode 100644 index 0000000..7acd04f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/send.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/server.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/server.svg new file mode 100644 index 0000000..12da01c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/settings.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/settings.svg new file mode 100644 index 0000000..5760862 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/share-2.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/share-2.svg new file mode 100644 index 0000000..2510fae --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/share-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/share.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/share.svg new file mode 100644 index 0000000..81585cd --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/share.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/shield-off.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/shield-off.svg new file mode 100644 index 0000000..39c3fc8 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/shield-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/shield.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/shield.svg new file mode 100644 index 0000000..3b1a1e1 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/shield.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/shopping-bag.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/shopping-bag.svg new file mode 100644 index 0000000..08f6e46 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/shopping-bag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/shopping-cart.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/shopping-cart.svg new file mode 100644 index 0000000..b9630c1 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/shopping-cart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/shuffle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/shuffle.svg new file mode 100644 index 0000000..4bb4e7f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/shuffle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/sidebar.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/sidebar.svg new file mode 100644 index 0000000..5e84d3e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/sidebar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/skip-back.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/skip-back.svg new file mode 100644 index 0000000..1255006 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/skip-back.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/skip-forward.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/skip-forward.svg new file mode 100644 index 0000000..aa65b12 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/skip-forward.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/slack.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/slack.svg new file mode 100644 index 0000000..dbd7fb7 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/slack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/slash.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/slash.svg new file mode 100644 index 0000000..d21181f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/slash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/sliders.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/sliders.svg new file mode 100644 index 0000000..84d8747 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/sliders.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/smartphone.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/smartphone.svg new file mode 100644 index 0000000..2efcdda --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/smartphone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/smile.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/smile.svg new file mode 100644 index 0000000..11e903a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/smile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/speaker.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/speaker.svg new file mode 100644 index 0000000..20d97bb --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/speaker.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/square.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/square.svg new file mode 100644 index 0000000..3a19622 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/star.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/star.svg new file mode 100644 index 0000000..c3d639f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/stop-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/stop-circle.svg new file mode 100644 index 0000000..c18e20d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/stop-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/sun.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/sun.svg new file mode 100644 index 0000000..d2c57a6 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/sun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/sunrise.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/sunrise.svg new file mode 100644 index 0000000..669c224 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/sunrise.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/sunset.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/sunset.svg new file mode 100644 index 0000000..a424f86 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/sunset.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/tablet.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/tablet.svg new file mode 100644 index 0000000..1b8b8be --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/tablet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/tag.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/tag.svg new file mode 100644 index 0000000..1f7f05e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/tag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/target.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/target.svg new file mode 100644 index 0000000..8b8b838 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/target.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/terminal.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/terminal.svg new file mode 100644 index 0000000..3ecf001 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/terminal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/thermometer.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/thermometer.svg new file mode 100644 index 0000000..8ebdc9d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/thermometer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/thumbs-down.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/thumbs-down.svg new file mode 100644 index 0000000..cb09313 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/thumbs-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/thumbs-up.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/thumbs-up.svg new file mode 100644 index 0000000..6fabfb9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/thumbs-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/toggle-left.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/toggle-left.svg new file mode 100644 index 0000000..8687c57 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/toggle-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/toggle-right.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/toggle-right.svg new file mode 100644 index 0000000..6eaac5e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/toggle-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/tool.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/tool.svg new file mode 100644 index 0000000..a04f508 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/tool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/trash-2.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/trash-2.svg new file mode 100644 index 0000000..bd2da1c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/trash-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/trash.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/trash.svg new file mode 100644 index 0000000..a6dddb8 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/trash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/trello.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/trello.svg new file mode 100644 index 0000000..3cfd290 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/trello.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/trending-down.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/trending-down.svg new file mode 100644 index 0000000..8204d10 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/trending-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/trending-up.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/trending-up.svg new file mode 100644 index 0000000..3ac41d6 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/trending-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/triangle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/triangle.svg new file mode 100644 index 0000000..8522ca5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/triangle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/truck.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/truck.svg new file mode 100644 index 0000000..4b3e9ac --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/truck.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/tv.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/tv.svg new file mode 100644 index 0000000..f564a15 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/tv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/twitch.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/twitch.svg new file mode 100644 index 0000000..27f1159 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/twitch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/twitter.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/twitter.svg new file mode 100644 index 0000000..7184c17 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/twitter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/type.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/type.svg new file mode 100644 index 0000000..fd9f29a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/type.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/umbrella.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/umbrella.svg new file mode 100644 index 0000000..bce8096 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/umbrella.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/underline.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/underline.svg new file mode 100644 index 0000000..c75fedb --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/underline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/unlock.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/unlock.svg new file mode 100644 index 0000000..235730f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/unlock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/upload-cloud.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/upload-cloud.svg new file mode 100644 index 0000000..e1f7cad --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/upload-cloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/upload.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/upload.svg new file mode 100644 index 0000000..a5c6bf9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/user-check.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/user-check.svg new file mode 100644 index 0000000..0a5d1ab --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/user-check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/user-minus.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/user-minus.svg new file mode 100644 index 0000000..b09b058 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/user-minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/user-plus.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/user-plus.svg new file mode 100644 index 0000000..64eda6c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/user-plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/user-x.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/user-x.svg new file mode 100644 index 0000000..619f6c3 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/user-x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/user.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/user.svg new file mode 100644 index 0000000..72d2270 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/users.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/users.svg new file mode 100644 index 0000000..e77a712 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/users.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/video-off.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/video-off.svg new file mode 100644 index 0000000..0c85fe3 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/video-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/video.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/video.svg new file mode 100644 index 0000000..034b02a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/video.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/voicemail.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/voicemail.svg new file mode 100644 index 0000000..708e808 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/voicemail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/volume-1.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/volume-1.svg new file mode 100644 index 0000000..45b9cd5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/volume-1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/volume-2.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/volume-2.svg new file mode 100644 index 0000000..7278eaf --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/volume-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/volume-x.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/volume-x.svg new file mode 100644 index 0000000..8190bac --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/volume-x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/volume.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/volume.svg new file mode 100644 index 0000000..e8c2832 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/volume.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/watch.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/watch.svg new file mode 100644 index 0000000..eaf24cc --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/watch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/wifi-off.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/wifi-off.svg new file mode 100644 index 0000000..6eea9f4 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/wifi-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/wifi.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/wifi.svg new file mode 100644 index 0000000..8b75868 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/wifi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/wind.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/wind.svg new file mode 100644 index 0000000..ebe4ad9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/wind.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/x-circle.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/x-circle.svg new file mode 100644 index 0000000..6c3813f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/x-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/x-octagon.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/x-octagon.svg new file mode 100644 index 0000000..f7f014e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/x-octagon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/x-square.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/x-square.svg new file mode 100644 index 0000000..50fe747 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/x-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/x.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/x.svg new file mode 100644 index 0000000..32c50ce --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/youtube.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/youtube.svg new file mode 100644 index 0000000..214f75c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/zap-off.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/zap-off.svg new file mode 100644 index 0000000..f68f368 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/zap-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/zap.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/zap.svg new file mode 100644 index 0000000..56be365 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/zap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/zoom-in.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/zoom-in.svg new file mode 100644 index 0000000..b17eb21 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/zoom-in.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/icons/zoom-out.svg b/home-modules/explicit-configs/awesome/awesome-buttons/icons/zoom-out.svg new file mode 100644 index 0000000..b18c7f6 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-buttons/icons/zoom-out.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-buttons/screenshot.gif b/home-modules/explicit-configs/awesome/awesome-buttons/screenshot.gif new file mode 100644 index 0000000..6584a45 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-buttons/screenshot.gif differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/.luacheckrc b/home-modules/explicit-configs/awesome/awesome-wm-widgets/.luacheckrc new file mode 100644 index 0000000..e4f47fa --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/.luacheckrc @@ -0,0 +1,24 @@ +self = false + +globals = { + "screen", + "mouse", + "root", + "client" +} + +read_globals = { + "awesome", + "button", + "dbus", + "drawable", + "drawin", + "key", + "keygrabber", + "mousegrabber", + "selection", + "tag", + "window", + "table.unpack", + "math.atan2", +} \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/CODEOWNERS b/home-modules/explicit-configs/awesome/awesome-wm-widgets/CODEOWNERS new file mode 100644 index 0000000..0661fff --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/CODEOWNERS @@ -0,0 +1 @@ +* @streetturtle diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/LICENSE b/home-modules/explicit-configs/awesome/awesome-wm-widgets/LICENSE new file mode 100644 index 0000000..1f89e49 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/README.md new file mode 100644 index 0000000..9a5819f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/README.md @@ -0,0 +1,63 @@ +

+ logo +

+ +

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

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

+ +

+ +Brightness, volume and battery widgets: + +

+ +

+ +![screenshot](./screenshot.png) + +Some more screenshots in this reddit [post](https://www.reddit.com/r/unixporn/comments/8qijmx/awesomewm_dark_theme/) + +# Installation + +Clone the repo under **~/.config/awesome/**, then follow an Installation section of widget's readme file. + +# Stargazers + +[![Stargazers over time](https://starchart.cc/streetturtle/awesome-wm-widgets.svg)](https://starchart.cc/streetturtle/awesome-wm-widgets) + +# Troubleshooting + +In case of any doubts/questions/problems: + - create an [issue](https://github.com/streetturtle/awesome-wm-widgets/issues/new/choose) + - raise a question on [Discussions](https://github.com/streetturtle/awesome-wm-widgets/discussions)! + - ping me on AwesomeWM's discord, here's an [invite](https://discord.gg/BPat4F87dg) + +# Support + +If you find anything useful here, you can: + - star a repo - this really motivates me to work on this project + - or + - or even become a [sponsor](https://github.com/sponsors/streetturtle) + +# Contributors + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/Screenshot from 2019-03-01 14-28-18.png b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/Screenshot from 2019-03-01 14-28-18.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/README.md new file mode 100644 index 0000000..b09b2cb --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/README.md @@ -0,0 +1,44 @@ +# 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 + +## Customizations + +It is possible to customize widget by providing a table with the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `icon_control` | `false` | Set or unset whether _any_ click on the screen closes the dropdown after it has been opened. | + + +## 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(), + ... +``` + +Or use the following. + +```lua + ... + apt_widget({icon_control=true}), + ... +``` + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/apt-widget.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/apt-widget.lua new file mode 100644 index 0000000..f560cb6 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/apt-widget.lua @@ -0,0 +1,361 @@ +------------------------------------------------- +-- 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") + +--- Utility Function to handle alternative paths +local function script_path() + local str = debug.getinfo(2, "S").source:sub(2) + return str:match("(.*/)") +end + +local WIDGET_DIR = script_path() +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 = "APT 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, + }, + layout = wibox.container.place, + }, + visible = true, + 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 icon_control = args.icon_control + + 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(gears.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(gears.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(gears.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))) + + wibox_popup:connect_signal("mouse::leave", function() + if wibox_popup.visible and not icon_control then + wibox_popup.visible = false + end + end) + + return apt_widget +end + +return setmetatable(apt_widget, { + __call = function(_, ...) + return worker(...) + end, +}) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/black.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/black.svg new file mode 100644 index 0000000..95c8410 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/black.svg @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/help-circle.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/help-circle.svg new file mode 100644 index 0000000..51fddd8 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/help-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/orange.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/orange.svg new file mode 100644 index 0000000..0ec1388 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/orange.svg @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/refresh-cw.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/refresh-cw.svg new file mode 100644 index 0000000..39f52a5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/refresh-cw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/watch.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/watch.svg new file mode 100644 index 0000000..661a560 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/watch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/white-black.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/white-black.svg new file mode 100644 index 0000000..dc7ee55 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/white-black.svg @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/white-orange.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/white-orange.svg new file mode 100644 index 0000000..c353bb5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/icons/white-orange.svg @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/screenshots/screenshot.gif b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/screenshots/screenshot.gif new file mode 100644 index 0000000..6ffe2aa Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/apt-widget/screenshots/screenshot.gif differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/awesome-o.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/awesome-o.png new file mode 100644 index 0000000..424d008 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/awesome-o.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/awesome.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/awesome.png new file mode 100644 index 0000000..6960955 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/awesome.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/README.md new file mode 100644 index 0000000..8fc07f7 --- /dev/null +++ b/home-modules/explicit-configs/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` | Font | +| `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/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/bat-wid-1.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/bat-wid-1.png new file mode 100644 index 0000000..00e1618 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/bat-wid-1.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/bat-wid-2.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/bat-wid-2.png new file mode 100644 index 0000000..ae20af2 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/bat-wid-2.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/bat-wid-22.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/bat-wid-22.png new file mode 100644 index 0000000..38761f7 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/bat-wid-22.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/bat-wid-3.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/bat-wid-3.png new file mode 100644 index 0000000..352b496 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/bat-wid-3.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/battery.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/battery.lua new file mode 100644 index 0000000..4b02a7c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/battery.lua @@ -0,0 +1,209 @@ +------------------------------------------------- +-- 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 + -- Match a line with status and charge level + local status, charge_str, _ = string.match(s, '.+: ([%a%s]+), (%d?%d?%d)%%,?(.*)') + if status ~= nil then + -- Enforce that for each entry in battery_info there is an + -- entry in capacities of zero. If a battery has status + -- "Unknown" then there is no capacity reported and we treat it + -- as zero capactiy for later calculations. + table.insert(battery_info, {status = status, charge = tonumber(charge_str)}) + table.insert(capacities, 0) + end + + -- Match a line where capacity is reported + local cap_str = string.match(s, '.+:.+last full capacity (%d+)') + if cap_str ~= nil then + capacities[#capacities] = tonumber(cap_str) or 0 + end + end + + local capacity = 0 + 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 + + -- Adds up total (capacity-weighted) charge and total capacity. + -- It effectively ignores batteries with status "Unknown" as we + -- treat them with capacity zero. + charge = charge + batt.charge * capacities[i] + capacity = capacity + 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/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/spaceman.jpg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/spaceman.jpg new file mode 100644 index 0000000..73ddaf3 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/battery-widget/spaceman.jpg differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/10_c.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/10_c.png new file mode 100644 index 0000000..3faf753 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/10_c.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/10_d.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/10_d.png new file mode 100644 index 0000000..c9aa8d3 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/10_d.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/20_c.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/20_c.png new file mode 100644 index 0000000..f0a191d Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/20_c.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/20_d.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/20_d.png new file mode 100644 index 0000000..15fabed Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/20_d.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/80_c.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/80_c.png new file mode 100644 index 0000000..e6dae75 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/80_c.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/80_d.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/80_d.png new file mode 100644 index 0000000..220c8e3 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/80_d.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/README.md new file mode 100644 index 0000000..6f878e0 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/batteryarc.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/batteryarc.lua new file mode 100644 index 0000000..b2b25ae --- /dev/null +++ b/home-modules/explicit-configs/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 and charge > 0 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/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/spaceman.jpg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/spaceman.jpg new file mode 100644 index 0000000..73ddaf3 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/spaceman.jpg differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/warning.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/warning.png new file mode 100644 index 0000000..55ca790 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/batteryarc-widget/warning.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/README.md new file mode 100644 index 0000000..6bcc119 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/bitbucket-icon-gradient-blue.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/bitbucket-icon-gradient-blue.svg new file mode 100644 index 0000000..ea700ea --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/bitbucket-icon-gradient-blue.svg @@ -0,0 +1 @@ +bitbucket-icon-gradient-blue \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/bitbucket.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/bitbucket.lua new file mode 100644 index 0000000..b85e653 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/check.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/check.svg new file mode 100644 index 0000000..e9e44ac --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/clipboard.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/clipboard.svg new file mode 100644 index 0000000..5c6dfd3 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/clipboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/copy.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/copy.svg new file mode 100644 index 0000000..bab2098 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/git-pull-request.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/git-pull-request.svg new file mode 100644 index 0000000..c2e2867 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/git-pull-request.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/message-circle.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/message-circle.svg new file mode 100644 index 0000000..43eacbb --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/message-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/test_bitbucket_api.sh b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/test_bitbucket_api.sh new file mode 100755 index 0000000..378b3ef --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/user.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/user.svg new file mode 100644 index 0000000..4058dee --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/bitbucket-widget/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/brightness-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/brightness-widget/README.md new file mode 100644 index 0000000..f10adb3 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/brightness-widget/README.md @@ -0,0 +1,95 @@ +# 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 | +| `rmb_set_max` | false | Right mouse click sets the brightness level to maximum | + +_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/home-modules/explicit-configs/awesome/awesome-wm-widgets/brightness-widget/br-wid-1.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/brightness-widget/br-wid-1.png new file mode 100644 index 0000000..b00b0e6 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/brightness-widget/br-wid-1.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/brightness-widget/brightness.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/brightness-widget/brightness.lua new file mode 100644 index 0000000..7cc2379 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/brightness-widget/brightness.lua @@ -0,0 +1,200 @@ +------------------------------------------------- +-- 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 + local rmb_set_max = args.rmb_set_max or false + if program == 'light' then + get_brightness_cmd = 'light -G' + set_brightness_cmd = 'light -S %d' -- + inc_brightness_cmd = 'light -A ' .. step + dec_brightness_cmd = 'light -U ' .. step + elseif program == 'xbacklight' then + get_brightness_cmd = 'xbacklight -get' + set_brightness_cmd = 'xbacklight -set %d' -- + inc_brightness_cmd = 'xbacklight -inc ' .. step + dec_brightness_cmd = 'xbacklight -dec ' .. step + elseif program == 'brightnessctl' then + get_brightness_cmd = "sh -c 'brightnessctl -m | cut -d, -f4 | tr -d %'" + set_brightness_cmd = "brightnessctl set %d%%" -- + inc_brightness_cmd = "brightnessctl set +" .. step .. "%" + dec_brightness_cmd = "brightnessctl set " .. step .. "-%" + else + show_warning(program .. " command is not supported by the widget") + return + end + + if type == 'icon_and_text' then + brightness_widget.widget = wibox.widget { + { + { + image = path_to_icon, + resize = false, + widget = wibox.widget.imagebox, + }, + valign = 'center', + layout = wibox.container.place + }, + { + id = 'txt', + font = font, + widget = wibox.widget.textbox + }, + spacing = 4, + layout = wibox.layout.fixed.horizontal, + set_value = function(self, level) + local display_level = level + if percentage then + display_level = display_level .. '%' + end + self:get_children_by_id('txt')[1]:set_text(display_level) + end + } + elseif type == 'arc' then + brightness_widget.widget = wibox.widget { + { + { + image = path_to_icon, + resize = true, + widget = wibox.widget.imagebox, + }, + valign = 'center', + layout = wibox.container.place + }, + max_value = 100, + thickness = 2, + start_angle = 4.71238898, -- 2pi*3/4 + forced_height = 18, + forced_width = 18, + paddings = 2, + widget = wibox.container.arcchart, + set_value = function(self, level) + self:set_value(level) + end + } + else + show_warning(type .. " type is not supported by the widget") + return + + end + + local update_widget = function(widget, stdout, _, _, _) + local brightness_level = tonumber(string.format("%.0f", stdout)) + current_level = brightness_level + widget:set_value(brightness_level) + end + + function brightness_widget:set(value) + current_level = value + spawn.easy_async(string.format(set_brightness_cmd, value), function() + spawn.easy_async_with_shell(get_brightness_cmd, function(out) + update_widget(brightness_widget.widget, out) + end) + end) + end + local old_level = 0 + function brightness_widget:toggle() + if rmb_set_max then + brightness_widget:set(100) + else + if old_level < 0.1 then + -- avoid toggling between '0' and 'almost 0' + old_level = 1 + end + if current_level < 0.1 then + -- restore previous level + current_level = old_level + else + -- save current brightness for later + old_level = current_level + current_level = 0 + end + brightness_widget:set(current_level) + end + end + function brightness_widget:inc() + spawn.easy_async(inc_brightness_cmd, function() + spawn.easy_async_with_shell(get_brightness_cmd, function(out) + update_widget(brightness_widget.widget, out) + end) + end) + end + function brightness_widget:dec() + spawn.easy_async(dec_brightness_cmd, function() + spawn.easy_async_with_shell(get_brightness_cmd, function(out) + update_widget(brightness_widget.widget, out) + end) + end) + end + + brightness_widget.widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() brightness_widget:set(base) end), + awful.button({}, 3, function() brightness_widget:toggle() end), + awful.button({}, 4, function() brightness_widget:inc() end), + awful.button({}, 5, function() brightness_widget:dec() end) + ) + ) + + watch(get_brightness_cmd, timeout, update_widget, brightness_widget.widget) + + if tooltip then + awful.tooltip { + objects = { brightness_widget.widget }, + timer_function = function() + return current_level .. " %" + end, + } + end + + return brightness_widget.widget +end + +return setmetatable(brightness_widget, { + __call = function(_, ...) + return worker(...) + end, +}) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/brightness-widget/brightness.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/brightness-widget/brightness.svg new file mode 100644 index 0000000..d334372 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/brightness-widget/brightness.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/README.md new file mode 100644 index 0000000..c16930a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/README.md @@ -0,0 +1,92 @@ +# Calendar Widget + +Calendar widget for Awesome WM - slightly improved version of the `wibox.widget.calendar`. + +## Features + + +### Customization + +| Name | Default | Description | +|--------------|-----------|-------------------------------------| +| theme | `naughty` | The theme to use | +| placement | `top` | The position of the popup | +| radius | 8 | The popup radius | +| start_sunday | false | Start the week on Sunday | +| week_numbers | false | Show ISO week numbers (Mon = first) | +| auto_hide | false | Auto hide the popup after timeout | +| timeout | 2 | Auto hide timeout length | + + - themes: + + | Name | Screenshot | + |---|---| + | nord | ![nord_theme](./nord.png) | + | outrun | ![outrun_theme](./outrun.png) | + | light | ![outrun_theme](./light.png) | + | dark | ![outrun_theme](./dark.png) | + | naughty (default) | from local theme | + + - setup widget placement + + top center - in case you clock is centered: + + ![calendar_top](./calendar_top.png) + + top right - for default awesome config: + + ![calendar_top_right](./calendar_top_right.png) + + bottom right - in case your wibar at the bottom: + + ![calendar_bottom_right](./calendar_bottom_right.png) + + - setup first day of week + + By setting `start_sunday` to true: + ![calendar_start_sunday](./calendar_start_sunday.png) + + - mouse support: + move to the next and previous month. Using mouse buttons or scroll wheel. + + You can configure this by specifying the button to move to next/previous. + Usually these are configured as follows. If you want to use other mouse buttons, you can find their number using `xev`. + + | number | button | + |--------|---------------| + | 4 | scroll up | + | 5 | scroll down | + | 1 | left click | + | 2 | right click | + | 3 | middles click | + + By default `previous_month_button` is 5, `next_month_button` is 4. + + +## How to use + +This widget needs an 'anchor' - another widget which triggers visibility of the calendar. Default `mytextclock` is the perfect candidate! +Just after mytextclock is instantiated, create the widget and add the mouse listener to it. + +```lua +local calendar_widget = require("awesome-wm-widgets.calendar-widget.calendar") +-- ... +-- Create a textclock widget +mytextclock = wibox.widget.textclock() +-- default +local cw = calendar_widget() +-- or customized +local cw = calendar_widget({ + theme = 'outrun', + placement = 'bottom_right', + start_sunday = true, + radius = 8, +-- with customized next/previous (see table above) + previous_month_button = 1, + next_month_button = 3, +}) +mytextclock:connect_signal("button::press", + function(_, _, _, button) + if button == 1 then cw.toggle() end + end) +``` diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar.lua new file mode 100644 index 0000000..bfc4104 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar.lua @@ -0,0 +1,285 @@ +------------------------------------------------- +-- Calendar Widget for Awesome Window Manager +-- Shows the current month and supports scroll up/down to switch month +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/calendar-widget + +-- @author Pavel Makhov +-- @copyright 2019 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local beautiful = require("beautiful") +local wibox = require("wibox") +local gears = require("gears") +local naughty = require("naughty") + +local calendar_widget = {} + +local function worker(user_args) + + local calendar_themes = { + nord = { + bg = '#2E3440', + fg = '#D8DEE9', + focus_date_bg = '#88C0D0', + focus_date_fg = '#000000', + weekend_day_bg = '#3B4252', + weekday_fg = '#88C0D0', + header_fg = '#E5E9F0', + border = '#4C566A' + }, + outrun = { + bg = '#0d0221', + fg = '#D8DEE9', + focus_date_bg = '#650d89', + focus_date_fg = '#2de6e2', + weekend_day_bg = '#261447', + weekday_fg = '#2de6e2', + header_fg = '#f6019d', + border = '#261447' + }, + dark = { + bg = '#000000', + fg = '#ffffff', + focus_date_bg = '#ffffff', + focus_date_fg = '#000000', + weekend_day_bg = '#444444', + weekday_fg = '#ffffff', + header_fg = '#ffffff', + border = '#333333' + }, + light = { + bg = '#ffffff', + fg = '#000000', + focus_date_bg = '#000000', + focus_date_fg = '#ffffff', + weekend_day_bg = '#AAAAAA', + weekday_fg = '#000000', + header_fg = '#000000', + border = '#CCCCCC' + }, + monokai = { + bg = '#272822', + fg = '#F8F8F2', + focus_date_bg = '#AE81FF', + focus_date_fg = '#ffffff', + weekend_day_bg = '#75715E', + weekday_fg = '#FD971F', + header_fg = '#F92672', + border = '#75715E' + }, + naughty = { + bg = beautiful.notification_bg or beautiful.bg or beautiful.bg_normal, + fg = beautiful.notification_fg or beautiful.fg or beautiful.fg_normal, + focus_date_bg = beautiful.notification_fg or beautiful.fg or beautiful.fg_normal, + focus_date_fg = beautiful.notification_bg or beautiful.bg or beautiful.bg_normal, + weekend_day_bg = beautiful.bg_focus, + weekday_fg = beautiful.fg, + header_fg = beautiful.fg, + border = beautiful.border_normal + } + + } + + local args = user_args or {} + + if args.theme ~= nil and calendar_themes[args.theme] == nil then + naughty.notify({ + preset = naughty.config.presets.critical, + title = 'Calendar Widget', + text = 'Theme "' .. args.theme .. '" not found, fallback to default'}) + args.theme = 'naughty' + end + + local theme = args.theme or 'naughty' + local placement = args.placement or 'top' + local radius = args.radius or 8 + local next_month_button = args.next_month_button or 4 + local previous_month_button = args.previous_month_button or 5 + local start_sunday = args.start_sunday or false + local week_numbers = args.week_numbers or false + + local styles = {} + local function rounded_shape(size) + return function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, size) + end + end + + styles.month = { + padding = 4, + bg_color = calendar_themes[theme].bg, + border_width = 0, + } + + styles.normal = { + markup = function(t) return t end, + shape = rounded_shape(4) + } + + styles.focus = { + fg_color = calendar_themes[theme].focus_date_fg, + bg_color = calendar_themes[theme].focus_date_bg, + markup = function(t) return '' .. t .. '' end, + shape = rounded_shape(4) + } + + styles.header = { + fg_color = calendar_themes[theme].header_fg, + bg_color = calendar_themes[theme].bg, + markup = function(t) return '' .. t .. '' end + } + + styles.weekday = { + fg_color = calendar_themes[theme].weekday_fg, + bg_color = calendar_themes[theme].bg, + markup = function(t) return '' .. t .. '' end, + } + + local function decorate_cell(widget, flag, date) + if flag == 'monthheader' and not styles.monthheader then + flag = 'header' + end + + -- highlight only today's day + if flag == 'focus' then + local today = os.date('*t') + if not (today.month == date.month and today.year == date.year) then + flag = 'normal' + end + end + + local props = styles[flag] or {} + if props.markup and widget.get_text and widget.set_markup then + widget:set_markup(props.markup(widget:get_text())) + end + -- Change bg color for weekends + local default_bg + if (flag == "normal") then + local d = { year = date.year, month = (date.month or 1), day = (date.day or 1) } + local weekday = tonumber(os.date('%w', os.time(d))) + default_bg = (weekday == 0 or weekday == 6) + and calendar_themes[theme].weekend_day_bg + or calendar_themes[theme].bg + end + local ret = wibox.widget { + { + { + widget, + halign = 'center', + widget = wibox.container.place + }, + margins = (props.padding or 2) + (props.border_width or 0), + widget = wibox.container.margin + }, + shape = props.shape, + shape_border_color = props.border_color or '#000000', + shape_border_width = props.border_width or 0, + fg = props.fg_color or calendar_themes[theme].fg, + bg = props.bg_color or default_bg, + widget = wibox.container.background + } + + return ret + end + + local cal = wibox.widget { + date = os.date('*t'), + font = beautiful.get_font(), + fn_embed = decorate_cell, + long_weekdays = true, + start_sunday = start_sunday, + week_numbers = week_numbers, + widget = wibox.widget.calendar.month + } + + local popup = awful.popup { + ontop = true, + visible = false, + shape = rounded_shape(radius), + offset = { y = 5 }, + border_width = 1, + border_color = calendar_themes[theme].border, + widget = cal + } + + local auto_hide_timer = gears.timer({ + timeout = user_args.timeout or 2, + single_shot = true, + callback = function() + calendar_widget.toggle() + end, + }) + + popup:connect_signal("mouse::leave", function() + if user_args.auto_hide then + auto_hide_timer:again() + end + end) + popup:connect_signal("mouse::enter", function() + auto_hide_timer:stop() + end) + + popup:buttons( + awful.util.table.join( + awful.button({}, next_month_button, function() + local a = cal:get_date() + a.month = a.month + 1 + cal:set_date(nil) + cal:set_date(a) + popup:set_widget(cal) + end), + awful.button({}, previous_month_button, function() + local a = cal:get_date() + a.month = a.month - 1 + cal:set_date(nil) + cal:set_date(a) + popup:set_widget(cal) + end) + ) + ) + + function calendar_widget.toggle() + + if popup.visible then + auto_hide_timer:stop() + -- to faster render the calendar refresh it and just hide + cal:set_date(nil) -- the new date is not set without removing the old one + cal:set_date(os.date('*t')) + popup:set_widget(nil) -- just in case + popup:set_widget(cal) + popup.visible = not popup.visible + else + if placement == 'top' then + awful.placement.top(popup, { margins = { top = 30 }, parent = awful.screen.focused() }) + elseif placement == 'top_right' then + awful.placement.top_right(popup, { margins = { top = 30, right = 10}, parent = awful.screen.focused() }) + elseif placement == 'top_left' then + awful.placement.top_left(popup, { margins = { top = 30, left = 10}, parent = awful.screen.focused() }) + elseif placement == 'bottom_right' then + awful.placement.bottom_right(popup, { margins = { bottom = 30, right = 10}, + parent = awful.screen.focused() }) + elseif placement == 'bottom_left' then + awful.placement.bottom_left(popup, { margins = { bottom = 30, left = 10}, + parent = awful.screen.focused() }) + else + awful.placement.top(popup, { margins = { top = 30 }, parent = awful.screen.focused() }) + end + + popup.visible = true + if user_args.auto_hide then + auto_hide_timer:start() + end + + + end + end + + return calendar_widget + +end + +return setmetatable(calendar_widget, { __call = function(_, ...) + return worker(...) +end }) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar_bottom_right.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar_bottom_right.png new file mode 100644 index 0000000..2bc2e82 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar_bottom_right.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar_start_sunday.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar_start_sunday.png new file mode 100644 index 0000000..126a218 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar_start_sunday.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar_top.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar_top.png new file mode 100644 index 0000000..3e6b66b Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar_top.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar_top_right.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar_top_right.png new file mode 100644 index 0000000..4a29022 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/calendar_top_right.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/dark.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/dark.png new file mode 100644 index 0000000..540289f Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/dark.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/light.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/light.png new file mode 100644 index 0000000..ab675d1 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/light.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/nord.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/nord.png new file mode 100644 index 0000000..94f9f7e Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/nord.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/outrun.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/outrun.png new file mode 100644 index 0000000..d59c123 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/calendar-widget/outrun.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/cmus-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cmus-widget/README.md new file mode 100644 index 0000000..431533e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cmus-widget/README.md @@ -0,0 +1,54 @@ +# Cmus widget + +Cmus widget that shows the current playing track. + +![widget](./screenshots/cmus-widget.png) + +Left click toggles playback. + +## Installation + +Clone the repo under **~/.config/awesome/** and add widget in **rc.lua**: + +```lua +local cmus_widget = require('awesome-wm-widgets.cmus-widget.cmus') +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + cmus_widget(), + -- customized + cmus_widget{ + space = 5, + timeout = 5 + }, +``` + +### Shortcuts + +To improve responsiveness of the widget when playback is changed by a shortcut use corresponding methods of the widget: + +```lua +awful.key({ modkey, "Shift" }, "p", function () cmus_widget:play_pause() end, {description = "toggle track", group = "cmus"}), +awful.key({ }, "XF86AudioPlay", function () cmus_widget:play() end, {description = "play track", group = "cmus"}), +awful.key({ }, "XF86AudioPause", function () cmus_widget:play() end, {description = "pause track", group = "cmus"}), +awful.key({ }, "XF86AudioNext", function () cmus_widget:next_track() end, {description = "next track", group = "cmus"}), +awful.key({ }, "XF86AudioPrev", function () cmus_widget:prev_track() end, {description = "previous track", group = "cmus"}), +awful.key({ }, "XF86AudioStop", function () cmus_widget:stop() end, {description = "stop track", group = "cmus"}), +``` + +## Customization + +It is possible to customize the widget by providing a table with all or some of the following config parameters: + +### Generic parameter + +| Name | Default | Description | +|---|---|---| +| `font` | `beautiful.font` | Font name and size, like `Play 12` | +| `path_to_icons` | `/usr/share/icons/Arc/actions/symbolic/` | Alternative path for the icons | +| `timeout`| 10 | Refresh cooldown | +| `max_length` | 30 | Maximum lentgh of title. Text will be ellipsized if longer. | +| `space` | 3 | Space between icon and track title | diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/cmus-widget/cmus.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cmus-widget/cmus.lua new file mode 100644 index 0000000..a2eddf5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cmus-widget/cmus.lua @@ -0,0 +1,160 @@ +------------------------------------------------- +-- Cmus Widget for Awesome Window Manager +-- Show what's playing, play/pause, etc + +-- @author Augusto Gunsch +-- @copyright 2022 Augusto Gunsch +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") +local spawn = require("awful.spawn") +local beautiful = require('beautiful') + +local 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 cmus_widget = {} + +local function worker(user_args) + + local args = user_args or {} + local font = args.font or beautiful.font + + local path_to_icons = args.path_to_icons or "/usr/share/icons/Arc/actions/symbolic/" + local timeout = args.timeout or 10 + local max_length = args.max_length or 30 + local space = args.space or 3 + + cmus_widget.widget = wibox.widget { + { + { + id = "playback_icon", + resize = false, + widget = wibox.widget.imagebox, + }, + layout = wibox.container.place + }, + { + id = "text", + font = font, + widget = wibox.widget.textbox + }, + spacing = space, + layout = wibox.layout.fixed.horizontal, + update_icon = function(self, name) + self:get_children_by_id("playback_icon")[1]:set_image(path_to_icons .. name) + end, + set_title = function(self, title) + self:get_children_by_id("text")[1]:set_text(title) + end + } + + local function update_widget(widget, stdout, _, _, code) + if code == 0 then + local cmus_info = {} + + for s in stdout:gmatch("[^\r\n]+") do + local key, val = string.match(s, "^tag (%a+) (.+)$") + + if key and val then + cmus_info[key] = val + else + key, val = string.match(s, "^set (%a+) (.+)$") + + if key and val then + cmus_info[key] = val + else + key, val = string.match(s, "^(%a+) (.+)$") + if key and val then + cmus_info[key] = val + end + end + end + end + + local title = cmus_info.title + + if not title and cmus_info.file then + title = cmus_info.file:gsub("%..-$", "") + title = title:gsub("^.+/", "") + end + + if title then + if cmus_info["status"] == "playing" then + widget:update_icon("media-playback-start-symbolic.svg") + elseif cmus_info["status"] == "paused" then + widget:update_icon("media-playback-pause-symbolic.svg") + else + widget:update_icon("media-playback-stop-symbolic.svg") + end + + widget:set_title(ellipsize(title, max_length)) + widget.visible = true + else + widget.visible = false + end + else + widget.visible = false + end + end + + function cmus_widget:update() + spawn.easy_async("cmus-remote -Q", + function(stdout, _, _, code) + update_widget(cmus_widget.widget, stdout, _, _, code) + end) + end + + function cmus_widget:play_pause() + spawn("cmus-remote -u") + cmus_widget.update() + end + + function cmus_widget:pause() + spawn("cmus-remote -U") + cmus_widget.update() + end + + function cmus_widget:play() + spawn("cmus-remote -p") + cmus_widget.update() + end + + function cmus_widget:next_track() + spawn("cmus-remote -n") + cmus_widget.update() + end + + function cmus_widget:prev_track() + spawn("cmus-remote -r") + cmus_widget.update() + end + + function cmus_widget:stop() + spawn("cmus-remote -s") + cmus_widget.update() + end + + cmus_widget.widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() cmus_widget:play_pause() end) + ) + ) + + watch("cmus-remote -Q", timeout, update_widget, cmus_widget.widget) + + return cmus_widget.widget +end + +return setmetatable(cmus_widget, { __call = function(_, ...) + return worker(...) +end }) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/cmus-widget/screenshots/cmus-widget.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cmus-widget/screenshots/cmus-widget.png new file mode 100644 index 0000000..ba04401 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cmus-widget/screenshots/cmus-widget.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/README.md new file mode 100644 index 0000000..b1a3eae --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/README.md @@ -0,0 +1,71 @@ +# CPU widget + +[![GitHub issues by-label](https://img.shields.io/github/issues-raw/streetturtle/awesome-wm-widgets/cpu)](https://github.com/streetturtle/awesome-wm-widgets/labels/cpu) + +This widget shows the average CPU load among all cores of the machine: + +![screenshot](./cpu.gif) + +## How it works + +To measure the load I took Paul Colby's bash [script](http://colby.id.au/calculating-cpu-usage-from-proc-stat/) and rewrote it in Lua, which was quite simple. +So awesome simply reads the first line of /proc/stat: + +```bash +$ cat /proc/stat | grep '^cpu ' +cpu 197294 718 50102 2002182 3844 0 2724 0 0 0 +``` + +and calculates the percentage. + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `width` | 50 | Width of the widget | +| `step_width` | 2 | Width of the step | +| `step_spacing` | 1 | Space size between steps | +| `color` | `beautiful.fg_normal` | Color of the graph | +| `enable_kill_button` | false | Show button which kills the process | +| `process_info_max_length` | -1 | Truncate the process information. Some processes may have a very long list of parameters which won't fit in the screen, this options allows to truncate it to the given length. | +| `timeout` | 1 | How often in seconds the widget refreshes | + +### Example + +```lua +cpu_widget({ + width = 70, + step_width = 2, + step_spacing = 0, + color = '#434c5e' +}) +``` + +The config above results in the following widget: + +![custom](./custom.png) + +## Installation + +Clone/download repo and use widget in **rc.lua**: + +```lua +local cpu_widget = require("awesome-wm-widgets.cpu-widget.cpu-widget") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + cpu_widget(), + -- or custom + cpu_widget({ + width = 70, + step_width = 2, + step_spacing = 0, + color = '#434c5e' + }) + ... +``` diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/cpu-widget.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/cpu-widget.lua new file mode 100644 index 0000000..3cd0e8d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/cpu-widget.lua @@ -0,0 +1,329 @@ +------------------------------------------------- +-- CPU Widget for Awesome Window Manager +-- Shows the current CPU utilization +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/cpu-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local watch = require("awful.widget.watch") +local wibox = require("wibox") +local beautiful = require("beautiful") +local gears = require("gears") + +local CMD = [[sh -c "grep '^cpu.' /proc/stat; ps -eo 'pid:10,pcpu:5,pmem:5,comm:30,cmd' --sort=-pcpu ]] + .. [[| grep -v [p]s | grep -v [g]rep | head -11 | tail -n +2"]] + +-- A smaller command, less resource intensive, used when popup is not shown. +local CMD_slim = [[grep --max-count=1 '^cpu.' /proc/stat]] + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/cpu-widget' + +local cpu_widget = {} +local cpu_rows = { + spacing = 4, + layout = wibox.layout.fixed.vertical, +} +local is_update = true +local process_rows = { + layout = wibox.layout.fixed.vertical, +} + +-- Remove spaces at end and beggining of a string +local function trim(s) + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + +-- Checks if a string starts with a another string +local function starts_with(str, start) + return str:sub(1, #start) == start +end + + +local function create_textbox(args) + return wibox.widget{ + text = args.text, + align = args.align or 'left', + markup = args.markup, + forced_width = args.forced_width or 40, + widget = wibox.widget.textbox + } +end + +local function create_process_header(params) + local res = wibox.widget{ + create_textbox{markup = 'PID'}, + create_textbox{markup = 'Name'}, + { + create_textbox{markup = '%CPU'}, + create_textbox{markup = '%MEM'}, + params.with_action_column and create_textbox{forced_width = 20} or nil, + layout = wibox.layout.align.horizontal + }, + layout = wibox.layout.ratio.horizontal + } + res:ajust_ratio(2, 0.2, 0.47, 0.33) + + return res +end + +local function create_kill_process_button() + return wibox.widget{ + { + id = "icon", + image = WIDGET_DIR .. '/window-close-symbolic.svg', + resize = false, + opacity = 0.1, + widget = wibox.widget.imagebox + }, + widget = wibox.container.background + } +end + +local function worker(user_args) + + local args = user_args or {} + + local width = args.width or 50 + local step_width = args.step_width or 2 + local step_spacing = args.step_spacing or 1 + local color = args.color or beautiful.fg_normal + local background_color = args.background_color or "#00000000" + local enable_kill_button = args.enable_kill_button or false + local process_info_max_length = args.process_info_max_length or -1 + local timeout = args.timeout or 1 + + local cpugraph_widget = wibox.widget { + max_value = 100, + background_color = background_color, + forced_width = width, + step_width = step_width, + step_spacing = step_spacing, + widget = wibox.widget.graph, + color = "linear:0,0:0,20:0,#FF0000:0.3,#FFFF00:0.6," .. color + } + + -- This timer periodically executes the heavy command while the popup is open. + -- It is stopped when the popup is closed and only the slim command is run then. + -- This greatly improves performance while the popup is closed at the small cost + -- of a slightly longer popup opening time. + local popup_timer = gears.timer { + timeout = timeout + } + + local popup = awful.popup{ + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_normal, + maximum_width = 300, + offset = { y = 5 }, + widget = {} + } + + -- Do not update process rows when mouse cursor is over the widget + popup:connect_signal("mouse::enter", function() is_update = false end) + popup:connect_signal("mouse::leave", function() is_update = true end) + + cpugraph_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if popup.visible then + popup.visible = not popup.visible + -- When the popup is not visible, stop the timer + popup_timer:stop() + else + popup:move_next_to(mouse.current_widget_geometry) + -- Restart the timer, when the popup becomes visible + -- Emit the signal to start the timer directly and not wait the timeout first + popup_timer:start() + popup_timer:emit_signal("timeout") + end + end) + ) + ) + + --- By default graph widget goes from left to right, so we mirror it and push up a bit + cpu_widget = wibox.widget { + { + cpugraph_widget, + reflection = {horizontal = true}, + layout = wibox.container.mirror + }, + bottom = 2, + color = background_color, + widget = wibox.container.margin + } + + -- This part runs constantly, also when the popup is closed. + -- It updates the graph widget in the bar. + local maincpu = {} + watch(CMD_slim, timeout, function(widget, stdout) + + local _, user, nice, system, idle, iowait, irq, softirq, steal, _, _ = + stdout:match('(%w+)%s+(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)') + + local total = user + nice + system + idle + iowait + irq + softirq + steal + + local diff_idle = idle - tonumber(maincpu['idle_prev'] == nil and 0 or maincpu['idle_prev']) + local diff_total = total - tonumber(maincpu['total_prev'] == nil and 0 or maincpu['total_prev']) + local diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10 + + maincpu['total_prev'] = total + maincpu['idle_prev'] = idle + + widget:add_value(diff_usage) + end, + cpugraph_widget + ) + + -- This part runs whenever the timer is fired. + -- It therefore only runs when the popup is open. + local cpus = {} + popup_timer:connect_signal('timeout', function() + awful.spawn.easy_async(CMD, function(stdout, _, _, _) + local i = 1 + local j = 1 + for line in stdout:gmatch("[^\r\n]+") do + if starts_with(line, 'cpu') then + + if cpus[i] == nil then cpus[i] = {} end + + local name, user, nice, system, idle, iowait, irq, softirq, steal, _, _ = + line:match('(%w+)%s+(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)%s(%d+)') + + local total = user + nice + system + idle + iowait + irq + softirq + steal + + local diff_idle = idle - tonumber(cpus[i]['idle_prev'] == nil and 0 or cpus[i]['idle_prev']) + local diff_total = total - tonumber(cpus[i]['total_prev'] == nil and 0 or cpus[i]['total_prev']) + local diff_usage = (1000 * (diff_total - diff_idle) / diff_total + 5) / 10 + + cpus[i]['total_prev'] = total + cpus[i]['idle_prev'] = idle + + local row = wibox.widget + { + create_textbox{text = name}, + create_textbox{text = math.floor(diff_usage) .. '%'}, + { + max_value = 100, + value = diff_usage, + forced_height = 20, + forced_width = 150, + paddings = 1, + margins = 4, + border_width = 1, + border_color = beautiful.bg_focus, + background_color = beautiful.bg_normal, + bar_border_width = 1, + bar_border_color = beautiful.bg_focus, + color = "linear:150,0:0,0:0,#D08770:0.3,#BF616A:0.6," .. beautiful.fg_normal, + widget = wibox.widget.progressbar, + + }, + layout = wibox.layout.ratio.horizontal + } + row:ajust_ratio(2, 0.15, 0.15, 0.7) + cpu_rows[i] = row + i = i + 1 + else + if is_update == true then + + local pid = trim(string.sub(line, 1, 10)) + local cpu = trim(string.sub(line, 12, 16)) + local mem = trim(string.sub(line, 18, 22)) + local comm = trim(string.sub(line, 24, 53)) + local cmd = trim(string.sub(line, 54)) + + local kill_proccess_button = enable_kill_button and create_kill_process_button() or nil + + local pid_name_rest = wibox.widget{ + create_textbox{text = pid}, + create_textbox{text = comm}, + { + create_textbox{text = cpu, align = 'center'}, + create_textbox{text = mem, align = 'center'}, + kill_proccess_button, + layout = wibox.layout.fixed.horizontal + }, + layout = wibox.layout.ratio.horizontal + } + pid_name_rest:ajust_ratio(2, 0.2, 0.47, 0.33) + + local row = wibox.widget { + { + pid_name_rest, + top = 4, + bottom = 4, + widget = wibox.container.margin + }, + widget = wibox.container.background + } + + row:connect_signal("mouse::enter", function(c) c:set_bg(beautiful.bg_focus) end) + row:connect_signal("mouse::leave", function(c) c:set_bg(beautiful.bg_normal) end) + + if enable_kill_button then + row:connect_signal("mouse::enter", function() kill_proccess_button.icon.opacity = 1 end) + row:connect_signal("mouse::leave", function() kill_proccess_button.icon.opacity = 0.1 end) + + kill_proccess_button:buttons( + awful.util.table.join( awful.button({}, 1, function() + row:set_bg('#ff0000') + awful.spawn.with_shell('kill -9 ' .. pid) + end) ) ) + end + + awful.tooltip { + objects = { row }, + mode = 'outside', + preferred_positions = {'bottom'}, + timer_function = function() + local text = cmd + if process_info_max_length > 0 and text:len() > process_info_max_length then + text = text:sub(0, process_info_max_length - 3) .. '...' + end + + return text + :gsub('%s%-', '\n\t-') -- put arguments on a new line + :gsub(':/', '\n\t\t:/') -- java classpath uses : to separate jars + end, + } + + process_rows[j] = row + + j = j + 1 + end + + end + end + popup:setup { + { + cpu_rows, + { + orientation = 'horizontal', + forced_height = 15, + color = beautiful.bg_focus, + widget = wibox.widget.separator + }, + create_process_header{with_action_column = enable_kill_button}, + process_rows, + layout = wibox.layout.fixed.vertical, + }, + margins = 8, + widget = wibox.container.margin + } + end) + end) + + return cpu_widget +end + +return setmetatable(cpu_widget, { __call = function(_, ...) + return worker(...) +end }) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/cpu.gif b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/cpu.gif new file mode 100644 index 0000000..cb97262 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/cpu.gif differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/cpu.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/cpu.png new file mode 100644 index 0000000..96ba29f Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/cpu.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/custom.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/custom.png new file mode 100644 index 0000000..be275e4 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/custom.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/window-close-symbolic.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/window-close-symbolic.svg new file mode 100644 index 0000000..46ff888 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/cpu-widget/window-close-symbolic.svg @@ -0,0 +1,95 @@ + + + + + + + + Gnome Symbolic Icon Theme + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/README.md new file mode 100644 index 0000000..b34b37c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/README.md @@ -0,0 +1,42 @@ +# Docker / Podman Widget + +[![GitHub issues by-label](https://img.shields.io/github/issues-raw/streetturtle/awesome-wm-widgets/docker)](https://github.com/streetturtle/awesome-wm-widgets/labels/docker) +![Twitter URL](https://img.shields.io/twitter/url?url=https%3A%2F%2Fgithub.com%2Fstreetturtle%2Fawesome-wm-widgets%2Fedit%2Fmaster%2Fdocker-widget) + +The widget allows to manage docker and podman containers, namely start/stop/pause/unpause: + +

+ +

+ +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `icon` | `./docker-widget/icons/docker.svg` | Path to the icon | +| `number_of_containers` | -1 | Number of last created containers to show | +| `executable_name` | `docker` | Name of the executable to use, defaults to `docker` | +| `max_widget_width` | 270 | Maximum width of the widget before the text breaks | + +The `executable_name` allows you to use `Podman` instead of docker. This works since `Podman` is compatible to `docker` in the sense that the syntax and command outputs are identical. + +## 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/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/docker.gif b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/docker.gif new file mode 100644 index 0000000..3b39b5f Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/docker.gif differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/docker.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/docker.lua new file mode 100644 index 0000000..27465f9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/docker.lua @@ -0,0 +1,403 @@ +------------------------------------------------- +-- 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 "%s container ls -a -s -n %s]] + .. [[ --format '{{.Names}}::{{.ID}}::{{.Image}}::{{.Status}}::{{.Size}}'"]] + +local DOCKER_DEFAULT_STATUS_PATTERN = '(.*)::(.*)::(.*)::(%w*) (.*)::(.*)' +local DOCKER_CREATED_STATUS_PATTERN = '(.*)::(.*)::(.*)::Created::(.*)' + +--- 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, actual_status + if string.find(line, '::Created::') then + name, id, image, size = line:match(DOCKER_CREATED_STATUS_PATTERN) + actual_status = 'Created' + how_long = 'Never started' + else + name, id, image, status, how_long, size = line:match(DOCKER_DEFAULT_STATUS_PATTERN) + if status == 'Up' and how_long:find('Paused') then actual_status = 'Paused' + else actual_status = status end + 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, + is_created = function() return status == 'Created' end + } + return container +end + +local status_to_icon_name = { + Up = ICONS_DIR .. 'play.svg', + Created = ICONS_DIR .. 'square.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 + local executable_name = args.executable_name or 'docker' + -- 180 is the default width of the container details part of the widget and + -- 90 is the default width of the control buttons + local max_widget_width = args.max_widget_width or 180 + 90 + + 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() or container.is_created() then + start_stop_button = wibox.widget { + { + { + id = 'icon', + image = ICONS_DIR .. (container:is_exited() and 'play-btn.svg' or 'stop-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_exited() then command = 'start' else command = 'stop' end + + status_icon:set_opacity(0.2) + status_icon:emit_signal('widget::redraw_needed') + + spawn.easy_async(executable_name .. ' ' .. command .. ' ' .. container['name'], + function(_, stderr) + if stderr ~= '' then show_warning(stderr) end + spawn.easy_async( + string.format(LIST_CONTAINERS_CMD,executable_name, number_of_containers), + function(stdout, container_errors) + rebuild_widget(stdout, container_errors) + 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(executable_name .. ' ' .. command .. ' ' .. container['name'], + function(_, stderr) + if stderr ~= '' then show_warning(stderr) end + spawn.easy_async(string.format(LIST_CONTAINERS_CMD, + executable_name, 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(executable_name .. ' rm ' .. container['name'], + function(_, rm_stderr) + if rm_stderr ~= '' then show_warning(rm_stderr) end + spawn.easy_async(string.format(LIST_CONTAINERS_CMD, + executable_name, 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 + }, + -- 90 is the reserved width of the control buttons + forced_width = max_widget_width - 90, + 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, executable_name, 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/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/docker.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/docker.svg new file mode 100644 index 0000000..468ce94 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/docker.svg @@ -0,0 +1 @@ +Docker icon diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/pause-btn.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/pause-btn.svg new file mode 100644 index 0000000..ac2900b --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/pause-btn.svg @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/pause.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/pause.svg new file mode 100644 index 0000000..33f1ad2 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/pause.svg @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/play-btn.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/play-btn.svg new file mode 100644 index 0000000..573ae7e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/play-btn.svg @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/play-btn.svg- b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/play-btn.svg- new file mode 100644 index 0000000..455a61d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/play-btn.svg- @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/play.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/play.svg new file mode 100644 index 0000000..4f0ee04 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/play.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/square.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/square.svg new file mode 100644 index 0000000..d8424d1 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/square.svg @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/stop-btn.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/stop-btn.svg new file mode 100644 index 0000000..f676d01 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/stop-btn.svg @@ -0,0 +1,10 @@ + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/trash-btn.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/trash-btn.svg new file mode 100644 index 0000000..78d8035 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/trash-btn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/unpause-btn.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/unpause-btn.svg new file mode 100644 index 0000000..db5b25f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/docker-widget/icons/unpause-btn.svg @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/.env.example b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/.env.example new file mode 100644 index 0000000..0cdfc04 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/.env.example @@ -0,0 +1,5 @@ +EMAIL= +PASSWORD= +MAX_MSG_COUNT= +MAX_BODY_LENGTH= + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/.gitignore b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/.gitignore new file mode 100644 index 0000000..8d19362 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/.gitignore @@ -0,0 +1,2 @@ +.venv +.env diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/README.md new file mode 100644 index 0000000..d7baf4f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/README.md @@ -0,0 +1,54 @@ +# Email widget + +This widget consists of an icon with counter which shows number of unread emails: ![email icon](./em-wid-1.png) +and a popup message which appears when mouse hovers over an icon: ![email popup](./em-wid-2.png) + +## Installation +1. Clone this repository to your awesome config folder: + +```bash +git clone https://github.com/streetturtle/awesome-wm-widgets/email-widget ~/.config/awesome/email-widget +``` +2. Make virtual environment and install dependencies: + +```bash +cd ~/.config/awesome/email-widget +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` +3. Fill .env file with your credentials: + +```bash +cp .env.example .env +``` +4. Add widget to awesome: + +```lua +local email_widget = require("email-widget.email") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + email_widget, + ... +``` + +If you want to reduce time of getting emails, you can change maximum number of emails to be fetched in .env file. Default is 10. +If you want to configure width of popup window, you can change this line in email.lua file: + +```lua + width = 800, +``` +After this you can change MAX_BODY_LENGTH variable in .env file to change number of characters to be displayed in popup window. Default is 100. +Next step is restarting awesome. You can do this by pressing Mod+Ctrl+r. + +## How it works + +This widget uses the output of two python scripts, first is called every 20 seconds - it returns number of unread emails and second is called when mouse hovers over an icon and displays content of those emails. For both of them you'll need to provide your credentials and imap server. For testing, they can simply be called from console: + +``` bash +python ~/.config/awesome/email-widget/count_unread_emails.py +python ~/.config/awesome/email-widget/read_emails.py +``` diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/count_unread_emails.py b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/count_unread_emails.py new file mode 100644 index 0000000..c65a7dd --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/count_unread_emails.py @@ -0,0 +1,25 @@ +import imaplib +import re +from dotenv import load_dotenv +from pathlib import Path +import os +path_to_env = Path(__file__).parent / '.env' +load_dotenv(path_to_env) +EMAIL = os.getenv("EMAIL") +PASSWORD = os.getenv("PASSWORD") +if not EMAIL or not PASSWORD: + print("ERROR:Email or password not set in .env file.") + exit(0) + + +M = imaplib.IMAP4_SSL("imap.gmail.com", 993) +M.login(EMAIL, PASSWORD) + +status, counts = M.status("INBOX", "(MESSAGES UNSEEN)") + +if status == "OK": + unread = re.search(r"UNSEEN\s(\d+)", counts[0].decode("utf-8")).group(1) +else: + unread = "N/A" + +print(unread) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/em-wid-1.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/em-wid-1.png new file mode 100644 index 0000000..e4aa074 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/em-wid-1.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/em-wid-2.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/em-wid-2.png new file mode 100644 index 0000000..2bd2ead Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/em-wid-2.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/email.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/email.lua new file mode 100644 index 0000000..2d1a3da --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/email.lua @@ -0,0 +1,49 @@ +local wibox = require("wibox") +local awful = require("awful") +local naughty = require("naughty") +local watch = require("awful.widget.watch") + +local currentPath = debug.getinfo(1, "S").source:sub(2):match("(.*/)") + +local email_widget = wibox.widget.textbox() +email_widget:set_font('Play 9') +email_widget:set_text("Loading...") + + +local path_to_python_in_venv = currentPath .. "/.venv/bin/python" + +watch( + path_to_python_in_venv.." "..currentPath.."count_unread_emails.py", 20, function(_, stdout) + local is_error = (stdout:match("ERROR") ~= nil) + email_widget:set_text("status: "..tostring(is_error)) + if is_error then + email_widget:set_text(stdout) + return + end + local unread_emails_num = tonumber(stdout) or 0 + if (unread_emails_num > 0) then + email_widget:set_text(unread_emails_num) + elseif (unread_emails_num == 0) then + email_widget:set_text("") + end + end +) +local function show_emails() + awful.spawn.easy_async( + path_to_python_in_venv.." "..currentPath.."read_unread_emails.py", + function(stdout) + naughty.notify{ + text = stdout, + title = "Unread Emails", + timeout = 10, hover_timeout = 0.5, + width = 800, + parse = true, + } + end + ) +end + +email_widget:connect_signal("mouse::enter", function() show_emails() end) + +-- show_emails() +return email_widget \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/read_unread_emails.py b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/read_unread_emails.py new file mode 100644 index 0000000..30d3a8a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/read_unread_emails.py @@ -0,0 +1,115 @@ +import imaplib +import email +import html2text +import re +from email.header import make_header, decode_header +from pathlib import Path +import os +from dotenv import load_dotenv + +path_to_env = Path(__file__).parent / '.env' +load_dotenv(path_to_env) +EMAIL = os.getenv("EMAIL") +PASSWORD = os.getenv("PASSWORD") + +MAX_MSG_COUNT = int(os.getenv("MAX_MSG_COUNT", 5)) +MAX_BODY_LENGTH = int(os.getenv("MAX_BODY_LENGTH", 100)) + +GREEN = "\033[1;32m" +END_COLOR = "\033[0m" + +UNSEEN_FLAG = "(UNSEEN)" +BODY_PEEK_FLAG = "(BODY.PEEK[])" +def colorful_text(text, color): + """ + Function to format text with Pango markup for color. + """ + return f"{text}" + +def format_body(body, max_length=MAX_BODY_LENGTH): + body = body.decode("utf-8", errors="ignore") + + if "DOCTYPE" in body: + body = html2text.html2text(body) + body = body.replace("\n", "").replace("\r\n", "").replace(" ", "") + body = re.sub(r"\[.*\]\(.*\)", "", body) + + return body if len(body) < max_length else body[:max_length] + "..." + +def get_short_email_str(M, num_emails=MAX_MSG_COUNT): + rv, data = M.search(None, UNSEEN_FLAG) + email_list = list(reversed(data[0].split()))[:num_emails] + + for num in email_list: + try: + rv, data = M.fetch(num, BODY_PEEK_FLAG) + if rv != "OK": + print("ERROR getting message", num) + continue + + msg = email.message_from_bytes(data[0][1]) + + sender = make_header(decode_header(msg["From"])) + subject = make_header(decode_header(msg["Subject"])) + date = make_header(decode_header(msg["Date"])) + + email_info = ( + f"From: {colorful_text(str(sender).replace('<', '').replace('>', ''), 'green')}\n" + f"Subject: {colorful_text(str(subject), 'red')}\n" + f"Date: {date}\n" + ) + + if msg.is_multipart(): + for part in msg.walk(): + content_type = part.get_content_type() + content_disposition = str(part.get("Content-Disposition")) + + if ( + content_type == "text/plain" + and "attachment" not in content_disposition + ): + body = part.get_payload(decode=True) + email_info += format_body(body) + break + elif ( + content_type == "text/html" + and "attachment" not in content_disposition + ): + body = part.get_payload(decode=True) + body = html2text.html2text( + body.decode("utf-8", errors="ignore") + ) + email_info += format_body(body) + break + else: + body = msg.get_payload(decode=True) + email_info += format_body(body) + + email_info += "\n" + "=" * 50 + "\n" + yield email_info + + except Exception: + print("ERROR processing message: ", num) + +if __name__ == "__main__": + # Example usage: + # read_unread_emails.py + import time + start = time.time() + + M = imaplib.IMAP4_SSL("imap.gmail.com", 993) + try: + M.login(EMAIL, PASSWORD) + rv, data = M.select("INBOX") + + if rv == "OK": + for email_str in get_short_email_str(M, MAX_MSG_COUNT): + print(email_str) + else: + print("Error selecting INBOX") + except Exception: + print("Error logging in: ", EMAIL) + finally: + M.logout() + + print(f"Time taken: {time.time() - start:.2f} seconds") \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/requirements.txt b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/requirements.txt new file mode 100644 index 0000000..13b32cd --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/email-widget/requirements.txt @@ -0,0 +1,2 @@ +html2text==2020.1.16 +python-dotenv==1.0.0 diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/experiments/spotify-player/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/experiments/spotify-player/README.md new file mode 100644 index 0000000..b19d5ac --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/experiments/spotify-player/README.md @@ -0,0 +1,5 @@ +# Spotify Player + +In progress + +![spotify-player](./spotify-player.png) \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-indicator.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-indicator.svg new file mode 100644 index 0000000..0b96c0a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-indicator.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-player.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-player.lua new file mode 100644 index 0000000..981978b --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-player.lua @@ -0,0 +1,192 @@ +------------------------------------------------- +-- Spotify Player Widget for Awesome Window Manager +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/spotify-player + +-- @author Pavel Makhov +-- @copyright 2021 Pavel Makhov +------------------------------------------------- +--luacheck:ignore +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") +local spawn = require("awful.spawn") +local naughty = require("naughty") +local gears = require("gears") +local beautiful = require("beautiful") +local gfs = require("gears.filesystem") +local gs = require("gears.string") +local awesomebuttons = require("awesome-buttons.awesome-buttons") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/experiments/spotify-player/' +local ICON_DIR = WIDGET_DIR + +local spotify_player = {} + +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Spotify Player Widget', + text = message} +end + +local function worker(user_args) + + local args = user_args or {} + local artwork_size = args.artwork_size or 300 + + local timeout = args.timeout or 1 + + local popup = awful.popup{ + ontop = true, + bg = beautiful.bg_normal .. '88', + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + width = artwork_size, + maximum_width = 300, + offset = { y = 5 }, + widget = {} + } + + local rows = { + expand = 'none', + layout = wibox.layout.align.vertical, + } + + spotify_player.widget = wibox.widget { + image = ICON_DIR .. 'spotify-indicator.svg', + widget = wibox.widget.imagebox + } + + local artwork_widget = wibox.widget { + forced_height = artwork_size, + forced_width = artwork_size, + widget = wibox.widget.imagebox + } + + local artist_w = wibox.widget { + align = 'center', + widget = wibox.widget.textbox, + set_artist = function(self, artist) + self:set_markup('' .. artist .. '') + end + } + + local title_w = wibox.widget { + align = 'center', + forced_height = 30, + widget = wibox.widget.textbox, + set_title = function(self, title) + self:set_markup('' .. title .. '') + end + } + + local play_pause_btn = awesomebuttons.with_icon{ type = 'outline', icon = 'play', icon_size = 32, icon_margin = 8, color = '#1DB954', shape = 'circle', onclick = function() + spawn.with_shell('sp play') + end} + + local buttons_w = wibox.widget { + { + awesomebuttons.with_icon{ icon = 'rewind', icon_size = 32, icon_margin = 8, color = '#18800000', shape = 'circle', onclick = function() + spawn.with_shell('sp prev') + end}, + play_pause_btn, + awesomebuttons.with_icon{ icon = 'fast-forward', icon_size = 32, icon_margin = 8, color = '#18800000', shape = 'circle', onclick = function() + spawn.with_shell('sp next') + end}, + spacing = 16, + layout = wibox.layout.fixed.horizontal + }, + halign = 'center', + layout = wibox.container.place, + } + + local some_w = wibox.widget { + artwork_widget, + { + { + { + { + title_w, + artist_w, + buttons_w, + layout = wibox.layout.fixed.vertical + }, + top = 8, + bottom = 8, + widget = wibox.container.margin + }, + bg = '#33333388', + widget = wibox.container.background + }, + valign = 'bottom', + content_fill_horizontal = true, + layout = wibox.container.place, + }, + layout = wibox.layout.stack + } + + popup:setup({ + some_w, + layout = wibox.layout.fixed.vertical, + }) + + local update_widget = function(widget, stdout, stderr, _, _) + for i = 0, #rows do rows[i]=nil end + + if string.find(stdout, 'Error: Spotify is not running.') ~= nil then + return + end + + local track_id, length, art_url, album, album_artist, artist, auto_rating, disc_number, title, track_number, url = + string.match(stdout, 'trackid|(.*)\nlength|(.*)\nartUrl|(.*)\nalbum|(.*)\nalbumArtist|(.*)\nartist|(.*)\nautoRating|(.*)\ndiscNumber|(.*)\ntitle|(.*)\ntrackNumber|(.*)\nurl|(.*)') + + title = string.gsub(title, "&", '&') + artist_w:set_artist(artist) + title_w:set_title(title) + + -- spotify client bug: https://community.spotify.com/t5/Desktop-Linux/MPRIS-cover-art-url-file-not-found/td-p/4920104 + art_url = art_url:gsub('https://open.spotify.com', 'https://i.scdn.co') + if ((art_url ~= nil or art_url ~='') and not gfs.file_readable('/tmp/' .. track_id)) then + spawn.easy_async('touch /tmp/' .. track_id, function() + spawn.easy_async('curl -L -s --show-error --create-dirs -o /tmp/' .. track_id .. ' '.. art_url, function(stdout, stderr) + if stderr ~= '' then + show_warning(stderr) + return + end + artwork_widget:set_image('/tmp/' .. track_id) + end) + end) + else + artwork_widget:set_image('/tmp/' .. track_id) + end + end + + function spotify_player:tog() + if popup.visible then + popup.visible = not popup.visible + else + popup:move_next_to(mouse.current_widget_geometry) + end + end + + spotify_player.widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() spotify_player:tog() end) + ) + ) + + watch('sp metadata', timeout, update_widget) + + watch('sp status', 1, function(_, stdout) + stdout = string.gsub(stdout, "\n", "") + play_pause_btn:set_icon(stdout == 'Playing' and 'pause' or 'play') + end) + + return spotify_player +end + +return setmetatable(spotify_player, { __call = function(_, ...) return worker(...) end }) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-player.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-player.png new file mode 100644 index 0000000..7bfecfc Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/experiments/spotify-player/spotify-player.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/fs-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/fs-widget/README.md new file mode 100644 index 0000000..4d6327d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/fs-widget/README.md @@ -0,0 +1,29 @@ +# Filesystem Widget + +This widget shows file system disk space usage which is based on the `df` output. When clicked another widget appears with more detailed information. By default, it monitors the "/" mount. It can be configured with a list of mounts to monitor though only the first will show in the wibar. To have multiple mounts displayed on the wibar simply define multiple `fs_widgets` with different mounts as arguments. + +![](./screenshot.png) + +## Customizations + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `mounts` | `{ '/' }` | Table with mounts to monitor, check the output from a `df` command for available options (column `Mounted on`) | +| `timeout` | 60 | How often in seconds the widget refreshes | + +## Installation + +Clone/download repo and use the widget in **rc.lua**: + +```lua + local fs_widget = require("awesome-wm-widgets.fs-widget.fs-widget") + ... + s.mywibox:setup { + s.mytasklist, -- Middle widget + { -- Right widgets + fs_widget(), --default + fs_widget({ mounts = { '/', '/mnt/music' } }), -- multiple mounts + ... +``` diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/fs-widget/fs-widget.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/fs-widget/fs-widget.lua new file mode 100644 index 0000000..2e16df0 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/fs-widget/fs-widget.lua @@ -0,0 +1,190 @@ +local awful = require("awful") +local watch = require("awful.widget.watch") +local wibox = require("wibox") +local beautiful = require("beautiful") +local gears = require("gears") + +local storage_bar_widget = {} + +--- Table with widget configuration, consists of three sections: +--- - general - general configuration +--- - widget - configuration of the widget displayed on the wibar +--- - popup - configuration of the popup +local config = {} + +-- general +config.mounts = { '/' } +config.refresh_rate = 60 + +-- wibar widget +config.widget_width = 40 +config.widget_bar_color = '#aaaaaa' +config.widget_onclick_bg = '#ff0000' +config.widget_border_color = '#535d6c66' +config.widget_background_color = '#22222233' + +-- popup +config.popup_bg = '#22222233' +config.popup_border_width = 1 +config.popup_border_color = '#535d6c66' +config.popup_bar_color = '#aaaaaa' +config.popup_bar_background_color = '#22222233' +config.popup_bar_border_color = '#535d6c66' + +local function worker(user_args) + local args = user_args or {} + + -- Setup config for the widget instance. + -- The `_config` table will keep the first existing value after checking + -- in this order: user parameter > beautiful > module default. + local _config = {} + for prop, value in pairs(config) do + _config[prop] = args[prop] or beautiful[prop] or value + end + + storage_bar_widget = wibox.widget { + { + id = 'progressbar', + color = _config.widget_bar_color, + max_value = 100, + forced_height = 20, + forced_width = _config.widget_width, + paddings = 2, + margins = 4, + border_width = 1, + border_radius = 2, + border_color = _config.widget_border_color, + background_color = _config.widget_background_color, + widget = wibox.widget.progressbar + }, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + widget = wibox.container.background, + set_value = function(self, new_value) + self:get_children_by_id("progressbar")[1].value = new_value + end + } + + local disk_rows = { + { widget = wibox.widget.textbox }, + spacing = 4, + layout = wibox.layout.fixed.vertical, + } + + local disk_header = wibox.widget { + { + markup = 'Mount', + forced_width = 150, + align = 'left', + widget = wibox.widget.textbox, + }, + { + markup = 'Used', + align = 'left', + widget = wibox.widget.textbox, + }, + layout = wibox.layout.ratio.horizontal + } + disk_header:ajust_ratio(1, 0, 0.3, 0.7) + + local popup = awful.popup { + bg = _config.popup_bg, + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = _config.popup_border_width, + border_color = _config.popup_border_color, + maximum_width = 400, + offset = { y = 5 }, + widget = {} + } + + storage_bar_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if popup.visible then + popup.visible = not popup.visible + storage_bar_widget:set_bg('#00000000') + else + storage_bar_widget:set_bg(_config.widget_background_color) + popup:move_next_to(mouse.current_widget_geometry) + end + end) + ) + ) + + local disks = {} + watch([[bash -c "df | tail -n +2"]], _config.refresh_rate, + function(widget, stdout) + for line in stdout:gmatch("[^\r\n$]+") do + local filesystem, size, used, avail, perc, mount = + line:match('([%p%w]+)%s+([%d%w]+)%s+([%d%w]+)%s+([%d%w]+)%s+([%d]+)%%%s+([%p%w]+)') + + disks[mount] = {} + disks[mount].filesystem = filesystem + disks[mount].size = size + disks[mount].used = used + disks[mount].avail = avail + disks[mount].perc = perc + disks[mount].mount = mount + + if disks[mount].mount == _config.mounts[1] then + widget:set_value(tonumber(disks[mount].perc)) + end + end + + for k, v in ipairs(_config.mounts) do + + local row = wibox.widget { + { + text = disks[v].mount, + forced_width = 150, + widget = wibox.widget.textbox + }, + { + color = _config.popup_bar_color, + max_value = 100, + value = tonumber(disks[v].perc), + forced_height = 20, + paddings = 1, + margins = 4, + border_width = 1, + border_color = _config.popup_bar_border_color, + background_color = _config.popup_bar_background_color, + bar_border_width = 1, + bar_border_color = _config.popup_bar_border_color, + widget = wibox.widget.progressbar, + }, + { + text = math.floor(disks[v].used / 1024 / 1024) + .. '/' + .. math.floor(disks[v].size / 1024 / 1024) .. 'GiB(' + .. math.floor(disks[v].perc) .. '%)', + widget = wibox.widget.textbox + }, + layout = wibox.layout.ratio.horizontal + } + row:ajust_ratio(2, 0.3, 0.3, 0.4) + + disk_rows[k] = row + end + popup:setup { + { + disk_header, + disk_rows, + layout = wibox.layout.fixed.vertical, + }, + margins = 8, + widget = wibox.container.margin + } + end, + storage_bar_widget + ) + + return storage_bar_widget +end + +return setmetatable(storage_bar_widget, { __call = function(_, ...) + return worker(...) +end }) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/fs-widget/screenshot.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/fs-widget/screenshot.png new file mode 100644 index 0000000..41e6ccc Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/fs-widget/screenshot.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/README.md new file mode 100644 index 0000000..62c89a1 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/README.md @@ -0,0 +1,77 @@ +# Gerrit widget + +It shows number of currently assigned reviews in [Gerrit](https://www.gerritcodereview.com/) to the user (by default) : + + ![gerrit_widget](./gerrit_widget.png) + + when clicked it shows reviews in a list: + + ![popup](./popup.png) + + left click on an item will open review in the default browser, right click will copy the review number, which you can use to checkout this review by running `git-review -d `. + + Also, if a new review is assigned to the user, there will be a pop-up: + + ![new_review](./new_review.png) + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `icon`| `/.config/awesome/awesome-wm-widgets/gerrit-widget/gerrit_icon.svg`| Path to the icon | +| `host` | Required | Ex https://gerrit.tmnt.com | +| `query` | `is:reviewer AND status:open AND NOT is:wip` | Query to retrieve reviews | +| `timeout` | 10 | How often in seconds the widget refreshes | + +## Prerequisite + + - [curl](https://curl.haxx.se/) - is used to communicate with gerrit's [REST API](https://gerrit-review.googlesource.com/Documentation/rest-api.html) + - setup [netrc](https://ec.haxx.se/usingcurl-netrc.html) which is used to store username and password in order to call API's endpoints. + +## Installation + +1. This widget relies on Gerrit [REST API](https://gerrit-review.googlesource.com/Documentation/rest-api.html), so you need to have a permission to access it. You also need to setup [netrc](https://ec.haxx.se/usingcurl-netrc.html), as widget uses curl to communicate with API and you have to be authenticated. +To test if you have access to API and netrc setup is correct run following command, you should have a json response: + + ```bash + curl -s --request GET --netrc https://gerrit-host.com/a/changes/\?q\=status:open+AND+NOT+is:wip+AND+is:reviewer | tail -n +2 + ``` + Note: `tail -n +2` is needed to skip first line of the response, as gerrit returns some characters there in order to prevent XSS hacks. + +1. Download json parser for lua from [github.com/rxi/json.lua](https://github.com/rxi/json.lua) and place it under **~/.config/awesome/** (don't forget to star a repo): + + ```bash + wget -P ~/.config/awesome/ https://raw.githubusercontent.com/rxi/json.lua/master/json.lua + ``` + +1. Clone this repo (if not cloned yet) under **~/.config/awesome/**: + + ```bash + git clone https://github.com/streetturtle/awesome-wm-widgets.git ~/.config/awesome/ + ``` + +1. Require widget at the top of the **rc.lua**: + + ```lua + local gerrit_widget = require("awesome-wm-widgets.gerrit-widget.gerrit") + ``` + +1. Add widget to the tasklist: + + ```lua + s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + --default + gerrit_widget({host = 'https://gerrit.tmnt.com'}), + --customized + gerrit_widget({ + host = 'https://gerrit.tmnt.com', + query = 'is:reviewer AND is:wip' + }) + ... + ``` + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/gerrit.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/gerrit.lua new file mode 100644 index 0000000..682eb0f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/gerrit.lua @@ -0,0 +1,230 @@ +------------------------------------------------- +-- Gerrit Widget for Awesome Window Manager +-- Shows the number of currently assigned reviews +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/gerrit-widget + +-- @author Pavel Makhov +-- @copyright 2019 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local watch = require("awful.widget.watch") +local json = require("json") +local spawn = require("awful.spawn") +local naughty = require("naughty") +local gears = require("gears") +local beautiful = require("beautiful") +local gfs = require("gears.filesystem") + +local HOME_DIR = os.getenv("HOME") +local PATH_TO_AVATARS = HOME_DIR .. '/.cache/awmw/gerrit-widget/avatars/' + +local GET_CHANGES_CMD = [[bash -c "curl -s -X GET -n %s/a/changes/\\?q\\=%s | tail -n +2"]] +local GET_USER_CMD = [[bash -c "curl -s -X GET -n %s/accounts/%s/ | tail -n +2"]] +local DOWNLOAD_AVATAR_CMD = [[bash -c "curl --create-dirs -o %s %s"]] + +local gerrit_widget = {} + +local function worker(user_args) + + local args = user_args or {} + + local icon = args.icons or HOME_DIR .. '/.config/awesome/awesome-wm-widgets/gerrit-widget/gerrit_icon.svg' + local host = args.host or naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Gerrit Widget', + text = 'Gerrit host is unknown' + } + local query = args.query or 'is:reviewer AND status:open AND NOT is:wip' + local timeout = args.timeout or 10 + + local current_number_of_reviews + local previous_number_of_reviews = 0 + local name_dict = {} + + local rows = { + { widget = wibox.widget.textbox }, + layout = wibox.layout.fixed.vertical, + } + + local popup = awful.popup{ + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + maximum_width = 400, + offset = { y = 5 }, + widget = {} + } + + gerrit_widget = wibox.widget { + { + { + image = icon, + widget = wibox.widget.imagebox + }, + margins = 4, + layout = wibox.container.margin + }, + { + id = "txt", + widget = wibox.widget.textbox + }, + { + id = "new_rev", + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal, + set_text = function(self, new_value) + self.txt.text = new_value + end, + set_unseen_review = function(self, is_new_review) + self.new_rev.text = is_new_review and '*' or '' + end + } + + local function get_name_by_user_id(user_id) + if name_dict[user_id] == nil then + name_dict[user_id] = {} + end + + if name_dict[user_id].username == nil then + name_dict[user_id].username = '' + spawn.easy_async(string.format(GET_USER_CMD, host, user_id), function(stdout) + local user = json.decode(stdout) + name_dict[tonumber(user_id)].username = user.name + if not gfs.file_readable(PATH_TO_AVATARS .. user_id) then + spawn.easy_async( + string.format(DOWNLOAD_AVATAR_CMD, PATH_TO_AVATARS .. user_id, user.avatars[1].url)) + end + end) + return name_dict[user_id].username + end + + return name_dict[user_id].username + end + + local update_widget = function(widget, stdout, _, _, _) + local reviews = json.decode(stdout) + + current_number_of_reviews = rawlen(reviews) + + if current_number_of_reviews == 0 then + widget:set_visible(false) + return + else + widget:set_visible(true) + end + + widget:set_visible(true) + if current_number_of_reviews > previous_number_of_reviews then + widget:set_unseen_review(true) + naughty.notify{ + icon = HOME_DIR ..'/.config/awesome/awesome-wm-widgets/gerrit-widget/gerrit_icon.svg', + title = 'New Incoming Review', + text = reviews[1].project .. '\n' .. get_name_by_user_id(reviews[1].owner._account_id) .. + reviews[1].subject .. '\n', + run = function() spawn.with_shell("xdg-open https://" .. host .. '/' .. reviews[1]._number) end + } + end + + previous_number_of_reviews = current_number_of_reviews + widget:set_text(current_number_of_reviews) + + for i = 0, #rows do rows[i]=nil end + for _, review in ipairs(reviews) do + + local row = wibox.widget { + { + { + { + { + resize = true, + image = PATH_TO_AVATARS .. review.owner._account_id, + forced_width = 40, + forced_height = 40, + widget = wibox.widget.imagebox + }, + margins = 8, + layout = wibox.container.margin + }, + { + { + markup = '' .. review.project .. '', + align = 'center', + widget = wibox.widget.textbox + }, + { + text = ' ' .. review.subject, + widget = wibox.widget.textbox + }, + { + text = ' ' .. get_name_by_user_id(review.owner._account_id), + widget = wibox.widget.textbox + }, + layout = wibox.layout.align.vertical + }, + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + margins = 8, + layout = wibox.container.margin + }, + widget = wibox.container.background + } + + row:connect_signal("button::release", function() + spawn.with_shell("xdg-open " .. host .. '/' .. review._number) + end) + + row:connect_signal("mouse::enter", function(c) c:set_bg(beautiful.bg_focus) end) + row:connect_signal("mouse::leave", function(c) c:set_bg(beautiful.bg_normal) end) + + row:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell("xdg-open " .. host .. '/' .. review._number) + popup.visible = false + end), + awful.button({}, 3, function() + spawn.with_shell("echo '" .. review._number .."' | xclip -selection clipboard") + popup.visible = false + end) + ) + ) + + table.insert(rows, row) + end + + popup:setup(rows) + end + + gerrit_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + gerrit_widget:set_unseen_review(false) + if popup.visible then + popup.visible = not popup.visible + else + --local geo = mouse.current_widget_geometry + --if theme.calendar_placement == 'center' then + -- local x = geo.x + (geo.width / 2) - (popup:geometry().width / 2) -- align two widgets + -- popup:move_next_to({x = x, y = geo.y + 22, width = 0, height = geo.height}) + --else + -- popup:move_next_to(geo) + --end + + popup:move_next_to(mouse.current_widget_geometry) + end + end) + ) + ) + + watch(string.format(GET_CHANGES_CMD, host, query:gsub(" ", "+")), timeout, update_widget, gerrit_widget) + return gerrit_widget +end + +return setmetatable(gerrit_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/gerrit_icon.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/gerrit_icon.svg new file mode 100644 index 0000000..4ac5652 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/gerrit_icon.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/gerrit_widget.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/gerrit_widget.png new file mode 100644 index 0000000..926bc4b Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/gerrit_widget.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/new_review.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/new_review.png new file mode 100644 index 0000000..ebc9bad Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/new_review.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/popup.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/popup.png new file mode 100644 index 0000000..e08879a Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gerrit-widget/popup.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/README.md new file mode 100644 index 0000000..2b89d70 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/README.md @@ -0,0 +1,89 @@ +# GitHub Activity Widget + +Widget shows recent activities on GitHub. It is very similar to the GitHub's "All activity" feed on the main page: + +

+ +

+ +Mouse click on the item opens repo/issue/pr depending on the type of the activity. Mouse click on user's avatar opens user GitHub profile. + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `icon` | `github.png` from the widget sources | Widget icon displayed on the wibar | +| `username` | Required | GitHub username | +| `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/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/github-activity-widget.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/github-activity-widget.lua new file mode 100644 index 0000000..af29b35 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/alert-circle.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/alert-circle.svg new file mode 100644 index 0000000..1c42eaf --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/alert-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/git-branch.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/git-branch.svg new file mode 100644 index 0000000..3f06c34 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/git-branch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/git-pull-request.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/git-pull-request.svg new file mode 100644 index 0000000..c2e2867 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/git-pull-request.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/github.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/github.png new file mode 100644 index 0000000..628da97 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/github.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/message-square.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/message-square.svg new file mode 100644 index 0000000..758ba42 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/message-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/repo.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/repo.svg new file mode 100644 index 0000000..f74a595 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/repo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/star.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/star.svg new file mode 100644 index 0000000..0a3d39e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/icons/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/screenshot.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/screenshot.png new file mode 100644 index 0000000..f066cbc Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-activity-widget/screenshot.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/README.md new file mode 100644 index 0000000..509eb32 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/README.md @@ -0,0 +1,63 @@ +# Github Contributions Widget + +The widget is inspired by the https://github-contributions.now.sh/ and relies on it's API. + +It shows the contribution graph, similar to the one on the github profile page: ![screenshot](./screenshots/screenshot.jpg) + +You might wonder what could be the reason to have your github's contributions in front of you all day long? The more you contribute, the nicer widget looks! Check out [Thomashighbaugh](https://github.com/Thomashighbaugh)'s graph: + +![](./screenshots/Thomashighbaugh.png) + +## Customization + +It is possible to customize the widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `username` | `streetturtle` | GitHub username | +| `days` | 365 | Number of days in the past, more days - wider the widget | +| `color_of_empty_cells` | Theme's default | Color of the days with no contributions | +| `with_border` | true | Should the graph contains border or not | +| `margin_top` | 1 | Top margin | +| `theme` | `standard` | Color theme of the graph, see below | + +_Note:_ widget height is 21px (7 rows of 3x3 cells). So it would look nice on the wibar of 22-24px height. + +### Themes + +Following themes are available: + +| Theme name | Preview | +|---|---| +| standard | ![standard](./screenshots/standard.png) | +| classic | ![classic](./screenshots/classic.png) | +| teal | ![teal](./screenshots/teal.png) | +| leftpad | ![leftpad](./screenshots/leftpad.png) | +| dracula | ![dracula](./screenshots/dracula.png) | +| pink | ![pink](./screenshots/pink.png) | + +To add a new theme, simply add a new entry in `themes` table (themes.lua) with the colors of your theme. + +### Screenshots + +1000 days, with border: +![screenshot1](./screenshots/screenshot1.jpg) + +365 days, no border: +![screenshot2](./screenshots/screenshot2.jpg) + +## Installation + +Clone/download repo under **~/.config/awesome** and use widget in **rc.lua**: + +```lua +local github_contributions_widget = require("awesome-wm-widgets.github-contributions-widget.github-contributions-widget") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + github_contributions_widget({username = ''}), + ... +``` diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/github-contributions-widget.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/github-contributions-widget.lua new file mode 100644 index 0000000..113d474 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/github-contributions-widget.lua @@ -0,0 +1,104 @@ +------------------------------------------------- +-- Github Contributions Widget for Awesome Window Manager +-- Shows the contributions graph +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/github-contributions-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local naughty = require("naughty") +local wibox = require("wibox") +local gears = require("gears") +local widget_themes = require("awesome-wm-widgets.github-contributions-widget.themes") + +local GET_CONTRIBUTIONS_CMD = [[bash -c "curl -s https://github-contributions.vercel.app/api/v1/%s]] + .. [[ | jq -r '[.contributions[] ]] + .. [[ | select ( .date | strptime(\"%%Y-%%m-%%d\") | mktime < now)][:%s]| .[].intensity'"]] + +local github_contributions_widget = wibox.widget{ + reflection = { + horizontal = true, + vertical = true, + }, + widget = wibox.container.mirror +} + +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Github Contributions Widget', + text = message} +end + +local function worker(user_args) + + local args = user_args or {} + local username = args.username or 'streetturtle' + local days = args.days or 365 + local color_of_empty_cells = args.color_of_empty_cells + local with_border = args.with_border + local margin_top = args.margin_top or 1 + local theme = args.theme or 'standard' + + if widget_themes[theme] == nil then + show_warning('Theme ' .. theme .. ' does not exist') + theme = 'standard' + end + + if with_border == nil then with_border = true end + + local function get_square(color) + if color_of_empty_cells ~= nil and color == widget_themes[theme][0] then + color = color_of_empty_cells + end + + return wibox.widget{ + fit = function() + return 3, 3 + end, + draw = function(_, _, cr, _, _) + cr:set_source(gears.color(color)) + cr:rectangle(0, 0, with_border and 2 or 3, with_border and 2 or 3) + cr:fill() + end, + layout = wibox.widget.base.make_widget + } + end + + local col = {layout = wibox.layout.fixed.vertical} + local row = {layout = wibox.layout.fixed.horizontal} + local day_idx = 5 - os.date('%w') + for _ = 0, day_idx do + table.insert(col, get_square(color_of_empty_cells)) + end + + local update_widget = function(_, stdout, _, _, _) + for intensity in stdout:gmatch("[^\r\n]+") do + if day_idx %7 == 0 then + table.insert(row, col) + col = {layout = wibox.layout.fixed.vertical} + end + table.insert(col, get_square(widget_themes[theme][tonumber(intensity)])) + day_idx = day_idx + 1 + end + github_contributions_widget:setup( + { + row, + top = margin_top, + layout = wibox.container.margin + } + ) + end + + awful.spawn.easy_async(string.format(GET_CONTRIBUTIONS_CMD, username, days), + function(stdout) + update_widget(github_contributions_widget, stdout) + end) + + return github_contributions_widget +end + +return setmetatable(github_contributions_widget, { __call = function(_, ...) return worker(...) end }) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/Thomashighbaugh.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/Thomashighbaugh.png new file mode 100644 index 0000000..b31245b Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/Thomashighbaugh.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/classic.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/classic.png new file mode 100644 index 0000000..4652140 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/classic.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/dracula.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/dracula.png new file mode 100644 index 0000000..65fb769 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/dracula.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/leftpad.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/leftpad.png new file mode 100644 index 0000000..19e4f64 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/leftpad.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/pink.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/pink.png new file mode 100644 index 0000000..2fb7bc6 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/pink.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot.jpg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot.jpg new file mode 100644 index 0000000..15ad456 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot.jpg differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot1.jpg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot1.jpg new file mode 100644 index 0000000..d1eeb44 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot1.jpg differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot2.jpg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot2.jpg new file mode 100644 index 0000000..5ce47f2 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/screenshot2.jpg differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/standard.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/standard.png new file mode 100644 index 0000000..e10479a Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/standard.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/teal.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/teal.png new file mode 100644 index 0000000..f10de7a Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/screenshots/teal.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/themes.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/themes.lua new file mode 100644 index 0000000..574f8fa --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-contributions-widget/themes.lua @@ -0,0 +1,46 @@ +local themes = { + standard = { + [4] = '#216e39', + [3] = '#30a14e', + [2] = '#40c463', + [1] = '#9be9a8', + [0] = '#ebedf0' + }, + classic = { + [4] = '#196127', + [3] = '#239a3b', + [2] = '#7bc96f', + [1] = '#c6e48b', + [0] = '#ebedf0', + }, + teal = { + [4] = '#458B74', + [3] = '#66CDAA', + [2] = '#76EEC6', + [1] = '#7FFFD4', + [0] = '#ebedf0', + }, + leftpad = { + [4] = '#F6F6F6', + [3] = '#DDDDDD', + [2] = '#A5A5A5', + [1] = '#646464', + [0] = '#2F2F2F', + }, + dracula = { + [4] = '#ff79c6', + [3] = '#bd93f9', + [2] = '#6272a4', + [1] = '#44475a', + [0] = '#282a36' + }, + pink = { + [4] = '#61185f', + [3] = '#a74aa8', + [2] = '#ca5bcc', + [1] = '#e48bdc', + [0] = '#ebedf0', + } +} + +return themes \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/README.md new file mode 100644 index 0000000..fcd5762 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/README.md @@ -0,0 +1,43 @@ +# GitHub PRs Widget + +

+ GitHub issues by-label +

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

+ +

+ +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `reviewer` | Required | GitHub username | + +## 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/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/book.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/book.svg new file mode 100644 index 0000000..7833095 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/book.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/calendar.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/calendar.svg new file mode 100644 index 0000000..45a15fe --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/git-pull-request.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/git-pull-request.svg new file mode 100644 index 0000000..54c92b9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/git-pull-request.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/message-square.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/message-square.svg new file mode 100644 index 0000000..e37df4b --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/message-square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/user.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/user.svg new file mode 100644 index 0000000..7704341 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/icons/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/init.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/init.lua new file mode 100644 index 0000000..8d59ac8 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/screenshots/screenshot1.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/screenshots/screenshot1.png new file mode 100644 index 0000000..295b56e Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/github-prs-widget/screenshots/screenshot1.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/README.md new file mode 100644 index 0000000..0a5caf4 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/README.md @@ -0,0 +1,48 @@ +# Gitlab widget + +

+ GitHub issues by-label +

+ +The widget shows the number of merge requests assigned to the user and when clicked shows additional information, such as + - author's name and avatar (opens user profile page when clicked); + - MR name (opens MR when clicked); + - source and target branches; + - when was created; + - number of comments; + - number of approvals. + +![screenshot](./screenshot.png) + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `icon` | `./icons/gitlab-icon.svg` | Path to the icon | +| `host` | Required | e.g. `https://gitlab.yourcompany.com` | +| `access_token` | Required | e.g. `h2v531iYASDz6McxYk4A` | +| `timeout` | 60 | How often in seconds the widget should be refreshed | + +_Note:_ + - to get the access token, go to **User Settings** -> **Access Tokens** and generate a token with **api** scope + +## Installation + +Clone/download repo and use widget in **rc.lua**: + +```lua +local gitlab_widget = require("awesome-wm-widgets.gitlab-widget.gitlab") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + gitlab_widget{ + host = 'https://gitlab.yourcompany.com', + access_token = 'h2v531iYASDz6McxYk4A' + }, + ... +``` diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/gitlab.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/gitlab.lua new file mode 100644 index 0000000..c0716d7 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/icons/check.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/icons/check.svg new file mode 100644 index 0000000..e9e44ac --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/icons/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/icons/gitlab-icon.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/icons/gitlab-icon.svg new file mode 100644 index 0000000..abe3f37 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/icons/gitlab-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/icons/message-circle.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/icons/message-circle.svg new file mode 100644 index 0000000..43eacbb --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/icons/message-circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/screenshot.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/screenshot.png new file mode 100644 index 0000000..8ab6590 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/gitlab-widget/screenshot.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/jira-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/jira-widget/README.md new file mode 100644 index 0000000..69958fc --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/jira-widget/README.md @@ -0,0 +1,59 @@ +# Jira widget + +The widget shows the number of tickets assigned to the user (or any other result of a JQL query, see customization section) and when clicked shows them in the list, grouped by the ticket status. Left-click on the item opens the issue in the default browser: + +

+screenshot +

+ +## How it works + +Widget uses cURL to query Jira's [REST API](https://developer.atlassian.com/server/jira/platform/rest-apis/). In order to be authenticated, widget uses a [netrc](https://ec.haxx.se/usingcurl/usingcurl-netrc) feature of the cURL, which is basically to store basic auth credentials in a .netrc file in home folder. + +If you are on Atlassian Cloud, then instead of providing a password in netrc file you can set an [API token](https://confluence.atlassian.com/cloud/api-tokens-938839638.html) which is a safer option, as you can revoke/change the token at any time. + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| `host` | Required | e.g. `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/home-modules/explicit-configs/awesome/awesome-wm-widgets/jira-widget/icon/jira-mark-gradient-blue.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/jira-widget/icon/jira-mark-gradient-blue.svg new file mode 100644 index 0000000..74ea729 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/jira-widget/icon/jira-mark-gradient-blue.svg @@ -0,0 +1 @@ +jira-icon-gradient-blue \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/jira-widget/jira.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/jira-widget/jira.lua new file mode 100644 index 0000000..f34e951 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/jira-widget/screenshot/screenshot.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/jira-widget/screenshot/screenshot.png new file mode 100644 index 0000000..7bfe9b6 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/jira-widget/screenshot/screenshot.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/README.md new file mode 100644 index 0000000..6f8ca27 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/lock.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/lock.svg new file mode 100644 index 0000000..3cfa528 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/log-out.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/log-out.svg new file mode 100644 index 0000000..77afebb --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/log-out.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/moon.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/moon.svg new file mode 100644 index 0000000..60e6ce8 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/power.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/power.svg new file mode 100644 index 0000000..68b1be8 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/power.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/power_w.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/power_w.svg new file mode 100644 index 0000000..1f9c4e3 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/power_w.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/refresh-cw.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/refresh-cw.svg new file mode 100644 index 0000000..39f52a5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/icons/refresh-cw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/logout-menu.gif b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/logout-menu.gif new file mode 100644 index 0000000..9f17b51 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/logout-menu.gif differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/logout-menu.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/logout-menu.lua new file mode 100644 index 0000000..2ba7b6f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-menu-widget/logout-menu.lua @@ -0,0 +1,138 @@ +------------------------------------------------- +-- 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 + }, + fg = beautiful.fg_normal, + bg = beautiful.bg_normal, + widget = wibox.container.background + } + + row:connect_signal("mouse::enter", function(c) c:set_bg(beautiful.bg_focus) c:set_fg(beautiful.fg_focus) end) + row:connect_signal("mouse::leave", function(c) c:set_bg(beautiful.bg_normal) c:set_fg(beautiful.fg_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 + logout_menu_widget:set_bg('#00000000') + 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/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/README.md new file mode 100644 index 0000000..aa24f34 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/README.md @@ -0,0 +1,91 @@ +# Logout Popup Widget + +Widget which allows performing lock, reboot, log out, power off and sleep actions. It can be called either by a shortcut, or by clicking on a widget in wibar. + +

+ screenshot +

+ +When the widget is shown, following shortcuts can be used: + - Escape - hide widget + - s - shutdown + - r - reboot + - u - suspend + - k - lock + - l - log out + +# Installation + +Clone this (if not cloned yet) and the [awesome-buttons](https://github.com/streetturtle/awesome-buttons) repos under **./.config/awesome/** + +```bash +cd ./.config/awesome/ +git clone https://github.com/streetturtle/awesome-wm-widgets +git clone https://github.com/streetturtle/awesome-buttons +``` +Then + +- to show by a shortcut - define a shortcut in `globalkeys`: + + ```lua + local logout_popup = require("awesome-wm-widgets.logout-popup-widget.logout-popup") + ... + globalkeys = gears.table.join( + ... + awful.key({ modkey }, "l", function() logout_popup.launch() end, {description = "Show logout screen", group = "custom"}), + ``` + +- to show by clicking on a widget in wibar - add widget to the wibar: + + ```lua + local logout_popup = require("awesome-wm-widgets.logout-popup-widget.logout-popup") + + s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + logout_popup.widget{}, + ... + ``` + +# Customisation + +| Name | Default | Description | +|---|---|---| +| `icon` | `power.svg` | If used as widget - the path to the widget's icon | +| `icon_size` | 40 | Size of the icon | +| `icon_margin` | 16 | Margin around the icon | +| `bg_color` | `beautiful.bg_normal` | The color the background of the | +| `accent_color` | `beautiful.bg_focus` | The color of the buttons | +| `text_color` | `beautiful.fg_normal` | The color of text | +| `label_color` | `beautiful.fg_normal` | The color of the button's label | +| `phrases` | `{ 'Goodbye!' }` | The table with phrase(s) to show, if more than one provided, the phrase is chosen randomly. Leave empty (`{}`) to hide the phrase | +| `hide_on_leave` | `false` | If the popup should be hidden when the mouse leaves it | +| `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 | +| `onlogout_key` | l | Keybinding to execute the logout function | +| `onlock_key` | k | Keybinding to execute the lock function | +| `onreboot_key` | r | Keybinding to execute the reboot function | +| `onsuspend_key` | u | Keybinding to execute the suspend function | +| `onpoweroff_key` | s | Keybinding to execute the poweroff function | +| `ignore_case` | true | Ignore if CAPS LOCK is enabled | + +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/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-dark.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-dark.png new file mode 100644 index 0000000..06e7c9c Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-dark.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-dracula.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-dracula.png new file mode 100644 index 0000000..3c61c46 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-dracula.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-nord.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-nord.png new file mode 100644 index 0000000..9ab4b55 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-nord.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-outrun.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-outrun.png new file mode 100644 index 0000000..9be68b5 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-outrun.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-popup.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-popup.lua new file mode 100644 index 0000000..dab004b --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/logout-popup.lua @@ -0,0 +1,221 @@ +------------------------------------------------- +-- 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 hide_on_leave = args.hide_on_leave or false + + local onlogout = args.onlogout or function () awesome.quit() end + local onlock = args.onlock or function() awful.spawn.with_shell("i3lock-fancy") end + local onreboot = args.onreboot or function() awful.spawn.with_shell("reboot") end + local onsuspend = args.onsuspend or function() awful.spawn.easy_async_with_shell("i3lock-fancy", + function() + -- awful.spawn.with_shell("systemctl hybrid-sleep") + awful.spawn.with_shell("systemctl suspend") + end) + end + local onpoweroff = args.onpoweroff or function() awful.spawn.with_shell("shutdown now") end + + local onlogout_key = args.onlogout_key or 'l' + local onlock_key = args.onlock_key or 'k' + local onreboot_key = args.onreboot_key or 'r' + local onsuspend_key = args.onsuspend_key or 'u' + local onpoweroff_key = args.onpoweroff_key or 's' + local all_keys = onlogout_key .. onlock_key .. onreboot_key .. onsuspend_key .. onpoweroff_key + + local ignore_case = args.ignore_case or true + + 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 (' .. onlogout_key .. ')', + accent_color, label_color, onlogout, icon_size, icon_margin), + create_button('lock', 'Lock (' .. onlock_key .. ')', + accent_color, label_color, onlock, icon_size, icon_margin), + create_button('refresh-cw', 'Reboot (' .. onreboot_key .. ')', + accent_color, label_color, onreboot, icon_size, icon_margin), + create_button('moon', 'Suspend (' .. onsuspend_key .. ')', + accent_color, label_color, onsuspend, icon_size, icon_margin), + create_button('power', 'Power Off (' .. onpoweroff_key .. ')', + 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, + halign = '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 + if hide_on_leave then + w:connect_signal("mouse::leave", function() + phrase_widget:set_text('') + capi.keygrabber.stop() + w.visible = false + end) + end + + 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 + else + if ignore_case then + key = string.lower(key) + onlogout_key = string.lower(onlogout_key) + onlock_key = string.lower(onlock_key) + onreboot_key = string.lower(onreboot_key) + onsuspend_key = string.lower(onsuspend_key) + onpoweroff_key = string.lower(onpoweroff_key) + all_keys = string.lower(all_keys) + end + + if key == onpoweroff_key then onpoweroff() + elseif key == onreboot_key then onreboot() + elseif key == onsuspend_key then onsuspend() + elseif key == onlock_key then onlock() + elseif key == onlogout_key then onlogout() + end + + if string.match(all_keys, key) then + phrase_widget:set_text('') + capi.keygrabber.stop() + w.visible = false + end + 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/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/power.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/power.svg new file mode 100644 index 0000000..1f9c4e3 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/power.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/screenshot.gif b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/screenshot.gif new file mode 100644 index 0000000..4975c19 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/screenshot.gif differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/screenshot.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/screenshot.png new file mode 100644 index 0000000..74ed7f0 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/logout-popup-widget/screenshot.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/mpdarc-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/mpdarc-widget/README.md new file mode 100644 index 0000000..a83a39b --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/mpdarc-widget/mpdarc.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/mpdarc-widget/mpdarc.lua new file mode 100644 index 0000000..f1d6930 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/mpris-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/mpris-widget/README.md new file mode 100644 index 0000000..214243d --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/mpris-widget/init.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/mpris-widget/init.lua new file mode 100644 index 0000000..e5e149a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/mpris-widget/init.lua @@ -0,0 +1,342 @@ +------------------------------------------------- +-- 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 wibox = require('wibox') +local gears = require('gears') + +local playerctl = { + player_name = nil, +} + +function playerctl:set_player(name) + self.player_name = name + + if self.timer ~= nil then + self.timer:stop() + playerctl:watch(self.watch_params.timeout, self.watch_params.callback, self.watch_params.widget) + end +end + +function playerctl:cmd(cmd) return "playerctl -p '" .. self.player_name .. "' " .. cmd end + +local watch_fields = { + [1] = 'status', + [2] = 'xesam:artist', + [3] = 'xesam:title', + [4] = 'mpris:artUrl', + [5] = 'position', + [6] = 'mpris:length', + [7] = 'album', + [8] = 'xesam:contentCreated', +} + +local watch_cmd = string.format("-f '{{%s}}' metadata", table.concat(watch_fields, '}};{{')) + +function playerctl:watch(timeout, callback, widget) + local cmd = self:cmd(watch_cmd) + + self.watch_params = { timeout = timeout, callback = callback, widget = widget } + + local cb = function(cb_widget, stdout, _, _, _) + local words = gears.string.split(stdout, ';') + + local position, length, progress = tonumber(words[5]), tonumber(words[6]) + + if position ~= nil and length ~= nil and length > 0 then + progress = position / length + end + + local metadata = { + status = words[1], + artist = words[2], + current_song = words[3], + art_url = words[4], + position = position, + length = length, + album = words[7], + progress = progress, + } + + if words[8] ~= nil then + metadata.year = string.sub(words[8], 0, 4) + end + + callback(cb_widget, metadata) + end + + local _, timer = awful.widget.watch(cmd, timeout, cb, widget) + self.timer = timer +end + +function playerctl:toggle() awful.spawn(self:cmd('play-pause'), false) end + +function playerctl:next() awful.spawn(self:cmd('next'), false) end + +function playerctl:prev() awful.spawn(self:cmd('previous'), false) end + +local player_selector_popup = { + popup = awful.popup { + bg = beautiful.bg_normal, + fg = beautiful.fg_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 = {}, + }, + + rows = { layout = wibox.layout.fixed.vertical }, +} + +function player_selector_popup:add_radio_button(player_name) + local checkbox = wibox.widget { + layout = wibox.container.place, + valign = 'center', + { + checked = player_name == playerctl.player_name, + color = beautiful.bg_normal, + paddings = 2, + shape = gears.shape.circle, + forced_width = 20, + forced_height = 20, + check_color = beautiful.fg_normal, + widget = wibox.widget.checkbox, + }, + } + + checkbox:connect_signal('button::press', function() + playerctl:set_player(player_name) + self:toggle() + end) + + local row = 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, + fg = beautiful.fg_normal, + widget = wibox.container.background, + } + + table.insert(self.rows, row) +end + +function player_selector_popup:rebuild() + awful.spawn.easy_async('playerctl -l', function(stdout, _, _, _) + for i = 0, #self.rows do + self.rows[i] = nil + end + + for name in stdout:gmatch('[^\r\n]+') do + if name ~= '' and name ~= nil then + self:add_radio_button(name) + end + end + + self.popup:setup(self.rows) + self.popup.visible = true + self.popup:move_next_to(mouse.current_widget_geometry) + end) +end + +function player_selector_popup:toggle() + if self.popup.visible then + self.popup.visible = false + else + self:rebuild() + end +end + +local function duration(microseconds) + if microseconds == nil then + return '--:--' + end + + local seconds = math.floor(microseconds / 1000000) + local minutes = math.floor(seconds / 60) + seconds = seconds - minutes * 60 + local hours = math.floor(minutes / 60) + minutes = minutes - hours * 60 + if hours >= 1 then + return string.format('%d:%02d:%02d', hours, minutes, seconds) + end + return string.format('%d:%02d', minutes, seconds) +end + +local mpris_widget = {} + +local function worker(user_args) + local args = user_args or {} + + local font = args.font or 'Roboto Condensed 16px' + + local path_to_icons = '/usr/share/icons/Adwaita' + + local pause_icon = args.pause_icon or path_to_icons .. '/symbolic/actions/media-playback-pause-symbolic.svg' + local play_icon = args.play_icon or path_to_icons .. '/symbolic/actions/media-playback-start-symbolic.svg' + local stop_icon = args.stop_icon or path_to_icons .. '/symbolic/actions/media-playback-stop-symbolic.svg' + local library_icon = args.library_icon or path_to_icons .. '/symbolic/places/folder-music-symbolic.svg' + local popup_width = args.popup_width or 300 + + playerctl.player_name = args.default_player or 'mpv' + + local icon = wibox.widget { + widget = wibox.widget.imagebox, + image = play_icon, + } + + local progress_widget = wibox.widget { + widget = wibox.container.arcchart, + icon, + min_value = 0, + max_value = 1, + value = 0, + thickness = 2, + start_angle = 4.71238898, -- 2pi*3/4 + forced_height = 24, + forced_width = 24, + rounded_edge = true, + colors = { '#ffffff11', 'black' }, + paddings = 2, + } + + local artist_widget = wibox.widget { + font = font, + widget = wibox.widget.textbox, + } + + local title_widget = wibox.widget { + font = font, + widget = wibox.widget.textbox, + } + + mpris_widget = wibox.widget { + artist_widget, + progress_widget, + title_widget, + spacing = 4, + layout = wibox.layout.fixed.horizontal, + } + + local cover_art_widget = wibox.widget { + widget = wibox.widget.imagebox, + forced_height = 0, + forced_width = popup_width, + resize_allowed = true, + } + + local metadata_widget = wibox.widget { + widget = wibox.widget.textbox, + font = font, + forced_height = 100, + forced_width = popup_width, + } + + local update_metadata = function(meta) + artist_widget:set_text(meta.artist) + title_widget:set_text(meta.current_song) + + local s = meta.album + if meta.year ~= nil and #meta.year == 4 then + s = s .. ' (' .. meta.year .. ')' + end + s = s .. '\n' .. meta.current_song .. ' (' .. duration(meta.position) .. '/' .. duration(meta.length) .. ')' + metadata_widget:set_text(s) + + progress_widget.values = { 1.0 - (meta.progress or 0.0), meta.progress or 0.0 } + + -- poor man's urldecode + local art_url = meta.art_url:gsub('file://', '/') + art_url = art_url:gsub('%%(%x%x)', function(x) return string.char(tonumber(x, 16)) end) + + if art_url ~= nil and art_url ~= '' then + cover_art_widget.image = art_url + cover_art_widget.forced_height = popup_width + else + cover_art_widget.image = nil + cover_art_widget.forced_height = 0 + end + end + + local update_graphic = function(widget, metadata) + if metadata.current_song ~= nil then + if string.len(metadata.current_song) > 40 then + metadata.current_song = string.sub(metadata.current_song, 0, 38) .. '…' + end + end + + if metadata.status == 'Playing' then + icon.image = pause_icon + widget.colors = { beautiful.widget_main_color } + update_metadata(metadata) + elseif metadata.status == 'Paused' then + icon.image = play_icon + widget.colors = { beautiful.widget_main_color } + update_metadata(metadata) + elseif metadata.status == 'Stopped' then + icon.image = stop_icon + else -- no player is running + icon.image = library_icon + widget.colors = { beautiful.widget_red } + end + end + + mpris_widget:buttons( + awful.util.table.join( + awful.button({}, 3, function() player_selector_popup:toggle() end), + awful.button({}, 4, function() playerctl:next() end), + awful.button({}, 5, function() playerctl:prev() end), + awful.button({}, 1, function() playerctl:toggle() end) + ) + ) + + playerctl:watch(1, update_graphic, mpris_widget) + + local mpris_popup = awful.popup { + border_color = beautiful.border_color, + ontop = true, + visible = false, + widget = wibox.widget { + cover_art_widget, + metadata_widget, + layout = wibox.layout.fixed.vertical, + }, + } + + mpris_widget:connect_signal('mouse::enter', function() + mpris_popup.visible = true + mpris_popup:move_next_to(mouse.current_widget_geometry) + end) + mpris_widget:connect_signal('mouse::leave', function() mpris_popup.visible = false end) + --}} + + return mpris_widget +end + +return setmetatable(mpris_widget, { + __call = function(_, ...) return worker(...) end, +}) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/net-speed-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/net-speed-widget/README.md new file mode 100644 index 0000000..a09893e --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/net-speed-widget/icons/down.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/net-speed-widget/icons/down.svg new file mode 100644 index 0000000..9a98f39 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/net-speed-widget/icons/down.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/net-speed-widget/icons/up.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/net-speed-widget/icons/up.svg new file mode 100644 index 0000000..e3c12a7 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/net-speed-widget/icons/up.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/net-speed-widget/net-speed.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/net-speed-widget/net-speed.lua new file mode 100644 index 0000000..928b1e6 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/README.md new file mode 100644 index 0000000..01df3cf --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/README.md @@ -0,0 +1,37 @@ +# Pacman widget for AwesomeWM + +This widget displays the number of upgradable Pacman packages. Clicking the icon reveals a scrollable list of available upgrades. A full system upgrade can be performed from the widget via Polkit. + +![](screenshots/pacman.gif) + +## Requirements +`lxpolkit` is the default [Polkit agent](https://wiki.archlinux.org/title/Polkit). + +The widget also uses the `checkupdates` script from the `pacman-contrib` package. + + +## Installation + +Clone the repo under **~/.config/awesome/** and add the following to **rc.lua**: + +```lua +local pacman_widget = require('awesome-wm-widgets.pacman-widget.pacman') +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + pacman_widget(), + -- custom (shown with defaults) + pacman_widget { + interval = 600, -- Refresh every 10 minutes + popup_bg_color = '#222222', + popup_border_width = 1, + popup_border_color = '#7e7e7e', + popup_height = 10, -- 10 packages shown in scrollable window + popup_width = 300, + polkit_agent_path = '/usr/bin/lxpolkit' + }, +``` + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/icons/pacman-full.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/icons/pacman-full.svg new file mode 100644 index 0000000..50bb939 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/icons/pacman-full.svg @@ -0,0 +1,86 @@ + + + + + + + + Gnome Symbolic Icon Theme + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/icons/pacman.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/icons/pacman.svg new file mode 100644 index 0000000..9c1cb2c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/icons/pacman.svg @@ -0,0 +1,82 @@ + + + + + + + + Gnome Symbolic Icon Theme + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/icons/upgrade.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/icons/upgrade.svg new file mode 100644 index 0000000..0a556ae --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/icons/upgrade.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/pacman.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/pacman.lua new file mode 100644 index 0000000..5f5ff30 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/pacman.lua @@ -0,0 +1,256 @@ +local naughty = require("naughty") +local wibox = require("wibox") +local awful = require("awful") +local beautiful = require("beautiful") +local gears = require("gears") + +local DIR = os.getenv("HOME") .. "/.config/awesome/pacman-widget/" +local ICON_DIR = DIR .. "icons/" + +local pacman_widget = {} +local config, timer = {}, {} + +config.interval = 600 +config.popup_bg_color = "#222222" +config.popup_border_width = 1 +config.popup_border_color = "#7e7e7e" +config.popup_height = 10 +config.popup_width = 300 +config.polkit_agent_path = "/usr/bin/lxpolkit" + +local function worker(user_args) + local args, _config = user_args or {}, {} + for prop, value in pairs(config) do + _config[prop] = args[prop] or beautiful[prop] or value + end + + awful.spawn.once(_config.polkit_agent_path) + + pacman_widget = wibox.widget { + { + { + id = "icon", + resize = false, + widget = wibox.widget.imagebox, + }, + valign = "center", + layout = wibox.container.place, + }, + { + id = "txt", + font = args.font, + widget = wibox.widget.textbox + }, + spacing = 5, + layout = wibox.layout.fixed.horizontal, + } + function pacman_widget.set(new_value) + pacman_widget:get_children_by_id("txt")[1]:set_text(new_value) + pacman_widget:get_children_by_id("icon")[1]:set_image( + ICON_DIR .. (tonumber(new_value) > 0 and "pacman" or "pacman-full") .. ".svg" + ) + end + + local rows, ptr = wibox.layout.fixed.vertical(), 0 + rows:connect_signal("button::press", function(_,_,_,button) + if button == 4 then + if ptr > 0 then + rows.children[ptr].visible = true + ptr = ptr - 1 + end + elseif button == 5 then + if ptr < #rows.children and ((#rows.children - ptr) > _config.popup_height) then + ptr = ptr + 1 + rows.children[ptr].visible = false + end + end + end) + + local popup = awful.popup { + border_width = _config.popup_border_width, + border_color = _config.popup_border_color, + shape = gears.shape.rounded_rect, + visible = false, + ontop = true, + offset = { y = 5 }, + widget = {} + } + + pacman_widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() + if popup.visible then + popup.visible = false + else + popup.visible = true + popup:move_next_to(_G.mouse.current_widget_geometry) + end + end) + ) + ) + + local upgr_opacity = 0.6 + local upgr_btn = wibox.widget { + { + image = ICON_DIR .. "upgrade.svg", + resize = false, + layout = wibox.widget.imagebox + }, + opacity = upgr_opacity, + layout = wibox.container.background + } + + local old_cursor, old_wibox + local busy, upgrading = false, false + upgr_btn:connect_signal("mouse::enter", function(c) + if not busy then + c:set_opacity(1) + c:emit_signal("widget::redraw_needed") + local wb = _G.mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand2" + end + end) + upgr_btn:connect_signal("mouse::leave", function(c) + if not busy then + c:set_opacity(upgr_opacity) + c:emit_signal("widget::redraw_needed") + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end + end) + upgr_btn:connect_signal("button::press", function(c) + c:set_opacity(1) + c:emit_signal("widget::redraw_needed") + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + if not busy then + busy = true + local one_shot = true + awful.spawn.with_line_callback("bash -c " .. DIR .. "upgrade", { + stdout = function() + if one_shot then + upgrading, one_shot = true, false + timer:emit_signal("timeout") + end + end, + stderr = function(line) + if (line ~= nil and line ~= "") then + if string.find(line, "warning") then + naughty.notify({ + title = "Warning!", + text = line, + timeout = 0 + }) + else + naughty.notify({ + preset = naughty.config.presets.critical, + title = "Error!", + text = line, + }) + end + end + end, + exit = function() + upgrading, busy = false, false + c:set_opacity(upgr_opacity) + c:emit_signal("widget::redraw_needed") + timer:emit_signal("timeout") + end, + }) + end + end) + + timer = select(2, awful.widget.watch([[bash -c "checkupdates 2>/dev/null"]], + _config.interval, + function(widget, stdout) + local upgrades_tbl = {} + for value in stdout:gmatch("([^\n]+)") do + upgrades_tbl[#upgrades_tbl+1] = value + end + widget.set(#upgrades_tbl) + + local popup_header_height, popup_row_height = 30, 20 + local header = wibox.widget { + { + nil, + { + markup = "" .. (upgrading and "Upgrading " .. #upgrades_tbl .. " Packages" or + (#upgrades_tbl == 0 and "No" or #upgrades_tbl) .. " Available Upgrades") .. "", + layout = wibox.widget.textbox, + }, + #upgrades_tbl > 0 and { + upgr_btn, + valign = "center", + layout = wibox.container.place, + }, + expand = "none", + layout = wibox.layout.align.horizontal, + }, + forced_height = popup_header_height, + left = 20, + right = 20, + layout = wibox.container.margin + } + + for k, v in ipairs(upgrades_tbl) do + for i = 1, #rows.children do + if v == rows.children[i].get_txt() then goto continue end + end + local row = wibox.widget{ + { + id = "idx", + text = tostring(k), + widget = wibox.widget.textbox + }, + { + id = "txt", + text = v, + forced_height = popup_row_height, + paddings = 1, + widget = wibox.widget.textbox + }, + layout = wibox.layout.ratio.horizontal, + } + function row.get_txt() return row:get_children_by_id("txt")[1].text end + function row.set_idx(idx) row:get_children_by_id("idx")[1]:set_text(idx) end + row:ajust_ratio(2, 0.1, 0.9, 0) + rows:insert(k, row) + ::continue:: + end + + local height = popup_header_height + math.min(#upgrades_tbl, _config.popup_height) * popup_row_height + popup:setup { + { + { + { + { + header, + rows, + forced_height = height, + layout = wibox.layout.fixed.vertical + }, + content_fill_horizontal = true, + layout = wibox.container.place + }, + margins = 10, + layout = wibox.container.margin + }, + bg = _config.popup_bg_color, + layout = wibox.container.background + }, + forced_width = _config.popup_width, + layout = wibox.layout.fixed.horizontal + } + end, + pacman_widget + )) + return pacman_widget +end + +return setmetatable(pacman_widget, { __call = function(_, ...) return worker(...) end }) + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/screenshots/pacman.gif b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/screenshots/pacman.gif new file mode 100644 index 0000000..1c26bcc Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/screenshots/pacman.gif differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/upgrade b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/upgrade new file mode 100755 index 0000000..af2f9f7 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pacman-widget/upgrade @@ -0,0 +1,2 @@ +#!/bin/bash +pkexec --disable-internal-agent pacman -Syu --noconfirm diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/pactl-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pactl-widget/README.md new file mode 100644 index 0000000..6422d4a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pactl-widget/README.md @@ -0,0 +1,55 @@ +# Pactl volume widget + +This is a volume widget that uses `pactl` only for controlling volume and +selecting sinks and sources. Hence, it can be used with PulseAudio or PipeWire +likewise, unlike the original Volume widget. + +Other than that it is heavily based on the original widget, including its +customization and icon options. For screenshots, see the original widget. + +## Installation + +Clone the repo under **~/.config/awesome/** and add widget in **rc.lua**: + +```lua +local volume_widget = require('awesome-wm-widgets.pactl-widget.volume') +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + volume_widget(), + -- customized + volume_widget{ + widget_type = 'arc' + }, +``` + +### Shortcuts + +To improve responsiveness of the widget when volume level is changed by a shortcut use corresponding methods of the widget: + +```lua +awful.key({}, "XF86AudioRaiseVolume", function () volume_widget:inc(5) end), +awful.key({}, "XF86AudioLowerVolume", function () volume_widget:dec(5) end), +awful.key({}, "XF86AudioMute", function () volume_widget:toggle() end), +``` + +## Customization + +It is possible to customize the widget by providing a table with all or some of +the following config parameters: + +### Generic parameter + +| Name | Default | Description | +|---|---|---| +| `mixer_cmd` | `pavucontrol` | command to run on middle click (e.g. a mixer program) | +| `step` | 5 | How much the volume is raised or lowered at once (in %) | +| `widget_type`| `icon_and_text`| Widget type, one of `horizontal_bar`, `vertical_bar`, `icon`, `icon_and_text`, `arc` | +| `device` | `@DEFAULT_SINK@` | Select the device name to control | +| `tooltip` | false | Display volume level in a tooltip when the mouse cursor hovers the widget | + +For more details on parameters depending on the chosen widget type, please +refer to the original Volume widget. diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/pactl-widget/pactl.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pactl-widget/pactl.lua new file mode 100644 index 0000000..0dbae94 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pactl-widget/pactl.lua @@ -0,0 +1,124 @@ +local spawn = require("awful.spawn") +local utils = require("awesome-wm-widgets.pactl-widget.utils") + +local pactl = {} + + +function pactl.volume_increase(device, step) + spawn('pactl set-sink-volume ' .. device .. ' +' .. step .. '%', false) +end + +function pactl.volume_decrease(device, step) + spawn('pactl set-sink-volume ' .. device .. ' -' .. step .. '%', false) +end + +function pactl.mute_toggle(device) + spawn('pactl set-sink-mute ' .. device .. ' toggle', false) +end + +function pactl.get_volume(device) + local stdout = utils.popen_and_return('pactl get-sink-volume ' .. device) + + local volsum, volcnt = 0, 0 + for vol in string.gmatch(stdout, "(%d?%d?%d)%%") do + vol = tonumber(vol) + if vol ~= nil then + volsum = volsum + vol + volcnt = volcnt + 1 + end + end + + if volcnt == 0 then + return nil + end + + return volsum / volcnt +end + +function pactl.get_mute(device) + local stdout = utils.popen_and_return('LC_ALL=C pactl get-sink-mute ' .. device) + if string.find(stdout, "yes") then + return true + else + return false + end +end + +function pactl.get_sinks_and_sources() + local default_sink = utils.trim(utils.popen_and_return('pactl get-default-sink')) + local default_source = utils.trim(utils.popen_and_return('pactl get-default-source')) + + local sinks = {} + local sources = {} + + local device + local ports + local key + local value + local in_section + + for line in utils.popen_and_return('LC_ALL=C pactl list'):gmatch('[^\r\n]*') do + + if string.match(line, '^%a+ #') then + in_section = nil + end + + local is_sink_line = string.match(line, '^Sink #') + local is_source_line = string.match(line, '^Source #') + + if is_sink_line or is_source_line then + in_section = "main" + + device = { + id = line:match('#(%d+)'), + is_default = false + } + if is_sink_line then + table.insert(sinks, device) + else + table.insert(sources, device) + end + end + + -- Found a new subsection + if in_section ~= nil and string.match(line, '^\t%a+:$') then + in_section = utils.trim(line):lower() + in_section = string.sub(in_section, 1, #in_section-1) + + if in_section == 'ports' then + ports = {} + device['ports'] = ports + end + end + + -- Found a key-value pair + if string.match(line, "^\t*[^\t]+: ") then + local t = utils.split(line, ':') + key = utils.trim(t[1]):lower():gsub(' ', '_') + value = utils.trim(t[2]) + end + + -- Key value pair on 1st level + if in_section ~= nil and string.match(line, "^\t[^\t]+: ") then + device[key] = value + + if key == "name" and (value == default_sink or value == default_source) then + device['is_default'] = true + end + end + + -- Key value pair in ports section + if in_section == "ports" and string.match(line, "^\t\t[^\t]+: ") then + ports[key] = value + end + end + + return sinks, sources +end + +function pactl.set_default(type, name) + spawn('pactl set-default-' .. type .. ' "' .. name .. '"', false) +end + + +return pactl diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/pactl-widget/utils.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pactl-widget/utils.lua new file mode 100644 index 0000000..52e7869 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pactl-widget/utils.lua @@ -0,0 +1,28 @@ +local utils = {} + + +function utils.trim(str) + return string.match(str, "^%s*(.-)%s*$") +end + +function utils.split(string_to_split, separator) + if separator == nil then separator = "%s" end + local t = {} + + for str in string.gmatch(string_to_split, "([^".. separator .."]+)") do + table.insert(t, str) + end + + return t +end + +function utils.popen_and_return(cmd) + local handle = io.popen(cmd) + local result = handle:read("*a") + handle:close() + + return result +end + + +return utils diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/pactl-widget/volume.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pactl-widget/volume.lua new file mode 100644 index 0000000..1c3108c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pactl-widget/volume.lua @@ -0,0 +1,243 @@ +------------------------------------------------- +-- A purely pactl-based volume widget based on the original Volume widget +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/pactl-widget + +-- @author Stefan Huber +-- @copyright 2023 Stefan Huber +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local spawn = require("awful.spawn") +local gears = require("gears") +local beautiful = require("beautiful") + +local pactl = require("awesome-wm-widgets.pactl-widget.pactl") +local utils = require("awesome-wm-widgets.pactl-widget.utils") + + +local widget_types = { + icon_and_text = require("awesome-wm-widgets.volume-widget.widgets.icon-and-text-widget"), + icon = require("awesome-wm-widgets.volume-widget.widgets.icon-widget"), + arc = require("awesome-wm-widgets.volume-widget.widgets.arc-widget"), + horizontal_bar = require("awesome-wm-widgets.volume-widget.widgets.horizontal-bar-widget"), + vertical_bar = require("awesome-wm-widgets.volume-widget.widgets.vertical-bar-widget") +} +local volume = {} + +local rows = { layout = wibox.layout.fixed.vertical } + +local popup = awful.popup{ + bg = beautiful.bg_normal, + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + maximum_width = 400, + offset = { y = 5 }, + widget = {} +} + +local function build_main_line(device) + if device.active_port ~= nil and device.ports[device.active_port] ~= nil then + return device.description .. ' · ' .. utils.split(device.ports[device.active_port], " ")[1] + else + return device.description + end +end + +local function build_rows(devices, on_checkbox_click, device_type) + local device_rows = { layout = wibox.layout.fixed.vertical } + for _, device in pairs(devices) do + + local checkbox = wibox.widget { + checked = device.is_default, + color = beautiful.bg_normal, + paddings = 2, + shape = gears.shape.circle, + forced_width = 20, + forced_height = 20, + check_color = beautiful.fg_urgent, + widget = wibox.widget.checkbox + } + + checkbox:connect_signal("button::press", function() + pactl.set_default(device_type, device.name) + on_checkbox_click() + end) + + local row = wibox.widget { + { + { + { + checkbox, + valign = 'center', + layout = wibox.container.place, + }, + { + { + text = build_main_line(device), + align = 'left', + widget = wibox.widget.textbox + }, + left = 10, + layout = wibox.container.margin + }, + spacing = 8, + layout = wibox.layout.align.horizontal + }, + margins = 4, + layout = wibox.container.margin + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + } + + row:connect_signal("mouse::enter", function(c) c:set_bg(beautiful.bg_focus) end) + row:connect_signal("mouse::leave", function(c) c:set_bg(beautiful.bg_normal) end) + + local old_cursor, old_wibox + row:connect_signal("mouse::enter", function() + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + row:connect_signal("mouse::leave", function() + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + row:connect_signal("button::press", function() + pactl.set_default(device_type, device.name) + on_checkbox_click() + end) + + table.insert(device_rows, row) + end + + return device_rows +end + +local function build_header_row(text) + return wibox.widget{ + { + markup = "" .. text .. "", + align = 'center', + widget = wibox.widget.textbox + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + } +end + +local function rebuild_popup() + for i = 0, #rows do + rows[i]=nil + end + + local sinks, sources = pactl.get_sinks_and_sources() + table.insert(rows, build_header_row("SINKS")) + table.insert(rows, build_rows(sinks, function() rebuild_popup() end, "sink")) + table.insert(rows, build_header_row("SOURCES")) + table.insert(rows, build_rows(sources, function() rebuild_popup() end, "source")) + + popup:setup(rows) +end + +local function worker(user_args) + + local args = user_args or {} + + local mixer_cmd = args.mixer_cmd or 'pavucontrol' + local widget_type = args.widget_type + local refresh_rate = args.refresh_rate or 1 + local step = args.step or 5 + local device = args.device or '@DEFAULT_SINK@' + local tooltip = args.tooltip or false + + if widget_types[widget_type] == nil then + volume.widget = widget_types['icon_and_text'].get_widget(args.icon_and_text_args) + else + volume.widget = widget_types[widget_type].get_widget(args) + end + + local function update_graphic(widget) + local vol = pactl.get_volume(device) + if vol ~= nil then + widget:set_volume_level(vol) + end + + if pactl.get_mute(device) then + widget:mute() + else + widget:unmute() + end + end + + function volume:inc(s) + pactl.volume_increase(device, s or step) + update_graphic(volume.widget) + end + + function volume:dec(s) + pactl.volume_decrease(device, s or step) + update_graphic(volume.widget) + end + + function volume:toggle() + pactl.mute_toggle(device) + update_graphic(volume.widget) + end + + function volume:popup() + if popup.visible then + popup.visible = not popup.visible + else + rebuild_popup() + popup:move_next_to(mouse.current_widget_geometry) + end + end + + function volume:mixer() + if mixer_cmd then + spawn(mixer_cmd) + end + end + + volume.widget:buttons( + awful.util.table.join( + awful.button({}, 1, function() volume:toggle() end), + awful.button({}, 2, function() volume:mixer() end), + awful.button({}, 3, function() volume:popup() end), + awful.button({}, 4, function() volume:inc() end), + awful.button({}, 5, function() volume:dec() end) + ) + ) + + gears.timer { + timeout = refresh_rate, + call_now = true, + autostart = true, + callback = function() + update_graphic(volume.widget) + end + } + + if tooltip then + awful.tooltip { + objects = { volume.widget }, + timer_function = function() + return pactl.get_volume(device) .. " %" + end, + } + end + + return volume.widget +end + + +return setmetatable(volume, { __call = function(_, ...) return worker(...) end }) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/pomodoroarc-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pomodoroarc-widget/README.md new file mode 100644 index 0000000..49b1b2c --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/pomodoroarc-widget/pomodoroarc.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/pomodoroarc-widget/pomodoroarc.lua new file mode 100644 index 0000000..497a208 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/ram-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/ram-widget/README.md new file mode 100644 index 0000000..a932b52 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/ram-widget/out.gif b/home-modules/explicit-configs/awesome/awesome-wm-widgets/ram-widget/out.gif new file mode 100644 index 0000000..736f894 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/ram-widget/out.gif differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/ram-widget/ram-widget.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/ram-widget/ram-widget.lua new file mode 100644 index 0000000..867d28e --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-2/run-shell-2.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-2/run-shell-2.lua new file mode 100644 index 0000000..89491de --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-2/run.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-2/run.lua new file mode 100644 index 0000000..12644ba --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-3/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-3/README.md new file mode 100644 index 0000000..95749d1 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-3/blur.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-3/blur.png new file mode 100644 index 0000000..4e8b54c Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-3/blur.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-3/pixelate.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-3/pixelate.png new file mode 100644 index 0000000..fedf320 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-3/pixelate.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-3/run-shell.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell-3/run-shell.lua new file mode 100644 index 0000000..0015232 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell/README.md new file mode 100644 index 0000000..9ae7f5d --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell/out.mp4 b/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell/out.mp4 new file mode 100644 index 0000000..5db8172 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell/out.mp4 differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell/run-shell.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/run-shell/run-shell.lua new file mode 100644 index 0000000..a43c4d5 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/screenshot.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/screenshot.png new file mode 100644 index 0000000..9406ebf Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/screenshot.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/screenshot_with_sprtrs.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/screenshot_with_sprtrs.png new file mode 100644 index 0000000..361d5a2 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/screenshot_with_sprtrs.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/scripts/update_site.sh b/home-modules/explicit-configs/awesome/awesome-wm-widgets/scripts/update_site.sh new file mode 100755 index 0000000..35fc971 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/scripts/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-shell/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-shell/README.md new file mode 100644 index 0000000..0f4981d --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-shell/demo.gif b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-shell/demo.gif new file mode 100644 index 0000000..6696b3a Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-shell/demo.gif differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-shell/spotify-shell.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-shell/spotify-shell.lua new file mode 100644 index 0000000..0611e66 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/README.md new file mode 100644 index 0000000..841bb80 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/spo-wid-1.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/spo-wid-1.png new file mode 100644 index 0000000..5c7e403 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/spo-wid-1.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/spotify-widget-custom-paused.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/spotify-widget-custom-paused.png new file mode 100644 index 0000000..9ac9c4a Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/spotify-widget-custom-paused.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/spotify-widget-custom-playing.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/spotify-widget-custom-playing.png new file mode 100644 index 0000000..f9628f9 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/spotify-widget-custom-playing.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/spotify.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/spotify.lua new file mode 100644 index 0000000..0ea7cfd --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/spotify-widget/spotify.lua @@ -0,0 +1,174 @@ +------------------------------------------------- +-- 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 PLAY_PAUSE_CMD = sp_bin .. ' play' + local NEXT_SONG_CMD = sp_bin .. ' next' + local PREVIOUS_SONG_CMD = sp_bin .. ' prev' + + 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(PLAY_PAUSE_CMD, false) -- left click + elseif (button == 4) then + awful.spawn(NEXT_SONG_CMD, false) -- scroll up + elseif (button == 5) then + awful.spawn(PREVIOUS_SONG_CMD, 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/home-modules/explicit-configs/awesome/awesome-wm-widgets/stackoverflow-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/stackoverflow-widget/README.md new file mode 100644 index 0000000..c47abb6 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/stackoverflow-widget/screenshot.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/stackoverflow-widget/screenshot.png new file mode 100644 index 0000000..b0058ab Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/stackoverflow-widget/screenshot.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/stackoverflow-widget/so-icon.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/stackoverflow-widget/so-icon.svg new file mode 100644 index 0000000..5298d4c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/stackoverflow-widget/so-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/stackoverflow-widget/stackoverflow.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/stackoverflow-widget/stackoverflow.lua new file mode 100644 index 0000000..15d2837 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/README.md new file mode 100644 index 0000000..c97d845 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/checkbox-checked-symbolic.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/checkbox-checked-symbolic.svg new file mode 100644 index 0000000..afeca62 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/checkbox-checked-symbolic.svg @@ -0,0 +1,148 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/chevron-down.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/chevron-down.svg new file mode 100644 index 0000000..dceeb0f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/chevron-up.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/chevron-up.svg new file mode 100644 index 0000000..88474ce --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/chevron-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/list-add-symbolic.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/list-add-symbolic.svg new file mode 100644 index 0000000..9cc2d3a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/list-add-symbolic.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/todo.gif b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/todo.gif new file mode 100644 index 0000000..7160e21 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/todo.gif differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/todo.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/todo.lua new file mode 100644 index 0000000..78ca262 --- /dev/null +++ b/home-modules/explicit-configs/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/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/window-close-symbolic.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/window-close-symbolic.svg new file mode 100644 index 0000000..46ff888 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/todo-widget/window-close-symbolic.svg @@ -0,0 +1,95 @@ + + + + + + + + Gnome Symbolic Icon Theme + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/README.MD b/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/README.MD new file mode 100644 index 0000000..a82fda7 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/README.MD @@ -0,0 +1,38 @@ +# Translate Widget + +This widget allows quickly translate words or phrases without opening a browser - just using Awesome. To provide direction of the translation add the 2 letters code of the source and target languages at the end of the phrase, for example _hello enfr_ will translate _hello_ from English to French. This widget is based on [Watson Language Translator](https://www.ibm.com/watson/services/language-translator/) from IBM. + +![demo](./demo.gif) + +## Controls + + - Mod4 + c - opens a translate prompt; + - left click on the popup widget - copies the translation to the clipboard and closes widget; + - right click on the popup widget - copies text to translate to the clipboard and closes widget. + +## Installation + +1. Clone repo under **~/.config/awesome/** +1. Create an IBM Cloud API key at [cloud.ibm.com/iam/apikeys](https://cloud.ibm.com/iam/apikeys) +1. Copy a service URL by going to [resource list](https://cloud.ibm.com/resources), then under "Services" select "Language Translator" option, and then copy URL from the "Credentials" section +1. Require widget in **rc.lua**: + + ```lua + local translate = require("awesome-wm-widgets.translate-widget.translate") + ``` + +1. Add a shortcut to run translate prompt: + + ```lua + awful.key({ modkey }, "c", function() + translate.launch{api_key = '', url = 'url'} + end, { description = "run translate prompt", group = "launcher" }) + ``` + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/demo.gif b/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/demo.gif new file mode 100644 index 0000000..3645d47 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/demo.gif differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/demo1.gif b/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/demo1.gif new file mode 100644 index 0000000..62cc9f4 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/demo1.gif differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/gnome-translate.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/gnome-translate.svg new file mode 100644 index 0000000..ca02b1a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/gnome-translate.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/translate.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/translate.lua new file mode 100644 index 0000000..d680c4e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/translate-widget/translate.lua @@ -0,0 +1,201 @@ +------------------------------------------------- +-- Translate Widget based on the Yandex.Translate API +-- https://tech.yandex.com/translate/ + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local spawn = require("awful.spawn") +local capi = {keygrabber = keygrabber } +local beautiful = require("beautiful") +local json = require("json") +local naughty = require("naughty") +local wibox = require("wibox") +local gears = require("gears") +local gfs = require("gears.filesystem") + +local TRANSLATE_CMD = [[bash -c 'curl -s -u "apikey:%s" -H "Content-Type: application/json"]] + ..[[ -d '\''{"text": ["%s"], "model_id":"%s"}'\'' "%s/v3/translate?version=2018-05-01"']] +local ICON = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/translate-widget/gnome-translate.svg' + +--- Returns two values - string to translate and direction: +-- 'dog enfr' -> 'dog', 'en-fr' +-- @param input_string user's input which consists of +-- text to translate and direction, 'dog enfr' +local function extract(input_string) + local word, lang = input_string:match('^(.+)%s(%a%a%a%a)$') + + if word ~= nil and lang ~= nil then + lang = lang:sub(1, 2) .. '-' .. lang:sub(3) + end + return word, lang +end + +local function show_warning(message) + naughty.notify{ + preset = naughty.config.presets.critical, + title = 'Translate Shell', + text = message} +end + +local w = awful.popup { + widget = {}, + visible = false, + border_width = 1, + maximum_width = 400, + width = 400, + border_color = '#66ccff', + ontop = true, + bg = beautiful.bg_normal, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 3) + end, +} +awful.placement.top(w, { margins = {top = 40}}) + + +--- Main function - takes the user input and shows the widget with translation +-- @param request_string - user input (dog enfr) +local function translate(to_translate, lang, api_key, url) + + local cmd = string.format(TRANSLATE_CMD, api_key, to_translate, lang, url) + spawn.easy_async(cmd, function (stdout, stderr) + if stderr ~= '' then + show_warning(stderr) + end + + local resp = json.decode(stdout) + + w:setup { + { + { + { + { + image = ICON, + widget = wibox.widget.imagebox, + resize = false + }, + valign = 'center', + layout = wibox.container.place, + }, + { + { + id = 'src', + markup = '' .. lang:sub(1,2) .. ': ' + .. to_translate .. '', + widget = wibox.widget.textbox + }, + { + id = 'res', + markup = '' .. lang:sub(4) .. ': ' + .. resp.translations[1].translation .. '', + widget = wibox.widget.textbox + }, + id = 'text', + layout = wibox.layout.fixed.vertical, + }, + id = 'left', + spacing = 8, + layout = wibox.layout.fixed.horizontal + }, + bg = beautiful.bg_normal, + forced_width = 400, + widget = wibox.container.background + }, + color = beautiful.bg_normal, + margins = 8, + widget = wibox.container.margin + } + + w.visible = true + w:buttons( + awful.util.table.join( + awful.button({}, 1, function() + spawn.with_shell("echo '" .. resp.translations[1].translation .. "' | xclip -selection clipboard") + w.visible = false + end), + awful.button({}, 3, function() + spawn.with_shell("echo '" .. to_translate .."' | xclip -selection clipboard") + w.visible = false + end) + ) + ) + + capi.keygrabber.run(function(_, key, event) + if event == "release" then return end + if key then + capi.keygrabber.stop() + w.visible = false + end + end) + end) +end + +local prompt = awful.widget.prompt() +local input_widget = wibox { + visible = false, + width = 300, + height = 100, + maxmimum_width = 300, + maxmimum_height = 900, + ontop = true, + screen = mouse.screen, + expand = true, + bg = beautiful.bg_normal, + max_widget_size = 500, + border_width = 1, + border_color = '#66ccff', + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 3) + end, +} + +input_widget:setup{ + { + prompt, + bg = beautiful.bg_normal, + widget = wibox.container.background + }, + margins = 8, + widget = wibox.container.margin +} + +local function launch(user_args) + + local args = user_args or {} + + local api_key = args.api_key + local url = args.url + + awful.placement.top(input_widget, { margins = {top = 40}, parent = awful.screen.focused()}) + input_widget.visible = true + + awful.prompt.run { + prompt = "Translate: ", + textbox = prompt.widget, + history_path = gfs.get_dir('cache') .. '/translate_history', + bg_cursor = '#66ccff', + exe_callback = function(text) + if not text or #text == 0 then return end + local to_translate, lang = extract(text) + if not to_translate or #to_translate==0 or not lang or #lang == 0 then + naughty.notify({ + preset = naughty.config.presets.critical, + title = 'Translate Widget Error', + text = 'Language is not provided', + }) + return + end + translate(to_translate, lang, api_key, url) + end, + done_callback = function() + input_widget.visible = false + end + } +end + +return { + launch = launch +} diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/README.md new file mode 100644 index 0000000..48f7790 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/README.md @@ -0,0 +1,159 @@ +# Volume widget + +Volume widget based on [amixer](https://linux.die.net/man/1/amixer) (is used for controlling the audio volume) and [pacmd](https://linux.die.net/man/1/pacmd) (is used for selecting a sink/source). Also, the widget provides an easy way to customize how it looks, following types are supported out-of-the-box: + +![types](screenshots/variations.png) + +From left to right: `horizontal_bar`, `vertical_bar`, `icon`, `icon_and_text`, `arc` + +A right-click on the widget opens a popup where you can choose a sink/source: +![sink-sources](screenshots/volume-sink-sources.png) + +Left click toggles mute and middle click opens a mixer ([pavucontrol](https://freedesktop.org/software/pulseaudio/pavucontrol/) by default). + +### Features + + - switch between sinks/sources by right click on the widget; + - more responsive than previous versions of volume widget, which were refreshed once a second; + - 5 predefined customizable looks; + +## Installation + +Clone the repo under **~/.config/awesome/** and add widget in **rc.lua**: + +```lua +local volume_widget = require('awesome-wm-widgets.volume-widget.volume') +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + -- default + volume_widget(), + -- customized + volume_widget{ + widget_type = 'arc' + }, +``` + +Note that the widget uses following command to get the current volume: `amixer -c 1 -D pulse sget Master`, so please make sure that it works for you, otherwise you need to set some parameters by entering this command in the terminal: + +Command output: +- Some data of a mixer: Override all parameters you've changed +- Error `Invalid card number`: Change parameter `-c`/ `card` +- Error `Mixer attach pulse error: No such file or directory`: Change parameter `-D`/ `device` +- Error `Unable to find simple control 'Master',0`: Change parameter `mixctrl` + +Note: `amixer[ -c ...][ -D ...]` returns a list of Mixers for the selected card/ device. omitting `-D` falls back to `default`. + +### Shortcuts + +To improve responsiveness of the widget when volume level is changed by a shortcut use corresponding methods of the widget: + +```lua +awful.key({ modkey }, "]", function() volume_widget:inc(5) end), +awful.key({ modkey }, "[", function() volume_widget:dec(5) end), +awful.key({ modkey }, "\\", function() volume_widget:toggle() end), +``` + +You also can use Functional keycodes instead of symbols, e.g. `XF86AudioRaiseVolume` instead of `"]"`. + +```lua +awful.key({}, "XF86AudioRaiseVolume", function() volume_widget.inc() end), +awful.key({}, "XF86AudioLowerVolume", function() volume_widget.dec() end), +awful.key({}, "XF86AudioMute", function() volume_widget.toggle() end), +``` +If you don't know the name of the key, you can use `xev` to find it out. +Or you can use `amixer` and `playerctl` to control the volume and media players. + +```lua +awful.key({}, "XF86AudioLowerVolume", function () + awful.util.spawn("amixer -q -D pulse sset Master 5%-", false) end), +awful.key({}, "XF86AudioRaiseVolume", function () + awful.util.spawn("amixer -q -D pulse sset Master 5%+", false) end), +awful.key({}, "XF86AudioMute", function () + awful.util.spawn("amixer -D pulse set Master 1+ toggle", false) end), +-- Media Keys +awful.key({}, "XF86AudioPlay", function() + awful.util.spawn("playerctl play-pause", false) end), +awful.key({}, "XF86AudioNext", function() + awful.util.spawn("playerctl next", false) end), +awful.key({}, "XF86AudioPrev", function() + awful.util.spawn("playerctl previous", false) end), +``` + +## Customization + +It is possible to customize the widget by providing a table with all or some of the following config parameters: + +### Generic parameter + +| Name | Default | Description | +|---------------|-----------------|---------------------------------------------------------------------------------------------------------------------------------------| +| `mixer_cmd` | `pavucontrol` | command to run on middle click (e.g. a mixer program) | +| `toggle_cmd` | *nil* | Use custom command instead of `amixer ... toggle` because [amixer's unmute option seems to be broken](https://superuser.com/a/822085) | +| `step` | 5 | How much the volume is raised or lowered at once (in %) | +| `widget_type` | `icon_and_text` | Widget type, one of `horizontal_bar`, `vertical_bar`, `icon`, `icon_and_text`, `arc` | +| `card` | 0 | Select the card name to control | +| `device` | `pulse` | Select the device name to control | +| `mixctrl` | `Master` | Select the mixer name to control | +| `value_type` | `-M` | Select how the volume is increased/ decreased (intended for `-M`/ `-R` parameters). See `man amixer` for additional info | + +Note: If unmuting or toggling using the default amixer command does not work, this command may work: `pactl set-sink-mute [card] toggle` + +Depends on the chosen widget type add parameters from the corresponding section below: + +#### `icon` parameters + +| Name | Default | Description | +|------------|----------------------|-----------------------------------------------| +| `icon_dir` | `[widget_dir]/icons` | Path to the folder with icons (absolute path) | + +_Note:_ if you are changing icons, the folder should contain following .svg images: + - audio-volume-high-symbolic + - audio-volume-medium-symbolic + - audio-volume-low-symbolic + - audio-volume-muted-symbolic + +#### `icon_and_text` parameters + +| Name | Default | Description | +|------------|----------------------|-----------------------------------------------| +| `icon_dir` | `[widget_dir]/icons` | Path to the folder with icons (absolute path) | +| `font` | `beautiful.font` | Font name and size, like `Play 12` | + +#### `arc` parameters + +| Name | Default | Description | +|---|---|---| +| `thickness` | 2 | Thickness of the arc | +| `main_color` | `beautiful.fg_color` | Color of the arc | +| `bg_color` | `#ffffff11` | Color of the arc's background | +| `mute_color` | `beautiful.fg_urgent` | Color of the arc when mute | +| `size` | 18 | Size of the widget | + +#### `horizontal_bar` parameters + +| Name | Default | Description | +|---|---|---| +| `main_color` | `beautiful.fg_normal` | Color of the bar | +| `mute_color` | `beautiful.fg_urgent` | Color of the bar when mute | +| `bg_color` | `#ffffff11` | Color of the bar's background | +| `width` | 50 | The bar width | +| `margins` | 10 | Top and bottom margins (if your wibar is 22 px high, bar will be 2 px = 22 - 2*10) | +| `shape` | `bar` | [gears.shape](https://awesomewm.org/doc/api/libraries/gears.shape.html), could be `octogon`, `hexagon`, `powerline`, etc | +| `with_icon` | true | Show volume icon| + +_Note:_ I didn't figure out how does the `forced_height` property of progressbar widget work (maybe it doesn't work at all), thus there is a workaround with margins. + +#### `vertical_bar` parameters + +| Name | Default | Description | +|---|---|---| +| `main_color` | `beautiful.fg_normal` | Color of the bar | +| `mute_color` | `beautiful.fg_urgent` | Color of the bar when mute | +| `bg_color` | `#ffffff11` | Color of the bar's background | +| `width` | 10 | The bar width | +| `margins` | 20 | Top and bottom margins (if your wibar is 22 px high, bar will be 2 px = 22 - 2*10) | +| `shape` | `bar` | [gears.shape](https://awesomewm.org/doc/api/libraries/gears.shape.html), could be `octogon`, `hexagon`, `powerline`, etc | +| `with_icon` | true | Show volume icon| diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-high-symbolic.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-high-symbolic.svg new file mode 100644 index 0000000..985c107 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-high-symbolic.svg @@ -0,0 +1,88 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-low-symbolic.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-low-symbolic.svg new file mode 100644 index 0000000..7eb4531 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-low-symbolic.svg @@ -0,0 +1,88 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-medium-symbolic.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-medium-symbolic.svg new file mode 100644 index 0000000..11e44fe --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-medium-symbolic.svg @@ -0,0 +1,88 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-muted-symbolic.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-muted-symbolic.svg new file mode 100644 index 0000000..e577d05 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/icons/audio-volume-muted-symbolic.svg @@ -0,0 +1,88 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/screenshots/variations.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/screenshots/variations.png new file mode 100644 index 0000000..21d7ead Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/screenshots/variations.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/screenshots/volume-sink-sources.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/screenshots/volume-sink-sources.png new file mode 100644 index 0000000..7d010bc Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/screenshots/volume-sink-sources.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/utils.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/utils.lua new file mode 100644 index 0000000..417a666 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/utils.lua @@ -0,0 +1,105 @@ + + +local utils = {} + +local function split(string_to_split, separator) + if separator == nil then separator = "%s" end + local t = {} + + for str in string.gmatch(string_to_split, "([^".. separator .."]+)") do + table.insert(t, str) + end + + return t +end + +function utils.extract_sinks_and_sources(pacmd_output) + local sinks = {} + local sources = {} + local device + local properties + local ports + local in_sink = false + local in_source = false + local in_device = false + local in_properties = false + local in_ports = false + for line in pacmd_output:gmatch("[^\r\n]+") do + if string.match(line, 'source%(s%) available.') then + in_sink = false + in_source = true + end + if string.match(line, 'sink%(s%) available.') then + in_sink = true + in_source = false + end + + if string.match(line, 'index:') then + in_device = true + in_properties = false + device = { + id = line:match(': (%d+)'), + is_default = string.match(line, '*') ~= nil + } + if in_sink then + table.insert(sinks, device) + elseif in_source then + table.insert(sources, device) + end + end + + if string.match(line, '^\tproperties:') then + in_device = false + in_properties = true + properties = {} + device['properties'] = properties + end + + if string.match(line, 'ports:') then + in_device = false + in_properties = false + in_ports = true + ports = {} + device['ports'] = ports + end + + if string.match(line, 'active port:') then + in_device = false + in_properties = false + in_ports = false + device['active_port'] = line:match(': (.+)'):gsub('<',''):gsub('>','') + end + + if in_device then + local t = split(line, ': ') + local key = t[1]:gsub('\t+', ''):lower() + local value = t[2]:gsub('^<', ''):gsub('>$', '') + device[key] = value + end + + if in_properties then + local t = split(line, '=') + local key = t[1]:gsub('\t+', ''):gsub('%.', '_'):gsub('-', '_'):gsub(':', ''):gsub("%s+$", "") + local value + if t[2] == nil then + value = t[2] + else + value = t[2]:gsub('"', ''):gsub("^%s+", ""):gsub(' Analog Stereo', '') + end + properties[key] = value + end + + if in_ports then + local t = split(line, ': ') + local key = t[1] + if key ~= nil then + key = key:gsub('\t+', '') + end + ports[key] = t[2] + end + end + + return sinks, sources +end + +return utils \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/volume-2.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/volume-2.svg new file mode 100644 index 0000000..10f1c67 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/volume-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/volume.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/volume.lua new file mode 100644 index 0000000..ac27f81 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/volume.lua @@ -0,0 +1,311 @@ +------------------------------------------------- +-- The Ultimate Volume Widget for Awesome Window Manager +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/volume-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local awful = require("awful") +local wibox = require("wibox") +local spawn = require("awful.spawn") +local gears = require("gears") +local beautiful = require("beautiful") +local watch = require("awful.widget.watch") +local utils = require("awesome-wm-widgets.volume-widget.utils") + +local LIST_DEVICES_CMD = [[sh -c "pacmd list-sinks; pacmd list-sources"]] +local function GET_VOLUME_CMD(card, device, mixctrl, value_type) + return "amixer -c " .. card .. " -D " .. device .. " sget " .. mixctrl .. " " .. value_type +end +local function INC_VOLUME_CMD(card, device, mixctrl, value_type, step) + return "amixer -c " + .. card + .. " -D " + .. device + .. " sset " + .. mixctrl + .. " " + .. value_type + .. " " + .. step + .. "%+" +end -- luacheck: ignore +local function DEC_VOLUME_CMD(card, device, mixctrl, value_type, step) + return "amixer -c " + .. card + .. " -D " + .. device + .. " sset " + .. mixctrl + .. " " + .. value_type + .. " " + .. step + .. "%-" +end -- luacheck: ignore +local function TOG_VOLUME_CMD(card, device, mixctrl) + return "amixer -c " .. card .. " -D " .. device .. " sset " .. mixctrl .. " toggle" +end -- luacheck: ignore + +local widget_types = { + icon_and_text = require("awesome-wm-widgets.volume-widget.widgets.icon-and-text-widget"), + icon = require("awesome-wm-widgets.volume-widget.widgets.icon-widget"), + arc = require("awesome-wm-widgets.volume-widget.widgets.arc-widget"), + horizontal_bar = require("awesome-wm-widgets.volume-widget.widgets.horizontal-bar-widget"), + vertical_bar = require("awesome-wm-widgets.volume-widget.widgets.vertical-bar-widget"), +} +local volume = {} + +local rows = { layout = wibox.layout.fixed.vertical } + +local popup = awful.popup({ + bg = beautiful.bg_normal, + fg = beautiful.fg_normal, + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + maximum_width = 400, + offset = { y = 5 }, + widget = {}, +}) + +local function build_main_line(device) + if device.active_port ~= nil and device.ports[device.active_port] ~= nil then + return device.properties.device_description .. " · " .. device.ports[device.active_port] + else + return device.properties.device_description + end +end + +local function build_rows(devices, on_checkbox_click, device_type) + local device_rows = { layout = wibox.layout.fixed.vertical } + for _, device in pairs(devices) do + local checkbox = wibox.widget({ + checked = device.is_default, + color = beautiful.fg_normal, + paddings = 2, + shape = gears.shape.circle, + forced_width = 20, + forced_height = 20, + check_color = beautiful.fg_normal, + widget = wibox.widget.checkbox, + }) + + checkbox:connect_signal("button::press", function() + spawn.easy_async( + string.format([[sh -c 'pacmd set-default-%s "%s"']], device_type, device.name), + function() + on_checkbox_click() + end + ) + end) + + local row = wibox.widget({ + { + { + { + checkbox, + valign = "center", + layout = wibox.container.place, + }, + { + { + text = build_main_line(device), + align = "left", + widget = wibox.widget.textbox, + }, + left = 10, + layout = wibox.container.margin, + }, + spacing = 8, + layout = wibox.layout.align.horizontal, + }, + margins = 4, + layout = wibox.container.margin, + }, + bg = beautiful.bg_normal, + fg = beautiful.fg_normal, + widget = wibox.container.background, + }) + + row:connect_signal("mouse::enter", function(c) + checkbox:set_color(beautiful.fg_focus) + checkbox:set_check_color(beautiful.fg_focus) + c:set_fg(beautiful.fg_focus) + c:set_bg(beautiful.bg_focus) + end) + row:connect_signal("mouse::leave", function(c) + checkbox:set_color(beautiful.fg_normal) + checkbox:set_check_color(beautiful.fg_normal) + c:set_fg(beautiful.fg_normal) + c:set_bg(beautiful.bg_normal) + end) + + local old_cursor, old_wibox + row:connect_signal("mouse::enter", function() + local wb = mouse.current_wibox + old_cursor, old_wibox = wb.cursor, wb + wb.cursor = "hand1" + end) + row:connect_signal("mouse::leave", function() + if old_wibox then + old_wibox.cursor = old_cursor + old_wibox = nil + end + end) + + row:connect_signal("button::press", function() + spawn.easy_async( + string.format([[sh -c 'pacmd set-default-%s "%s"']], device_type, device.name), + function() + on_checkbox_click() + end + ) + end) + + table.insert(device_rows, row) + end + + return device_rows +end + +local function build_header_row(text) + return wibox.widget({ + { + markup = "" .. text .. "", + align = "center", + widget = wibox.widget.textbox, + }, + bg = beautiful.bg_normal, + fg = beautiful.fg_normal, + widget = wibox.container.background, + }) +end + +local function rebuild_popup() + spawn.easy_async(LIST_DEVICES_CMD, function(stdout) + local sinks, sources = utils.extract_sinks_and_sources(stdout) + + for i = 0, #rows do + rows[i] = nil + end + + table.insert(rows, build_header_row("SINKS")) + table.insert( + rows, + build_rows(sinks, function() + rebuild_popup() + end, "sink") + ) + table.insert(rows, build_header_row("SOURCES")) + table.insert( + rows, + build_rows(sources, function() + rebuild_popup() + end, "source") + ) + + popup:setup(rows) + end) +end + +local function worker(user_args) + local args = user_args or {} + + local mixer_cmd = args.mixer_cmd or "pavucontrol" + local widget_type = args.widget_type + local refresh_rate = args.refresh_rate or 1 + local step = args.step or 5 + local card = args.card or 0 + local device = args.device or "pulse" + local mixctrl = args.mixctrl or "Master" + local value_type = args.value_type or "-M" + local toggle_cmd = args.toggle_cmd or nil + + if widget_types[widget_type] == nil then + volume.widget = widget_types["icon_and_text"].get_widget(args.icon_and_text_args) + else + volume.widget = widget_types[widget_type].get_widget(args) + end + + local function update_graphic(widget, stdout) + local mute = string.match(stdout, "%[(o%D%D?)%]") -- \[(o\D\D?)\] - [on] or [off] + if mute == "off" then + widget:mute() + elseif mute == "on" then + widget:unmute() + end + local volume_level = string.match(stdout, "(%d?%d?%d)%%") -- (\d?\d?\d)\%) + volume_level = string.format("% 3d", volume_level) + widget:set_volume_level(volume_level) + end + + function volume:inc(s) + spawn.easy_async(INC_VOLUME_CMD(card, device, mixctrl, value_type, s or step), function(stdout) + update_graphic(volume.widget, stdout) + end) + end + + function volume:dec(s) + spawn.easy_async(DEC_VOLUME_CMD(card, device, mixctrl, value_type, s or step), function(stdout) + update_graphic(volume.widget, stdout) + end) + end + + function volume:toggle() + if toggle_cmd == nil then + spawn.easy_async(TOG_VOLUME_CMD(card, device, mixctrl), function(stdout) + update_graphic(volume.widget, stdout) + end) + else + spawn.easy_async(toggle_cmd, function(_stdout) + spawn.easy_async(GET_VOLUME_CMD(card, device, mixctrl, value_type), function(stdout) + update_graphic(volume.widget, stdout) + end) + end) + end + end + + function volume:mixer() + if mixer_cmd then + spawn.easy_async(mixer_cmd) + end + end + + volume.widget:buttons(awful.util.table.join( + awful.button({}, 3, function() + if popup.visible then + popup.visible = not popup.visible + else + rebuild_popup() + popup:move_next_to(mouse.current_widget_geometry) + end + end), + awful.button({}, 4, function() + volume:inc() + end), + awful.button({}, 5, function() + volume:dec() + end), + awful.button({}, 2, function() + volume:mixer() + end), + awful.button({}, 1, function() + volume:toggle() + end) + )) + + watch(GET_VOLUME_CMD(card, device, mixctrl, value_type), refresh_rate, update_graphic, volume.widget) + + return volume.widget +end + +return setmetatable(volume, { + __call = function(_, ...) + return worker(...) + end, +}) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/arc-widget.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/arc-widget.lua new file mode 100644 index 0000000..b512f12 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/arc-widget.lua @@ -0,0 +1,46 @@ +local wibox = require("wibox") +local beautiful = require('beautiful') + +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/volume-widget/icons/' + +local widget = {} + +function widget.get_widget(widgets_args) + local args = widgets_args or {} + + local thickness = args.thickness or 2 + local main_color = args.main_color or beautiful.fg_color + local bg_color = args.bg_color or '#ffffff11' + local mute_color = args.mute_color or beautiful.fg_urgent + local size = args.size or 18 + + return wibox.widget { + { + id = "icon", + image = ICON_DIR .. 'audio-volume-high-symbolic.svg', + resize = true, + widget = wibox.widget.imagebox, + }, + max_value = 100, + thickness = thickness, + start_angle = 4.71238898, -- 2pi*3/4 + forced_height = size, + forced_width = size, + bg = bg_color, + paddings = 2, + widget = wibox.container.arcchart, + set_volume_level = function(self, new_value) + self.value = new_value + end, + mute = function(self) + self.colors = { mute_color } + end, + unmute = function(self) + self.colors = { main_color } + end + } + +end + + +return widget \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/horizontal-bar-widget.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/horizontal-bar-widget.lua new file mode 100644 index 0000000..be1f38d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/horizontal-bar-widget.lua @@ -0,0 +1,58 @@ +local wibox = require("wibox") +local beautiful = require('beautiful') +local gears = require("gears") + +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/volume-widget/icons/' + +local widget = {} + +function widget.get_widget(widgets_args) + local args = widgets_args or {} + + local main_color = args.main_color or beautiful.fg_normal + local mute_color = args.mute_color or beautiful.fg_urgent + local bg_color = args.bg_color or '#ffffff11' + local width = args.width or 50 + local margins = args.margins or 10 + local shape = args.shape or 'bar' + local with_icon = args.with_icon == true and true or false + + local bar = wibox.widget { + { + { + id = "icon", + image = ICON_DIR .. 'audio-volume-high-symbolic.svg', + resize = false, + widget = wibox.widget.imagebox, + }, + valign = 'center', + visible = with_icon, + layout = wibox.container.place, + }, + { + id = 'bar', + max_value = 100, + forced_width = width, + color = main_color, + margins = { top = margins, bottom = margins }, + background_color = bg_color, + shape = gears.shape[shape], + widget = wibox.widget.progressbar, + }, + spacing = 4, + layout = wibox.layout.fixed.horizontal, + set_volume_level = function(self, new_value) + self:get_children_by_id('bar')[1]:set_value(tonumber(new_value)) + end, + mute = function(self) + self:get_children_by_id('bar')[1]:set_color(mute_color) + end, + unmute = function(self) + self:get_children_by_id('bar')[1]:set_color(main_color) + end + } + + return bar +end + +return widget diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/icon-and-text-widget.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/icon-and-text-widget.lua new file mode 100644 index 0000000..b1a2793 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/icon-and-text-widget.lua @@ -0,0 +1,59 @@ +local wibox = require("wibox") +local beautiful = require('beautiful') + +local widget = {} + +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/volume-widget/icons/' + +function widget.get_widget(widgets_args) + local args = widgets_args or {} + + local font = args.font or beautiful.font + local icon_dir = args.icon_dir or ICON_DIR + + return wibox.widget { + { + { + id = "icon", + resize = false, + widget = wibox.widget.imagebox, + }, + valign = 'center', + layout = wibox.container.place + }, + { + id = 'txt', + font = font, + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal, + set_volume_level = function(self, new_value) + self:get_children_by_id('txt')[1]:set_text(new_value) + local volume_icon_name + if self.is_muted then + volume_icon_name = 'audio-volume-muted-symbolic' + else + local new_value_num = tonumber(new_value) + if (new_value_num >= 0 and new_value_num < 33) then + volume_icon_name="audio-volume-low-symbolic" + elseif (new_value_num < 66) then + volume_icon_name="audio-volume-medium-symbolic" + else + volume_icon_name="audio-volume-high-symbolic" + end + end + self:get_children_by_id('icon')[1]:set_image(icon_dir .. volume_icon_name .. '.svg') + end, + mute = function(self) + self.is_muted = true + self:get_children_by_id('icon')[1]:set_image(icon_dir .. 'audio-volume-muted-symbolic.svg') + end, + unmute = function(self) + self.is_muted = false + end + } + +end + + +return widget \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/icon-widget.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/icon-widget.lua new file mode 100644 index 0000000..cc39a3d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/icon-widget.lua @@ -0,0 +1,46 @@ +local wibox = require("wibox") + +local widget = {} + +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/volume-widget/icons/' + +function widget.get_widget(widgets_args) + local args = widgets_args or {} + + local icon_dir = args.icon_dir or ICON_DIR + + return wibox.widget { + { + id = "icon", + resize = false, + widget = wibox.widget.imagebox, + }, + valign = 'center', + layout = wibox.container.place, + set_volume_level = function(self, new_value) + local volume_icon_name + if self.is_muted then + volume_icon_name = 'audio-volume-muted-symbolic' + else + local new_value_num = tonumber(new_value) + if (new_value_num >= 0 and new_value_num < 33) then + volume_icon_name="audio-volume-low-symbolic" + elseif (new_value_num < 66) then + volume_icon_name="audio-volume-medium-symbolic" + else + volume_icon_name="audio-volume-high-symbolic" + end + end + self:get_children_by_id('icon')[1]:set_image(icon_dir .. volume_icon_name .. '.svg') + end, + mute = function(self) + self.is_muted = true + self:get_children_by_id('icon')[1]:set_image(icon_dir .. 'audio-volume-muted-symbolic.svg') + end, + unmute = function(self) + self.is_muted = false + end + } +end + +return widget \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/vertical-bar-widget.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/vertical-bar-widget.lua new file mode 100644 index 0000000..6f32b50 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/volume-widget/widgets/vertical-bar-widget.lua @@ -0,0 +1,64 @@ +local wibox = require("wibox") +local beautiful = require('beautiful') +local gears = require("gears") + +local ICON_DIR = os.getenv("HOME") .. '/.config/awesome/awesome-wm-widgets/volume-widget/icons/' + +local widget = {} + +function widget.get_widget(widgets_args) + local args = widgets_args or {} + + local main_color = args.main_color or beautiful.fg_normal + local mute_color = args.mute_color or beautiful.fg_urgent + local bg_color = args.bg_color or '#ffffff11' + local width = args.width or 10 + local margins = args.height or 2 + local shape = args.shape or 'bar' + local with_icon = args.with_icon == true and true or false + + local bar = wibox.widget { + { + { + id = "icon", + image = ICON_DIR .. 'audio-volume-high-symbolic.svg', + resize = false, + widget = wibox.widget.imagebox, + }, + valign = 'center', + visible = with_icon, + layout = wibox.container.place, + }, + { + { + id = 'bar', + max_value = 100, + forced_width = width, + forced_height = 5, + margins = { top = margins, bottom = margins }, + color = main_color, + background_color = bg_color, + shape = gears.shape[shape], + widget = wibox.widget.progressbar, + }, + forced_width = width, + direction = 'east', + layout = wibox.container.rotate, + }, + spacing = 4, + layout = wibox.layout.fixed.horizontal, + set_volume_level = function(self, new_value) + self:get_children_by_id('bar')[1]:set_value(tonumber(new_value)) + end, + mute = function(self) + self:get_children_by_id('bar')[1]:set_color(mute_color) + end, + unmute = function(self) + self:get_children_by_id('bar')[1]:set_color(main_color) + end + } + + return bar +end + +return widget diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/README.md new file mode 100644 index 0000000..519f66a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/README.md @@ -0,0 +1,115 @@ +# WeatherAPI widget + +![Current Weather popup](./popup.png) + +The widget consists of one section: +- current weather, including humidity, wind speed, UV index + +## 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 | [Follow the documentation](https://www.weatherapi.com/docs/) | +| font_name | `beautiful.font:gsub("%s%d+$", "")` | **Name** of the font to use e.g. 'Play' | +| units | `metric` | `metric` for celsius, `imperial` for fahrenheit | +| 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 | +| show_forecast_on_hover | false | Show a forecast on hover, too | +| show_daily_forecast | false | Show forecast for next three days | +| show_hourly_forecast | false | Show hourly forecast section | +| timeout | 120 | How often in seconds the widget refreshes | + +### Icons: + +The widget comes with two predefined icon packs: + + - [weather-underground-icons](https://github.com/manifestinteractive/weather-underground-icons) + - [VitalyGorbachev](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 + +```lua +weather_api_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, + units = 'imperial', + font_name = 'Carter One', + icons = 'VitalyGorbachev', + icons_extension = '.svg', +}), +``` + +#### Only current weather + +```lua +weather_api_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, +}), +``` + +## Installation + +1. Download json parser for lua from + [github.com/rxi/json.lua](https://github.com/rxi/json.lua) and place it + under **~/.config/awesome/** + (don't forget to star a repo ): + + ```bash + wget -P ~/.config/awesome/ https://raw.githubusercontent.com/rxi/json.lua/master/json.lua + ``` + +1. Clone this repo under **~/.config/awesome/**: + + ```bash + git clone https://github.com/streetturtle/awesome-wm-widgets.git ~/.config/awesome/ + ``` + +1. [Get Weather API key](https://www.weatherapi.com/docs/). + +1. Require weather widget at the beginning of **rc.lua**: + + ```lua + local weather_api_widget = require("awesome-wm-widgets.weather-api-widget.weather") + ``` + +1. Add widget to the tasklist: + + ```lua + s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + --default + weather_api_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, + }), + , + --customized + weather_api_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, + units = 'imperial', + font_name = 'Carter One', + icons = 'VitalyGorbachev', + icons_extension = '.svg', + }), + ... + ``` + +## How it works + +The widget calls the API repeatedly in the specified intervals. The JSON +response is parsed and interpreted to build the popup. diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/example_response.json b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/example_response.json new file mode 100644 index 0000000..dfc8e8d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/example_response.json @@ -0,0 +1,3057 @@ +{ + "location": { + "name": "Toronto", + "region": "Ontario", + "country": "Canada", + "lat": 43.667, + "lon": -79.417, + "tz_id": "America/Toronto", + "localtime_epoch": 1729581202, + "localtime": "2024-10-22 03:13" + }, + "current": { + "last_updated_epoch": 1729580400, + "last_updated": "2024-10-22 03:00", + "temp_c": 13.2, + "temp_f": 55.8, + "is_day": 0, + "condition": { + "text": "Clear", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 226, + "wind_dir": "SW", + "pressure_mb": 1022.0, + "pressure_in": 30.17, + "precip_mm": 0.0, + "precip_in": 0.0, + "humidity": 88, + "cloud": 0, + "feelslike_c": 12.7, + "feelslike_f": 54.9, + "windchill_c": 14.6, + "windchill_f": 58.2, + "heatindex_c": 14.8, + "heatindex_f": 58.6, + "dewpoint_c": 12.9, + "dewpoint_f": 55.2, + "vis_km": 14.0, + "vis_miles": 8.0, + "uv": 0.0, + "gust_mph": 10.8, + "gust_kph": 17.4 + }, + "forecast": { + "forecastday": [ + { + "date": "2024-10-22", + "date_epoch": 1729555200, + "day": { + "maxtemp_c": 22.1, + "maxtemp_f": 71.8, + "mintemp_c": 12.7, + "mintemp_f": 54.9, + "avgtemp_c": 17.2, + "avgtemp_f": 62.9, + "maxwind_mph": 9.6, + "maxwind_kph": 15.5, + "totalprecip_mm": 0.0, + "totalprecip_in": 0.0, + "totalsnow_cm": 0.0, + "avgvis_km": 10.0, + "avgvis_miles": 6.0, + "avghumidity": 76, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "uv": 0.6 + }, + "astro": { + "sunrise": "07:42 AM", + "sunset": "06:22 PM", + "moonrise": "10:20 PM", + "moonset": "02:02 PM", + "moon_phase": "Waning Gibbous", + "moon_illumination": 74, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1729569600, + "time": "2024-10-22 00:00", + "temp_c": 16.6, + "temp_f": 61.8, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 4.9, + "wind_kph": 7.9, + "wind_degree": 247, + "wind_dir": "WSW", + "pressure_mb": 1021.0, + "pressure_in": 30.16, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 84, + "cloud": 0, + "feelslike_c": 16.6, + "feelslike_f": 61.8, + "windchill_c": 16.6, + "windchill_f": 61.8, + "heatindex_c": 16.6, + "heatindex_f": 61.8, + "dewpoint_c": 13.7, + "dewpoint_f": 56.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.3, + "gust_kph": 16.6, + "uv": 0 + }, + { + "time_epoch": 1729573200, + "time": "2024-10-22 01:00", + "temp_c": 15.9, + "temp_f": 60.6, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 4.7, + "wind_kph": 7.6, + "wind_degree": 238, + "wind_dir": "WSW", + "pressure_mb": 1021.0, + "pressure_in": 30.15, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 86, + "cloud": 0, + "feelslike_c": 15.9, + "feelslike_f": 60.6, + "windchill_c": 15.9, + "windchill_f": 60.6, + "heatindex_c": 15.9, + "heatindex_f": 60.6, + "dewpoint_c": 13.5, + "dewpoint_f": 56.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 9.8, + "gust_kph": 15.8, + "uv": 0 + }, + { + "time_epoch": 1729576800, + "time": "2024-10-22 02:00", + "temp_c": 15.3, + "temp_f": 59.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 233, + "wind_dir": "SW", + "pressure_mb": 1021.0, + "pressure_in": 30.16, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 88, + "cloud": 0, + "feelslike_c": 15.2, + "feelslike_f": 59.3, + "windchill_c": 15.2, + "windchill_f": 59.3, + "heatindex_c": 15.3, + "heatindex_f": 59.5, + "dewpoint_c": 13.2, + "dewpoint_f": 55.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.8, + "gust_kph": 17.4, + "uv": 0 + }, + { + "time_epoch": 1729580400, + "time": "2024-10-22 03:00", + "temp_c": 13.2, + "temp_f": 55.8, + "is_day": 0, + "condition": { + "text": "Clear", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 226, + "wind_dir": "SW", + "pressure_mb": 1022.0, + "pressure_in": 30.17, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 88, + "cloud": 0, + "feelslike_c": 14.6, + "feelslike_f": 58.2, + "windchill_c": 14.6, + "windchill_f": 58.2, + "heatindex_c": 14.8, + "heatindex_f": 58.6, + "dewpoint_c": 12.9, + "dewpoint_f": 55.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 14.0, + "vis_miles": 8.0, + "gust_mph": 10.8, + "gust_kph": 17.4, + "uv": 0 + }, + { + "time_epoch": 1729584000, + "time": "2024-10-22 04:00", + "temp_c": 14.3, + "temp_f": 57.7, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 227, + "wind_dir": "SW", + "pressure_mb": 1022.0, + "pressure_in": 30.17, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 89, + "cloud": 0, + "feelslike_c": 14.0, + "feelslike_f": 57.2, + "windchill_c": 14.0, + "windchill_f": 57.2, + "heatindex_c": 14.3, + "heatindex_f": 57.7, + "dewpoint_c": 12.6, + "dewpoint_f": 54.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.8, + "gust_kph": 17.4, + "uv": 0 + }, + { + "time_epoch": 1729587600, + "time": "2024-10-22 05:00", + "temp_c": 13.8, + "temp_f": 56.9, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 225, + "wind_dir": "SW", + "pressure_mb": 1022.0, + "pressure_in": 30.17, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 90, + "cloud": 1, + "feelslike_c": 13.5, + "feelslike_f": 56.3, + "windchill_c": 13.5, + "windchill_f": 56.3, + "heatindex_c": 13.8, + "heatindex_f": 56.9, + "dewpoint_c": 12.2, + "dewpoint_f": 54.0, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.8, + "gust_kph": 17.4, + "uv": 0 + }, + { + "time_epoch": 1729591200, + "time": "2024-10-22 06:00", + "temp_c": 13.4, + "temp_f": 56.2, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.1, + "wind_kph": 8.3, + "wind_degree": 228, + "wind_dir": "SW", + "pressure_mb": 1022.0, + "pressure_in": 30.18, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 91, + "cloud": 1, + "feelslike_c": 13.0, + "feelslike_f": 55.4, + "windchill_c": 13.0, + "windchill_f": 55.4, + "heatindex_c": 13.4, + "heatindex_f": 56.2, + "dewpoint_c": 12.0, + "dewpoint_f": 53.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.7, + "gust_kph": 17.2, + "uv": 0 + }, + { + "time_epoch": 1729594800, + "time": "2024-10-22 07:00", + "temp_c": 13.1, + "temp_f": 55.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.4, + "wind_kph": 8.6, + "wind_degree": 227, + "wind_dir": "SW", + "pressure_mb": 1022.0, + "pressure_in": 30.19, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 92, + "cloud": 7, + "feelslike_c": 12.5, + "feelslike_f": 54.5, + "windchill_c": 12.5, + "windchill_f": 54.5, + "heatindex_c": 13.1, + "heatindex_f": 55.5, + "dewpoint_c": 11.7, + "dewpoint_f": 53.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 11.1, + "gust_kph": 17.9, + "uv": 0 + }, + { + "time_epoch": 1729598400, + "time": "2024-10-22 08:00", + "temp_c": 13.0, + "temp_f": 55.5, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 5.8, + "wind_kph": 9.4, + "wind_degree": 224, + "wind_dir": "SW", + "pressure_mb": 1022.0, + "pressure_in": 30.19, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 92, + "cloud": 8, + "feelslike_c": 12.5, + "feelslike_f": 54.5, + "windchill_c": 12.5, + "windchill_f": 54.5, + "heatindex_c": 13.0, + "heatindex_f": 55.5, + "dewpoint_c": 11.5, + "dewpoint_f": 52.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 11.8, + "gust_kph": 18.9, + "uv": 0.0 + }, + { + "time_epoch": 1729602000, + "time": "2024-10-22 09:00", + "temp_c": 13.9, + "temp_f": 57.0, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 5.8, + "wind_kph": 9.4, + "wind_degree": 217, + "wind_dir": "SW", + "pressure_mb": 1022.0, + "pressure_in": 30.19, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 87, + "cloud": 11, + "feelslike_c": 13.5, + "feelslike_f": 56.3, + "windchill_c": 13.5, + "windchill_f": 56.3, + "heatindex_c": 13.9, + "heatindex_f": 57.0, + "dewpoint_c": 10.9, + "dewpoint_f": 51.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.5, + "gust_kph": 16.9, + "uv": 0.2 + }, + { + "time_epoch": 1729605600, + "time": "2024-10-22 10:00", + "temp_c": 16.0, + "temp_f": 60.9, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 6.9, + "wind_kph": 11.2, + "wind_degree": 219, + "wind_dir": "SW", + "pressure_mb": 1022.0, + "pressure_in": 30.19, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 76, + "cloud": 11, + "feelslike_c": 15.8, + "feelslike_f": 60.5, + "windchill_c": 15.8, + "windchill_f": 60.5, + "heatindex_c": 16.0, + "heatindex_f": 60.9, + "dewpoint_c": 10.5, + "dewpoint_f": 51.0, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.4, + "gust_kph": 16.7, + "uv": 0.8 + }, + { + "time_epoch": 1729609200, + "time": "2024-10-22 11:00", + "temp_c": 18.1, + "temp_f": 64.5, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 7.4, + "wind_kph": 11.9, + "wind_degree": 217, + "wind_dir": "SW", + "pressure_mb": 1022.0, + "pressure_in": 30.19, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 67, + "cloud": 4, + "feelslike_c": 18.0, + "feelslike_f": 64.4, + "windchill_c": 18.0, + "windchill_f": 64.4, + "heatindex_c": 18.1, + "heatindex_f": 64.5, + "dewpoint_c": 11.9, + "dewpoint_f": 53.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.3, + "gust_kph": 16.6, + "uv": 1.7 + }, + { + "time_epoch": 1729612800, + "time": "2024-10-22 12:00", + "temp_c": 19.5, + "temp_f": 67.2, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 7.6, + "wind_kph": 12.2, + "wind_degree": 213, + "wind_dir": "SSW", + "pressure_mb": 1022.0, + "pressure_in": 30.17, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 62, + "cloud": 3, + "feelslike_c": 19.5, + "feelslike_f": 67.1, + "windchill_c": 19.5, + "windchill_f": 67.1, + "heatindex_c": 19.5, + "heatindex_f": 67.2, + "dewpoint_c": 12.6, + "dewpoint_f": 54.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.7, + "gust_kph": 17.2, + "uv": 2.6 + }, + { + "time_epoch": 1729616400, + "time": "2024-10-22 13:00", + "temp_c": 20.4, + "temp_f": 68.7, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 7.6, + "wind_kph": 12.2, + "wind_degree": 208, + "wind_dir": "SSW", + "pressure_mb": 1021.0, + "pressure_in": 30.15, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 60, + "cloud": 4, + "feelslike_c": 20.4, + "feelslike_f": 68.7, + "windchill_c": 20.4, + "windchill_f": 68.7, + "heatindex_c": 22.0, + "heatindex_f": 71.6, + "dewpoint_c": 13.0, + "dewpoint_f": 55.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 11.4, + "gust_kph": 18.3, + "uv": 3.0 + }, + { + "time_epoch": 1729620000, + "time": "2024-10-22 14:00", + "temp_c": 21.3, + "temp_f": 70.3, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 8.3, + "wind_kph": 13.3, + "wind_degree": 202, + "wind_dir": "SSW", + "pressure_mb": 1020.0, + "pressure_in": 30.13, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 60, + "cloud": 10, + "feelslike_c": 21.2, + "feelslike_f": 70.2, + "windchill_c": 21.2, + "windchill_f": 70.2, + "heatindex_c": 23.3, + "heatindex_f": 73.9, + "dewpoint_c": 13.3, + "dewpoint_f": 55.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 12.9, + "gust_kph": 20.7, + "uv": 2.9 + }, + { + "time_epoch": 1729623600, + "time": "2024-10-22 15:00", + "temp_c": 21.5, + "temp_f": 70.8, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 9.4, + "wind_kph": 15.1, + "wind_degree": 192, + "wind_dir": "SSW", + "pressure_mb": 1020.0, + "pressure_in": 30.11, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 59, + "cloud": 8, + "feelslike_c": 21.5, + "feelslike_f": 70.7, + "windchill_c": 21.5, + "windchill_f": 70.7, + "heatindex_c": 23.9, + "heatindex_f": 75.0, + "dewpoint_c": 13.8, + "dewpoint_f": 56.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 15.2, + "gust_kph": 24.4, + "uv": 2.2 + }, + { + "time_epoch": 1729627200, + "time": "2024-10-22 16:00", + "temp_c": 21.6, + "temp_f": 70.8, + "is_day": 1, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "wind_mph": 9.6, + "wind_kph": 15.5, + "wind_degree": 191, + "wind_dir": "SSW", + "pressure_mb": 1019.0, + "pressure_in": 30.09, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 59, + "cloud": 26, + "feelslike_c": 21.6, + "feelslike_f": 70.8, + "windchill_c": 21.6, + "windchill_f": 70.8, + "heatindex_c": 24.2, + "heatindex_f": 75.6, + "dewpoint_c": 13.4, + "dewpoint_f": 56.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 16.1, + "gust_kph": 25.9, + "uv": 1.3 + }, + { + "time_epoch": 1729630800, + "time": "2024-10-22 17:00", + "temp_c": 21.4, + "temp_f": 70.6, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 9.4, + "wind_kph": 15.1, + "wind_degree": 189, + "wind_dir": "S", + "pressure_mb": 1019.0, + "pressure_in": 30.08, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 61, + "cloud": 18, + "feelslike_c": 21.4, + "feelslike_f": 70.6, + "windchill_c": 21.4, + "windchill_f": 70.6, + "heatindex_c": 24.3, + "heatindex_f": 75.8, + "dewpoint_c": 13.8, + "dewpoint_f": 56.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 16.9, + "gust_kph": 27.3, + "uv": 0.5 + }, + { + "time_epoch": 1729634400, + "time": "2024-10-22 18:00", + "temp_c": 20.6, + "temp_f": 69.1, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 8.9, + "wind_kph": 14.4, + "wind_degree": 189, + "wind_dir": "S", + "pressure_mb": 1019.0, + "pressure_in": 30.08, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 67, + "cloud": 6, + "feelslike_c": 20.6, + "feelslike_f": 69.1, + "windchill_c": 20.6, + "windchill_f": 69.1, + "heatindex_c": 22.1, + "heatindex_f": 71.7, + "dewpoint_c": 15.0, + "dewpoint_f": 59.0, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 17.3, + "gust_kph": 27.9, + "uv": 0.0 + }, + { + "time_epoch": 1729638000, + "time": "2024-10-22 19:00", + "temp_c": 19.0, + "temp_f": 66.2, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 8.1, + "wind_kph": 13.0, + "wind_degree": 185, + "wind_dir": "S", + "pressure_mb": 1019.0, + "pressure_in": 30.08, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 73, + "cloud": 9, + "feelslike_c": 19.0, + "feelslike_f": 66.2, + "windchill_c": 19.0, + "windchill_f": 66.2, + "heatindex_c": 19.7, + "heatindex_f": 67.5, + "dewpoint_c": 14.8, + "dewpoint_f": 58.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 16.3, + "gust_kph": 26.3, + "uv": 0 + }, + { + "time_epoch": 1729641600, + "time": "2024-10-22 20:00", + "temp_c": 17.9, + "temp_f": 64.1, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 7.2, + "wind_kph": 11.5, + "wind_degree": 181, + "wind_dir": "S", + "pressure_mb": 1018.0, + "pressure_in": 30.07, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 75, + "cloud": 8, + "feelslike_c": 17.9, + "feelslike_f": 64.1, + "windchill_c": 17.9, + "windchill_f": 64.1, + "heatindex_c": 18.2, + "heatindex_f": 64.8, + "dewpoint_c": 13.0, + "dewpoint_f": 55.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 14.7, + "gust_kph": 23.6, + "uv": 0 + }, + { + "time_epoch": 1729645200, + "time": "2024-10-22 21:00", + "temp_c": 17.1, + "temp_f": 62.8, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 6.5, + "wind_kph": 10.4, + "wind_degree": 182, + "wind_dir": "S", + "pressure_mb": 1017.0, + "pressure_in": 30.04, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 79, + "cloud": 30, + "feelslike_c": 17.1, + "feelslike_f": 62.8, + "windchill_c": 17.1, + "windchill_f": 62.8, + "heatindex_c": 17.3, + "heatindex_f": 63.1, + "dewpoint_c": 12.9, + "dewpoint_f": 55.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 13.1, + "gust_kph": 21.1, + "uv": 0 + }, + { + "time_epoch": 1729648800, + "time": "2024-10-22 22:00", + "temp_c": 17.1, + "temp_f": 62.8, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 5.6, + "wind_kph": 9.0, + "wind_degree": 176, + "wind_dir": "S", + "pressure_mb": 1017.0, + "pressure_in": 30.03, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 80, + "cloud": 41, + "feelslike_c": 17.1, + "feelslike_f": 62.8, + "windchill_c": 17.1, + "windchill_f": 62.8, + "heatindex_c": 17.2, + "heatindex_f": 62.9, + "dewpoint_c": 12.9, + "dewpoint_f": 55.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 11.2, + "gust_kph": 18.0, + "uv": 0 + }, + { + "time_epoch": 1729652400, + "time": "2024-10-22 23:00", + "temp_c": 16.4, + "temp_f": 61.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 5.4, + "wind_kph": 8.6, + "wind_degree": 177, + "wind_dir": "S", + "pressure_mb": 1016.0, + "pressure_in": 29.99, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 58, + "cloud": 5, + "feelslike_c": 16.4, + "feelslike_f": 61.5, + "windchill_c": 16.4, + "windchill_f": 61.5, + "heatindex_c": 16.4, + "heatindex_f": 61.5, + "dewpoint_c": 8.9, + "dewpoint_f": 47.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.5, + "gust_kph": 17.0, + "uv": 0 + } + ] + }, + { + "date": "2024-10-23", + "date_epoch": 1729641600, + "day": { + "maxtemp_c": 20.1, + "maxtemp_f": 68.2, + "mintemp_c": 9.2, + "mintemp_f": 48.5, + "avgtemp_c": 15.5, + "avgtemp_f": 60.0, + "maxwind_mph": 18.1, + "maxwind_kph": 29.2, + "totalprecip_mm": 0.0, + "totalprecip_in": 0.0, + "totalsnow_cm": 0.0, + "avgvis_km": 10.0, + "avgvis_miles": 6.0, + "avghumidity": 71, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "uv": 0.5 + }, + "astro": { + "sunrise": "07:43 AM", + "sunset": "06:20 PM", + "moonrise": "11:29 PM", + "moonset": "02:47 PM", + "moon_phase": "Waning Gibbous", + "moon_illumination": 64, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1729656000, + "time": "2024-10-23 00:00", + "temp_c": 15.9, + "temp_f": 60.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 194, + "wind_dir": "SSW", + "pressure_mb": 1015.0, + "pressure_in": 29.98, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 80, + "cloud": 51, + "feelslike_c": 15.9, + "feelslike_f": 60.7, + "windchill_c": 15.9, + "windchill_f": 60.7, + "heatindex_c": 15.9, + "heatindex_f": 60.7, + "dewpoint_c": 12.2, + "dewpoint_f": 54.0, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 12.7, + "gust_kph": 20.4, + "uv": 0 + }, + { + "time_epoch": 1729659600, + "time": "2024-10-23 01:00", + "temp_c": 15.6, + "temp_f": 60.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 9.4, + "wind_kph": 15.1, + "wind_degree": 204, + "wind_dir": "SSW", + "pressure_mb": 1015.0, + "pressure_in": 29.96, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 79, + "cloud": 50, + "feelslike_c": 15.6, + "feelslike_f": 60.1, + "windchill_c": 15.6, + "windchill_f": 60.1, + "heatindex_c": 15.6, + "heatindex_f": 60.1, + "dewpoint_c": 11.9, + "dewpoint_f": 53.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 17.3, + "gust_kph": 27.9, + "uv": 0 + }, + { + "time_epoch": 1729663200, + "time": "2024-10-23 02:00", + "temp_c": 15.4, + "temp_f": 59.6, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 10.1, + "wind_kph": 16.2, + "wind_degree": 209, + "wind_dir": "SSW", + "pressure_mb": 1014.0, + "pressure_in": 29.95, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 78, + "cloud": 49, + "feelslike_c": 15.0, + "feelslike_f": 59.0, + "windchill_c": 15.0, + "windchill_f": 59.0, + "heatindex_c": 15.4, + "heatindex_f": 59.6, + "dewpoint_c": 11.5, + "dewpoint_f": 52.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 18.2, + "gust_kph": 29.3, + "uv": 0 + }, + { + "time_epoch": 1729666800, + "time": "2024-10-23 03:00", + "temp_c": 15.2, + "temp_f": 59.3, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 11.2, + "wind_kph": 18.0, + "wind_degree": 210, + "wind_dir": "SSW", + "pressure_mb": 1013.0, + "pressure_in": 29.92, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 78, + "cloud": 29, + "feelslike_c": 14.5, + "feelslike_f": 58.0, + "windchill_c": 14.5, + "windchill_f": 58.0, + "heatindex_c": 15.2, + "heatindex_f": 59.3, + "dewpoint_c": 11.3, + "dewpoint_f": 52.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 19.4, + "gust_kph": 31.2, + "uv": 0 + }, + { + "time_epoch": 1729670400, + "time": "2024-10-23 04:00", + "temp_c": 15.0, + "temp_f": 59.1, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 12.1, + "wind_kph": 19.4, + "wind_degree": 208, + "wind_dir": "SSW", + "pressure_mb": 1013.0, + "pressure_in": 29.9, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 78, + "cloud": 19, + "feelslike_c": 14.0, + "feelslike_f": 57.1, + "windchill_c": 14.0, + "windchill_f": 57.1, + "heatindex_c": 15.0, + "heatindex_f": 59.1, + "dewpoint_c": 11.2, + "dewpoint_f": 52.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 20.6, + "gust_kph": 33.1, + "uv": 0 + }, + { + "time_epoch": 1729674000, + "time": "2024-10-23 05:00", + "temp_c": 15.0, + "temp_f": 59.1, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 12.8, + "wind_kph": 20.5, + "wind_degree": 212, + "wind_dir": "SSW", + "pressure_mb": 1012.0, + "pressure_in": 29.88, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 78, + "cloud": 9, + "feelslike_c": 14.1, + "feelslike_f": 57.4, + "windchill_c": 14.1, + "windchill_f": 57.4, + "heatindex_c": 15.0, + "heatindex_f": 59.1, + "dewpoint_c": 11.1, + "dewpoint_f": 51.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 20.8, + "gust_kph": 33.6, + "uv": 0 + }, + { + "time_epoch": 1729677600, + "time": "2024-10-23 06:00", + "temp_c": 15.1, + "temp_f": 59.2, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.4, + "wind_kph": 21.6, + "wind_degree": 210, + "wind_dir": "SSW", + "pressure_mb": 1011.0, + "pressure_in": 29.86, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 80, + "cloud": 19, + "feelslike_c": 14.5, + "feelslike_f": 58.0, + "windchill_c": 14.5, + "windchill_f": 58.0, + "heatindex_c": 15.1, + "heatindex_f": 59.2, + "dewpoint_c": 11.6, + "dewpoint_f": 52.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 21.6, + "gust_kph": 34.8, + "uv": 0 + }, + { + "time_epoch": 1729681200, + "time": "2024-10-23 07:00", + "temp_c": 15.1, + "temp_f": 59.3, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 14.3, + "wind_kph": 23.0, + "wind_degree": 213, + "wind_dir": "SSW", + "pressure_mb": 1011.0, + "pressure_in": 29.84, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 81, + "cloud": 25, + "feelslike_c": 14.8, + "feelslike_f": 58.7, + "windchill_c": 14.8, + "windchill_f": 58.7, + "heatindex_c": 15.1, + "heatindex_f": 59.3, + "dewpoint_c": 11.8, + "dewpoint_f": 53.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 22.7, + "gust_kph": 36.6, + "uv": 0 + }, + { + "time_epoch": 1729684800, + "time": "2024-10-23 08:00", + "temp_c": 15.8, + "temp_f": 60.4, + "is_day": 1, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "wind_mph": 15.0, + "wind_kph": 24.1, + "wind_degree": 218, + "wind_dir": "SW", + "pressure_mb": 1010.0, + "pressure_in": 29.83, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 81, + "cloud": 30, + "feelslike_c": 15.6, + "feelslike_f": 60.1, + "windchill_c": 15.6, + "windchill_f": 60.1, + "heatindex_c": 15.8, + "heatindex_f": 60.4, + "dewpoint_c": 12.1, + "dewpoint_f": 53.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 23.1, + "gust_kph": 37.1, + "uv": 0.0 + }, + { + "time_epoch": 1729688400, + "time": "2024-10-23 09:00", + "temp_c": 16.4, + "temp_f": 61.5, + "is_day": 1, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "wind_mph": 15.4, + "wind_kph": 24.8, + "wind_degree": 219, + "wind_dir": "SW", + "pressure_mb": 1009.0, + "pressure_in": 29.81, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 77, + "cloud": 47, + "feelslike_c": 16.3, + "feelslike_f": 61.4, + "windchill_c": 16.3, + "windchill_f": 61.4, + "heatindex_c": 16.4, + "heatindex_f": 61.5, + "dewpoint_c": 12.3, + "dewpoint_f": 54.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 22.9, + "gust_kph": 36.8, + "uv": 0.2 + }, + { + "time_epoch": 1729692000, + "time": "2024-10-23 10:00", + "temp_c": 17.0, + "temp_f": 62.6, + "is_day": 1, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "wind_mph": 16.1, + "wind_kph": 25.9, + "wind_degree": 222, + "wind_dir": "SW", + "pressure_mb": 1009.0, + "pressure_in": 29.79, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 75, + "cloud": 55, + "feelslike_c": 17.0, + "feelslike_f": 62.5, + "windchill_c": 17.0, + "windchill_f": 62.5, + "heatindex_c": 17.0, + "heatindex_f": 62.6, + "dewpoint_c": 12.5, + "dewpoint_f": 54.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 22.4, + "gust_kph": 36.1, + "uv": 0.7 + }, + { + "time_epoch": 1729695600, + "time": "2024-10-23 11:00", + "temp_c": 17.9, + "temp_f": 64.3, + "is_day": 1, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/119.png", + "code": 1006 + }, + "wind_mph": 15.9, + "wind_kph": 25.6, + "wind_degree": 227, + "wind_dir": "SW", + "pressure_mb": 1009.0, + "pressure_in": 29.78, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 72, + "cloud": 63, + "feelslike_c": 17.9, + "feelslike_f": 64.2, + "windchill_c": 17.9, + "windchill_f": 64.2, + "heatindex_c": 17.9, + "heatindex_f": 64.3, + "dewpoint_c": 12.6, + "dewpoint_f": 54.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 21.5, + "gust_kph": 34.6, + "uv": 1.3 + }, + { + "time_epoch": 1729699200, + "time": "2024-10-23 12:00", + "temp_c": 18.7, + "temp_f": 65.7, + "is_day": 1, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/119.png", + "code": 1006 + }, + "wind_mph": 15.2, + "wind_kph": 24.5, + "wind_degree": 239, + "wind_dir": "WSW", + "pressure_mb": 1008.0, + "pressure_in": 29.76, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 67, + "cloud": 58, + "feelslike_c": 18.7, + "feelslike_f": 65.6, + "windchill_c": 18.7, + "windchill_f": 65.6, + "heatindex_c": 18.7, + "heatindex_f": 65.7, + "dewpoint_c": 12.7, + "dewpoint_f": 54.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 20.7, + "gust_kph": 33.4, + "uv": 1.9 + }, + { + "time_epoch": 1729702800, + "time": "2024-10-23 13:00", + "temp_c": 19.4, + "temp_f": 66.9, + "is_day": 1, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/119.png", + "code": 1006 + }, + "wind_mph": 15.0, + "wind_kph": 24.1, + "wind_degree": 246, + "wind_dir": "WSW", + "pressure_mb": 1007.0, + "pressure_in": 29.74, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 65, + "cloud": 55, + "feelslike_c": 19.4, + "feelslike_f": 66.9, + "windchill_c": 19.4, + "windchill_f": 66.9, + "heatindex_c": 19.4, + "heatindex_f": 66.9, + "dewpoint_c": 12.7, + "dewpoint_f": 54.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 20.8, + "gust_kph": 33.4, + "uv": 2.3 + }, + { + "time_epoch": 1729706400, + "time": "2024-10-23 14:00", + "temp_c": 18.9, + "temp_f": 66.1, + "is_day": 1, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "wind_mph": 15.7, + "wind_kph": 25.2, + "wind_degree": 263, + "wind_dir": "W", + "pressure_mb": 1007.0, + "pressure_in": 29.73, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 62, + "cloud": 52, + "feelslike_c": 18.9, + "feelslike_f": 66.1, + "windchill_c": 18.9, + "windchill_f": 66.1, + "heatindex_c": 18.9, + "heatindex_f": 66.1, + "dewpoint_c": 12.7, + "dewpoint_f": 54.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 20.9, + "gust_kph": 33.7, + "uv": 2.2 + }, + { + "time_epoch": 1729710000, + "time": "2024-10-23 15:00", + "temp_c": 18.3, + "temp_f": 65.0, + "is_day": 1, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "wind_mph": 17.0, + "wind_kph": 27.4, + "wind_degree": 285, + "wind_dir": "WNW", + "pressure_mb": 1008.0, + "pressure_in": 29.77, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 63, + "cloud": 75, + "feelslike_c": 18.3, + "feelslike_f": 65.0, + "windchill_c": 18.3, + "windchill_f": 65.0, + "heatindex_c": 18.3, + "heatindex_f": 65.0, + "dewpoint_c": 11.4, + "dewpoint_f": 52.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 21.0, + "gust_kph": 33.8, + "uv": 1.6 + }, + { + "time_epoch": 1729713600, + "time": "2024-10-23 16:00", + "temp_c": 17.6, + "temp_f": 63.7, + "is_day": 1, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/116.png", + "code": 1003 + }, + "wind_mph": 17.7, + "wind_kph": 28.4, + "wind_degree": 302, + "wind_dir": "WNW", + "pressure_mb": 1009.0, + "pressure_in": 29.79, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 64, + "cloud": 86, + "feelslike_c": 17.6, + "feelslike_f": 63.7, + "windchill_c": 17.6, + "windchill_f": 63.7, + "heatindex_c": 17.6, + "heatindex_f": 63.7, + "dewpoint_c": 10.8, + "dewpoint_f": 51.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 21.2, + "gust_kph": 34.1, + "uv": 0.9 + }, + { + "time_epoch": 1729717200, + "time": "2024-10-23 17:00", + "temp_c": 15.9, + "temp_f": 60.6, + "is_day": 1, + "condition": { + "text": "Overcast ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/122.png", + "code": 1009 + }, + "wind_mph": 17.0, + "wind_kph": 27.4, + "wind_degree": 305, + "wind_dir": "NW", + "pressure_mb": 1009.0, + "pressure_in": 29.81, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 64, + "cloud": 97, + "feelslike_c": 15.1, + "feelslike_f": 59.2, + "windchill_c": 15.1, + "windchill_f": 59.2, + "heatindex_c": 15.9, + "heatindex_f": 60.6, + "dewpoint_c": 10.2, + "dewpoint_f": 50.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 19.8, + "gust_kph": 31.8, + "uv": 0.4 + }, + { + "time_epoch": 1729720800, + "time": "2024-10-23 18:00", + "temp_c": 14.3, + "temp_f": 57.8, + "is_day": 1, + "condition": { + "text": "Overcast ", + "icon": "//cdn.weatherapi.com/weather/64x64/day/122.png", + "code": 1009 + }, + "wind_mph": 16.1, + "wind_kph": 25.9, + "wind_degree": 304, + "wind_dir": "NW", + "pressure_mb": 1012.0, + "pressure_in": 29.88, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 63, + "cloud": 58, + "feelslike_c": 12.7, + "feelslike_f": 54.9, + "windchill_c": 12.7, + "windchill_f": 54.9, + "heatindex_c": 14.3, + "heatindex_f": 57.8, + "dewpoint_c": 7.2, + "dewpoint_f": 45.0, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 19.6, + "gust_kph": 31.5, + "uv": 0.0 + }, + { + "time_epoch": 1729724400, + "time": "2024-10-23 19:00", + "temp_c": 12.9, + "temp_f": 55.2, + "is_day": 0, + "condition": { + "text": "Overcast ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/122.png", + "code": 1009 + }, + "wind_mph": 18.1, + "wind_kph": 29.2, + "wind_degree": 308, + "wind_dir": "NW", + "pressure_mb": 1013.0, + "pressure_in": 29.91, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 62, + "cloud": 39, + "feelslike_c": 10.5, + "feelslike_f": 50.9, + "windchill_c": 10.5, + "windchill_f": 50.9, + "heatindex_c": 12.9, + "heatindex_f": 55.2, + "dewpoint_c": 5.7, + "dewpoint_f": 42.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 21.6, + "gust_kph": 34.7, + "uv": 0 + }, + { + "time_epoch": 1729728000, + "time": "2024-10-23 20:00", + "temp_c": 11.4, + "temp_f": 52.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 17.4, + "wind_kph": 28.1, + "wind_degree": 304, + "wind_dir": "NW", + "pressure_mb": 1014.0, + "pressure_in": 29.95, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 61, + "cloud": 19, + "feelslike_c": 8.3, + "feelslike_f": 47.0, + "windchill_c": 8.3, + "windchill_f": 47.0, + "heatindex_c": 11.4, + "heatindex_f": 52.5, + "dewpoint_c": 4.3, + "dewpoint_f": 39.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 21.5, + "gust_kph": 34.6, + "uv": 0 + }, + { + "time_epoch": 1729731600, + "time": "2024-10-23 21:00", + "temp_c": 10.3, + "temp_f": 50.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 17.0, + "wind_kph": 27.4, + "wind_degree": 308, + "wind_dir": "NW", + "pressure_mb": 1016.0, + "pressure_in": 29.99, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 61, + "cloud": 10, + "feelslike_c": 6.8, + "feelslike_f": 44.2, + "windchill_c": 6.8, + "windchill_f": 44.2, + "heatindex_c": 10.3, + "heatindex_f": 50.5, + "dewpoint_c": 2.7, + "dewpoint_f": 36.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 21.3, + "gust_kph": 34.3, + "uv": 0 + }, + { + "time_epoch": 1729735200, + "time": "2024-10-23 22:00", + "temp_c": 13.1, + "temp_f": 55.6, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 15.9, + "wind_kph": 25.6, + "wind_degree": 310, + "wind_dir": "NW", + "pressure_mb": 1017.0, + "pressure_in": 30.02, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 61, + "cloud": 5, + "feelslike_c": 11.4, + "feelslike_f": 52.5, + "windchill_c": 11.4, + "windchill_f": 52.5, + "heatindex_c": 13.1, + "heatindex_f": 55.6, + "dewpoint_c": 2.0, + "dewpoint_f": 35.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 19.7, + "gust_kph": 31.8, + "uv": 0 + }, + { + "time_epoch": 1729738800, + "time": "2024-10-23 23:00", + "temp_c": 10.5, + "temp_f": 51.0, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 14.3, + "wind_kph": 23.0, + "wind_degree": 311, + "wind_dir": "NW", + "pressure_mb": 1016.0, + "pressure_in": 30.01, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 82, + "cloud": 52, + "feelslike_c": 7.6, + "feelslike_f": 45.7, + "windchill_c": 7.6, + "windchill_f": 45.7, + "heatindex_c": 10.5, + "heatindex_f": 51.0, + "dewpoint_c": 12.9, + "dewpoint_f": 55.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 18.3, + "gust_kph": 29.4, + "uv": 0 + } + ] + }, + { + "date": "2024-10-24", + "date_epoch": 1729728000, + "day": { + "maxtemp_c": 12.3, + "maxtemp_f": 54.1, + "mintemp_c": 4.5, + "mintemp_f": 40.1, + "avgtemp_c": 8.6, + "avgtemp_f": 47.5, + "maxwind_mph": 13.2, + "maxwind_kph": 21.2, + "totalprecip_mm": 0.0, + "totalprecip_in": 0.0, + "totalsnow_cm": 0.0, + "avgvis_km": 10.0, + "avgvis_miles": 6.0, + "avghumidity": 55, + "daily_will_it_rain": 0, + "daily_chance_of_rain": 0, + "daily_will_it_snow": 0, + "daily_chance_of_snow": 0, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "uv": 0.6 + }, + "astro": { + "sunrise": "07:44 AM", + "sunset": "06:19 PM", + "moonrise": "No moonrise", + "moonset": "03:21 PM", + "moon_phase": "Last Quarter", + "moon_illumination": 53, + "is_moon_up": 0, + "is_sun_up": 0 + }, + "hour": [ + { + "time_epoch": 1729742400, + "time": "2024-10-24 00:00", + "temp_c": 9.1, + "temp_f": 48.4, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 13.2, + "wind_kph": 21.2, + "wind_degree": 311, + "wind_dir": "NW", + "pressure_mb": 1018.0, + "pressure_in": 30.06, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 62, + "cloud": 18, + "feelslike_c": 5.6, + "feelslike_f": 42.2, + "windchill_c": 5.6, + "windchill_f": 42.2, + "heatindex_c": 9.1, + "heatindex_f": 48.4, + "dewpoint_c": 1.1, + "dewpoint_f": 33.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 16.9, + "gust_kph": 27.2, + "uv": 0 + }, + { + "time_epoch": 1729746000, + "time": "2024-10-24 01:00", + "temp_c": 8.3, + "temp_f": 47.0, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 11.9, + "wind_kph": 19.1, + "wind_degree": 312, + "wind_dir": "NW", + "pressure_mb": 1018.0, + "pressure_in": 30.07, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 62, + "cloud": 27, + "feelslike_c": 4.6, + "feelslike_f": 40.3, + "windchill_c": 4.6, + "windchill_f": 40.3, + "heatindex_c": 8.3, + "heatindex_f": 47.0, + "dewpoint_c": 1.0, + "dewpoint_f": 33.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 15.2, + "gust_kph": 24.4, + "uv": 0 + }, + { + "time_epoch": 1729749600, + "time": "2024-10-24 02:00", + "temp_c": 7.7, + "temp_f": 45.9, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 10.5, + "wind_kph": 16.9, + "wind_degree": 311, + "wind_dir": "NW", + "pressure_mb": 1018.0, + "pressure_in": 30.07, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 63, + "cloud": 36, + "feelslike_c": 4.0, + "feelslike_f": 39.2, + "windchill_c": 4.0, + "windchill_f": 39.2, + "heatindex_c": 7.7, + "heatindex_f": 45.9, + "dewpoint_c": 1.0, + "dewpoint_f": 33.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 13.5, + "gust_kph": 21.8, + "uv": 0 + }, + { + "time_epoch": 1729753200, + "time": "2024-10-24 03:00", + "temp_c": 7.4, + "temp_f": 45.2, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 9.8, + "wind_kph": 15.8, + "wind_degree": 309, + "wind_dir": "NW", + "pressure_mb": 1020.0, + "pressure_in": 30.11, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 64, + "cloud": 55, + "feelslike_c": 3.7, + "feelslike_f": 38.6, + "windchill_c": 3.7, + "windchill_f": 38.6, + "heatindex_c": 7.4, + "heatindex_f": 45.2, + "dewpoint_c": 0.9, + "dewpoint_f": 33.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 12.9, + "gust_kph": 20.7, + "uv": 0 + }, + { + "time_epoch": 1729756800, + "time": "2024-10-24 04:00", + "temp_c": 7.1, + "temp_f": 44.7, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 8.9, + "wind_kph": 14.4, + "wind_degree": 319, + "wind_dir": "NW", + "pressure_mb": 1020.0, + "pressure_in": 30.13, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 65, + "cloud": 64, + "feelslike_c": 3.5, + "feelslike_f": 38.3, + "windchill_c": 3.5, + "windchill_f": 38.3, + "heatindex_c": 7.1, + "heatindex_f": 44.7, + "dewpoint_c": 0.8, + "dewpoint_f": 33.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 11.7, + "gust_kph": 18.9, + "uv": 0 + }, + { + "time_epoch": 1729760400, + "time": "2024-10-24 05:00", + "temp_c": 6.4, + "temp_f": 43.5, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 8.1, + "wind_kph": 13.0, + "wind_degree": 338, + "wind_dir": "NNW", + "pressure_mb": 1021.0, + "pressure_in": 30.15, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 65, + "cloud": 73, + "feelslike_c": 2.8, + "feelslike_f": 37.1, + "windchill_c": 2.8, + "windchill_f": 37.1, + "heatindex_c": 6.4, + "heatindex_f": 43.5, + "dewpoint_c": 0.8, + "dewpoint_f": 33.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.5, + "gust_kph": 17.0, + "uv": 0 + }, + { + "time_epoch": 1729764000, + "time": "2024-10-24 06:00", + "temp_c": 5.7, + "temp_f": 42.3, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 6.7, + "wind_kph": 10.8, + "wind_degree": 355, + "wind_dir": "N", + "pressure_mb": 1022.0, + "pressure_in": 30.19, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 64, + "cloud": 39, + "feelslike_c": 2.2, + "feelslike_f": 35.9, + "windchill_c": 2.2, + "windchill_f": 35.9, + "heatindex_c": 5.7, + "heatindex_f": 42.3, + "dewpoint_c": -0.5, + "dewpoint_f": 31.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 8.8, + "gust_kph": 14.2, + "uv": 0 + }, + { + "time_epoch": 1729767600, + "time": "2024-10-24 07:00", + "temp_c": 5.1, + "temp_f": 41.2, + "is_day": 0, + "condition": { + "text": "Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/119.png", + "code": 1006 + }, + "wind_mph": 7.6, + "wind_kph": 12.2, + "wind_degree": 356, + "wind_dir": "N", + "pressure_mb": 1023.0, + "pressure_in": 30.21, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 64, + "cloud": 22, + "feelslike_c": 1.6, + "feelslike_f": 34.8, + "windchill_c": 1.6, + "windchill_f": 34.8, + "heatindex_c": 5.1, + "heatindex_f": 41.2, + "dewpoint_c": -1.2, + "dewpoint_f": 29.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.0, + "gust_kph": 16.1, + "uv": 0 + }, + { + "time_epoch": 1729771200, + "time": "2024-10-24 08:00", + "temp_c": 5.8, + "temp_f": 42.4, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 8.5, + "wind_kph": 13.7, + "wind_degree": 356, + "wind_dir": "N", + "pressure_mb": 1024.0, + "pressure_in": 30.23, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 63, + "cloud": 5, + "feelslike_c": 2.6, + "feelslike_f": 36.7, + "windchill_c": 2.6, + "windchill_f": 36.7, + "heatindex_c": 5.8, + "heatindex_f": 42.4, + "dewpoint_c": -1.8, + "dewpoint_f": 28.7, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 11.1, + "gust_kph": 17.8, + "uv": 0.0 + }, + { + "time_epoch": 1729774800, + "time": "2024-10-24 09:00", + "temp_c": 6.7, + "temp_f": 44.0, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 8.3, + "wind_kph": 13.3, + "wind_degree": 359, + "wind_dir": "N", + "pressure_mb": 1024.0, + "pressure_in": 30.24, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 54, + "cloud": 2, + "feelslike_c": 3.9, + "feelslike_f": 38.9, + "windchill_c": 3.9, + "windchill_f": 38.9, + "heatindex_c": 6.7, + "heatindex_f": 44.0, + "dewpoint_c": -2.5, + "dewpoint_f": 27.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.3, + "gust_kph": 16.5, + "uv": 0.2 + }, + { + "time_epoch": 1729778400, + "time": "2024-10-24 10:00", + "temp_c": 7.6, + "temp_f": 45.6, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 5.4, + "wind_kph": 8.6, + "wind_degree": 3, + "wind_dir": "N", + "pressure_mb": 1024.0, + "pressure_in": 30.24, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 49, + "cloud": 1, + "feelslike_c": 5.2, + "feelslike_f": 41.3, + "windchill_c": 5.2, + "windchill_f": 41.3, + "heatindex_c": 7.6, + "heatindex_f": 45.6, + "dewpoint_c": -2.8, + "dewpoint_f": 27.0, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 6.4, + "gust_kph": 10.2, + "uv": 0.8 + }, + { + "time_epoch": 1729782000, + "time": "2024-10-24 11:00", + "temp_c": 8.8, + "temp_f": 47.8, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 2.7, + "wind_kph": 4.3, + "wind_degree": 343, + "wind_dir": "NNW", + "pressure_mb": 1024.0, + "pressure_in": 30.24, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 44, + "cloud": 0, + "feelslike_c": 6.8, + "feelslike_f": 44.2, + "windchill_c": 6.8, + "windchill_f": 44.2, + "heatindex_c": 8.8, + "heatindex_f": 47.8, + "dewpoint_c": -3.1, + "dewpoint_f": 26.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 3.1, + "gust_kph": 5.0, + "uv": 1.6 + }, + { + "time_epoch": 1729785600, + "time": "2024-10-24 12:00", + "temp_c": 9.7, + "temp_f": 49.5, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 2.0, + "wind_kph": 3.2, + "wind_degree": 299, + "wind_dir": "WNW", + "pressure_mb": 1023.0, + "pressure_in": 30.22, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 41, + "cloud": 0, + "feelslike_c": 8.1, + "feelslike_f": 46.5, + "windchill_c": 8.1, + "windchill_f": 46.5, + "heatindex_c": 9.7, + "heatindex_f": 49.5, + "dewpoint_c": -2.5, + "dewpoint_f": 27.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 2.3, + "gust_kph": 3.7, + "uv": 2.4 + }, + { + "time_epoch": 1729789200, + "time": "2024-10-24 13:00", + "temp_c": 10.6, + "temp_f": 51.0, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 3.6, + "wind_kph": 5.8, + "wind_degree": 263, + "wind_dir": "W", + "pressure_mb": 1023.0, + "pressure_in": 30.21, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 40, + "cloud": 0, + "feelslike_c": 9.2, + "feelslike_f": 48.6, + "windchill_c": 9.2, + "windchill_f": 48.6, + "heatindex_c": 10.6, + "heatindex_f": 51.0, + "dewpoint_c": -2.2, + "dewpoint_f": 28.0, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 4.1, + "gust_kph": 6.6, + "uv": 2.9 + }, + { + "time_epoch": 1729792800, + "time": "2024-10-24 14:00", + "temp_c": 11.2, + "temp_f": 52.2, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 3.6, + "wind_kph": 5.8, + "wind_degree": 252, + "wind_dir": "WSW", + "pressure_mb": 1023.0, + "pressure_in": 30.2, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 39, + "cloud": 0, + "feelslike_c": 10.2, + "feelslike_f": 50.4, + "windchill_c": 10.2, + "windchill_f": 50.4, + "heatindex_c": 11.2, + "heatindex_f": 52.2, + "dewpoint_c": -1.9, + "dewpoint_f": 28.5, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 4.1, + "gust_kph": 6.6, + "uv": 2.7 + }, + { + "time_epoch": 1729796400, + "time": "2024-10-24 15:00", + "temp_c": 11.6, + "temp_f": 53.0, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 3.4, + "wind_kph": 5.4, + "wind_degree": 205, + "wind_dir": "SSW", + "pressure_mb": 1022.0, + "pressure_in": 30.19, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 43, + "cloud": 0, + "feelslike_c": 11.0, + "feelslike_f": 51.8, + "windchill_c": 11.0, + "windchill_f": 51.8, + "heatindex_c": 11.6, + "heatindex_f": 53.0, + "dewpoint_c": -0.4, + "dewpoint_f": 31.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 3.9, + "gust_kph": 6.2, + "uv": 2.1 + }, + { + "time_epoch": 1729800000, + "time": "2024-10-24 16:00", + "temp_c": 12.0, + "temp_f": 53.5, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 4.7, + "wind_kph": 7.6, + "wind_degree": 181, + "wind_dir": "S", + "pressure_mb": 1022.0, + "pressure_in": 30.18, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 44, + "cloud": 0, + "feelslike_c": 11.6, + "feelslike_f": 52.8, + "windchill_c": 11.6, + "windchill_f": 52.8, + "heatindex_c": 12.0, + "heatindex_f": 53.5, + "dewpoint_c": 0.3, + "dewpoint_f": 32.6, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 5.5, + "gust_kph": 8.9, + "uv": 1.2 + }, + { + "time_epoch": 1729803600, + "time": "2024-10-24 17:00", + "temp_c": 11.2, + "temp_f": 52.1, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 5.8, + "wind_kph": 9.4, + "wind_degree": 170, + "wind_dir": "S", + "pressure_mb": 1022.0, + "pressure_in": 30.18, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 46, + "cloud": 0, + "feelslike_c": 10.8, + "feelslike_f": 51.4, + "windchill_c": 10.8, + "windchill_f": 51.4, + "heatindex_c": 11.2, + "heatindex_f": 52.1, + "dewpoint_c": 1.1, + "dewpoint_f": 33.9, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 7.4, + "gust_kph": 12.0, + "uv": 0.5 + }, + { + "time_epoch": 1729807200, + "time": "2024-10-24 18:00", + "temp_c": 10.3, + "temp_f": 50.6, + "is_day": 1, + "condition": { + "text": "Sunny", + "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png", + "code": 1000 + }, + "wind_mph": 6.9, + "wind_kph": 11.2, + "wind_degree": 165, + "wind_dir": "SSE", + "pressure_mb": 1022.0, + "pressure_in": 30.18, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 53, + "cloud": 2, + "feelslike_c": 9.8, + "feelslike_f": 49.6, + "windchill_c": 9.8, + "windchill_f": 49.6, + "heatindex_c": 10.3, + "heatindex_f": 50.6, + "dewpoint_c": 1.2, + "dewpoint_f": 34.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 9.3, + "gust_kph": 15.0, + "uv": 0.0 + }, + { + "time_epoch": 1729810800, + "time": "2024-10-24 19:00", + "temp_c": 9.4, + "temp_f": 48.9, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 6.0, + "wind_kph": 9.7, + "wind_degree": 171, + "wind_dir": "S", + "pressure_mb": 1022.0, + "pressure_in": 30.19, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 57, + "cloud": 4, + "feelslike_c": 8.8, + "feelslike_f": 47.8, + "windchill_c": 8.8, + "windchill_f": 47.8, + "heatindex_c": 9.4, + "heatindex_f": 48.9, + "dewpoint_c": 1.2, + "dewpoint_f": 34.2, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 8.2, + "gust_kph": 13.2, + "uv": 0 + }, + { + "time_epoch": 1729814400, + "time": "2024-10-24 20:00", + "temp_c": 9.8, + "temp_f": 49.6, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 6.9, + "wind_kph": 11.2, + "wind_degree": 178, + "wind_dir": "S", + "pressure_mb": 1022.0, + "pressure_in": 30.19, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 60, + "cloud": 5, + "feelslike_c": 8.6, + "feelslike_f": 47.5, + "windchill_c": 8.6, + "windchill_f": 47.5, + "heatindex_c": 9.8, + "heatindex_f": 49.6, + "dewpoint_c": 1.3, + "dewpoint_f": 34.3, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 9.7, + "gust_kph": 15.6, + "uv": 0 + }, + { + "time_epoch": 1729818000, + "time": "2024-10-24 21:00", + "temp_c": 9.9, + "temp_f": 49.8, + "is_day": 0, + "condition": { + "text": "Partly Cloudy ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/116.png", + "code": 1003 + }, + "wind_mph": 7.6, + "wind_kph": 12.2, + "wind_degree": 189, + "wind_dir": "S", + "pressure_mb": 1022.0, + "pressure_in": 30.18, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 55, + "cloud": 42, + "feelslike_c": 8.5, + "feelslike_f": 47.3, + "windchill_c": 8.5, + "windchill_f": 47.3, + "heatindex_c": 9.9, + "heatindex_f": 49.8, + "dewpoint_c": 1.6, + "dewpoint_f": 34.8, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.8, + "gust_kph": 17.4, + "uv": 0 + }, + { + "time_epoch": 1729821600, + "time": "2024-10-24 22:00", + "temp_c": 9.2, + "temp_f": 48.5, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 7.2, + "wind_kph": 11.5, + "wind_degree": 201, + "wind_dir": "SSW", + "pressure_mb": 1022.0, + "pressure_in": 30.18, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 59, + "cloud": 5, + "feelslike_c": 6.3, + "feelslike_f": 43.4, + "windchill_c": 6.3, + "windchill_f": 43.4, + "heatindex_c": 9.2, + "heatindex_f": 48.5, + "dewpoint_c": 2.5, + "dewpoint_f": 36.4, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 10.2, + "gust_kph": 16.4, + "uv": 0 + }, + { + "time_epoch": 1729825200, + "time": "2024-10-24 23:00", + "temp_c": 9.3, + "temp_f": 48.7, + "is_day": 0, + "condition": { + "text": "Clear ", + "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png", + "code": 1000 + }, + "wind_mph": 6.3, + "wind_kph": 10.1, + "wind_degree": 213, + "wind_dir": "SSW", + "pressure_mb": 1017.0, + "pressure_in": 30.04, + "precip_mm": 0.0, + "precip_in": 0.0, + "snow_cm": 0.0, + "humidity": 60, + "cloud": 0, + "feelslike_c": 7.2, + "feelslike_f": 44.9, + "windchill_c": 7.2, + "windchill_f": 44.9, + "heatindex_c": 9.3, + "heatindex_f": 48.8, + "dewpoint_c": 1.2, + "dewpoint_f": 34.1, + "will_it_rain": 0, + "chance_of_rain": 0, + "will_it_snow": 0, + "chance_of_snow": 0, + "vis_km": 10.0, + "vis_miles": 6.0, + "gust_mph": 9.2, + "gust_kph": 14.8, + "uv": 0 + } + ] + } + ] + } +} diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/broken-clouds-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/broken-clouds-night.svg new file mode 100644 index 0000000..8b7fc48 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/broken-clouds-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/broken-clouds.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/broken-clouds.svg new file mode 100644 index 0000000..d42ea59 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/broken-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/clear-sky-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/clear-sky-night.svg new file mode 100644 index 0000000..44f096c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/clear-sky-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/clear-sky.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/clear-sky.svg new file mode 100644 index 0000000..dc82163 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/clear-sky.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/few-clouds-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/few-clouds-night.svg new file mode 100644 index 0000000..8b7fc48 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/few-clouds-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/few-clouds.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/few-clouds.svg new file mode 100644 index 0000000..d42ea59 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/few-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/mist-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/mist-night.svg new file mode 100644 index 0000000..960b07d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/mist-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/mist.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/mist.svg new file mode 100644 index 0000000..770f8d7 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/mist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/rain-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/rain-night.svg new file mode 100644 index 0000000..11ecf00 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/rain-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/rain.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/rain.svg new file mode 100644 index 0000000..11ecf00 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/scattered-clouds-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/scattered-clouds-night.svg new file mode 100644 index 0000000..8b7fc48 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/scattered-clouds-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/scattered-clouds.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/scattered-clouds.svg new file mode 100644 index 0000000..d42ea59 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/scattered-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/shower-rain-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/shower-rain-night.svg new file mode 100644 index 0000000..4d1897c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/shower-rain-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/shower-rain.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/shower-rain.svg new file mode 100644 index 0000000..4d1897c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/shower-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/snow-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/snow-night.svg new file mode 100644 index 0000000..bee891e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/snow-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/snow.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/snow.svg new file mode 100644 index 0000000..e2ea140 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/thunderstorm-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/thunderstorm-night.svg new file mode 100644 index 0000000..1813197 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/thunderstorm-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/thunderstorm.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/thunderstorm.svg new file mode 100644 index 0000000..44a733c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/VitalyGorbachev/thunderstorm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/broken-clouds-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/broken-clouds-night.png new file mode 100644 index 0000000..061d1cd Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/broken-clouds-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/broken-clouds.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/broken-clouds.png new file mode 100644 index 0000000..5967d92 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/broken-clouds.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/clear-sky-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/clear-sky-night.png new file mode 100644 index 0000000..cc40d0f Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/clear-sky-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/clear-sky.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/clear-sky.png new file mode 100644 index 0000000..acf8e5c Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/clear-sky.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/few-clouds-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/few-clouds-night.png new file mode 100644 index 0000000..9c34fab Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/few-clouds-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/few-clouds.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/few-clouds.png new file mode 100644 index 0000000..7580fc5 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/few-clouds.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/mist-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/mist-night.png new file mode 100644 index 0000000..102142a Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/mist-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/mist.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/mist.png new file mode 100644 index 0000000..102142a Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/mist.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/rain-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/rain-night.png new file mode 100644 index 0000000..49f0903 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/rain-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/rain.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/rain.png new file mode 100644 index 0000000..49f0903 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/rain.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/scattered-clouds-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/scattered-clouds-night.png new file mode 100644 index 0000000..63cb1b2 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/scattered-clouds-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/scattered-clouds.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/scattered-clouds.png new file mode 100644 index 0000000..63cb1b2 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/scattered-clouds.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/shower-rain-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/shower-rain-night.png new file mode 100644 index 0000000..49f0903 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/shower-rain-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/shower-rain.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/shower-rain.png new file mode 100644 index 0000000..49f0903 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/shower-rain.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/snow-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/snow-night.png new file mode 100644 index 0000000..0a7f006 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/snow-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/snow.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/snow.png new file mode 100644 index 0000000..0a7f006 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/snow.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/thunderstorm-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/thunderstorm-night.png new file mode 100644 index 0000000..2102104 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/thunderstorm-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/thunderstorm.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/thunderstorm.png new file mode 100644 index 0000000..2102104 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/icons/weather-underground-icons/thunderstorm.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/locale/de.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/locale/de.lua new file mode 100644 index 0000000..2a9236a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/locale/de.lua @@ -0,0 +1,13 @@ +local de = { + warning_title = "Wetter Widget", + parameter_warning = "Folgende benötigte Parameter fehlen: ", + directions = { + "N", "NNO", "NO", "ONO", "O", "OSO", "SO", "SSO", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N" + }, + feels_like = "Gefühlt: ", + wind = "Wind: ", + humidity = "Luftfeuchtigkeit: ", + uv = "UV-Index: " +} + +return de diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/locale/en.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/locale/en.lua new file mode 100644 index 0000000..377dc6f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/locale/en.lua @@ -0,0 +1,14 @@ +local en = { + warning_title = "Weather Widget", + parameter_warning = "Required parameters are not set: ", + directions = { + "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", + "WSW", "W", "WNW", "NW", "NNW", "N" + }, + feels_like = "Feels like ", + wind = "Wind: ", + humidity = "Humidity: ", + uv = "UV: " +} + +return en diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/locale/fr.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/locale/fr.lua new file mode 100644 index 0000000..de50814 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/locale/fr.lua @@ -0,0 +1,14 @@ +local fr = { + warning_title = "Widget Météo", + parameter_warning = "Les paramètres suivants sont obligatoires : ", + directions = { + "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSO", "SO", + "OSO", "O", "ONO", "NO", "NNO", "N" + }, + feels_like = "ressentie à ", + wind = "Vent : ", + humidity = "Humidité : ", + uv = "Indice UV : " +} + +return fr diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/locale/pt.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/locale/pt.lua new file mode 100644 index 0000000..e7f4012 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/locale/pt.lua @@ -0,0 +1,14 @@ +local pt = { + warning_title = "Widget do tempo", + parameter_warning = "Parâmetros necessários não definidos: ", + directions = { + "N", "NNE", "NE", "ENE", "L", "ESE", "SE", "SSE", "S", "SSO", "SO", + "OSO", "O", "ONO", "NO", "NNO", "N" + }, + feels_like = "Sensação de ", + wind = "Vento: ", + humidity = "Umidade: ", + uv = "UV: " +} + +return pt diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/popup.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/popup.png new file mode 100644 index 0000000..1e668a7 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/popup.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/weather.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/weather.lua new file mode 100644 index 0000000..6bc720c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-api-widget/weather.lua @@ -0,0 +1,665 @@ +------------------------------------------------- +-- Weather Widget based on the WeatherAPI +-- https://weatherapi.com/ +-- +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +-- @copyright 2024 André Jaenisch +------------------------------------------------- +local awful = require("awful") +local watch = require("awful.widget.watch") +local json = require("json") +local naughty = require("naughty") +local wibox = require("wibox") +local gears = require("gears") +local beautiful = require("beautiful") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/weather-widget' +local GET_FORECAST_CMD = [[bash -c "curl -s --show-error -X GET '%s'"]] + +local SYS_LANG = os.getenv("LANG"):sub(1, 2) +if SYS_LANG == "C" or SYS_LANG == "C." then + -- C-locale is a common fallback for simple English + SYS_LANG = "en" +end +-- default language is ENglish +local LANG = gears.filesystem.file_readable(WIDGET_DIR .. "/" .. "locale/" .. + SYS_LANG .. ".lua") and SYS_LANG or "en" +local LCLE = require("awesome-wm-widgets.weather-widget.locale." .. LANG) +-- WeatherAPI supports only these according to https://www.weatherapi.com/docs/ +-- ar, bn, bg, zh, zh_tw, cs, da, nl, fi, fr, de, el, hi, hu, it, ja, jv, ko, +-- zh_cmn, mr, pl, pt, pa, ro, ru, sr, si, sk, es, sv, ta, te, tr, uk, ur, vi, +-- zh_wuu, zh_hsn, zh_yue, zu + + +local function show_warning(message) + naughty.notify { + preset = naughty.config.presets.critical, + title = LCLE.warning_title, + text = message + } +end + +if SYS_LANG ~= LANG then + show_warning("Your language is not supported yet. Language set to English") +end + +local weather_widget = {} +local warning_shown = false +local tooltip = awful.tooltip { + mode = 'outside', + preferred_positions = {'bottom'} +} + +local weather_popup = awful.popup { + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + maximum_width = 400, + offset = {y = 5}, + hide_on_right_click = true, + widget = {} +} + +--- Maps WeatherAPI condition code to file name w/o extension +--- See https://www.weatherapi.com/docs/#weather-icons +--- Day/Night is determined at time of mapping the weather to an icon +local icon_map = { + [1000] = "clear-sky", + [1003] = "few-clouds", + [1006] = "scattered-clouds", + [1009] = "scattered-clouds", + [1030] = "mist", + [1063] = "rain", + [1066] = "snow", + [1069] = "rain", + [1072] = "snow", + [1087] = "thunderstorm", + [1114] = "snow", + [1117] = "snow", + [1135] = "mist", + [1147] = "mist", + [1150] = "snow", + [1153] = "snow", + [1168] = "snow", + [1171] = "snow", + [1180] = "rain", + [1183] = "rain", + [1186] = "rain", + [1189] = "rain", + [1192] = "rain", + [1195] = "rain", + [1198] = "rain", + [1201] = "rain", + [1204] = "snow", + [1207] = "snow", + [1210] = "snow", + [1213] = "snow", + [1216] = "snow", + [1219] = "snow", + [1222] = "snow", + [1225] = "snow", + [1237] = "snow", + [1240] = "rain", + [1243] = "rain", + [1246] = "rain", + [1249] = "snow", + [1252] = "snow", + [1255] = "snow", + [1258] = "snow", + [1261] = "snow", + [1264] = "snow", + [1273] = "thunderstorm", + [1276] = "thunderstorm", + [1279] = "thunderstorm", + [1282] = "thunderstorm" +} + +--- Convert degrees Celsius to Fahrenheit +local function celsius_to_fahrenheit(c) return c * 9 / 5 + 32 end + +-- Convert degrees Fahrenheit to Celsius +local function fahrenheit_to_celsius(f) return (f - 32) * 5 / 9 end + +local function gen_temperature_str(temp, fmt_str, show_other_units, units) + local temp_str = string.format(fmt_str, temp) + local s = temp_str .. '°' .. (units == 'metric' and 'C' or 'F') + + if (show_other_units) then + local temp_conv, units_conv + if (units == 'metric') then + temp_conv = celsius_to_fahrenheit(temp) + units_conv = 'F' + else + temp_conv = fahrenheit_to_celsius(temp) + units_conv = 'C' + end + + local temp_conv_str = string.format(fmt_str, temp_conv) + s = s .. ' ' .. '(' .. temp_conv_str .. '°' .. units_conv .. ')' + end + return s +end + +local function uvi_index_color(uvi) + local color + if uvi >= 0 and uvi < 3 then color = '#a3be8c' + elseif uvi >= 3 and uvi < 6 then color = '#ebcb8b' + elseif uvi >= 6 and uvi < 8 then color = '#d08770' + elseif uvi >= 8 and uvi < 11 then color = '#bf616a' + elseif uvi >= 11 then color = '#b48ead' + end + + return '' .. uvi .. '' +end + +local function worker(user_args) + + local args = user_args or {} + + --- Validate required parameters + if args.coordinates == nil or args.api_key == nil then + show_warning(LCLE.parameter_warning .. + (args.coordinates == nil and 'coordinates' or '') .. + (args.api_key == nil and ', api_key ' or '')) + return + end + + local coordinates = args.coordinates + local api_key = args.api_key + local font_name = args.font_name or beautiful.font:gsub("%s%d+$", "") + local units = args.units or 'metric' + local time_format_12h = args.time_format_12h + local both_units_widget = args.both_units_widget or false + local icon_pack_name = args.icons or 'weather-underground-icons' + local icons_extension = args.icons_extension or '.png' + local show_forecast_on_hover = args.show_forecast_on_hover or false + local show_daily_forecast = args.show_daily_forecast or false + local show_hourly_forecast = args.show_hourly_forecast or false + local timeout = args.timeout or 120 + local ICONS_DIR = WIDGET_DIR .. '/icons/' .. icon_pack_name .. '/' + -- Forecast endpoint includes current. I could map show_daily_forecast to days here. + -- Currently overfetching but only showing when opting in. + local weather_api = + ('https://api.weatherapi.com/v1/forecast.json' .. + '?q=' .. coordinates[1] .. ',' .. coordinates[2] .. '&key=' .. api_key .. + '&units=' .. units .. '&lang=' .. LANG .. '&days=3') + + weather_widget = wibox.widget { + { + { + { + { + id = 'icon', + resize = true, + widget = wibox.widget.imagebox + }, + valign = 'center', + widget = wibox.container.place, + }, + { + id = 'txt', + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal, + }, + left = 4, + right = 4, + layout = wibox.container.margin + }, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + widget = wibox.container.background, + set_image = function(self, path) + self:get_children_by_id('icon')[1].image = path + end, + set_text = function(self, text) + self:get_children_by_id('txt')[1].text = text + end, + is_ok = function(self, is_ok) + if is_ok then + self:get_children_by_id('icon')[1]:set_opacity(1) + self:get_children_by_id('icon')[1]:emit_signal('widget:redraw_needed') + else + self:get_children_by_id('icon')[1]:set_opacity(0.2) + self:get_children_by_id('icon')[1]:emit_signal('widget:redraw_needed') + end + end + } + + local current_weather_widget = wibox.widget { + { + { + { + id = 'icon', + resize = true, + forced_width = 128, + forced_height = 128, + widget = wibox.widget.imagebox + }, + align = 'center', + widget = wibox.container.place + }, + { + id = 'description', + font = font_name .. ' 10', + align = 'center', + widget = wibox.widget.textbox + }, + forced_width = 128, + layout = wibox.layout.align.vertical + }, + { + { + { + id = 'temp', + font = font_name .. ' 36', + widget = wibox.widget.textbox + }, + { + id = 'feels_like_temp', + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.vertical + }, + { + { + id = 'wind', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + { + id = 'humidity', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + { + id = 'uv', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + expand = 'inside', + layout = wibox.layout.align.vertical + }, + spacing = 16, + forced_width = 150, + layout = wibox.layout.fixed.vertical + }, + forced_width = 300, + layout = wibox.layout.flex.horizontal, + update = function(self, weather) + local day_night_extension = "" + if not weather.is_day then + day_night_extension = "-night" + end + + self:get_children_by_id('icon')[1]:set_image( + ICONS_DIR .. icon_map[weather.condition.code] .. day_night_extension .. icons_extension) + self:get_children_by_id('temp')[1]:set_text(gen_temperature_str(weather.temp_c, '%.0f', false, units)) + self:get_children_by_id('feels_like_temp')[1]:set_text( + LCLE.feels_like .. gen_temperature_str(weather.feelslike_c, '%.0f', false, units)) + self:get_children_by_id('description')[1]:set_text(weather.condition.text) + self:get_children_by_id('wind')[1]:set_markup( + LCLE.wind .. '' .. weather.wind_kph .. 'km/h (' .. weather.wind_dir .. ')') + self:get_children_by_id('humidity')[1]:set_markup(LCLE.humidity .. '' .. weather.humidity .. '%') + self:get_children_by_id('uv')[1]:set_markup(LCLE.uv .. uvi_index_color(weather.uv)) + end + } + + local daily_forecast_widget = { + forced_width = 300, + layout = wibox.layout.flex.horizontal, + update = function(self, forecast) + local count = #self + for i = 0, count do self[i] = nil end + for i, day in ipairs(forecast) do + -- Free plan allows forecast for up to three days, each with hours + if i > 3 then break end + local day_night_extension = "" + if not day.is_day then + day_night_extension = "-night" + end + + local day_forecast = wibox.widget { + { + text = os.date('%a', tonumber(day.date_epoch)), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + { + { + { + image = ICONS_DIR + .. icon_map[day.day.condition.code] + .. day_night_extension + .. icons_extension, + resize = true, + forced_width = 48, + forced_height = 48, + widget = wibox.widget.imagebox + }, + align = 'center', + layout = wibox.container.place + }, + { + text = day.day.condition.text, + font = font_name .. ' 8', + align = 'center', + forced_height = 50, + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.vertical + }, + { + { + text = gen_temperature_str(day.day.mintemp_c, '%.0f', false, units), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + { + text = gen_temperature_str(day.day.maxtemp_c, '%.0f', false, units), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.vertical + }, + spacing = 8, + layout = wibox.layout.fixed.vertical + } + table.insert(self, day_forecast) + end + end + } + + local hourly_forecast_graph = wibox.widget { + step_width = 12, + color = '#EBCB8B', + background_color = beautiful.bg_normal, + forced_height = 100, + forced_width = 300, + widget = wibox.widget.graph, + set_max_value = function(self, new_max_value) + self.max_value = new_max_value + end, + set_min_value = function(self, new_min_value) + self.min_value = new_min_value + end, + } + + local hourly_forecast_negative_graph = wibox.widget { + step_width = 12, + color = '#5E81AC', + background_color = beautiful.bg_normal, + forced_height = 100, + forced_width = 300, + widget = wibox.widget.graph, + set_max_value = function(self, new_max_value) + self.max_value = new_max_value + end, + set_min_value = function(self, new_min_value) + self.min_value = new_min_value + end, + } + + local hourly_forecast_widget = { + layout = wibox.layout.fixed.vertical, + update = function(self, hourly) + local hours_below = { + id = 'hours', + forced_width = 300, + layout = wibox.layout.flex.horizontal + } + local temp_below = { + id = 'temp', + forced_width = 300, + layout = wibox.layout.flex.horizontal + } + + local max_temp = -1000 + local min_temp = 1000 + local values= {} + + -- Yeah, this looks weird. I would expect to have to use ipairs + for i, hour in pairs(hourly) do + if i > 25 then + break + end + + values[i] = hour.temp_c + + if max_temp < hour.temp_c then + max_temp = hour.temp_c + end + + if min_temp > hour.temp_c then + min_temp = hour.temp_c + end + + if (i - 1) % 5 == 0 then + table.insert(hours_below, wibox.widget { + text = os.date(time_format_12h and '%I%p' or '%H:00', tonumber(hour.time_epoch)), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }) + + table.insert(temp_below, wibox.widget { + markup = '' + .. string.format('%.0f', hour.temp_c) .. '°' .. '', + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }) + end + end + + hourly_forecast_graph:set_max_value(math.max(max_temp, math.abs(min_temp))) + hourly_forecast_graph:set_min_value(min_temp > 0 and min_temp * 0.7 or 0) -- move graph a bit up + + hourly_forecast_negative_graph:set_max_value(math.abs(min_temp)) + hourly_forecast_negative_graph:set_min_value(max_temp < 0 and math.abs(max_temp) * 0.7 or 0) + + for _, value in ipairs(values) do + if value >= 0 then + hourly_forecast_graph:add_value(value) + hourly_forecast_negative_graph:add_value(0) + else + hourly_forecast_graph:add_value(0) + hourly_forecast_negative_graph:add_value(math.abs(value)) + end + end + + local count = #self + for i = 0, count do + self[i] = nil + end + + -- all temperatures are positive + if min_temp > 0 then + table.insert(self, wibox.widget { + { + hourly_forecast_graph, + reflection = { horizontal = true }, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'bottom', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + table.insert(self, hours_below) + + -- all temperatures are negative + elseif max_temp < 0 then + table.insert(self, hours_below) + table.insert(self, wibox.widget { + { + hourly_forecast_negative_graph, + reflection = { horizontal = true, vertical = true }, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'top', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + + -- mixed temperatures + else + table.insert(self, wibox.widget { + { + hourly_forecast_graph, + reflection = { horizontal = true }, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'bottom', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + + table.insert(self, wibox.widget { + { + hourly_forecast_negative_graph, + reflection = { horizontal = true, vertical = true }, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'top', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + end + end + } + + local function update_widget(widget, stdout, stderr) + if stderr ~= '' then + if not warning_shown then + if (stderr ~= 'curl: (52) Empty reply from server' + and stderr ~= 'curl: (28) Failed to connect to api.weatherapi.com port 443: Connection timed out' + and stderr:find('^curl: %(18%) transfer closed with %d+ bytes remaining to read$') ~= nil + ) then + show_warning(stderr) + end + warning_shown = true + widget:is_ok(false) + tooltip:add_to_object(widget) + + widget:connect_signal('mouse::enter', function() tooltip.text = stderr end) + end + return + end + + if string.match(stdout, '<') ~= nil then + if not warning_shown then + warning_shown = true + widget:is_ok(false) + tooltip:add_to_object(widget) + + widget:connect_signal('mouse::enter', function() tooltip.text = stdout end) + end + return + end + + warning_shown = false + tooltip:remove_from_object(widget) + widget:is_ok(true) + + local result = json.decode(stdout) + + local day_night_extension = "" + if not result.current.is_day then + day_night_extension = "-night" + end + + widget:set_image(ICONS_DIR .. icon_map[result.current.condition.code] .. day_night_extension .. icons_extension) + -- TODO: if units isn't "metric", read temp_f instead + widget:set_text(gen_temperature_str(result.current.temp_c, '%.0f', both_units_widget, units)) + + current_weather_widget:update(result.current) + + local final_widget = { + current_weather_widget, + spacing = 16, + layout = wibox.layout.fixed.vertical + } + + + if show_hourly_forecast then + hourly_forecast_widget:update(result.forecast.forecastday[1].hour) + table.insert(final_widget, hourly_forecast_widget) + end + + if show_daily_forecast then + daily_forecast_widget:update(result.forecast.forecastday) + table.insert(final_widget, daily_forecast_widget) + end + + weather_popup:setup({ + { + final_widget, + margins = 10, + widget = wibox.container.margin + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + }) + end + + weather_widget:buttons(gears.table.join(awful.button({}, 1, function() + if weather_popup.visible then + weather_widget:set_bg('#00000000') + weather_popup.visible = not weather_popup.visible + else + weather_widget:set_bg(beautiful.bg_focus) + weather_popup:move_next_to(mouse.current_widget_geometry) + end + end))) + + weather_widget:connect_signal("mouse::enter", function() + if show_forecast_on_hover then + weather_widget:set_bg(beautiful.bg_focus) + weather_popup:move_next_to(mouse.current_widget_geometry) + end + end) + + weather_widget:connect_signal("mouse::leave", function() + if show_forecast_on_hover and weather_popup.visible then + weather_widget:set_bg('#00000000') + weather_popup.visible = not weather_popup.visible + end + end) + + watch( + string.format(GET_FORECAST_CMD, weather_api), + timeout, -- API limit is 1k req/day; day has 1440 min; every 2 min is good + update_widget, weather_widget + ) + + return weather_widget +end + +return setmetatable(weather_widget, {__call = function(_, ...) return worker(...) end}) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/README.md new file mode 100644 index 0000000..23c82b8 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/README.md @@ -0,0 +1,144 @@ +# Weather widget + +

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

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

+ screenshot +

+ +The widget consists of three sections: + - current weather, including humidity, wind speed, UV index + - hourly forecast for the next 24 hours + - daily forecast for the next five days + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| coordinates | Required | Table with two elements: latitude and longitude, e.g. `{46.204400, 6.143200}` | +| api_key | Required | Get it [here](https://openweathermap.org/appid) | +| font_name | `beautiful.font:gsub("%s%d+$", "")` | **Name** of the font to use e.g. 'Play' | +| both_units_widget | false | Show temperature in both units - '28°C (83°F) | +| units | `metric` | `metric` for celsius, `imperial` for fahrenheit | +| show_hourly_forecast | false | Show hourly forecase section | +| time_format_12h |false | 12 or 24 hour format (13:00 - default or 1pm) | +| show_daily_forecast | false | Show daily forecast section | +| icon_pack_name | `weather-underground-icons` | Name of the icon pack, could be `weather-underground-icon` or `VitalyGorbachev` or create your own, more details below | +| icons_extension | `.png` | File extension of icons in the pack | +| timeout | 120 | How often in seconds the widget refreshes | + +### Icons: + +The widget comes with two predefined icon packs: + + - weather-underground-icons taken from [here](https://github.com/manifestinteractive/weather-underground-icons) + - VitalyGorbachev taken from [here](https://www.flaticon.com/authors/vitaly-gorbachev) + +To add your custom icons, create a folder with the pack name under `/icons` and use the folder name in widget's config. There should be 18 icons, preferably 128x128 minimum. Icons should also respect the naming convention, please check widget's source. + +### Examples: + +#### Custom font, icons + +![example1](./example1.png) + +```lua +weather_curl_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, + time_format_12h = true, + units = 'imperial', + both_units_widget = true, + font_name = 'Carter One', + icons = 'VitalyGorbachev', + icons_extension = '.svg', + show_hourly_forecast = true, + show_daily_forecast = true, +}), +``` + +#### Only current weather + +![example2](./example2.png) + +```lua +weather_curl_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, +}), +``` + +## Installation + +1. Download json parser for lua from [github.com/rxi/json.lua](https://github.com/rxi/json.lua) and place it under **~/.config/awesome/** (don't forget to star a repo ): + + ```bash + wget -P ~/.config/awesome/ https://raw.githubusercontent.com/rxi/json.lua/master/json.lua + ``` + +1. Clone this repo under **~/.config/awesome/**: + + ```bash + git clone https://github.com/streetturtle/awesome-wm-widgets.git ~/.config/awesome/ + ``` + +1. Get Open Weather Map app id here: [openweathermap.org/appid](https://openweathermap.org/appid). + +1. Require weather widget at the beginning of **rc.lua**: + + ```lua + local weather_widget = require("awesome-wm-widgets.weather-widget.weather") + ``` + +1. Add widget to the tasklist: + + ```lua + s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + --default + weather_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, + }), + , + --customized + weather_curl_widget({ + api_key='', + coordinates = {45.5017, -73.5673}, + time_format_12h = true, + units = 'imperial', + both_units_widget = true, + font_name = 'Carter One', + icons = 'VitalyGorbachev', + icons_extension = '.svg', + show_hourly_forecast = true, + show_daily_forecast = true, + }), + ... + ``` + +## More screenshots + +Only negative temperature: + +![negative](./negative.png) + +Both positive and negative tempertature: + +![both](./both.png) + +## How it works + +TBW diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/both.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/both.png new file mode 100644 index 0000000..0947a37 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/both.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/example1.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/example1.png new file mode 100644 index 0000000..7074faa Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/example1.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/example2.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/example2.png new file mode 100644 index 0000000..857274b Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/example2.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/example_response.json b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/example_response.json new file mode 100644 index 0000000..2b90a6e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/example_response.json @@ -0,0 +1,1419 @@ +{ + "lat": 45.5, + "lon": -73.57, + "timezone": "America/Toronto", + "timezone_offset": -14400, + "current": { + "dt": 1603155313, + "sunrise": 1603106181, + "sunset": 1603144896, + "temp": 8.91, + "feels_like": 7.97, + "pressure": 1025, + "humidity": 100, + "dew_point": 8.91, + "uvi": 2.37, + "clouds": 90, + "visibility": 4828, + "wind_speed": 1, + "wind_deg": 40, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + }, + { + "id": 701, + "main": "Mist", + "description": "mist", + "icon": "50n" + } + ], + "rain": { + "1h": 0.65 + } + }, + "hourly": [ + { + "dt": 1603152000, + "temp": -8.91, + "feels_like": 7.95, + "pressure": 1025, + "humidity": 100, + "dew_point": 8.91, + "clouds": 90, + "visibility": 10000, + "wind_speed": 1.03, + "wind_deg": 32, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 0.4 + } + }, + { + "dt": 1603155600, + "temp": -9.16, + "feels_like": 7.7, + "pressure": 1025, + "humidity": 91, + "dew_point": 7.77, + "clouds": 95, + "visibility": 10000, + "wind_speed": 1.34, + "wind_deg": 67, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 0.29, + "rain": { + "1h": 0.87 + } + }, + { + "dt": 1603159200, + "temp": -9.24, + "feels_like": 7.7, + "pressure": 1024, + "humidity": 88, + "dew_point": 7.36, + "clouds": 98, + "visibility": 10000, + "wind_speed": 1.32, + "wind_deg": 48, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 0.59, + "rain": { + "1h": 0.42 + } + }, + { + "dt": 1603162800, + "temp": -9.18, + "feels_like": 6.91, + "pressure": 1023, + "humidity": 86, + "dew_point": 6.96, + "clouds": 99, + "visibility": 10000, + "wind_speed": 2.23, + "wind_deg": 42, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0.86 + }, + { + "dt": 1603166400, + "temp": -9.09, + "feels_like": 6.46, + "pressure": 1023, + "humidity": 88, + "dew_point": 7.21, + "clouds": 100, + "visibility": 10000, + "wind_speed": 2.83, + "wind_deg": 46, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 0.94, + "rain": { + "1h": 0.6 + } + }, + { + "dt": 1603170000, + "temp": -8.96, + "feels_like": 6.43, + "pressure": 1022, + "humidity": 91, + "dew_point": 7.62, + "clouds": 100, + "visibility": 5405, + "wind_speed": 2.81, + "wind_deg": 18, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 1.89 + } + }, + { + "dt": 1603173600, + "temp": -8.84, + "feels_like": 6.29, + "pressure": 1021, + "humidity": 91, + "dew_point": 7.6, + "clouds": 100, + "visibility": 7599, + "wind_speed": 2.8, + "wind_deg": 35, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 2.07 + } + }, + { + "dt": 1603177200, + "temp": -8.92, + "feels_like": 6.34, + "pressure": 1021, + "humidity": 92, + "dew_point": 7.78, + "clouds": 100, + "visibility": 8594, + "wind_speed": 2.91, + "wind_deg": 44, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 1.19 + } + }, + { + "dt": 1603180800, + "temp": -9.08, + "feels_like": 7.18, + "pressure": 1020, + "humidity": 93, + "dew_point": 8.06, + "clouds": 100, + "visibility": 9347, + "wind_speed": 2.06, + "wind_deg": 37, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 1.02 + } + }, + { + "dt": 1603184400, + "temp": -8.98, + "feels_like": 6.28, + "pressure": 1019, + "humidity": 93, + "dew_point": 8, + "clouds": 100, + "visibility": 6164, + "wind_speed": 3.16, + "wind_deg": 354, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 1.89 + } + }, + { + "dt": 1603188000, + "temp": -8.69, + "feels_like": 5.78, + "pressure": 1019, + "humidity": 92, + "dew_point": 7.58, + "clouds": 100, + "visibility": 5143, + "wind_speed": 3.31, + "wind_deg": 29, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 2.6 + } + }, + { + "dt": 1603191600, + "temp": -8.6, + "feels_like": 6.08, + "pressure": 1019, + "humidity": 92, + "dew_point": 7.42, + "clouds": 100, + "visibility": 6072, + "wind_speed": 2.73, + "wind_deg": 29, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 2.3 + } + }, + { + "dt": 1603195200, + "temp": -8.56, + "feels_like": 6.68, + "pressure": 1019, + "humidity": 92, + "dew_point": 7.45, + "clouds": 100, + "visibility": 6697, + "wind_speed": 1.8, + "wind_deg": 16, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 2.58 + } + }, + { + "dt": 1603198800, + "temp": -8.74, + "feels_like": 6.88, + "pressure": 1020, + "humidity": 91, + "dew_point": 7.38, + "clouds": 100, + "visibility": 10000, + "wind_speed": 1.77, + "wind_deg": 319, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 1.06 + } + }, + { + "dt": 1603202400, + "temp": -8.93, + "feels_like": 7.32, + "pressure": 1020, + "humidity": 90, + "dew_point": 7.48, + "clouds": 100, + "visibility": 10000, + "wind_speed": 1.42, + "wind_deg": 291, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 0.95 + } + }, + { + "dt": 1603206000, + "temp": -9.07, + "feels_like": 6.75, + "pressure": 1021, + "humidity": 89, + "dew_point": 7.43, + "clouds": 100, + "visibility": 10000, + "wind_speed": 2.43, + "wind_deg": 276, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 0.31 + } + }, + { + "dt": 1603209600, + "temp": -9.31, + "feels_like": 6.58, + "pressure": 1022, + "humidity": 86, + "dew_point": 7.17, + "clouds": 100, + "visibility": 10000, + "wind_speed": 2.93, + "wind_deg": 262, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "pop": 0.8 + }, + { + "dt": 1603213200, + "temp": -10.07, + "feels_like": 6.68, + "pressure": 1023, + "humidity": 80, + "dew_point": 6.78, + "clouds": 100, + "visibility": 10000, + "wind_speed": 3.77, + "wind_deg": 269, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "pop": 0.8 + }, + { + "dt": 1603216800, + "temp": -11.87, + "feels_like": 7.99, + "pressure": 1023, + "humidity": 67, + "dew_point": 6.15, + "clouds": 99, + "visibility": 10000, + "wind_speed": 4.21, + "wind_deg": 265, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "pop": 0.8 + }, + { + "dt": 1603220400, + "temp": -12.05, + "feels_like": 7.95, + "pressure": 1024, + "humidity": 64, + "dew_point": 5.63, + "clouds": 100, + "visibility": 10000, + "wind_speed": 4.38, + "wind_deg": 270, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1603224000, + "temp": -11.74, + "feels_like": 7.54, + "pressure": 1025, + "humidity": 63, + "dew_point": 5.08, + "clouds": 100, + "visibility": 10000, + "wind_speed": 4.38, + "wind_deg": 276, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1603227600, + "temp": -11.09, + "feels_like": 7.13, + "pressure": 1026, + "humidity": 62, + "dew_point": 4.24, + "clouds": 100, + "visibility": 10000, + "wind_speed": 3.79, + "wind_deg": 293, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04d" + } + ], + "pop": 0 + }, + { + "dt": 1603231200, + "temp": -10.13, + "feels_like": 6.51, + "pressure": 1027, + "humidity": 63, + "dew_point": 3.43, + "clouds": 100, + "visibility": 10000, + "wind_speed": 3.13, + "wind_deg": 318, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603234800, + "temp": -9.53, + "feels_like": 7.02, + "pressure": 1028, + "humidity": 63, + "dew_point": 3.02, + "clouds": 100, + "visibility": 10000, + "wind_speed": 1.4, + "wind_deg": 329, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603238400, + "temp": -9.2, + "feels_like": 7.3, + "pressure": 1028, + "humidity": 65, + "dew_point": 3.03, + "clouds": 100, + "visibility": 10000, + "wind_speed": 0.56, + "wind_deg": 52, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603242000, + "temp": -8.73, + "feels_like": 6.57, + "pressure": 1029, + "humidity": 68, + "dew_point": 3.28, + "clouds": 100, + "visibility": 10000, + "wind_speed": 0.98, + "wind_deg": 75, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603245600, + "temp": -8.12, + "feels_like": 5.55, + "pressure": 1029, + "humidity": 71, + "dew_point": 3.27, + "clouds": 89, + "visibility": 10000, + "wind_speed": 1.57, + "wind_deg": 68, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603249200, + "temp": -7.83, + "feels_like": 4.86, + "pressure": 1029, + "humidity": 71, + "dew_point": 3.05, + "clouds": 93, + "visibility": 10000, + "wind_speed": 2.07, + "wind_deg": 68, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603252800, + "temp": -7.49, + "feels_like": 4.21, + "pressure": 1029, + "humidity": 72, + "dew_point": 2.8, + "clouds": 94, + "visibility": 10000, + "wind_speed": 2.48, + "wind_deg": 66, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603256400, + "temp": -6.92, + "feels_like": 3.31, + "pressure": 1029, + "humidity": 73, + "dew_point": 2.47, + "clouds": 96, + "visibility": 10000, + "wind_speed": 2.87, + "wind_deg": 81, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603260000, + "temp": -6.49, + "feels_like": 2.48, + "pressure": 1029, + "humidity": 74, + "dew_point": 2.22, + "clouds": 96, + "visibility": 10000, + "wind_speed": 3.38, + "wind_deg": 78, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603263600, + "temp": -6.3, + "feels_like": 1.81, + "pressure": 1028, + "humidity": 71, + "dew_point": 1.55, + "clouds": 100, + "visibility": 10000, + "wind_speed": 3.89, + "wind_deg": 84, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603267200, + "temp": -6.22, + "feels_like": 1.39, + "pressure": 1027, + "humidity": 69, + "dew_point": 0.99, + "clouds": 98, + "visibility": 10000, + "wind_speed": 4.27, + "wind_deg": 74, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0 + }, + { + "dt": 1603270800, + "temp": -6.69, + "feels_like": 1.66, + "pressure": 1026, + "humidity": 65, + "dew_point": 0.79, + "clouds": 96, + "visibility": 10000, + "wind_speed": 4.47, + "wind_deg": 69, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0.05 + }, + { + "dt": 1603274400, + "temp": -6.53, + "feels_like": 1.74, + "pressure": 1024, + "humidity": 68, + "dew_point": 1.07, + "clouds": 97, + "visibility": 10000, + "wind_speed": 4.23, + "wind_deg": 65, + "weather": [ + { + "id": 804, + "main": "Clouds", + "description": "overcast clouds", + "icon": "04n" + } + ], + "pop": 0.28 + }, + { + "dt": 1603278000, + "temp": -6.41, + "feels_like": 1.87, + "pressure": 1023, + "humidity": 73, + "dew_point": 1.96, + "clouds": 97, + "visibility": 10000, + "wind_speed": 4.08, + "wind_deg": 73, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 0.35, + "rain": { + "1h": 0.17 + } + }, + { + "dt": 1603281600, + "temp": -6.42, + "feels_like": 2.71, + "pressure": 1022, + "humidity": 79, + "dew_point": 3.21, + "clouds": 98, + "visibility": 9620, + "wind_speed": 3.16, + "wind_deg": 71, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 0.62, + "rain": { + "1h": 0.37 + } + }, + { + "dt": 1603285200, + "temp": -6.67, + "feels_like": 3.38, + "pressure": 1021, + "humidity": 86, + "dew_point": 4.59, + "clouds": 100, + "visibility": 10000, + "wind_speed": 2.95, + "wind_deg": 84, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "pop": 0.9, + "rain": { + "1h": 1.08 + } + }, + { + "dt": 1603288800, + "temp": -8.55, + "feels_like": 5.61, + "pressure": 1019, + "humidity": 87, + "dew_point": 6.64, + "clouds": 100, + "visibility": 10000, + "wind_speed": 3.05, + "wind_deg": 135, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 0.23 + } + }, + { + "dt": 1603292400, + "temp": -10.85, + "feels_like": 8.09, + "pressure": 1018, + "humidity": 95, + "dew_point": 10.13, + "clouds": 100, + "visibility": 10000, + "wind_speed": 4.04, + "wind_deg": 150, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 0.31 + } + }, + { + "dt": 1603296000, + "temp": -13.37, + "feels_like": 10.29, + "pressure": 1017, + "humidity": 90, + "dew_point": 11.93, + "clouds": 100, + "visibility": 10000, + "wind_speed": 5.19, + "wind_deg": 170, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 0.32 + } + }, + { + "dt": 1603299600, + "temp": -13.92, + "feels_like": 11.34, + "pressure": 1015, + "humidity": 94, + "dew_point": 13.07, + "clouds": 100, + "visibility": 6450, + "wind_speed": 5.01, + "wind_deg": 177, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 3.08 + } + }, + { + "dt": 1603303200, + "temp": -14.85, + "feels_like": 12.78, + "pressure": 1014, + "humidity": 95, + "dew_point": 14.1, + "clouds": 100, + "visibility": 10000, + "wind_speed": 4.79, + "wind_deg": 183, + "weather": [ + { + "id": 502, + "main": "Rain", + "description": "heavy intensity rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 4.94 + } + }, + { + "dt": 1603306800, + "temp": -15.94, + "feels_like": 13.56, + "pressure": 1014, + "humidity": 93, + "dew_point": 14.96, + "clouds": 100, + "visibility": 7138, + "wind_speed": 5.61, + "wind_deg": 207, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 2.71 + } + }, + { + "dt": 1603310400, + "temp": -16.72, + "feels_like": 14.6, + "pressure": 1014, + "humidity": 93, + "dew_point": 15.66, + "clouds": 100, + "visibility": 10000, + "wind_speed": 5.64, + "wind_deg": 208, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 1.33 + } + }, + { + "dt": 1603314000, + "temp": -16.74, + "feels_like": 14.61, + "pressure": 1014, + "humidity": 94, + "dew_point": 15.81, + "clouds": 100, + "visibility": 10000, + "wind_speed": 5.75, + "wind_deg": 216, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "pop": 1, + "rain": { + "1h": 0.89 + } + }, + { + "dt": 1603317600, + "temp": -17.05, + "feels_like": 14.53, + "pressure": 1015, + "humidity": 92, + "dew_point": 15.83, + "clouds": 100, + "visibility": 10000, + "wind_speed": 6.3, + "wind_deg": 234, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 0.51 + } + }, + { + "dt": 1603321200, + "temp": -16.74, + "feels_like": 14.18, + "pressure": 1016, + "humidity": 87, + "dew_point": 14.65, + "clouds": 100, + "visibility": 10000, + "wind_speed": 5.74, + "wind_deg": 257, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10n" + } + ], + "pop": 1, + "rain": { + "1h": 0.22 + } + } + ], + "daily": [ + { + "dt": 1603123200, + "sunrise": 1603106181, + "sunset": 1603144896, + "temp": { + "day": 12, + "min": 8.91, + "max": 12.73, + "night": 9.05, + "eve": 9.72, + "morn": 12.73 + }, + "feels_like": { + "day": 9.92, + "night": 7.02, + "eve": 7.88, + "morn": 8.02 + }, + "pressure": 1025, + "humidity": 78, + "dew_point": 8.34, + "wind_speed": 2.41, + "wind_deg": 242, + "weather": [ + { + "id": 501, + "main": "Rain", + "description": "moderate rain", + "icon": "10d" + } + ], + "clouds": 100, + "pop": 1, + "rain": 8.77, + "uvi": 2.37 + }, + { + "dt": 1603209600, + "sunrise": 1603192663, + "sunset": 1603231195, + "temp": { + "day": 9.07, + "min": 7.83, + "max": 11.87, + "night": 7.83, + "eve": 11.09, + "morn": 8.98 + }, + "feels_like": { + "day": 6.75, + "night": 4.86, + "eve": 7.13, + "morn": 6.28 + }, + "pressure": 1021, + "humidity": 89, + "dew_point": 7.43, + "wind_speed": 2.43, + "wind_deg": 276, + "weather": [ + { + "id": 502, + "main": "Rain", + "description": "heavy intensity rain", + "icon": "10d" + } + ], + "clouds": 100, + "pop": 1, + "rain": 18.46, + "uvi": 2.3 + }, + { + "dt": 1603296000, + "sunrise": 1603279145, + "sunset": 1603317495, + "temp": { + "day": 10.85, + "min": 6.42, + "max": 16.74, + "night": 12.25, + "eve": 16.74, + "morn": 6.69 + }, + "feels_like": { + "day": 8.09, + "night": 8.59, + "eve": 14.61, + "morn": 1.66 + }, + "pressure": 1018, + "humidity": 95, + "dew_point": 10.13, + "wind_speed": 4.04, + "wind_deg": 150, + "weather": [ + { + "id": 502, + "main": "Rain", + "description": "heavy intensity rain", + "icon": "10d" + } + ], + "clouds": 100, + "pop": 1, + "rain": 16.19, + "uvi": 2.32 + }, + { + "dt": 1603382400, + "sunrise": 1603365627, + "sunset": 1603403795, + "temp": { + "day": 9.95, + "min": 7.29, + "max": 11.27, + "night": 7.29, + "eve": 11.01, + "morn": 9.44 + }, + "feels_like": { + "day": 5.45, + "night": 2.63, + "eve": 8.07, + "morn": 5.65 + }, + "pressure": 1027, + "humidity": 57, + "dew_point": 2.03, + "wind_speed": 4, + "wind_deg": 283, + "weather": [ + { + "id": 802, + "main": "Clouds", + "description": "scattered clouds", + "icon": "03d" + } + ], + "clouds": 27, + "pop": 0, + "uvi": 2.36 + }, + { + "dt": 1603468800, + "sunrise": 1603452109, + "sunset": 1603490097, + "temp": { + "day": 12.02, + "min": 6.62, + "max": 17.04, + "night": 15.91, + "eve": 17.04, + "morn": 7.09 + }, + "feels_like": { + "day": 8.48, + "night": 11.82, + "eve": 12.58, + "morn": 3.07 + }, + "pressure": 1022, + "humidity": 72, + "dew_point": 7.29, + "wind_speed": 4.1, + "wind_deg": 147, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "clouds": 99, + "pop": 0.29, + "rain": 0.22, + "uvi": 2.2 + }, + { + "dt": 1603555200, + "sunrise": 1603538592, + "sunset": 1603576400, + "temp": { + "day": 8.39, + "min": 6.83, + "max": 15.86, + "night": 6.83, + "eve": 9.56, + "morn": 12.99 + }, + "feels_like": { + "day": 3.79, + "night": 3.04, + "eve": 6.58, + "morn": 10.39 + }, + "pressure": 1022, + "humidity": 58, + "dew_point": 0.71, + "wind_speed": 3.87, + "wind_deg": 10, + "weather": [ + { + "id": 500, + "main": "Rain", + "description": "light rain", + "icon": "10d" + } + ], + "clouds": 72, + "pop": 0.9, + "rain": 2.54, + "uvi": 2.07 + }, + { + "dt": 1603641600, + "sunrise": 1603625075, + "sunset": 1603662705, + "temp": { + "day": 5.33, + "min": 3.23, + "max": 7.24, + "night": 4.97, + "eve": 6.59, + "morn": 3.97 + }, + "feels_like": { + "day": 1.26, + "night": 0.02, + "eve": 2.58, + "morn": -0.34 + }, + "pressure": 1025, + "humidity": 61, + "dew_point": -5.56, + "wind_speed": 2.67, + "wind_deg": 37, + "weather": [ + { + "id": 803, + "main": "Clouds", + "description": "broken clouds", + "icon": "04d" + } + ], + "clouds": 74, + "pop": 0.08, + "uvi": 2.25 + }, + { + "dt": 1603728000, + "sunrise": 1603711558, + "sunset": 1603749010, + "temp": { + "day": 3.7, + "min": 2.09, + "max": 3.88, + "night": 3.54, + "eve": 3.54, + "morn": 2.09 + }, + "feels_like": { + "day": -0.28, + "night": -0.76, + "eve": -0.86, + "morn": -2.81 + }, + "pressure": 1021, + "humidity": 90, + "dew_point": 2.33, + "wind_speed": 3.35, + "wind_deg": 32, + "weather": [ + { + "id": 502, + "main": "Rain", + "description": "heavy intensity rain", + "icon": "10d" + } + ], + "clouds": 100, + "pop": 1, + "rain": 12.43 + } + ] +} \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/broken-clouds-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/broken-clouds-night.svg new file mode 100644 index 0000000..8b7fc48 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/broken-clouds-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/broken-clouds.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/broken-clouds.svg new file mode 100644 index 0000000..d42ea59 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/broken-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/clear-sky-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/clear-sky-night.svg new file mode 100644 index 0000000..44f096c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/clear-sky-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/clear-sky.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/clear-sky.svg new file mode 100644 index 0000000..dc82163 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/clear-sky.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/few-clouds-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/few-clouds-night.svg new file mode 100644 index 0000000..8b7fc48 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/few-clouds-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/few-clouds.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/few-clouds.svg new file mode 100644 index 0000000..d42ea59 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/few-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/mist-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/mist-night.svg new file mode 100644 index 0000000..960b07d --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/mist-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/mist.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/mist.svg new file mode 100644 index 0000000..770f8d7 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/mist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/rain-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/rain-night.svg new file mode 100644 index 0000000..11ecf00 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/rain-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/rain.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/rain.svg new file mode 100644 index 0000000..11ecf00 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/scattered-clouds-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/scattered-clouds-night.svg new file mode 100644 index 0000000..8b7fc48 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/scattered-clouds-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/scattered-clouds.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/scattered-clouds.svg new file mode 100644 index 0000000..d42ea59 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/scattered-clouds.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/shower-rain-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/shower-rain-night.svg new file mode 100644 index 0000000..4d1897c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/shower-rain-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/shower-rain.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/shower-rain.svg new file mode 100644 index 0000000..4d1897c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/shower-rain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/snow-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/snow-night.svg new file mode 100644 index 0000000..bee891e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/snow-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/snow.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/snow.svg new file mode 100644 index 0000000..e2ea140 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/snow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/thunderstorm-night.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/thunderstorm-night.svg new file mode 100644 index 0000000..1813197 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/thunderstorm-night.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/thunderstorm.svg b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/thunderstorm.svg new file mode 100644 index 0000000..44a733c --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/VitalyGorbachev/thunderstorm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/broken-clouds-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/broken-clouds-night.png new file mode 100644 index 0000000..061d1cd Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/broken-clouds-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/broken-clouds.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/broken-clouds.png new file mode 100755 index 0000000..5967d92 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/broken-clouds.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/clear-sky-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/clear-sky-night.png new file mode 100644 index 0000000..cc40d0f Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/clear-sky-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/clear-sky.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/clear-sky.png new file mode 100755 index 0000000..acf8e5c Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/clear-sky.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/few-clouds-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/few-clouds-night.png new file mode 100644 index 0000000..9c34fab Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/few-clouds-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/few-clouds.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/few-clouds.png new file mode 100755 index 0000000..7580fc5 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/few-clouds.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/mist-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/mist-night.png new file mode 100755 index 0000000..102142a Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/mist-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/mist.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/mist.png new file mode 100755 index 0000000..102142a Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/mist.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/rain-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/rain-night.png new file mode 100755 index 0000000..49f0903 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/rain-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/rain.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/rain.png new file mode 100755 index 0000000..49f0903 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/rain.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/scattered-clouds-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/scattered-clouds-night.png new file mode 100755 index 0000000..63cb1b2 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/scattered-clouds-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/scattered-clouds.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/scattered-clouds.png new file mode 100755 index 0000000..63cb1b2 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/scattered-clouds.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/shower-rain-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/shower-rain-night.png new file mode 100755 index 0000000..49f0903 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/shower-rain-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/shower-rain.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/shower-rain.png new file mode 100755 index 0000000..49f0903 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/shower-rain.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/snow-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/snow-night.png new file mode 100755 index 0000000..0a7f006 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/snow-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/snow.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/snow.png new file mode 100755 index 0000000..0a7f006 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/snow.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/thunderstorm-night.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/thunderstorm-night.png new file mode 100755 index 0000000..2102104 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/thunderstorm-night.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/thunderstorm.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/thunderstorm.png new file mode 100755 index 0000000..2102104 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/icons/weather-underground-icons/thunderstorm.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/locale/de.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/locale/de.lua new file mode 100644 index 0000000..2a9236a --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/locale/de.lua @@ -0,0 +1,13 @@ +local de = { + warning_title = "Wetter Widget", + parameter_warning = "Folgende benötigte Parameter fehlen: ", + directions = { + "N", "NNO", "NO", "ONO", "O", "OSO", "SO", "SSO", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW", "N" + }, + feels_like = "Gefühlt: ", + wind = "Wind: ", + humidity = "Luftfeuchtigkeit: ", + uv = "UV-Index: " +} + +return de diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/locale/en.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/locale/en.lua new file mode 100644 index 0000000..377dc6f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/locale/en.lua @@ -0,0 +1,14 @@ +local en = { + warning_title = "Weather Widget", + parameter_warning = "Required parameters are not set: ", + directions = { + "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", + "WSW", "W", "WNW", "NW", "NNW", "N" + }, + feels_like = "Feels like ", + wind = "Wind: ", + humidity = "Humidity: ", + uv = "UV: " +} + +return en diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/locale/fr.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/locale/fr.lua new file mode 100644 index 0000000..de50814 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/locale/fr.lua @@ -0,0 +1,14 @@ +local fr = { + warning_title = "Widget Météo", + parameter_warning = "Les paramètres suivants sont obligatoires : ", + directions = { + "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSO", "SO", + "OSO", "O", "ONO", "NO", "NNO", "N" + }, + feels_like = "ressentie à ", + wind = "Vent : ", + humidity = "Humidité : ", + uv = "Indice UV : " +} + +return fr diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/locale/pt.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/locale/pt.lua new file mode 100644 index 0000000..e7f4012 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/locale/pt.lua @@ -0,0 +1,14 @@ +local pt = { + warning_title = "Widget do tempo", + parameter_warning = "Parâmetros necessários não definidos: ", + directions = { + "N", "NNE", "NE", "ENE", "L", "ESE", "SE", "SSE", "S", "SSO", "SO", + "OSO", "O", "ONO", "NO", "NNO", "N" + }, + feels_like = "Sensação de ", + wind = "Vento: ", + humidity = "Umidade: ", + uv = "UV: " +} + +return pt diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/negative.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/negative.png new file mode 100644 index 0000000..afcf567 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/negative.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/weather-widget.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/weather-widget.png new file mode 100644 index 0000000..c7fc37e Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/weather-widget.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/weather.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/weather.lua new file mode 100644 index 0000000..3ec1c3f --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/weather-widget/weather.lua @@ -0,0 +1,575 @@ +------------------------------------------------- +-- Weather Widget based on the OpenWeatherMap +-- https://openweathermap.org/ +-- +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- +local awful = require("awful") +local watch = require("awful.widget.watch") +local json = require("json") +local naughty = require("naughty") +local wibox = require("wibox") +local gears = require("gears") +local beautiful = require("beautiful") + +local HOME_DIR = os.getenv("HOME") +local WIDGET_DIR = HOME_DIR .. '/.config/awesome/awesome-wm-widgets/weather-widget' +local GET_FORECAST_CMD = [[bash -c "curl -s --show-error -X GET '%s'"]] + +local SYS_LANG = os.getenv("LANG"):sub(1, 2) +if SYS_LANG == "C" or SYS_LANG == "C." then + -- C-locale is a common fallback for simple English + SYS_LANG = "en" +end +-- default language is ENglish +local LANG = gears.filesystem.file_readable(WIDGET_DIR .. "/" .. "locale/" .. + SYS_LANG .. ".lua") and SYS_LANG or "en" +local LCLE = require("awesome-wm-widgets.weather-widget.locale." .. LANG) + + +local function show_warning(message) + naughty.notify { + preset = naughty.config.presets.critical, + title = LCLE.warning_title, + text = message + } +end + +if SYS_LANG ~= LANG then + show_warning("Your language is not supported yet. Language set to English") +end + +local weather_widget = {} +local warning_shown = false +local tooltip = awful.tooltip { + mode = 'outside', + preferred_positions = {'bottom'} +} + +local weather_popup = awful.popup { + ontop = true, + visible = false, + shape = gears.shape.rounded_rect, + border_width = 1, + border_color = beautiful.bg_focus, + maximum_width = 400, + offset = {y = 5}, + hide_on_right_click = true, + widget = {} +} + +--- Maps openWeatherMap icon name to file name w/o extension +local icon_map = { + ["01d"] = "clear-sky", + ["02d"] = "few-clouds", + ["03d"] = "scattered-clouds", + ["04d"] = "broken-clouds", + ["09d"] = "shower-rain", + ["10d"] = "rain", + ["11d"] = "thunderstorm", + ["13d"] = "snow", + ["50d"] = "mist", + ["01n"] = "clear-sky-night", + ["02n"] = "few-clouds-night", + ["03n"] = "scattered-clouds-night", + ["04n"] = "broken-clouds-night", + ["09n"] = "shower-rain-night", + ["10n"] = "rain-night", + ["11n"] = "thunderstorm-night", + ["13n"] = "snow-night", + ["50n"] = "mist-night" +} + +--- Return wind direction as a string +local function to_direction(degrees) + -- Ref: https://www.campbellsci.eu/blog/convert-wind-directions + if degrees == nil then return "Unknown dir" end + local directions = LCLE.directions + return directions[math.floor((degrees % 360) / 22.5) + 1] +end + +--- Convert degrees Celsius to Fahrenheit +local function celsius_to_fahrenheit(c) return c * 9 / 5 + 32 end + +-- Convert degrees Fahrenheit to Celsius +local function fahrenheit_to_celsius(f) return (f - 32) * 5 / 9 end + +local function gen_temperature_str(temp, fmt_str, show_other_units, units) + local temp_str = string.format(fmt_str, temp) + local s = temp_str .. '°' .. (units == 'metric' and 'C' or 'F') + + if (show_other_units) then + local temp_conv, units_conv + if (units == 'metric') then + temp_conv = celsius_to_fahrenheit(temp) + units_conv = 'F' + else + temp_conv = fahrenheit_to_celsius(temp) + units_conv = 'C' + end + + local temp_conv_str = string.format(fmt_str, temp_conv) + s = s .. ' ' .. '(' .. temp_conv_str .. '°' .. units_conv .. ')' + end + return s +end + +local function uvi_index_color(uvi) + local color + if uvi >= 0 and uvi < 3 then color = '#A3BE8C' + elseif uvi >= 3 and uvi < 6 then color = '#EBCB8B' + elseif uvi >= 6 and uvi < 8 then color = '#D08770' + elseif uvi >= 8 and uvi < 11 then color = '#BF616A' + elseif uvi >= 11 then color = '#B48EAD' + end + + return '' .. uvi .. '' +end + +local function worker(user_args) + + local args = user_args or {} + + --- Validate required parameters + if args.coordinates == nil or args.api_key == nil then + show_warning(LCLE.parameter_warning .. + (args.coordinates == nil and 'coordinates' or '') .. + (args.api_key == nil and ', api_key ' or '')) + return + end + + local coordinates = args.coordinates + local api_key = args.api_key + local font_name = args.font_name or beautiful.font:gsub("%s%d+$", "") + local units = args.units or 'metric' + local time_format_12h = args.time_format_12h + local both_units_widget = args.both_units_widget or false + local show_hourly_forecast = args.show_hourly_forecast + local show_daily_forecast = args.show_daily_forecast + local icon_pack_name = args.icons or 'weather-underground-icons' + local icons_extension = args.icons_extension or '.png' + local timeout = args.timeout or 120 + + local ICONS_DIR = WIDGET_DIR .. '/icons/' .. icon_pack_name .. '/' + local owm_one_cal_api = + ('https://api.openweathermap.org/data/2.5/onecall' .. + '?lat=' .. coordinates[1] .. '&lon=' .. coordinates[2] .. '&appid=' .. api_key .. + '&units=' .. units .. '&exclude=minutely' .. + (show_hourly_forecast == false and ',hourly' or '') .. + (show_daily_forecast == false and ',daily' or '') .. + '&lang=' .. LANG) + + weather_widget = wibox.widget { + { + { + { + { + id = 'icon', + resize = true, + widget = wibox.widget.imagebox + }, + valign = 'center', + widget = wibox.container.place, + }, + { + id = 'txt', + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.horizontal, + }, + left = 4, + right = 4, + layout = wibox.container.margin + }, + shape = function(cr, width, height) + gears.shape.rounded_rect(cr, width, height, 4) + end, + widget = wibox.container.background, + set_image = function(self, path) + self:get_children_by_id('icon')[1].image = path + end, + set_text = function(self, text) + self:get_children_by_id('txt')[1].text = text + end, + is_ok = function(self, is_ok) + if is_ok then + self:get_children_by_id('icon')[1]:set_opacity(1) + self:get_children_by_id('icon')[1]:emit_signal('widget:redraw_needed') + else + self:get_children_by_id('icon')[1]:set_opacity(0.2) + self:get_children_by_id('icon')[1]:emit_signal('widget:redraw_needed') + end + end + } + + local current_weather_widget = wibox.widget { + { + { + { + id = 'icon', + resize = true, + forced_width = 128, + forced_height = 128, + widget = wibox.widget.imagebox + }, + align = 'center', + widget = wibox.container.place + }, + { + id = 'description', + font = font_name .. ' 10', + align = 'center', + widget = wibox.widget.textbox + }, + forced_width = 128, + layout = wibox.layout.align.vertical + }, + { + { + { + id = 'temp', + font = font_name .. ' 36', + widget = wibox.widget.textbox + }, + { + id = 'feels_like_temp', + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.vertical + }, + { + { + id = 'wind', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + { + id = 'humidity', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + { + id = 'uv', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + expand = 'inside', + layout = wibox.layout.align.vertical + }, + spacing = 16, + forced_width = 150, + layout = wibox.layout.fixed.vertical + }, + forced_width = 300, + layout = wibox.layout.flex.horizontal, + update = function(self, weather) + self:get_children_by_id('icon')[1]:set_image( + ICONS_DIR .. icon_map[weather.weather[1].icon] .. icons_extension) + self:get_children_by_id('temp')[1]:set_text(gen_temperature_str(weather.temp, '%.0f', false, units)) + self:get_children_by_id('feels_like_temp')[1]:set_text( + LCLE.feels_like .. gen_temperature_str(weather.feels_like, '%.0f', false, units)) + self:get_children_by_id('description')[1]:set_text(weather.weather[1].description) + self:get_children_by_id('wind')[1]:set_markup( + LCLE.wind .. '' .. weather.wind_speed .. 'm/s (' .. to_direction(weather.wind_deg) .. ')') + self:get_children_by_id('humidity')[1]:set_markup(LCLE.humidity .. '' .. weather.humidity .. '%') + self:get_children_by_id('uv')[1]:set_markup(LCLE.uv .. uvi_index_color(weather.uvi)) + end + } + + + local daily_forecast_widget = { + forced_width = 300, + layout = wibox.layout.flex.horizontal, + update = function(self, forecast, timezone_offset) + local count = #self + for i = 0, count do self[i]=nil end + for i, day in ipairs(forecast) do + if i > 5 then break end + local day_forecast = wibox.widget { + { + text = os.date('%a', tonumber(day.dt) + tonumber(timezone_offset)), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + { + { + { + image = ICONS_DIR .. icon_map[day.weather[1].icon] .. icons_extension, + resize = true, + forced_width = 48, + forced_height = 48, + widget = wibox.widget.imagebox + }, + align = 'center', + layout = wibox.container.place + }, + { + text = day.weather[1].description, + font = font_name .. ' 8', + align = 'center', + forced_height = 50, + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.vertical + }, + { + { + text = gen_temperature_str(day.temp.day, '%.0f', false, units), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + { + text = gen_temperature_str(day.temp.night, '%.0f', false, units), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }, + layout = wibox.layout.fixed.vertical + }, + spacing = 8, + layout = wibox.layout.fixed.vertical + } + table.insert(self, day_forecast) + end + end + } + + local hourly_forecast_graph = wibox.widget { + step_width = 12, + color = '#EBCB8B', + background_color = beautiful.bg_normal, + forced_height = 100, + forced_width = 300, + widget = wibox.widget.graph, + set_max_value = function(self, new_max_value) + self.max_value = new_max_value + end, + set_min_value = function(self, new_min_value) + self.min_value = new_min_value + end + } + local hourly_forecast_negative_graph = wibox.widget { + step_width = 12, + color = '#5E81AC', + background_color = beautiful.bg_normal, + forced_height = 100, + forced_width = 300, + widget = wibox.widget.graph, + set_max_value = function(self, new_max_value) + self.max_value = new_max_value + end, + set_min_value = function(self, new_min_value) + self.min_value = new_min_value + end + } + + local hourly_forecast_widget = { + layout = wibox.layout.fixed.vertical, + update = function(self, hourly) + local hours_below = { + id = 'hours', + forced_width = 300, + layout = wibox.layout.flex.horizontal + } + local temp_below = { + id = 'temp', + forced_width = 300, + layout = wibox.layout.flex.horizontal + } + + local max_temp = -1000 + local min_temp = 1000 + local values = {} + for i, hour in ipairs(hourly) do + if i > 25 then break end + values[i] = hour.temp + if max_temp < hour.temp then max_temp = hour.temp end + if min_temp > hour.temp then min_temp = hour.temp end + if (i - 1) % 5 == 0 then + table.insert(hours_below, wibox.widget { + text = os.date(time_format_12h and '%I%p' or '%H:00', tonumber(hour.dt)), + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }) + table.insert(temp_below, wibox.widget { + markup = '' + .. string.format('%.0f', hour.temp) .. '°' .. '', + align = 'center', + font = font_name .. ' 9', + widget = wibox.widget.textbox + }) + end + end + + hourly_forecast_graph:set_max_value(math.max(max_temp, math.abs(min_temp))) + hourly_forecast_graph:set_min_value(min_temp > 0 and min_temp * 0.7 or 0) -- move graph a bit up + + hourly_forecast_negative_graph:set_max_value(math.abs(min_temp)) + hourly_forecast_negative_graph:set_min_value(max_temp < 0 and math.abs(max_temp) * 0.7 or 0) + + for _, value in ipairs(values) do + if value >= 0 then + hourly_forecast_graph:add_value(value) + hourly_forecast_negative_graph:add_value(0) + else + hourly_forecast_graph:add_value(0) + hourly_forecast_negative_graph:add_value(math.abs(value)) + end + end + + local count = #self + for i = 0, count do self[i]=nil end + + -- all temperatures are positive + if min_temp > 0 then + table.insert(self, wibox.widget{ + { + hourly_forecast_graph, + reflection = {horizontal = true}, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'bottom', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + table.insert(self, hours_below) + + -- all temperatures are negative + elseif max_temp < 0 then + table.insert(self, hours_below) + table.insert(self, wibox.widget{ + { + hourly_forecast_negative_graph, + reflection = {horizontal = true, vertical = true}, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'top', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + + -- there are both negative and positive temperatures + else + table.insert(self, wibox.widget{ + { + hourly_forecast_graph, + reflection = {horizontal = true}, + widget = wibox.container.mirror + }, + { + temp_below, + valign = 'bottom', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + table.insert(self, wibox.widget{ + { + hourly_forecast_negative_graph, + reflection = {horizontal = true, vertical = true}, + widget = wibox.container.mirror + }, + { + hours_below, + valign = 'top', + widget = wibox.container.place + }, + id = 'graph', + layout = wibox.layout.stack + }) + end + end + } + + local function update_widget(widget, stdout, stderr) + if stderr ~= '' then + if not warning_shown then + if (stderr ~= 'curl: (52) Empty reply from server' + and stderr ~= 'curl: (28) Failed to connect to api.openweathermap.org port 443: Connection timed out' + and stderr:find('^curl: %(18%) transfer closed with %d+ bytes remaining to read$') ~= nil + ) then + show_warning(stderr) + end + warning_shown = true + widget:is_ok(false) + tooltip:add_to_object(widget) + + widget:connect_signal('mouse::enter', function() tooltip.text = stderr end) + end + return + end + + warning_shown = false + tooltip:remove_from_object(widget) + widget:is_ok(true) + + local result = json.decode(stdout) + + widget:set_image(ICONS_DIR .. icon_map[result.current.weather[1].icon] .. icons_extension) + widget:set_text(gen_temperature_str(result.current.temp, '%.0f', both_units_widget, units)) + + current_weather_widget:update(result.current) + + local final_widget = { + current_weather_widget, + spacing = 16, + layout = wibox.layout.fixed.vertical + } + + if show_hourly_forecast then + hourly_forecast_widget:update(result.hourly) + table.insert(final_widget, hourly_forecast_widget) + end + + if show_daily_forecast then + daily_forecast_widget:update(result.daily, result.timezone_offset) + table.insert(final_widget, daily_forecast_widget) + end + + weather_popup:setup({ + { + final_widget, + margins = 10, + widget = wibox.container.margin + }, + bg = beautiful.bg_normal, + widget = wibox.container.background + }) + end + + weather_widget:buttons(gears.table.join(awful.button({}, 1, function() + if weather_popup.visible then + weather_widget:set_bg('#00000000') + weather_popup.visible = not weather_popup.visible + else + weather_widget:set_bg(beautiful.bg_focus) + weather_popup:move_next_to(mouse.current_widget_geometry) + end + end))) + + watch( + string.format(GET_FORECAST_CMD, owm_one_cal_api), + timeout, -- API limit is 1k req/day; day has 1440 min; every 2 min is good + update_widget, weather_widget + ) + + return weather_widget +end + +return setmetatable(weather_widget, {__call = function(_, ...) return worker(...) end}) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/widgets-icons.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/widgets-icons.png new file mode 100644 index 0000000..ba9c551 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/widgets-icons.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/README.md b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/README.md new file mode 100644 index 0000000..6e9662e --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/README.md @@ -0,0 +1,74 @@ +# word clock widget + +Widget displaying current time using words: + +![screenshot](./screenshots/halfpastthree.png) + +## Customization + +It is possible to customize widget by providing a table with all or some of the following config parameters: + +| Name | Default | Description | +|---|---|---| +| main_color | `beautiful.fg_normal` | Color of the word on odd position | +| accent_color | `beautiful.fg_urgent` | Color of the word on even position | +| font | `beautiful.font` | Font (`Play 20`) | +| is_human_readable | false | _nine fifteen_ or _fifteen past nine_ | +| military_time | false | 12 or 24 time format | +| with_spaces | false | Separate words with spaces | + +## Installation + +Clone repo, include widget and use it in **rc.lua**: + +```lua +local word_clock = require("awesome-wm-widgets.word-clock-widget.word-clock") +... +s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + ... + word_clock(), + ... +``` + +# Screenshots + +```lua + word_clock{ + font = 'Carter One 12', + accent_color = '#ff79c6', + main_color = '#8be9fd', + is_human_readable = true, +} +``` +![](./screenshots/halfpastthree_color.png) + + +```lua +word_clock{ + font = 'Carter One 12', + is_human_readable = true, +} +``` +![](./screenshots/twentythreepastnine.png) + + +```lua +word_clock{ + font = 'Carter One 12', + is_human_readable = true, + military_time = true +} +``` +![](./screenshots/twentythreepasttwentyone.png) + + +```lua +word_clock{ + font = 'Carter One 12', + accent_color = '#f00', + main_color = '#0f0', +} +``` +![](./screenshots/onetwentyseven.png) diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/halfpastthree.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/halfpastthree.png new file mode 100644 index 0000000..af9e0d9 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/halfpastthree.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/halfpastthree_color.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/halfpastthree_color.png new file mode 100644 index 0000000..3c2cdd7 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/halfpastthree_color.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/onetwentyseven.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/onetwentyseven.png new file mode 100644 index 0000000..08e852c Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/onetwentyseven.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/testpasttwentyone.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/testpasttwentyone.png new file mode 100644 index 0000000..41d266f Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/testpasttwentyone.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/twentythreepastnine.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/twentythreepastnine.png new file mode 100644 index 0000000..7d18e22 Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/twentythreepastnine.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/twentythreepasttwentyone.png b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/twentythreepasttwentyone.png new file mode 100644 index 0000000..8a8218f Binary files /dev/null and b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/screenshots/twentythreepasttwentyone.png differ diff --git a/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/word-clock.lua b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/word-clock.lua new file mode 100644 index 0000000..12d5d83 --- /dev/null +++ b/home-modules/explicit-configs/awesome/awesome-wm-widgets/word-clock-widget/word-clock.lua @@ -0,0 +1,140 @@ +------------------------------------------------- +-- Text Clock Widget for Awesome Window Manager +-- Shows current time in words, e.g. 11.54 -> eleven fifty four +-- More details could be found here: +-- https://github.com/streetturtle/awesome-wm-widgets/tree/master/text-clock-widget + +-- @author Pavel Makhov +-- @copyright 2020 Pavel Makhov +------------------------------------------------- + +local wibox = require("wibox") +local beautiful = require("beautiful") +local gears = require("gears") + +local function tablelength(T) + local count = 0 + for _ in pairs(T) do count = count + 1 end + return count +end + +local function split(string_to_split, separator) + if separator == nil then separator = "%s" end + local t = {} + + for str in string.gmatch(string_to_split, "([^".. separator .."]+)") do + table.insert(t, str) + end + + return t +end + +local function convertNumberToName(num) + local lowNames = {"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", + "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", + "eighteen", "nineteen"}; + local tensNames = {"twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"} + local tens, ones, result + + if num < tablelength(lowNames) then + result = lowNames[num + 1]; + else + tens = math.floor(num / 10); + ones = num % 10; + if (tens <= 9) then + result = tensNames[tens - 2 + 1]; + if (ones > 0) then + result = result .. " " .. lowNames[ones + 1]; + end + else + result = "unknown" + end + end + return result; +end + +local text_clock = {} + +local function worker(user_args) + + local args = user_args or {} + + local main_color = args.main_color or beautiful.fg_normal + local accent_color = args.accent_color or beautiful.fg_urgent + local font = args.font or beautiful.font + local is_human_readable = args.is_human_readable + local military_time = args.military_time + local with_spaces = args.with_spaces + + if military_time == nil then military_time = false end + if with_spaces == nil then with_spaces = false end + if is_human_readable == nil then is_human_readable = false end + + text_clock = wibox.widget { + { + id = 'clock', + font = font, + widget = wibox.widget.textbox, + }, + layout = wibox.layout.align.horizontal, + set_text = function(self, time) + local t = split(time) + local res = '' + for i, v in ipairs(t) do + res = res .. '' .. v .. '' + .. (with_spaces and ' ' or '') + end + self:get_children_by_id('clock')[1]:set_markup(res) + end + } + + gears.timer { + timeout = 1, + call_now = true, + autostart = true, + callback = function() + local time = os.date((military_time and '%H' or '%I') .. ':%M') + local h,m = time:match('(%d+):(%d+)') + local min = tonumber(m) + local hour = tonumber(h) + + if is_human_readable then + + if min == 0 then + text_clock:set_text(convertNumberToName(hour) .. " o'clock") + else + local mm + if min == 15 or min == 45 then + mm = 'quater' + elseif min == 30 then + mm = 'half' + else + mm = convertNumberToName((min < 31) and min or 60 - min) + end + + local to_past + + if min < 31 then + to_past = 'past' + else + to_past = 'to' + hour = hour + 1 + end + + text_clock:set_text(mm .. ' ' .. to_past .. ' ' .. convertNumberToName(hour)) + end + else + text_clock:set_text(convertNumberToName(hour) .. ' ' .. convertNumberToName(min)) + end + end + } + + return text_clock + +end + +return setmetatable(text_clock, { __call = function(_, ...) + return worker(...) +end }) \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/deficient/README.md b/home-modules/explicit-configs/awesome/deficient/README.md new file mode 100644 index 0000000..50a7f4c --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/README.md @@ -0,0 +1,54 @@ +A collection of lightweight widgets for [awesome wm](https://awesomewm.org/), +compatible with awesome 4.x that were previously hosted as separate repositories. + + +# Installation + +Clone this repository into your awesome config folder: + +```bash +cd ~/.config/awesome +git clone https://github.com/deficient/deficient +``` + + +# Usage + +The widgets can usually be instanciated similar to this: + +```lua +local deficient = require("deficient") + +local calendar = deficient.calendar {} +local quicklaunch = deficient.quicklaunch {} +-- etc +``` + +For more detailed usage instructions, refer to the individual widgets: + +- [battery-widget](./battery-widget) +- [brightness](./brightness) +- [calendar](./calendar) +- [cpuinfo](./cpuinfo) +- [keyboard-layout-indicator](./keyboard-layout-indicator) +- [leds-widget](./leds-widget) +- [quicklaunch](./quicklaunch) +- [screensaver](./screensaver) +- [volume-control](./volume-control) + + +# Dependencies + +Some of the widgets also have individual dependencies if you want to use them. + +- *battery indicator*: + * `acpid` (recommended) +- *brightness control*: + * [acpilight](https://archlinux.org/packages/extra/any/acpilight/) or + [xorg-xbacklight](https://archlinux.org/packages/extra/x86_64/xorg-xbacklight/) for `xbacklight` + * [brightnessctl](https://archlinux.org/packages/extra/x86_64/brightnessctl/) for `brightnessctl` +- *screensaver*: + * `xorg-xset` +- *volume control*: + * `pavucontrol` (recommended) + * `acpid` (recommended) diff --git a/home-modules/explicit-configs/awesome/deficient/UNLICENSE b/home-modules/explicit-configs/awesome/deficient/UNLICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to diff --git a/home-modules/explicit-configs/awesome/deficient/battery-widget/README.md b/home-modules/explicit-configs/awesome/deficient/battery-widget/README.md new file mode 100644 index 0000000..26c114e --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/battery-widget/README.md @@ -0,0 +1,162 @@ +## awesome.battery-widget + +Battery indicator widget for awesome window manager. + +![Screenshot](/battery-widget/screenshot.png?raw=true "Screenshot") + +Displays status information from `/sys/class/power_supply`. + + +### Dependencies + +Optionally, in order to receive status updates, you will also need `acpid`: + +```bash +pacman -S acpid +systemctl enable --now acpid +``` + + +### Usage + +In order to add a battery widget to your wibox, you have to import the module +and then instanciate a widget with the desired options like this: + +```lua +local deficient = require("deficient") + + +-- instanciate widget +local battery_widget = deficient.battery_widget { + -- pass options here +} + + +-- add to wibox +s.mywibox:setup { + ..., + { -- Right widgets + ..., + battery_widget, + }, +} +``` + +If you pass an adapter name using the `adapter = "..."` option, a widget for +that specific battery adapter will be instanciated. If the `adapter` option is +not specified, the call will return a table containing widgets for each of the +battery adapters in `/sys/class/power_supply`. In that case if there are no +batteries an empty table will be returned and no error will occur on machines +without batteries. + + +### Options + +The behaviour and appearance of the widget can be tweaked using a few options. +This is an example using all available options: + +```lua +battery_widget { + ac = "AC", + adapter = "BAT0", + ac_prefix = "AC: ", + battery_prefix = "Bat: ", + percent_colors = { + { 25, "red" }, + { 50, "orange"}, + {999, "green" }, + }, + listen = true, + timeout = 10, + widget_text = "${AC_BAT}${color_on}${percent}%${color_off}", + widget_font = "Deja Vu Sans Mono 16", + tooltip_text = "Battery ${state}${time_est}\nCapacity: ${capacity_percent}%", + alert_threshold = 5, + alert_timeout = 0, + alert_title = "Low battery !", + alert_text = "${AC_BAT}${time_est}", + alert_icon = "~/Downloads/low_battery_icon.png", + warn_full_battery = true, + full_battery_icon = "~/Downloads/full_battery_icon.png", +} +``` + +`adapter` +The name of the directory entry in `/sys/class/power_supply` corresponding to the requested battery adapter. + +`ac` +The name of the directory entry in `/sys/class/power_supply` corresponding to your AC status. + +`ac_prefix` +The prefix to populate `${AC_BAT}` when your computer is using ac power. If your font supports unicode characters, you could use "🔌". + +`battery_prefix` +The prefix to populate `${AC_BAT}` when your computer is using battery power. If your font supports unicode characters, you could use "🔋". Can also be configured as a table like `percent_colors` to show different prefixes at different battery percentages. + +`percent_colors` (`limits` for backwards compatibility) +The colors that the percentage changes to, as well as the upper-bound limit of when it will change. Ex. `{100, "green"}` means any percentage lower than 100 is colored green. + +`listen` +Tells the widget to listen to updates via `acpi_listen`. When an event is fired, the widget updates. + +`timeout` +The time interval that the widget waits before it updates itself, in seconds. + +`widget_text`, `tooltip_text` +The text which shows up on the toolbar and when you highlight the widget, respectively. Please refer to function `battery_widget:update()` for other interpolatable variables. + +`widget_font` +The font description used for the widget text, for instance "Deja Vu Sans Mono 16". If this is empty or unspecified, the default font will be used. + +`alert_threshold` +The percentage used as the maximum value at which an alert will be generated, `-1` to disable alerts. Once the alert is dismissed (or expired) it will not show up again until the battery has been charging. + +`alert_timeout` +The time after which the alert expire, `0` for no timeout. + +`alert_title`, `alert_text`, `alert_icon` +The text which shows up on the alert notification, respectively the title, body text and image path. + +`warn_full_battery`, boolean +Whether a notification should be displayed when the battery gets fully charged. + +`full_battery_icon` +Path to the image, which should be shown as part of the notification when battery gets fully charged (depends on `warn_full_battery`). + +### Usage Examples + +Percentage tables can be used for `ac_prefix`, `battery_prefix`, and `percent_colors` to show different things depending on the battery charge level, e.g.: + +```lua +battery_widget { + -- Show different prefixes when charging on AC + ac_prefix = { + { 25, "not charged" }, + { 50, "1/4 charged" }, + { 75, "2/4 charged" }, + { 95, "3/4 charged" }, + {100, "fully charged" }, + }, + + -- Show a visual indicator of charge level when on battery power + battery_prefix = { + { 25, "#--- "}, + { 50, "##-- "}, + { 75, "###- "}, + {100, "#### "}, + } +} +``` + +`ac_prefix`, `battery_prefix`, and `widget_text` can be further customized with spans to specify colors or fonts, e.g.: + +```lua +battery_widget { + -- Use different colors for ac_prefix and battery_prefix + ac_prefix = 'AC: ', + battery_prefix = 'Bat: ', + + -- Use a bold font for both prefixes (overrides widget_font) + widget_text = '${AC_BAT}${color_on}${percent}%${color_off}' +} +``` diff --git a/home-modules/explicit-configs/awesome/deficient/battery-widget/battery-widget.lua b/home-modules/explicit-configs/awesome/deficient/battery-widget/battery-widget.lua new file mode 100644 index 0000000..b07a1f7 --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/battery-widget/battery-widget.lua @@ -0,0 +1,306 @@ +-- Battery widget + +local awful = require("awful") +local gears = require("gears") +local wibox = require("wibox") +local naughty = require("naughty") + +local timer = gears.timer or timer +local watch = awful.spawn and awful.spawn.with_line_callback + +------------------------------------------ +-- Private utility functions +------------------------------------------ + +local tolower = string.lower + +local function file_exists(command) + local f = io.open(command) + if f then f:close() end + return f and true or false +end + +local function readfile(command) + local file = io.open(command) + if not file then return nil end + local text = file:read('*all') + file:close() + return text +end + +local function color_tags(color) + if color + then return '', '' + else return '', '' + end +end + +local function round(value) + return math.floor(value + 0.5) +end + +local function trim(s) + if not s then return nil end + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + +local function read_trim(filename) + return trim(readfile(filename)) or "" +end + +local function substitute(template, context) + if type(template) == "string" then + return (template:gsub("%${([%w_]+)}", function(key) + return tostring(context[key] or "Err!") + end)) + else + -- function / functor: + return template(context) + end +end + +local function lookup_by_limits(limits, value) + if type(limits) == "table" then + local last = nil + if value then + for k, v in ipairs(limits) do + if (value <= v[1]) then + return v[2] + end + last = v[2] + end + end + return last + else + return limits + end +end + +------------------------------------------ +-- Battery widget interface +------------------------------------------ + +local battery_widget = {} +local sysfs_names = { + charging = { + present = "present", + state = "status", + rate = "current_now", + charge = "charge_now", + capacity = "charge_full", + design = "charge_full_design", + percent = "capacity", + }, + discharging = { + present = "present", + state = "status", + rate = "power_now", + charge = "energy_now", + capacity = "energy_full", + design = "energy_full_design", + percent = "capacity" + }, +} + +function battery_widget:new(args) + if args.adapter then + return setmetatable({}, {__index = self}):init(args) + end + -- creates an empty container wibox, which can be added to your panel even if its empty + local widgets = { layout = wibox.layout.fixed.horizontal } + local batteries, mains, usb, ups = self:discover() + local ac = mains[1] or usb[1] or ups[1] + for i, adapter in ipairs(batteries) do + local _args = setmetatable({adapter = adapter, ac = ac}, {__index = args}) + table.insert(widgets, self(_args).widget) + end + return widgets +end + +function battery_widget:discover() + local pow = "/sys/class/power_supply/" + local adapters = { Battery = {}, UPS = {}, Mains = {}, USB = {} } + for adapter in io.popen("ls -1 " .. pow):lines() do + local type = read_trim(pow .. adapter .. "/type") + table.insert(adapters[type], adapter) + end + return adapters.Battery, adapters.Mains, adapters.USB, adapters.UPS +end + +function battery_widget:init(args) + self.ac = args.ac or "AC" + self.adapter = args.adapter or "BAT0" + self.ac_prefix = args.ac_prefix or "AC: " + self.battery_prefix = args.battery_prefix or "Bat: " + self.percent_colors = args.percent_colors or args.limits or { + { 25, "red" }, + { 50, "orange"}, + {999, "green" }, + } + + self.widget_text = args.widget_text or ( + "${AC_BAT}${color_on}${percent}%${color_off}") + self.tooltip_text = args.tooltip_text or ( + "Battery ${state}${time_est}\nCapacity: ${capacity_percent}%") + + self.alert_threshold = args.alert_threshold or 5 + self.alert_timeout = args.alert_timeout or 0 + self.alert_title = args.alert_title or "Low battery !" + self.alert_text = args.alert_text or "${AC_BAT}${time_est}" + self.alert_icon = args.alert_icon or nil + + self.widget = wibox.widget.textbox() + self.widget.font = args.widget_font + self.tooltip = awful.tooltip({objects={self.widget}}) + + self.warn_full_battery = args.warn_full_battery + self.full_battery_icon = args.full_battery_icon or nil + + self.widget:buttons(awful.util.table.join( + awful.button({ }, 1, function() self:update() end), + awful.button({ }, 3, function() self:update() end) + )) + + self.timer = timer({ timeout = args.timeout or 10 }) + self.timer:connect_signal("timeout", function() self:update() end) + self.timer:start() + self:update() + + if (args.listen or args.listen == nil) and watch then + self.listener = watch("acpi_listen", { + stdout = function(line) self:update() end, + }) + awesome.connect_signal("exit", function() + awesome.kill(self.listener, awesome.unix_signal.SIGTERM) + end) + end + + return self +end + +function battery_widget:get_state() + local pow = "/sys/class/power_supply/" + local ac = pow .. self.ac + local bat = pow .. self.adapter + local sysfs = (file_exists(bat.."/"..sysfs_names.charging.rate) + and sysfs_names.charging + or sysfs_names.discharging) + + -- If there is no battery on this machine. + if not sysfs.state then return nil end + + -- return value + local r = { + state = tolower (read_trim(bat.."/"..sysfs.state)), + present = tonumber(read_trim(bat.."/"..sysfs.present)), + rate = tonumber(read_trim(bat.."/"..sysfs.rate)), + charge = tonumber(read_trim(bat.."/"..sysfs.charge)), + capacity = tonumber(read_trim(bat.."/"..sysfs.capacity)), + design = tonumber(read_trim(bat.."/"..sysfs.design)), + percent = tonumber(read_trim(bat.."/"..sysfs.percent)), + } + + r.ac_state = tonumber(read_trim(ac.."/online")) + + if r.state == "unknown" then + r.state = "charged" + end + + if r.percent == nil and r.charge and r.capacity then + r.percent = round(r.charge * 100 / r.capacity) + end + + return r +end + +function battery_widget:update() + local ctx = self:get_state() + + -- If there is no battery on this machine. + if not ctx then return nil end + + -- AC/battery prefix + ctx.AC_BAT = (ctx.ac_state == 1 + and lookup_by_limits(self.ac_prefix, ctx.percent) + or lookup_by_limits(self.battery_prefix, ctx.percent) + or "Err!") + + -- Colors + ctx.color_on, ctx.color_off = color_tags( + lookup_by_limits(self.percent_colors, ctx.percent)) + + -- estimate time + ctx.charge_dir = 0 -- +1|0|-1 -> charging|static|discharging + ctx.time_left = nil -- time until charging/discharging complete + ctx.time_text = "" + ctx.time_est = "" + + if ctx.rate and ctx.rate ~= 0 then + if not ctx.state or ctx.state == "discharging" then + ctx.charge_dir = -1 + ctx.time_left = ctx.charge / ctx.rate + elseif ctx.state == "charging" then + ctx.charge_dir = 1 + ctx.time_left = (ctx.capacity - ctx.charge) / ctx.rate + end + end + + if ctx.time_left then + ctx.hours = math.floor((ctx.time_left)) + ctx.minutes = math.floor((ctx.time_left - ctx.hours) * 60) + if ctx.hours > 0 + then ctx.time_text = ctx.hours .. "h " .. ctx.minutes .. "m" + else ctx.time_text = ctx.minutes .. "m" + end + ctx.time_est = ": " .. ctx.time_text .. " remaining" + end + + -- capacity text + if ctx.capacity and ctx.design then + ctx.capacity_percent = round(ctx.capacity/ctx.design*100) + end + + -- for use in functions + ctx.obj = self + + -- update text + self.widget:set_markup(substitute(self.widget_text, ctx)) + self.tooltip:set_text(substitute(self.tooltip_text, ctx)) + + -- low battery notification + if naughty then + if (ctx.state == "discharging" and + ctx.percent and ctx.percent <= self.alert_threshold) then + self:notify(substitute(self.alert_title, ctx), + substitute(self.alert_text, ctx), + self.alert_icon) + elseif ctx.state == "full" and self.warn_full_battery then + self:notify('Battery Full!', 'Remove power cord', self.full_battery_icon) + else + if self.alert then + naughty.destroy( + self.alert, + naughty.notificationClosedReason.dismissedByCommand) + self.alert = nil + end + end + end +end + +function battery_widget:notify(title, text, icon) + if self.alert then + naughty.replace_text(self.alert, title, text) + else + self.alert = naughty.notify({ + title = title, + text = text, + icon = icon, + preset = naughty.config.presets.critical, + timeout = self.alert_timeout + }) + end +end + +return setmetatable(battery_widget, { + __call = battery_widget.new, +}) diff --git a/home-modules/explicit-configs/awesome/deficient/battery-widget/init.lua b/home-modules/explicit-configs/awesome/deficient/battery-widget/init.lua new file mode 120000 index 0000000..686d9d4 --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/battery-widget/init.lua @@ -0,0 +1 @@ +battery-widget.lua \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/deficient/battery-widget/screenshot.png b/home-modules/explicit-configs/awesome/deficient/battery-widget/screenshot.png new file mode 100644 index 0000000..dc4e190 Binary files /dev/null and b/home-modules/explicit-configs/awesome/deficient/battery-widget/screenshot.png differ diff --git a/home-modules/explicit-configs/awesome/deficient/brightness/README.md b/home-modules/explicit-configs/awesome/deficient/brightness/README.md new file mode 100644 index 0000000..6c25ef4 --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/brightness/README.md @@ -0,0 +1,93 @@ +## awesome-brightness + +Brightness indicator/control widget for [awesome wm](https://awesomewm.org/) +based on ``xbacklight`` or ``brightnessctl``. + + +### Dependencies + +The module requires either `xbacklight` or `brightnessctl` to work. +Thus, on archlinux, you'll need to install at least one of the following +system packages: + +- [acpilight](https://archlinux.org/packages/extra/any/acpilight/) or + [xorg-xbacklight](https://archlinux.org/packages/extra/x86_64/xorg-xbacklight/) for `xbacklight` +- [brightnessctl](https://archlinux.org/packages/extra/x86_64/brightnessctl/) for `brightnessctl` + +I've experienced `xorg-xbacklight` not work on certain laptops. So, if you +find that the widget is not working, try a different backend. + + +### Usage + +In your `~/.config/awesome/rc.lua`: + +```lua +local deficient = require("deficient") + + +-- instanciate widget +local brightness_ctrl = deficient.brightness { + -- pass options here +} + + +-- add widget to the wibox: +s.mywibox:setup { + ..., + { -- Right widgets + ..., + brightness_ctrl.widget, + }, +} +``` + +Note that you need to pass `.widget` to the wibox, not the instance itself! + +The flag `brightness_ctrl.is_valid` indicates successful initialization. + + +### Usage options + +Full example: + +```lua +local brightness_ctrl = require("deficient.brightness") { + backend = nil, + step = 5, + timeout = 3, + levels = {1, 25, 50, 75, 100}, +} +``` + +`backend` +Picks command with which to perform brightness queries and updates. +Allowed values are `nil` (meaning *autodetect*), `"xbacklight"` or +`"brightnessctl"`. Default: `nil`. + +`step` +How many percentage points to increase or decrease the brightness level when +clicking the widget. Default: 3. + +`timeout` +Interval in seconds at which to check the current brightness level and update +the widget text. Default: 5. + +`levels` +Cycle through these brightness percentages on middle-click. +Default: ``{1, 25, 50, 75, 100}`. + + +### Troubleshooting + +If you get errors on startup, try executing `xbacklight -get` or +`brightnessctl -c backlight get` in a terminal. + +If you get the error "No outputs have backlight property", make sure you have +installed an appropriate display driver, e.g. for intel cards: + +```bash +sudo pacman -S xf86-video-intel +``` + +You may need to restart afterwards. diff --git a/home-modules/explicit-configs/awesome/deficient/brightness/brightness.lua b/home-modules/explicit-configs/awesome/deficient/brightness/brightness.lua new file mode 100644 index 0000000..f331211 --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/brightness/brightness.lua @@ -0,0 +1,230 @@ +--[[ + +Brightness control +================== + +based on `xbacklight`! + +alternative ways to control brightness: + sudo setpci -s 00:02.0 F4.B=80 + xgamma -gamma .75 + xrandr --output LVDS1 --brightness 0.9 + echo X > /sys/class/backlight/intel_backlight/brightness + xbacklight + +--]] + +local awful = require("awful") +local wibox = require("wibox") +local gears = require("gears") +local gtable = require("gears.table") +local naughty = require("naughty") + +local timer = gears.timer or timer +local exec = awful.spawn.easy_async + + +------------------------------------------ +-- Private utility functions +------------------------------------------ + +local function warning(text) + if not naughty then return end + local args = { + title = "Brightness Control", + preset = naughty.config.presets.normal, + } + if naughty.notification then + args.message = text + naughty.notification(args) + else + args.text = text + naughty.notify(args) + end +end + +local function readcommand(command) + -- I know, you should *never* use `io.popen`, but it's called at most once + -- per backend through the whole awesome session… I promise! + local file = io.popen(command) + local text = file:read('*all') + file:close() + return text +end + + +------------------------------------------ +-- Backend: brightnessctl +------------------------------------------ + +local backends = {} + +backends.brightnessctl = { + cmd = "brightnessctl", + _max = nil, + + supported = function(self) + return tonumber(readcommand("brightnessctl --class=backlight max")) ~= nil + end, + + parse_output = function(self, output) + -- dev,class,curr,percent,max + local _, _, _, percent, _ = output:match("(.*),(.*),(%d*),(%d*)%%,(%d*)") + return tonumber(percent) + end, + + get = function(self, callback) + exec({ self.cmd, "--class=backlight", "-m", "info" }, function(output) + callback(self:parse_output(output)) + end) + end, + + set = function(self, percent, callback) + exec({ self.cmd, "--class=backlight", "-m", "set", percent .. "%" }, function(output) + callback(self:parse_output(output)) + end) + end, + + up = function(self, step, callback) + exec({ self.cmd, "--class=backlight", "-m", "set", step .. "%+" }, function(output) + callback(self:parse_output(output)) + end) + end, + + down = function(self, step, callback) + exec({ self.cmd, "--class=backlight", "-m", "set", step .. "%-" }, function(output) + callback(self:parse_output(output)) + end) + end, +} + +------------------------------------------ +-- Backend: xbacklight +------------------------------------------ + +backends.xbacklight = { + cmd = "xbacklight", + + supported = function(self) + return tonumber(readcommand("xbacklight -get")) ~= nil + end, + + get = function(self, callback) + exec({self.cmd, "-get"}, function(output) + callback(tonumber(output)) + end) + end, + + set = function(self, value, callback) + exec({self.cmd, "-set", tostring(value)}, callback) + end, + + up = function(self, step, callback) + exec({self.cmd, "-inc", tostring(step)}, callback) + end, + + down = function(self, step, callback) + exec({self.cmd, "-dec", tostring(step)}, callback) + end, +} + + +------------------------------------------ +-- Brightness control interface +------------------------------------------ +local bcontrol = { backends = backends } + +function bcontrol:new(args) + return setmetatable({}, {__index = self}):init(args) +end + +function bcontrol:init(args) + -- determine backend + local backend = args.backend + + if type(backend) == "string" then + backend = backends[backend] + if backend == nil then + warning("Unknown backend: " .. args.backend) + end + end + + if backend == nil then + if backends.brightnessctl:supported() then + backend = backends.brightnessctl + elseif backends.xbacklight:supported() then + backend = backends.xbacklight + else + backend = nil + warning("Neither brightnessctl nor xbacklight seems to work") + end + end + + self.is_valid = backend ~= nil + self.backend = backend + self.step = tonumber(args.step or '5') + self.levels = args.levels or {1, 25, 50, 75, 100} + + self.widget = wibox.widget.textbox() + + if self.is_valid then + self.widget:buttons(gtable.join( + awful.button({ }, 1, function() self:up() end), + awful.button({ }, 3, function() self:down() end), + awful.button({ }, 2, function() self:toggle() end), + awful.button({ }, 4, function() self:up(1) end), + awful.button({ }, 5, function() self:down(1) end) + )) + + self.timer = timer({ + timeout = args.timeout or 3, + callback = function() self:update() end, + autostart = true, + call_now = true + }) + end + + return self +end + +function bcontrol:set_text(value) + local brightness = math.floor(0.5 + value) + self.widget:set_text(string.format(" [%3d] ", brightness)) +end + +function bcontrol:update(opt_value) + if opt_value and string.match(opt_value, "%S+") then + self:set_text(opt_value) + else + self.backend:get(function(...) self:set_text(...) end) + end +end + +function bcontrol:set(brightness, callback) + self.backend:set(brightness, callback or function(...) self:update(...) end) +end + +function bcontrol:up(step, callback) + self.backend:up(step or self.step, callback or function(...) self:update(...) end) +end + +function bcontrol:down(step, callback) + self.backend:down(step or self.step, callback or function(...) self:update(...) end) +end + +function bcontrol:toggle() + self.backend:get(function(value) + local ilevel = 1 + for i, lv in ipairs(self.levels) do + if math.abs(lv - value) < math.abs(self.levels[ilevel] - value) then + ilevel = i + end + end + self:set(self.levels[ilevel % #(self.levels) + 1]) + end) +end + +return setmetatable(bcontrol, { + __call = bcontrol.new, +}) +-- vim: set ts=4 sw=4 et: diff --git a/home-modules/explicit-configs/awesome/deficient/brightness/init.lua b/home-modules/explicit-configs/awesome/deficient/brightness/init.lua new file mode 120000 index 0000000..3456761 --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/brightness/init.lua @@ -0,0 +1 @@ +brightness.lua \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/deficient/calendar/README.md b/home-modules/explicit-configs/awesome/deficient/calendar/README.md new file mode 100644 index 0000000..cb01da4 --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/calendar/README.md @@ -0,0 +1,36 @@ +## awesome-calendar + +Small calendar popup for awesome window manager. + +![Screenshot](/calendar/screenshot.png?raw=true "Screenshot") + +This is a polished up and improved module based on the `calendar2.lua` module +by Bernd Zeimetz and Marc Dequènes. + + +### Usage + +In your `rc.lua`: + +```lua +local deficient = require("deficient") + + +-- instanciate widget +local calendar_widget = deficient.calendar({}) + +-- attach it as popup to your text clock widget +calendar_widget:attach(mytextclock) +``` + +You can also add some options to customize the widget's display. For instance: + +``` +calendar_widget = deficient.calendar({ + fdow = 7, -- Set Sunday as first day of the week (default is + -- 1 = Monday) + position = "bottom_right", -- Useful if you prefer your wibox at the bottomn + -- of the screen +}) +calendar_widget:attach(mytextclock) +``` diff --git a/home-modules/explicit-configs/awesome/deficient/calendar/calendar.lua b/home-modules/explicit-configs/awesome/deficient/calendar/calendar.lua new file mode 100644 index 0000000..9910948 --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/calendar/calendar.lua @@ -0,0 +1,174 @@ +-- original code made by Bzed and published on http://awesome.naquadah.org/wiki/Calendar_widget +-- modified by Marc Dequènes (Duck) (2009-12-29), under the same licence, +-- and with the following changes: +-- + transformed to module +-- + the current day formating is customizable + +-- captures +local os = os +local capi = { + mouse = mouse, + screen = screen, +} +local awful = require("awful") +local naughty = require("naughty") + +local version_major, version_minor = awesome.version:match("(%d+)%.(%d+)") +version_major = tonumber(version_major) +version_minor = tonumber(version_minor) +local can_update_size = version_major and version_minor and version_major >= 4 and version_minor >= 2 + +------------------------------------------ +-- utility functions +------------------------------------------ + +local function format_date(format, date) + return os.date(format, os.time(date)) +end + + +------------------------------------------ +-- calendar popup widget +------------------------------------------ + +local calendar = {} + +function calendar:new(args) + return setmetatable({}, {__index = self}):init(args) +end + +function calendar:init(args) + self.num_lines = 0 + self.today_color = args.today_color or "#00ff00" + -- first day of week: monday=1, …, sunday=7 + self.fdow = args.fdow or 1 + -- notification area: + self.html = args.html or '\n%s' + -- highlight current date: + self.today = args.today or '%2i' + self.anyday = args.anyday or '%2i' + self.page_title = args.page_title or '%B %Y' -- month year + self.col_title = args.col_title or '%a ' -- weekday + -- Date equality check is based on day_id. We deliberately ignore the year + -- to highlight the same day in different years: + self.day_id = args.day_id or '%m-%d' + self.empty_sep = args.empty_sep or " -" + self.week_col = args.week_col or " %V" + self.days_style = args.days_style or {} + self.position = args.position or naughty.config.defaults.position + return self +end + +function calendar:day_style(day_of_week) + return self.days_style[day_of_week] or '%s' +end + +function calendar:page(month, year) + + local today = format_date(self.day_id) + + -- 2001 started with a monday: + local d0 = format_date("*t", {year=2001, month=1, day=self.fdow }) + local dA = format_date("*t", {year=year, month=month, day=1 }) + local dB = format_date("*t", {year=year, month=month+1, day=0 }) + local tA = {year=year, month=month, day=1 } + local colA = (dA.wday - d0.wday) % 7 + + local page_title = format_date(self.page_title, tA) + + -- print column titles (weekday) + local page = " " + for d = 0, 6 do + page = page .. self:day_style(d+1):format(format_date(self.col_title, { + year = d0.year, + month = d0.month, + day = d0.day + d, + })) + end + + -- print empty space before first day + page = page .. "\n" .. format_date(self.week_col, tA) + for column = 1, colA do + page = page .. self.empty_sep + end + + -- iterate all days of the month + local nLines = 1 + local column = colA + for day = 1, dB.day do + if column == 7 then + column = 0 + nLines = nLines + 1 + page = page .. "\n" .. format_date(self.week_col, {year=year, month=month, day=day}) + end + if today == format_date(self.day_id, {day=day, month=month, year=year}) then + page = page .. " " .. self.today:format(day) + else + page = page .. " " .. self:day_style(column+1):format(self.anyday:format(day)) + end + column = column + 1 + end + + for column = column, 6 do + page = page .. self.empty_sep + end + + return page_title, self.html:format(page) +end + +function calendar:switch(months) + self:show(self.year, self.month+months) +end + +function calendar:show(year, month) + local today = os.time() + self.month = month or os.date('%m', today) + self.year = year or os.date('%Y', today) + local title, text = self:page(self.month, self.year) + + -- NOTE: `naughty.replace_text` does not update bounds and can therefore + -- not be used when the size increases (before #1756 was merged): + local num_lines = select(2, text:gsub('\n', '')) + local will_fit = can_update_size or num_lines <= self.num_lines + if naughty.replace_text and self.notification and will_fit then + naughty.replace_text(self.notification, title, text) + else + self:hide() + self.notification = naughty.notify({ + title = title, + text = text, + timeout = 0, + hover_timeout = 0.5, + screen = capi.mouse.screen, + position = self.position, + }) + self.num_lines = num_lines + end +end + +function calendar:hide() + if self.notification then + naughty.destroy(self.notification) + self.notification = nil + self.num_lines = 0 + end +end + +function calendar:attach(widget) + widget:connect_signal('mouse::enter', function() self:show() end) + widget:connect_signal('mouse::leave', function() self:hide() end) + widget:buttons(awful.util.table.join( + awful.button({ }, 1, function() self:switch( -1) end), + awful.button({ }, 3, function() self:switch( 1) end), + awful.button({ }, 4, function() self:switch( -1) end), + awful.button({ }, 5, function() self:switch( 1) end), + awful.button({ 'Shift' }, 1, function() self:switch(-12) end), + awful.button({ 'Shift' }, 3, function() self:switch( 12) end), + awful.button({ 'Shift' }, 4, function() self:switch(-12) end), + awful.button({ 'Shift' }, 5, function() self:switch( 12) end) + )) +end + +return setmetatable(calendar, { + __call = calendar.new, +}) diff --git a/home-modules/explicit-configs/awesome/deficient/calendar/init.lua b/home-modules/explicit-configs/awesome/deficient/calendar/init.lua new file mode 120000 index 0000000..27082e6 --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/calendar/init.lua @@ -0,0 +1 @@ +calendar.lua \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/deficient/calendar/screenshot.png b/home-modules/explicit-configs/awesome/deficient/calendar/screenshot.png new file mode 100644 index 0000000..bac0c9d Binary files /dev/null and b/home-modules/explicit-configs/awesome/deficient/calendar/screenshot.png differ diff --git a/home-modules/explicit-configs/awesome/deficient/init.lua b/home-modules/explicit-configs/awesome/deficient/init.lua new file mode 100644 index 0000000..e4311fe --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/init.lua @@ -0,0 +1,8 @@ +local modname = ... +local function load_submodule(table, key) + local submodule = require(modname .. "." .. key:gsub("_", "-")) + table[key] = submodule + return submodule +end + +return setmetatable({}, {__index = load_submodule}); diff --git a/home-modules/explicit-configs/awesome/deficient/volume-control/README.md b/home-modules/explicit-configs/awesome/deficient/volume-control/README.md new file mode 100644 index 0000000..249b6f5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/volume-control/README.md @@ -0,0 +1,168 @@ +## awesome.volume-control + +Volume indicator+control widget for awesome window manager. + +![Screenshot](/volume-control/screenshot.png?raw=true "Screenshot") + + +### Dependencies + +Optional but recommended dependencies: + +* pavucontrol (optional) +* acpid (optional) + +```bash +pacman -S pavucontrol # open volume manager with middle/right click +pacman -S acpid # instant status updates (acpi_listen) +systemctl enable acpid +``` + +You will also need `amixer` and `alsactl`, most likely your distro has a +package called `alsa-utils` that contains them. + +If you are using `pipewire`, you have to configure it to manage clients +using the userspace component of ALSA. For example on Arch Linux, this can +be done by installing the package `pipewire-alsa`. For Debian, you can +follow the instructions provided in the +[Debian Wiki](https://wiki.debian.org/PipeWire#For_ALSA). + +Similarly, if you are using `pulseaudio`, you need to configure it to manage +clients using the userspace component of ALSA. For Arch Linux, that means +installing the package `pulseaudio-alsa`. + + +### Usage + +In your `~/.config/awesome/rc.lua`: + +```lua +local deficient = require("deficient") + + +-- instanciate volume control, using default settings: +volumecfg = deficient.volume_control({}) + + +-- add the widget to your wibox +... +right_layout:add(volumecfg.widget) +... + + +-- add key bindings +local globalkeys = awful.util.table.join( + ... + awful.key({}, "XF86AudioRaiseVolume", function() volumecfg:up() end), + awful.key({}, "XF86AudioLowerVolume", function() volumecfg:down() end), + awful.key({}, "XF86AudioMute", function() volumecfg:toggle() end), + ... +) +``` + +### Known issues + +One common pitfall is using the wrong sound device. On systems with pulseaudio, +it's usually best to create the control with: + +```lua +volumecfg = deficient.volume_control {device="pulse"} +``` + +On some systems, clicking the widget will mute audio, however clicking it again +will only unmute *Master* while leaving other subsystems (Speaker, …) muted, +see e.g. [#10](https://github.com/deficient/volume-control/pull/10). This may +be fixed by setting the device to *pulse*, as described above. + +For pre-2019 `alsa-utils`, if you have the `listen` enabled, unplugging USB +headphones sometimes causes the process that monitors for audio status changes +(`alsactl monitor`) to spin at 100% CPU, see +[#11](https://github.com/deficient/volume-control/issues/11). When this +happens, you can safely kill the process or restart awesome (`Mod4 + Control + +R`). This bug was fixed in `alsa-utils 1.1.7`. + +### Constructor + +You can specify any subset of the following arguments to the constructor. +The default values are as follows: + +```lua +volumecfg = deficient.volume_control({ + device = nil, -- e.g.: "default", "pulse" + cardid = nil, -- e.g.: 0, 1, ... + channel = "Master", + step = '5%', -- step size for up/down + lclick = "toggle", -- mouse actions described below + mclick = "pavucontrol", + rclick = "pavucontrol", + listen = false, -- enable/disable listening for audio status changes + widget = nil, -- use this instead of creating a awful.widget.textbox + font = nil, -- font used for the widget's text + callback = nil, -- called to update the widget: `callback(self, state)` + widget_text = { + on = '% 3d%% ', -- three digits, fill with leading spaces + off = '% 3dM ', + }, + tooltip_text = [[ +Volume: ${volume}% ${state} +Channel: ${channel} +Device: ${device} +Card: ${card}]], +}) +``` + +### Mouse actions + +The easiest way to customize what happens on left/right/middle click is to +specify additional arguments to the constructor. These can be of any of the +following kinds: + +- name of a member function: `"up"`, `"down"`, `"toggle"`, `"mute"`, `"get"` +- command string to execute +- a callable that will be called with the volume control as first parameter + +E.g.: + +```lua +volumecfg = deficient.volume_control({ + lclick="toggle", -- name of member function + mclick=TERMINAL .. " -x alsamixer", -- command to execute + rclick=function(self) self:mute() end, -- callable, equivalent to "mute" +}) +``` + +### Icon widget + +You can use the module as a basis to implement your own volume widget. For +example, an icon widget can be created as follows: + +```lua +local function get_image(volume, state) + local icondir = os.getenv("HOME") .. "/.local/share/icons/" + if volume == 0 or state == "off" then return icondir .. "audio_mute.png" + elseif volume <= 33 then return icondir .. "audio_low.png" + elseif volume <= 66 then return icondir .. "audio_med.png" + else return icondir .. "audio_high.png" + end +end + +local volume_widget = deficient.volume_control { + tooltip = true, + widget = wibox.widget.imagebox(), + callback = function(self, setting) + self.widget:set_image( + get_image(setting.volume, setting.state)) + end, +} +``` + +However, in this case, I recommend to use +[pasystray](https://github.com/christophgysin/pasystray) instead. + + +### Alternatives + +If you like a volume control with an icon instead of text, I suggest to use +[pasystray](https://github.com/christophgysin/pasystray), which is a more +comprehensive solution and built for the systray (not awesome widget) with a +much nicer menu. diff --git a/home-modules/explicit-configs/awesome/deficient/volume-control/init.lua b/home-modules/explicit-configs/awesome/deficient/volume-control/init.lua new file mode 120000 index 0000000..1029aea --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/volume-control/init.lua @@ -0,0 +1 @@ +volume-control.lua \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/deficient/volume-control/screenshot.png b/home-modules/explicit-configs/awesome/deficient/volume-control/screenshot.png new file mode 100644 index 0000000..2acd89a Binary files /dev/null and b/home-modules/explicit-configs/awesome/deficient/volume-control/screenshot.png differ diff --git a/home-modules/explicit-configs/awesome/deficient/volume-control/volume-control.lua b/home-modules/explicit-configs/awesome/deficient/volume-control/volume-control.lua new file mode 100644 index 0000000..94b02b3 --- /dev/null +++ b/home-modules/explicit-configs/awesome/deficient/volume-control/volume-control.lua @@ -0,0 +1,262 @@ +-- Volume Control +local awful = require("awful") +local wibox = require("wibox") +local gears = require("gears") +local naughty = require("naughty") + +-- compatibility fallbacks for 3.5: +local timer = gears.timer or timer +local spawn = awful.spawn or awful.util.spawn +local watch = awful.spawn and awful.spawn.with_line_callback + +local function exec(command, callback) + awful.spawn.easy_async(command, callback or function() end) +end + + +------------------------------------------ +-- Private utility functions +------------------------------------------ + +local function substitute(template, context) + if type(template) == "string" then + return (template:gsub("%${([%w_]+)}", function(key) + return tostring(context[key] or "default") + end)) + else + -- function / functor: + return template(context) + end +end + +local function new(self, ...) + local instance = setmetatable({}, {__index = self}) + return instance:init(...) or instance +end + +local function class(base) + return setmetatable({new = new}, { + __call = new, + __index = base, + }) +end + +------------------------------------------ +-- Volume control interface +------------------------------------------ + +local vcontrol = class() + +function vcontrol:init(args) + self.callbacks = {} + self.cmd = "amixer" + self.device = args.device or nil + self.cardid = args.cardid or nil + self.channel = args.channel or "Master" + self.step = args.step or '5%' + + self.timer = timer({ timeout = args.timeout or 0.5 }) + self.timer:connect_signal("timeout", function() self:get() end) + self.timer:start() + + if args.listen and watch then + self.listener = watch({'stdbuf', '-oL', 'alsactl', 'monitor'}, { + stdout = function(line) self:get() end, + }) + awesome.connect_signal("exit", function() + awesome.kill(self.listener, awesome.unix_signal.SIGTERM) + end) + end +end + +function vcontrol:register(callback) + if callback then + table.insert(self.callbacks, callback) + end +end + +function vcontrol:action(action) + if self[action] then self[action](self) + elseif type(action) == "function" then action(self) + elseif type(action) == "string" then spawn(action) + end +end + +function vcontrol:update(status) + local volume = status:match("(%d?%d?%d)%%") + local state = status:match("%[(o[nf]*)%]") + if volume and state then + local volume = tonumber(volume) + local state = state:lower() + local muted = state == "off" + for _, callback in ipairs(self.callbacks) do + callback(self, { + volume = volume, + state = state, + muted = muted, + on = not muted, + }) + end + end +end + +function vcontrol:mixercommand(args, callback) + local args = awful.util.table.join( + {self.cmd}, + (self.cmd == "amixer") and {"-M"} or {}, + self.device and {"-D", self.device} or {}, + self.cardid and {"-c", self.cardid} or {}, + args) + exec(args, callback or function(output) + self:update(output) + end) +end + +function vcontrol:get() + self:mixercommand({ "get", self.channel }) +end + +function vcontrol:up() + self:mixercommand({ "set", self.channel, self.step .. "+" }) +end + +function vcontrol:down() + self:mixercommand({ "set", self.channel, self.step .. "-" }) +end + +function vcontrol:toggle() + self:mixercommand({ "set", self.channel, "toggle" }) +end + +function vcontrol:mute() + self:mixercommand({ "set", "Master", "mute" }) +end + +function vcontrol:unmute() + self:mixercommand({ "set", "Master", "unmute" }) +end + +function vcontrol:list_sinks(callback) + exec("env LC_ALL=C pactl list sinks", function(output) + local sinks = {} + local sink + for line in output:gmatch("[^\r\n]+") do + if line:match("Sink #%d+") then + sink = {} + table.insert(sinks, sink) + else + local k, v = line:match("^%s*(%S+):%s*(.-)%s*$") + if k and v then sink[k:lower()] = v end + end + end + callback(sinks) + end) +end + +function vcontrol:set_default_sink(name, callback) + exec({"pactl set-default-sink", name}, callback) +end + +------------------------------------------ +-- Volume control widget +------------------------------------------ + +-- derive so that users can still call up/down/mute etc +local vwidget = class(vcontrol) + +function vwidget:init(args) + vcontrol.init(self, args) + + self.lclick = args.lclick or "toggle" + self.mclick = args.mclick or "pavucontrol" + self.rclick = args.rclick or self.show_menu + + self.font = args.font or nil + self.widget = args.widget or (self:create_widget(args) or self.widget) + self.tooltip = args.tooltip and (self:create_tooltip(args) or self.tooltip) + + self:register(args.callback or self.update_widget) + self:register(args.tooltip and self.update_tooltip) + + self.widget:buttons(awful.util.table.join( + awful.button({}, 1, function() self:action(self.lclick) end), + awful.button({}, 2, function() self:action(self.mclick) end), + awful.button({}, 3, function() self:action(self.rclick) end), + awful.button({}, 4, function() self:up() end), + awful.button({}, 5, function() self:down() end) + )) + + self:get() +end + +-- text widget +function vwidget:create_widget(args) + self.widget_text = args.widget_text or { + on = '% 3d%% ', + off = '% 3dM ', + } + self.widget = wibox.widget.textbox() + if self.font then + self.widget.font = self.font + end +end + +function vwidget:create_menu(callback) + self:list_sinks(function(sinks) + local sinks_submenu = {} + for i, sink in ipairs(sinks) do + table.insert(sinks_submenu, {sink.description, function() + self:set_default_sink(sink.name) + end}) + end + callback(awful.menu { items = { + { "mute", function() self:mute() end }, + { "unmute", function() self:unmute() end }, + { "Default Sink", sinks_submenu }, + { "pavucontrol", function() self:action("pavucontrol") end }, + } }) + end) +end + +function vwidget:show_menu() + if self.menu then + self.menu:hide() + else + self:create_menu(function(menu) + self.menu = menu + self.menu:show() + self.menu.wibox:connect_signal("property::visible", function() + self.menu = nil + end) + end) + end +end + +function vwidget:update_widget(setting) + self.widget:set_markup( + self.widget_text[setting.state]:format(setting.volume)) +end + +-- tooltip +function vwidget:create_tooltip(args) + self.tooltip_text = args.tooltip_text or [[ +Volume: ${volume}% ${state} +Channel: ${channel} +Device: ${device} +Card: ${card}]] + self.tooltip = args.tooltip and awful.tooltip({objects={self.widget}}) +end + +function vwidget:update_tooltip(setting) + self.tooltip:set_text(substitute(self.tooltip_text, { + volume = setting.volume, + state = setting.state, + device = self.device, + card = self.card, + channel = self.channel, + })) +end + +-- provide direct access to the control class +vwidget.control = vcontrol +return vwidget diff --git a/home-modules/explicit-configs/awesome/lain/.luacheckrc b/home-modules/explicit-configs/awesome/lain/.luacheckrc new file mode 100644 index 0000000..9944223 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/.luacheckrc @@ -0,0 +1,27 @@ +-- Only allow symbols available in all Lua versions +std = "min" + +allow_defined = true +max_line_length = false +cache = true + +-- Global objects defined by the C code +read_globals = { + "awesome", + "mousegrabber", + "table.unpack", + "unpack", + "utf8" +} + +globals = { + "client", + "mouse", + "root", + "screen" +} + +-- https://luacheck.readthedocs.io/en/stable/warnings.html +ignore = { + "131" +} diff --git a/home-modules/explicit-configs/awesome/lain/LICENSE b/home-modules/explicit-configs/awesome/lain/LICENSE new file mode 100644 index 0000000..23cb790 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/home-modules/explicit-configs/awesome/lain/README.rst b/home-modules/explicit-configs/awesome/lain/README.rst new file mode 100644 index 0000000..616c7cd --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/README.rst @@ -0,0 +1,40 @@ +Lain +==== + +.. image:: https://github.com/lcpz/lain/actions/workflows/main.yml/badge.svg + +------------------------------------------------- +Layouts, widgets and utilities for Awesome WM 4.x +------------------------------------------------- + +:Author: Luca CPZ +:Version: git +:License: GNU-GPL2_ +:Source: https://github.com/lcpz/lain + +Description +----------- + +Successor of awesome-vain_, this module provides alternative layouts, asynchronous widgets and utility functions for Awesome_. + +Contributions +------------- + +Constructive criticism and suggestions are welcome. + +If you want to create a pull request, make sure that: + +- Your code fits with the general style of the module. In particular, you should use the same indentation pattern that the code uses, and also avoid adding space at the ends of lines. + +- Your code its easy to understand, maintainable, and modularized. You should also avoid code duplication wherever possible by adding functions to or using lain.helpers_. If something is unclear, or you can not write it in such a way that it will be clear, explain it with a comment. + +- You test your changes before submitting to make sure that your code works and does not break other parts of the module. + +- You update ``wiki`` submodule with a thorough section, if necessary. + +Contributed widgets have to be put in ``widget/contrib``. + +.. _GNU-GPL2: http://www.gnu.org/licenses/gpl-2.0.html +.. _awesome-vain: https://github.com/vain/awesome-vain +.. _Awesome: https://github.com/awesomeWM/awesome +.. _lain.helpers: https://github.com/lcpz/lain/blob/master/helpers.lua diff --git a/home-modules/explicit-configs/awesome/lain/helpers.lua b/home-modules/explicit-configs/awesome/lain/helpers.lua new file mode 100644 index 0000000..ef9e08b --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/helpers.lua @@ -0,0 +1,203 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + +--]] + +local spawn = require("awful.spawn") +local timer = require("gears.timer") +local debug = require("debug") +local io = { lines = io.lines, + open = io.open } +local pairs = pairs +local rawget = rawget +local tsort = table.sort +local unpack = unpack or table.unpack -- lua 5.1 retro-compatibility + +-- Lain helper functions for internal use +-- lain.helpers +local helpers = {} + +helpers.lain_dir = debug.getinfo(1, 'S').source:match[[^@(.*/).*$]] +helpers.icons_dir = helpers.lain_dir .. 'icons/' +helpers.scripts_dir = helpers.lain_dir .. 'scripts/' + +-- {{{ Modules loader + +function helpers.wrequire(t, k) + return rawget(t, k) or require(t._NAME .. '.' .. k) +end + +-- }}} + +-- {{{ File operations + +-- check if the file exists and is readable +function helpers.file_exists(path) + local file = io.open(path, "rb") + if file then file:close() end + return file ~= nil +end + +-- get a table with all lines from a file +function helpers.lines_from(path) + local lines = {} + for line in io.lines(path) do + lines[#lines + 1] = line + end + return lines +end + +-- get a table with all lines from a file matching regexp +function helpers.lines_match(regexp, path) + local lines = {} + for line in io.lines(path) do + if string.match(line, regexp) then + lines[#lines + 1] = line + end + end + return lines +end + +-- get first line of a file +function helpers.first_line(path) + local file, first = io.open(path, "rb"), nil + if file then + first = file:read("*l") + file:close() + end + return first +end + +-- get first non empty line from a file +function helpers.first_nonempty_line(path) + for line in io.lines(path) do + if #line then return line end + end + return nil +end + +-- }}} + +-- {{{ Timer maker + +helpers.timer_table = {} + +function helpers.newtimer(name, timeout, fun, nostart, stoppable) + if not name or #name == 0 then return end + name = (stoppable and name) or timeout + if not helpers.timer_table[name] then + helpers.timer_table[name] = timer({ timeout = timeout }) + helpers.timer_table[name]:start() + end + helpers.timer_table[name]:connect_signal("timeout", fun) + if not nostart then + helpers.timer_table[name]:emit_signal("timeout") + end + return stoppable and helpers.timer_table[name] +end + +-- }}} + +-- {{{ Pipe operations + +-- run a command and execute a function on its output (asynchronous pipe) +-- @param cmd the input command +-- @param callback function to execute on cmd output +-- @return cmd PID +function helpers.async(cmd, callback) + return spawn.easy_async(cmd, + function (stdout, _, _, exit_code) + callback(stdout, exit_code) + end) +end + +-- like above, but call spawn.easy_async with a shell +function helpers.async_with_shell(cmd, callback) + return spawn.easy_async_with_shell(cmd, + function (stdout, _, _, exit_code) + callback(stdout, exit_code) + end) +end + +-- run a command and execute a function on its output line by line +function helpers.line_callback(cmd, callback) + return spawn.with_line_callback(cmd, { + stdout = function (line) + callback(line) + end, + }) +end + +-- }}} + +-- {{{ A map utility + +helpers.map_table = {} + +function helpers.set_map(element, value) + helpers.map_table[element] = value +end + +function helpers.get_map(element) + return helpers.map_table[element] +end + +-- }}} + +-- {{{ Misc + +-- check if an element exist on a table +function helpers.element_in_table(element, tbl) + for _, i in pairs(tbl) do + if i == element then + return true + end + end + return false +end + +-- iterate over table of records sorted by keys +function helpers.spairs(t) + -- collect the keys + local keys = {} + for k in pairs(t) do keys[#keys+1] = k end + + tsort(keys) + + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keys[i], t[keys[i]] + end + end +end + +-- create the partition of singletons of a given set +-- example: the trivial partition set of {a, b, c}, is {{a}, {b}, {c}} +function helpers.trivial_partition_set(set) + local ss = {} + for _,e in pairs(set) do + ss[#ss+1] = {e} + end + return ss +end + +-- create the powerset of a given set +function helpers.powerset(s) + if not s then return {} end + local t = {{}} + for i = 1, #s do + for j = 1, #t do + t[#t+1] = {s[i],unpack(t[j])} + end + end + return t +end + +-- }}} + +return helpers diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/1.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/1.png new file mode 100644 index 0000000..d2fb62e Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/1.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/10.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/10.png new file mode 100644 index 0000000..507b079 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/10.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/11.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/11.png new file mode 100644 index 0000000..336141b Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/11.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/12.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/12.png new file mode 100644 index 0000000..c589729 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/12.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/13.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/13.png new file mode 100644 index 0000000..377518b Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/13.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/14.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/14.png new file mode 100644 index 0000000..6f4a9fe Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/14.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/15.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/15.png new file mode 100644 index 0000000..1a271c1 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/15.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/16.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/16.png new file mode 100644 index 0000000..5e65835 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/16.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/17.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/17.png new file mode 100644 index 0000000..f3fa0a9 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/17.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/18.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/18.png new file mode 100644 index 0000000..7acb37a Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/18.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/19.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/19.png new file mode 100644 index 0000000..a557957 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/19.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/2.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/2.png new file mode 100644 index 0000000..17b33e0 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/2.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/20.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/20.png new file mode 100644 index 0000000..558d111 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/20.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/21.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/21.png new file mode 100644 index 0000000..0bbedc8 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/21.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/22.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/22.png new file mode 100644 index 0000000..762d262 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/22.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/23.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/23.png new file mode 100644 index 0000000..a39dcee Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/23.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/24.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/24.png new file mode 100644 index 0000000..c00dbca Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/24.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/25.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/25.png new file mode 100644 index 0000000..dc9243c Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/25.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/26.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/26.png new file mode 100644 index 0000000..50bb182 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/26.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/27.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/27.png new file mode 100644 index 0000000..0fbf9fc Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/27.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/28.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/28.png new file mode 100644 index 0000000..def6ab2 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/28.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/29.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/29.png new file mode 100644 index 0000000..531923c Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/29.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/3.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/3.png new file mode 100644 index 0000000..98b552d Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/3.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/30.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/30.png new file mode 100644 index 0000000..ca58151 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/30.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/31.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/31.png new file mode 100644 index 0000000..6e8da21 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/31.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/4.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/4.png new file mode 100644 index 0000000..4335979 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/4.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/5.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/5.png new file mode 100644 index 0000000..576ec11 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/5.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/6.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/6.png new file mode 100644 index 0000000..56fa8ab Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/6.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/7.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/7.png new file mode 100644 index 0000000..7c90b3a Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/7.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/8.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/8.png new file mode 100644 index 0000000..9d1f28e Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/8.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/black/9.png b/home-modules/explicit-configs/awesome/lain/icons/cal/black/9.png new file mode 100644 index 0000000..00d0933 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/black/9.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/1.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/1.png new file mode 100644 index 0000000..a0faa20 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/1.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/10.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/10.png new file mode 100644 index 0000000..7d9343b Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/10.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/11.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/11.png new file mode 100644 index 0000000..7af5e99 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/11.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/12.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/12.png new file mode 100644 index 0000000..b164f85 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/12.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/13.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/13.png new file mode 100644 index 0000000..fef74f3 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/13.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/14.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/14.png new file mode 100644 index 0000000..d747a6b Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/14.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/15.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/15.png new file mode 100644 index 0000000..64418a6 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/15.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/16.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/16.png new file mode 100644 index 0000000..8b86700 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/16.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/17.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/17.png new file mode 100644 index 0000000..033b5ff Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/17.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/18.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/18.png new file mode 100644 index 0000000..0cf1c24 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/18.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/19.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/19.png new file mode 100644 index 0000000..bfd3530 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/19.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/2.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/2.png new file mode 100644 index 0000000..e7f3fa4 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/2.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/20.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/20.png new file mode 100644 index 0000000..9a5a1fb Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/20.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/21.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/21.png new file mode 100644 index 0000000..266ab9f Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/21.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/22.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/22.png new file mode 100644 index 0000000..f486289 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/22.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/23.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/23.png new file mode 100644 index 0000000..244dceb Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/23.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/24.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/24.png new file mode 100644 index 0000000..0ce1c75 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/24.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/25.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/25.png new file mode 100644 index 0000000..48d279c Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/25.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/26.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/26.png new file mode 100644 index 0000000..7535855 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/26.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/27.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/27.png new file mode 100644 index 0000000..2aa9074 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/27.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/28.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/28.png new file mode 100644 index 0000000..0201976 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/28.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/29.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/29.png new file mode 100644 index 0000000..9305b9b Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/29.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/3.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/3.png new file mode 100644 index 0000000..f1eb5de Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/3.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/30.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/30.png new file mode 100644 index 0000000..1ba61aa Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/30.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/31.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/31.png new file mode 100644 index 0000000..e9a873b Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/31.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/4.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/4.png new file mode 100644 index 0000000..ee1ed6a Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/4.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/5.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/5.png new file mode 100644 index 0000000..466aa71 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/5.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/6.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/6.png new file mode 100644 index 0000000..0a7bf4d Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/6.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/7.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/7.png new file mode 100644 index 0000000..e971951 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/7.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/8.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/8.png new file mode 100644 index 0000000..cb03d0b Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/8.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/cal/white/9.png b/home-modules/explicit-configs/awesome/lain/icons/cal/white/9.png new file mode 100644 index 0000000..fca554a Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/cal/white/9.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/default/cascade.png b/home-modules/explicit-configs/awesome/lain/icons/layout/default/cascade.png new file mode 100644 index 0000000..292a057 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/default/cascade.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/default/cascadetile.png b/home-modules/explicit-configs/awesome/lain/icons/layout/default/cascadetile.png new file mode 100644 index 0000000..ba30f43 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/default/cascadetile.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/default/cascadetilew.png b/home-modules/explicit-configs/awesome/lain/icons/layout/default/cascadetilew.png new file mode 100644 index 0000000..d15eb70 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/default/cascadetilew.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/default/cascadew.png b/home-modules/explicit-configs/awesome/lain/icons/layout/default/cascadew.png new file mode 100644 index 0000000..da64bd6 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/default/cascadew.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerfair.png b/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerfair.png new file mode 100644 index 0000000..188c243 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerfair.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerfairw.png b/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerfairw.png new file mode 100644 index 0000000..ed4bcf5 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerfairw.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerwork.png b/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerwork.png new file mode 100644 index 0000000..51e06bc Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerwork.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerworkh.png b/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerworkh.png new file mode 100644 index 0000000..c59092f Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerworkh.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerworkhw.png b/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerworkhw.png new file mode 100644 index 0000000..7820f8c Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerworkhw.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerworkw.png b/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerworkw.png new file mode 100644 index 0000000..85e6996 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/default/centerworkw.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/default/termfair.png b/home-modules/explicit-configs/awesome/lain/icons/layout/default/termfair.png new file mode 100644 index 0000000..06226c1 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/default/termfair.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/default/termfairw.png b/home-modules/explicit-configs/awesome/lain/icons/layout/default/termfairw.png new file mode 100644 index 0000000..0a8b576 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/default/termfairw.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/cascade.png b/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/cascade.png new file mode 100644 index 0000000..fbe4fac Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/cascade.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/cascadetile.png b/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/cascadetile.png new file mode 100644 index 0000000..2e03a80 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/cascadetile.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/centerfair.png b/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/centerfair.png new file mode 100644 index 0000000..75dc993 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/centerfair.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/centerwork.png b/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/centerwork.png new file mode 100644 index 0000000..af7a863 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/centerwork.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/centerworkh.png b/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/centerworkh.png new file mode 100644 index 0000000..88019b3 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/centerworkh.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/termfair.png b/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/termfair.png new file mode 100644 index 0000000..f7640b5 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/layout/zenburn/termfair.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/mail.png b/home-modules/explicit-configs/awesome/lain/icons/mail.png new file mode 100644 index 0000000..9c0c7a3 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/mail.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/no_net.png b/home-modules/explicit-configs/awesome/lain/icons/no_net.png new file mode 100644 index 0000000..3613372 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/no_net.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/01d.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/01d.png new file mode 100644 index 0000000..569965e Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/01d.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/01n.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/01n.png new file mode 100644 index 0000000..ce5b135 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/01n.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/02d.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/02d.png new file mode 100644 index 0000000..2ba9799 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/02d.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/02n.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/02n.png new file mode 100644 index 0000000..12e4283 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/02n.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/03d.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/03d.png new file mode 100644 index 0000000..1cf0e9d Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/03d.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/03n.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/03n.png new file mode 100644 index 0000000..89a42b8 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/03n.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/04d.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/04d.png new file mode 100644 index 0000000..e7fb67f Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/04d.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/04n.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/04n.png new file mode 120000 index 0000000..b9a83df --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/04n.png @@ -0,0 +1 @@ +04d.png \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/09d.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/09d.png new file mode 100644 index 0000000..cfa066a Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/09d.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/09n.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/09n.png new file mode 120000 index 0000000..cca1f5d --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/09n.png @@ -0,0 +1 @@ +09d.png \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/10d.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/10d.png new file mode 100644 index 0000000..712d0c8 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/10d.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/10n.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/10n.png new file mode 120000 index 0000000..6e01227 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/10n.png @@ -0,0 +1 @@ +10d.png \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/11d.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/11d.png new file mode 100644 index 0000000..3b62f7c Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/11d.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/11n.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/11n.png new file mode 120000 index 0000000..b227917 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/11n.png @@ -0,0 +1 @@ +11d.png \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/13d.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/13d.png new file mode 100644 index 0000000..e265b01 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/13d.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/13n.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/13n.png new file mode 120000 index 0000000..94e5a52 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/13n.png @@ -0,0 +1 @@ +13d.png \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/50d.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/50d.png new file mode 100644 index 0000000..905ace3 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/50d.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/50n.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/50n.png new file mode 120000 index 0000000..e3ba961 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/50n.png @@ -0,0 +1 @@ +50d.png \ No newline at end of file diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/README.md b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/README.md new file mode 100644 index 0000000..f908fbd --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/README.md @@ -0,0 +1,3 @@ +[Plain Weather Icons](http://merlinthered.deviantart.com/art/plain-weather-icons-157162192), created by [MerlinTheRed](http://merlinthered.deviantart.com/). + + diff --git a/home-modules/explicit-configs/awesome/lain/icons/openweathermap/na.png b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/na.png new file mode 100644 index 0000000..1cc5132 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/openweathermap/na.png differ diff --git a/home-modules/explicit-configs/awesome/lain/icons/taskwarrior.png b/home-modules/explicit-configs/awesome/lain/icons/taskwarrior.png new file mode 100644 index 0000000..c64fe86 Binary files /dev/null and b/home-modules/explicit-configs/awesome/lain/icons/taskwarrior.png differ diff --git a/home-modules/explicit-configs/awesome/lain/init.lua b/home-modules/explicit-configs/awesome/lain/init.lua new file mode 100644 index 0000000..b59d5dd --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/init.lua @@ -0,0 +1,15 @@ +--[[ + + Lain + Layouts, widgets and utilities for Awesome WM + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + +--]] + +return { + layout = require("lain.layout"), + util = require("lain.util"), + widget = require("lain.widget") +} diff --git a/home-modules/explicit-configs/awesome/lain/lain-scm-1.rockspec b/home-modules/explicit-configs/awesome/lain/lain-scm-1.rockspec new file mode 100644 index 0000000..57fd1f5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/lain-scm-1.rockspec @@ -0,0 +1,57 @@ +rockspec_format = "3.0" +package = "lain" +version = "scm-1" +source = { + url = "git+https://github.com/lcpz/lain.git" +} +description = { + summary = "Layout, widgets and utilities for Awesome WM", + detailed = "Alternative layouts, asynchronous widgets and utility functions for Awesome WM. Non-Lua dependency: curl (for IMAP, MPD and weather widgets).", + homepage = "https://github.com/lcpz/lain", + issues_url = "https://github.com/lcpz/lain/issues", + maintainer = "Luca Cpz", + license = "GPL2" +} +dependencies = { + "lua >= 5.3", + "dkjson >= 2.6-1" +} +supported_platforms = { "linux" } +build = { + type = "builtin", + modules = { + ["lain"] = "init.lua", + ["lain.helpers"] = "helpers.lua", + ["lain.layout"] = "layout/init.lua", + ["lain.layout.cascade"] = "layout/cascade.lua", + ["lain.layout.centerwork"] = "layout/centerwork.lua", + ["lain.layout.termfair"] = "layout/termfair.lua", + ["lain.util"] = "util/init.lua", + -- ["lain.util.dkjson"] = "util/dkjson.lua", -- RESOLVED BY DEPENDENCY TO dkjson + ["lain.util.markup"] = "util/markup.lua", + ["lain.util.menu_iterator"] = "util/menu_iterator.lua", + ["lain.util.quake"] = "util/quake.lua", + ["lain.util.separators"] = "util/separators.lua", + ["lain.widget"] = "widget/init.lua", + ["lain.widget.contrib"] = "widget/contrib/init.lua", + ["lain.widget.contrib.moc"] = "widget/contrib/moc.lua", + ["lain.widget.contrib.redshift"] = "widget/contrib/redshift.lua", + ["lain.widget.contrib.task"] = "widget/contrib/task.lua", + ["lain.widget.contrib.tp_smapi"] = "widget/contrib/tp_smapi.lua", + ["lain.widget.alsa"] = "widget/alsa.lua", + ["lain.widget.alsabar"] = "widget/alsabar.lua", + ["lain.widget.bat"] = "widget/bat.lua", + ["lain.widget.cal"] = "widget/cal.lua", + ["lain.widget.cpu"] = "widget/cpu.lua", + ["lain.widget.fs"] = "widget/fs.lua", + ["lain.widget.imap"] = "widget/imap.lua", + ["lain.widget.mem"] = "widget/mem.lua", + ["lain.widget.mpd"] = "widget/mpd.lua", + ["lain.widget.net"] = "widget/net.lua", + ["lain.widget.pulse"] = "widget/pulse.lua", + ["lain.widget.pulsebar"] = "widget/pulsebar.lua", + ["lain.widget.sysload"] = "widget/sysload.lua", + ["lain.widget.temp"] = "widget/temp.lua", + ["lain.widget.weather"] = "widget/weather.lua" + } +} diff --git a/home-modules/explicit-configs/awesome/lain/layout/cascade.lua b/home-modules/explicit-configs/awesome/lain/layout/cascade.lua new file mode 100644 index 0000000..cbc3877 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/layout/cascade.lua @@ -0,0 +1,172 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2014, projektile + * (c) 2013, Luca CPZ + * (c) 2010-2012, Peter Hofmann + +--]] + +local floor = math.floor +local screen = screen + +local cascade = { + name = "cascade", + nmaster = 0, + offset_x = 32, + offset_y = 8, + tile = { + name = "cascadetile", + nmaster = 0, + ncol = 0, + mwfact = 0, + offset_x = 5, + offset_y = 32, + extra_padding = 0 + } +} + +local function do_cascade(p, tiling) + local t = p.tag or screen[p.screen].selected_tag + local wa = p.workarea + local cls = p.clients + + if #cls == 0 then return end + + if not tiling then + -- Cascade windows. + + local num_c + if cascade.nmaster > 0 then + num_c = cascade.nmaster + else + num_c = t.master_count + end + + -- Opening a new window will usually force all existing windows to + -- get resized. This wastes a lot of CPU time. So let's set a lower + -- bound to "how_many": This wastes a little screen space but you'll + -- get a much better user experience. + local how_many = (#cls >= num_c and #cls) or num_c + + local current_offset_x = cascade.offset_x * (how_many - 1) + local current_offset_y = cascade.offset_y * (how_many - 1) + + -- Iterate. + for i = 1,#cls,1 do + local c = cls[i] + local g = {} + + g.x = wa.x + (how_many - i) * cascade.offset_x + g.y = wa.y + (i - 1) * cascade.offset_y + g.width = wa.width - current_offset_x + g.height = wa.height - current_offset_y + + if g.width < 1 then g.width = 1 end + if g.height < 1 then g.height = 1 end + + p.geometries[c] = g + end + else + -- Layout with one fixed column meant for a master window. Its + -- width is calculated according to mwfact. Other clients are + -- cascaded or "tabbed" in a slave column on the right. + + -- (1) (2) (3) (4) + -- +----------+---+ +----------+---+ +----------+---+ +----------+---+ + -- | | | | | 3 | | | 4 | | +---+| + -- | | | -> | | | -> | +---++ -> | +---+|+ + -- | 1 | 2 | | 1 +---++ | 1 | 3 || | 1 +---+|+| + -- | | | | | 2 || | +---++| | +---+|+ | + -- | | | | | || | | 2 | | | | 2 |+ | + -- +----------+---+ +---------+---++ +--------+---+-+ +------+---+---+ + + local mwfact + if cascade.tile.mwfact > 0 then + mwfact = cascade.tile.mwfact + else + mwfact = t.master_width_factor + end + + -- Make slave windows overlap main window? Do this if ncol is 1. + local overlap_main + if cascade.tile.ncol > 0 then + overlap_main = cascade.tile.ncol + else + overlap_main = t.column_count + end + + -- Minimum space for slave windows? See cascade.tile.lua. + local num_c + if cascade.tile.nmaster > 0 then + num_c = cascade.tile.nmaster + else + num_c = t.master_count + end + + local how_many = (#cls - 1 >= num_c and (#cls - 1)) or num_c + + local current_offset_x = cascade.tile.offset_x * (how_many - 1) + local current_offset_y = cascade.tile.offset_y * (how_many - 1) + + if #cls <= 0 then return end + + -- Main column, fixed width and height. + local c = cls[1] + local g = {} + -- Rounding is necessary to prevent the rendered size of slavewid + -- from being 1 pixel off when the result is not an integer. + local mainwid = floor(wa.width * mwfact) + local slavewid = wa.width - mainwid + + if overlap_main == 1 then + g.width = wa.width + + -- The size of the main window may be reduced a little bit. + -- This allows you to see if there are any windows below the + -- main window. + -- This only makes sense, though, if the main window is + -- overlapping everything else. + g.width = g.width - cascade.tile.extra_padding + else + g.width = mainwid + end + + g.height = wa.height + g.x = wa.x + g.y = wa.y + + if g.width < 1 then g.width = 1 end + if g.height < 1 then g.height = 1 end + + p.geometries[c] = g + + -- Remaining clients stacked in slave column, new ones on top. + if #cls <= 1 then return end + for i = 2,#cls do + c = cls[i] + g = {} + + g.width = slavewid - current_offset_x + g.height = wa.height - current_offset_y + + g.x = wa.x + mainwid + (how_many - (i - 1)) * cascade.tile.offset_x + g.y = wa.y + (i - 2) * cascade.tile.offset_y + + if g.width < 1 then g.width = 1 end + if g.height < 1 then g.height = 1 end + + p.geometries[c] = g + end + end +end + +function cascade.tile.arrange(p) + return do_cascade(p, true) +end + +function cascade.arrange(p) + return do_cascade(p, false) +end + +return cascade diff --git a/home-modules/explicit-configs/awesome/lain/layout/centerwork.lua b/home-modules/explicit-configs/awesome/lain/layout/centerwork.lua new file mode 100644 index 0000000..c105676 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/layout/centerwork.lua @@ -0,0 +1,276 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2018, Eugene Pakhomov + * (c) 2016, Henrik Antonsson + * (c) 2015, Joerg Jaspert + * (c) 2014, projektile + * (c) 2013, Luca CPZ + * (c) 2010-2012, Peter Hofmann + +--]] + +local floor = math.floor +local max = math.max +local mouse = mouse +local mousegrabber = mousegrabber +local screen = screen + +local centerwork = { + name = "centerwork", + horizontal = { name = "centerworkh" } +} + +local function arrange(p, layout) + local t = p.tag or screen[p.screen].selected_tag + local wa = p.workarea + local cls = p.clients + + if #cls == 0 then return end + + local g = {} + + -- Main column, fixed width and height + local mwfact = t.master_width_factor + local mainhei = floor(wa.height * mwfact) + local mainwid = floor(wa.width * mwfact) + local slavewid = wa.width - mainwid + local slaveLwid = floor(slavewid / 2) + local slaveRwid = slavewid - slaveLwid + local slavehei = wa.height - mainhei + local slaveThei = floor(slavehei / 2) + local slaveBhei = slavehei - slaveThei + local nbrFirstSlaves = floor(#cls / 2) + local nbrSecondSlaves = floor((#cls - 1) / 2) + + local slaveFirstDim, slaveSecondDim = 0, 0 + + if layout.name == "centerwork" then -- vertical + if nbrFirstSlaves > 0 then slaveFirstDim = floor(wa.height / nbrFirstSlaves) end + if nbrSecondSlaves > 0 then slaveSecondDim = floor(wa.height / nbrSecondSlaves) end + + g.height = wa.height + g.width = mainwid + + g.x = wa.x + slaveLwid + g.y = wa.y + else -- horizontal + if nbrFirstSlaves > 0 then slaveFirstDim = floor(wa.width / nbrFirstSlaves) end + if nbrSecondSlaves > 0 then slaveSecondDim = floor(wa.width / nbrSecondSlaves) end + + g.height = mainhei + g.width = wa.width + + g.x = wa.x + g.y = wa.y + slaveThei + end + + g.width = max(g.width, 1) + g.height = max(g.height, 1) + + p.geometries[cls[1]] = g + + -- Auxiliary clients + if #cls <= 1 then return end + for i = 2, #cls do + g = {} + local idxChecker, dimToAssign + + local rowIndex = floor(i/2) + + if layout.name == "centerwork" then + if i % 2 == 0 then -- left slave + g.x = wa.x + g.y = wa.y + (rowIndex - 1) * slaveFirstDim + g.width = slaveLwid + + idxChecker, dimToAssign = nbrFirstSlaves, slaveFirstDim + else -- right slave + g.x = wa.x + slaveLwid + mainwid + g.y = wa.y + (rowIndex - 1) * slaveSecondDim + g.width = slaveRwid + + idxChecker, dimToAssign = nbrSecondSlaves, slaveSecondDim + end + + -- if last slave in row, use remaining space for it + if rowIndex == idxChecker then + g.height = wa.y + wa.height - g.y + else + g.height = dimToAssign + end + else + if i % 2 == 0 then -- top slave + g.x = wa.x + (rowIndex - 1) * slaveFirstDim + g.y = wa.y + g.height = slaveThei + + idxChecker, dimToAssign = nbrFirstSlaves, slaveFirstDim + else -- bottom slave + g.x = wa.x + (rowIndex - 1) * slaveSecondDim + g.y = wa.y + slaveThei + mainhei + g.height = slaveBhei + + idxChecker, dimToAssign = nbrSecondSlaves, slaveSecondDim + end + + -- if last slave in row, use remaining space for it + if rowIndex == idxChecker then + g.width = wa.x + wa.width - g.x + else + g.width = dimToAssign + end + end + + g.width = max(g.width, 1) + g.height = max(g.height, 1) + + p.geometries[cls[i]] = g + end +end + +local function mouse_resize_handler(c, _, _, _, orientation) + local wa = c.screen.workarea + local mwfact = c.screen.selected_tag.master_width_factor + local g = c:geometry() + local offset = 0 + local cursor = "cross" + + local corner_coords + + if orientation == 'vertical' then + if g.height + 15 >= wa.height then + offset = g.height * .5 + cursor = "sb_h_double_arrow" + elseif g.y + g.height + 15 <= wa.y + wa.height then + offset = g.height + end + corner_coords = { x = wa.x + wa.width * (1 - mwfact) / 2, y = g.y + offset } + else + if g.width + 15 >= wa.width then + offset = g.width * .5 + cursor = "sb_v_double_arrow" + elseif g.x + g.width + 15 <= wa.x + wa.width then + offset = g.width + end + corner_coords = { y = wa.y + wa.height * (1 - mwfact) / 2, x = g.x + offset } + end + + mouse.coords(corner_coords) + + local prev_coords = {} + + mousegrabber.run(function(m) + if not c.valid then return false end + for _, v in ipairs(m.buttons) do + if v then + prev_coords = { x = m.x, y = m.y } + local new_mwfact + if orientation == 'vertical' then + new_mwfact = 1 - (m.x - wa.x) / wa.width * 2 + else + new_mwfact = 1 - (m.y - wa.y) / wa.height * 2 + end + c.screen.selected_tag.master_width_factor = math.min(math.max(new_mwfact, 0.01), 0.99) + return true + end + end + return prev_coords.x == m.x and prev_coords.y == m.y + end, cursor) +end + +function centerwork.arrange(p) + return arrange(p, centerwork) +end + +function centerwork.horizontal.arrange(p) + return arrange(p, centerwork.horizontal) +end + +function centerwork.mouse_resize_handler(c, corner, x, y) + return mouse_resize_handler(c, corner, x, y, 'vertical') +end + +function centerwork.horizontal.mouse_resize_handler(c, corner, x, y) + return mouse_resize_handler(c, corner, x, y, 'horizontal') +end + + +--[[ +Make focus.byidx and swap.byidx behave more consistently with other layouts. +--]] + +local awful = require("awful") +local gears = require("gears") +local client = client + +local function compare_position(a, b) + if a.x == b.x then + return a.y < b.y + else + return a.x < b.x + end +end + +local function clients_by_position() + local this = client.focus + if this then + local sorted = {} + for _, c in ipairs(client.focus.first_tag:clients()) do + if not c.minimized then sorted[#sorted+1] = c end + end + table.sort(sorted, compare_position) + + local idx = 0 + for i, that in ipairs(sorted) do + if this.window == that.window then + idx = i + end + end + + if idx > 0 then + return { sorted = sorted, idx = idx } + end + end + return {} +end + +local function in_centerwork() + return client.focus and client.focus.first_tag.layout.name == "centerwork" +end + +centerwork.focus = {} + + +--[[ +Drop in replacements for awful.client.focus.byidx and awful.client.swap.byidx +that behaves consistently with other layouts. +--]] + +function centerwork.focus.byidx(i) + if in_centerwork() then + local cls = clients_by_position() + if cls.idx then + local target = cls.sorted[gears.math.cycle(#cls.sorted, cls.idx + i)] + awful.client.focus.byidx(0, target) + end + else + awful.client.focus.byidx(i) + end +end + +centerwork.swap = {} + +function centerwork.swap.byidx(i) + if in_centerwork() then + local cls = clients_by_position() + if cls.idx then + local target = cls.sorted[gears.math.cycle(#cls.sorted, cls.idx + i)] + client.focus:swap(target) + end + else + awful.client.swap.byidx(i) + end +end + +return centerwork diff --git a/home-modules/explicit-configs/awesome/lain/layout/init.lua b/home-modules/explicit-configs/awesome/lain/layout/init.lua new file mode 100644 index 0000000..6478b06 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/layout/init.lua @@ -0,0 +1,19 @@ +--[[ + + Lain + Layouts, widgets and utilities for Awesome WM + + Layouts section + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + * (c) 2010-2012, Peter Hofmann + +--]] + +local wrequire = require("lain.helpers").wrequire +local setmetatable = setmetatable + +local layout = { _NAME = "lain.layout" } + +return setmetatable(layout, { __index = wrequire }) diff --git a/home-modules/explicit-configs/awesome/lain/layout/termfair.lua b/home-modules/explicit-configs/awesome/lain/layout/termfair.lua new file mode 100644 index 0000000..cf018ef --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/layout/termfair.lua @@ -0,0 +1,282 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2014, projektile + * (c) 2013, Luca CPZ + * (c) 2010, Nicolas Estibals + * (c) 2010-2012, Peter Hofmann + +--]] + +local math = math +local screen = screen +local tonumber = tonumber + +local termfair = { name = "termfair" } +termfair.center = { name = "centerfair" } +termfair.stable = { name = "stablefair" } + +local function do_fair(p, orientation) + local t = p.tag or screen[p.screen].selected_tag + local wa = p.workarea + local cls = p.clients + + if #cls == 0 then return end + + -- How many vertical columns? Read from nmaster on the tag. + local num_x = tonumber(termfair.nmaster) or t.master_count + local ncol = tonumber(termfair.ncol) or t.column_count + if num_x <= 2 then num_x = 2 end + if ncol <= 1 then ncol = 1 end + local width = math.floor(wa.width/num_x) + + if orientation == "west" then + -- Layout with fixed number of vertical columns (read from nmaster). + -- New windows align from left to right. When a row is full, a new + -- one above it is created. Like this: + + -- (1) (2) (3) + -- +---+---+---+ +---+---+---+ +---+---+---+ + -- | | | | | | | | | | | | + -- | 1 | | | -> | 1 | 2 | | -> | 1 | 2 | 3 | -> + -- | | | | | | | | | | | | + -- +---+---+---+ +---+---+---+ +---+---+---+ + + -- (4) (5) (6) + -- +---+---+---+ +---+---+---+ +---+---+---+ + -- | 1 | | | | 1 | 2 | | | 1 | 2 | 3 | + -- +---+---+---+ -> +---+---+---+ -> +---+---+---+ + -- | 2 | 3 | 4 | | 3 | 4 | 5 | | 4 | 5 | 6 | + -- +---+---+---+ +---+---+---+ +---+---+---+ + + local num_y = math.max(math.ceil(#cls / num_x), ncol) + local height = math.floor(wa.height/num_y) + local cur_num_x = num_x + local at_x = 0 + local at_y = 0 + + local remaining_clients = #cls + + -- We start the first row. Left-align by limiting the number of + -- available slots. + if remaining_clients < num_x then + cur_num_x = remaining_clients + end + + -- Iterate in reversed order. + for i = #cls,1,-1 do + -- Get x and y position. + local c = cls[i] + local this_x = cur_num_x - at_x - 1 + local this_y = num_y - at_y - 1 + + -- Calculate geometry. + local g = {} + if this_x == (num_x - 1) then + g.width = wa.width - (num_x - 1)*width + else + g.width = width + end + + if this_y == (num_y - 1) then + g.height = wa.height - (num_y - 1)*height + else + g.height = height + end + + g.x = wa.x + this_x*width + g.y = wa.y + this_y*height + + if g.width < 1 then g.width = 1 end + if g.height < 1 then g.height = 1 end + + p.geometries[c] = g + + remaining_clients = remaining_clients - 1 + + -- Next grid position. + at_x = at_x + 1 + if at_x == num_x then + -- Row full, create a new one above it. + at_x = 0 + at_y = at_y + 1 + + -- We start a new row. Left-align. + if remaining_clients < num_x then + cur_num_x = remaining_clients + end + end + end + elseif orientation == "stable" then + -- Layout with fixed number of vertical columns (read from nmaster). + -- New windows align from left to right. When a row is full, a new + -- one below it is created. Like this: + + -- (1) (2) (3) + -- +---+---+---+ +---+---+---+ +---+---+---+ + -- | | | | | | | | | | | | + -- | 1 | | | -> | 1 | 2 | | -> | 1 | 2 | 3 | -> + -- | | | | | | | | | | | | + -- +---+---+---+ +---+---+---+ +---+---+---+ + + -- (4) (5) (6) + -- +---+---+---+ +---+---+---+ +---+---+---+ + -- | 1 | 2 | 3 | | 1 | 2 | 3 | | 1 | 2 | 3 | + -- +---+---+---+ +---+---+---+ +---+---+---+ + -- | 4 | | | | 4 | 5 | | | 4 | 5 | 6 | + -- +---+---+---+ -> +---+---+---+ -> +---+---+---+ + + local num_y = math.max(math.ceil(#cls / num_x), ncol) + local height = math.floor(wa.height/num_y) + + for i = #cls,1,-1 do + -- Get x and y position. + local c = cls[i] + local this_x = (i - 1) % num_x + local this_y = math.floor((i - this_x - 1) / num_x) + + -- Calculate geometry. + local g = {} + if this_x == (num_x - 1) then + g.width = wa.width - (num_x - 1)*width + else + g.width = width + end + + if this_y == (num_y - 1) then + g.height = wa.height - (num_y - 1)*height + else + g.height = height + end + + g.x = wa.x + this_x*width + g.y = wa.y + this_y*height + + if g.width < 1 then g.width = 1 end + if g.height < 1 then g.height = 1 end + + p.geometries[c] = g + end + elseif orientation == "center" then + -- Layout with fixed number of vertical columns (read from nmaster). + -- Cols are centerded until there is nmaster columns, then windows + -- are stacked in the slave columns, with at most ncol clients per + -- column if possible. + + -- with nmaster=3 and ncol=1 you'll have + -- (1) (2) (3) + -- +---+---+---+ +-+---+---+-+ +---+---+---+ + -- | | | | | | | | | | | | | + -- | | 1 | | -> | | 1 | 2 | | -> | 1 | 2 | 3 | -> + -- | | | | | | | | | | | | | + -- +---+---+---+ +-+---+---+-+ +---+---+---+ + + -- (4) (5) + -- +---+---+---+ +---+---+---+ + -- | | | 3 | | | 2 | 4 | + -- + 1 + 2 +---+ -> + 1 +---+---+ + -- | | | 4 | | | 3 | 5 | + -- +---+---+---+ +---+---+---+ + + if #cls < num_x then + -- Less clients than the number of columns, let's center it! + local offset_x = wa.x + (wa.width - #cls*width) / 2 + for i = 1, #cls do + local g = { y = wa.y } + g.width = width + g.height = wa.height + if g.width < 1 then g.width = 1 end + if g.height < 1 then g.height = 1 end + g.x = offset_x + (i - 1) * width + p.geometries[cls[i]] = g + end + else + -- More clients than the number of columns, let's arrange it! + -- Master client deserves a special treatement + local g = {} + g.width = wa.width - (num_x - 1)*width + g.height = wa.height + if g.width < 1 then g.width = 1 end + if g.height < 1 then g.height = 1 end + g.x = wa.x + g.y = wa.y + p.geometries[cls[1]] = g + + -- Treat the other clients + + -- Compute distribution of clients among columns + local num_y = {} + local remaining_clients = #cls-1 + local ncol_min = math.ceil(remaining_clients/(num_x-1)) + + if ncol >= ncol_min then + for i = (num_x-1), 1, -1 do + if (remaining_clients-i+1) < ncol then + num_y[i] = remaining_clients-i + 1 + else + num_y[i] = ncol + end + remaining_clients = remaining_clients - num_y[i] + end + else + local rem = remaining_clients % (num_x-1) + if rem == 0 then + for i = 1, num_x-1 do + num_y[i] = ncol_min + end + else + for i = 1, num_x-1 do + num_y[i] = ncol_min - 1 + end + for i = 0, rem-1 do + num_y[num_x-1-i] = num_y[num_x-1-i] + 1 + end + end + end + + -- Compute geometry of the other clients + local nclient = 2 -- we start with the 2nd client + local wx = g.x + g.width + for i = 1, (num_x-1) do + local height = math.floor(wa.height / num_y[i]) + local wy = wa.y + for _ = 0, (num_y[i]-2) do + g = {} + g.x = wx + g.y = wy + g.height = height + g.width = width + if g.width < 1 then g.width = 1 end + if g.height < 1 then g.height = 1 end + p.geometries[cls[nclient]] = g + nclient = nclient + 1 + wy = wy + height + end + g = {} + g.x = wx + g.y = wy + g.height = wa.height - (num_y[i] - 1)*height + g.width = width + if g.width < 1 then g.width = 1 end + if g.height < 1 then g.height = 1 end + p.geometries[cls[nclient]] = g + nclient = nclient + 1 + wx = wx + width + end + end + end +end + +function termfair.center.arrange(p) + return do_fair(p, "center") +end + +function termfair.stable.arrange(p) + return do_fair(p, "stable") +end + +function termfair.arrange(p) + return do_fair(p, "west") +end + +return termfair diff --git a/home-modules/explicit-configs/awesome/lain/util/dkjson.lua b/home-modules/explicit-configs/awesome/lain/util/dkjson.lua new file mode 100644 index 0000000..61cccb9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/util/dkjson.lua @@ -0,0 +1,747 @@ +-- Module options: +local always_use_lpeg = false +local register_global_module_table = false +local global_module_name = 'json' + +--[==[ + +David Kolf's JSON module for Lua 5.1 - 5.4 + +Version 2.6 + + +For the documentation see the corresponding readme.txt or visit +. + +You can contact the author by sending an e-mail to 'david' at the +domain 'dkolf.de'. + + +Copyright (C) 2010-2021 David Heiko Kolf + +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. + +--]==] + +-- global dependencies: +local pairs, type, tostring, tonumber, getmetatable, setmetatable = + pairs, type, tostring, tonumber, getmetatable, setmetatable +local error, require, pcall, select = error, require, pcall, select +local floor, huge = math.floor, math.huge +local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat = + string.rep, string.gsub, string.sub, string.byte, string.char, + string.find, string.len, string.format +local strmatch = string.match +local concat = table.concat + +local json = { version = "dkjson 2.6" } + +local jsonlpeg = {} + +if register_global_module_table then + if always_use_lpeg then + _G[global_module_name] = jsonlpeg + else + _G[global_module_name] = json + end +end + +_ENV = nil -- blocking globals in Lua 5.2 and later + +pcall (function() + -- Enable access to blocked metatables. + -- Don't worry, this module doesn't change anything in them. + local debmeta = require "debug".getmetatable + if debmeta then getmetatable = debmeta end +end) + +json.null = setmetatable ({}, { + __tojson = function () return "null" end +}) + +local function isarray (tbl) + local max, n, arraylen = 0, 0, 0 + for k,v in pairs (tbl) do + if k == 'n' and type(v) == 'number' then + arraylen = v + if v > max then + max = v + end + else + if type(k) ~= 'number' or k < 1 or floor(k) ~= k then + return false + end + if k > max then + max = k + end + n = n + 1 + end + end + if max > 10 and max > arraylen and max > n * 2 then + return false -- don't create an array with too many holes + end + return true, max +end + +local escapecodes = { + ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", + ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t" +} + +local function escapeutf8 (uchar) + local value = escapecodes[uchar] + if value then + return value + end + local a, b, c, d = strbyte (uchar, 1, 4) + a, b, c, d = a or 0, b or 0, c or 0, d or 0 + if a <= 0x7f then + value = a + elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then + value = (a - 0xc0) * 0x40 + b - 0x80 + elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then + value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80 + elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then + value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80 + else + return "" + end + if value <= 0xffff then + return strformat ("\\u%.4x", value) + elseif value <= 0x10ffff then + -- encode as UTF-16 surrogate pair + value = value - 0x10000 + local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400) + return strformat ("\\u%.4x\\u%.4x", highsur, lowsur) + else + return "" + end +end + +local function fsub (str, pattern, repl) + -- gsub always builds a new string in a buffer, even when no match + -- exists. First using find should be more efficient when most strings + -- don't contain the pattern. + if strfind (str, pattern) then + return gsub (str, pattern, repl) + else + return str + end +end + +local function quotestring (value) + -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js + value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8) + if strfind (value, "[\194\216\220\225\226\239]") then + value = fsub (value, "\194[\128-\159\173]", escapeutf8) + value = fsub (value, "\216[\128-\132]", escapeutf8) + value = fsub (value, "\220\143", escapeutf8) + value = fsub (value, "\225\158[\180\181]", escapeutf8) + value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8) + value = fsub (value, "\226\129[\160-\175]", escapeutf8) + value = fsub (value, "\239\187\191", escapeutf8) + value = fsub (value, "\239\191[\176-\191]", escapeutf8) + end + return "\"" .. value .. "\"" +end +json.quotestring = quotestring + +local function replace(str, o, n) + local i, j = strfind (str, o, 1, true) + if i then + return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1) + else + return str + end +end + +-- locale independent num2str and str2num functions +local decpoint, numfilter + +local function updatedecpoint () + decpoint = strmatch(tostring(0.5), "([^05+])") + -- build a filter that can be used to remove group separators + numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+" +end + +updatedecpoint() + +local function num2str (num) + return replace(fsub(tostring(num), numfilter, ""), decpoint, ".") +end + +local function str2num (str) + local num = tonumber(replace(str, ".", decpoint)) + if not num then + updatedecpoint() + num = tonumber(replace(str, ".", decpoint)) + end + return num +end + +local function addnewline2 (level, buffer, buflen) + buffer[buflen+1] = "\n" + buffer[buflen+2] = strrep (" ", level) + buflen = buflen + 2 + return buflen +end + +function json.addnewline (state) + if state.indent then + state.bufferlen = addnewline2 (state.level or 0, + state.buffer, state.bufferlen or #(state.buffer)) + end +end + +local encode2 -- forward declaration + +local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state) + local kt = type (key) + if kt ~= 'string' and kt ~= 'number' then + return nil, "type '" .. kt .. "' is not supported as a key by JSON." + end + if prev then + buflen = buflen + 1 + buffer[buflen] = "," + end + if indent then + buflen = addnewline2 (level, buffer, buflen) + end + buffer[buflen+1] = quotestring (key) + buffer[buflen+2] = ":" + return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state) +end + +local function appendcustom(res, buffer, state) + local buflen = state.bufferlen + if type (res) == 'string' then + buflen = buflen + 1 + buffer[buflen] = res + end + return buflen +end + +local function exception(reason, value, state, buffer, buflen, defaultmessage) + defaultmessage = defaultmessage or reason + local handler = state.exception + if not handler then + return nil, defaultmessage + else + state.bufferlen = buflen + local ret, msg = handler (reason, value, state, defaultmessage) + if not ret then return nil, msg or defaultmessage end + return appendcustom(ret, buffer, state) + end +end + +function json.encodeexception(_reason, _value, _state, defaultmessage) + return quotestring("<" .. defaultmessage .. ">") +end + +encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state) + local valtype = type (value) + local valmeta = getmetatable (value) + valmeta = type (valmeta) == 'table' and valmeta -- only tables + local valtojson = valmeta and valmeta.__tojson + if valtojson then + if tables[value] then + return exception('reference cycle', value, state, buffer, buflen) + end + tables[value] = true + state.bufferlen = buflen + local ret, msg = valtojson (value, state) + if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end + tables[value] = nil + buflen = appendcustom(ret, buffer, state) + elseif value == nil then + buflen = buflen + 1 + buffer[buflen] = "null" + elseif valtype == 'number' then + local s + if value ~= value or value >= huge or -value >= huge then + -- This is the behaviour of the original JSON implementation. + s = "null" + else + s = num2str (value) + end + buflen = buflen + 1 + buffer[buflen] = s + elseif valtype == 'boolean' then + buflen = buflen + 1 + buffer[buflen] = value and "true" or "false" + elseif valtype == 'string' then + buflen = buflen + 1 + buffer[buflen] = quotestring (value) + elseif valtype == 'table' then + if tables[value] then + return exception('reference cycle', value, state, buffer, buflen) + end + tables[value] = true + level = level + 1 + local isa, n = isarray (value) + if n == 0 and valmeta and valmeta.__jsontype == 'object' then + isa = false + end + local msg + if isa then -- JSON array + buflen = buflen + 1 + buffer[buflen] = "[" + for i = 1, n do + buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + if i < n then + buflen = buflen + 1 + buffer[buflen] = "," + end + end + buflen = buflen + 1 + buffer[buflen] = "]" + else -- JSON object + local prev = false + buflen = buflen + 1 + buffer[buflen] = "{" + local order = valmeta and valmeta.__jsonorder or globalorder + if order then + local used = {} + n = #order + for i = 1, n do + local k = order[i] + local v = value[k] + if v ~= nil then + used[k] = true + buflen, _msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + prev = true -- add a seperator before the next element + end + end + for k,v in pairs (value) do + if not used[k] then + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + prev = true -- add a seperator before the next element + end + end + else -- unordered + for k,v in pairs (value) do + buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state) + if not buflen then return nil, msg end + prev = true -- add a seperator before the next element + end + end + if indent then + buflen = addnewline2 (level - 1, buffer, buflen) + end + buflen = buflen + 1 + buffer[buflen] = "}" + end + tables[value] = nil + else + return exception ('unsupported type', value, state, buffer, buflen, + "type '" .. valtype .. "' is not supported by JSON.") + end + return buflen +end + +function json.encode (value, state) + state = state or {} + local oldbuffer = state.buffer + local buffer = oldbuffer or {} + state.buffer = buffer + updatedecpoint() + local ret, msg = encode2 (value, state.indent, state.level or 0, + buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state) + if not ret then + error (msg, 2) + elseif oldbuffer == buffer then + state.bufferlen = ret + return true + else + state.bufferlen = nil + state.buffer = nil + return concat (buffer) + end +end + +local function loc (str, where) + local line, pos, linepos = 1, 1, 0 + while true do + pos = strfind (str, "\n", pos, true) + if pos and pos < where then + line = line + 1 + linepos = pos + pos = pos + 1 + else + break + end + end + return "line " .. line .. ", column " .. (where - linepos) +end + +local function unterminated (str, what, where) + return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where) +end + +local function scanwhite (str, pos) + while true do + pos = strfind (str, "%S", pos) + if not pos then return nil end + local sub2 = strsub (str, pos, pos + 1) + if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then + -- UTF-8 Byte Order Mark + pos = pos + 3 + elseif sub2 == "//" then + pos = strfind (str, "[\n\r]", pos + 2) + if not pos then return nil end + elseif sub2 == "/*" then + pos = strfind (str, "*/", pos + 2) + if not pos then return nil end + pos = pos + 2 + else + return pos + end + end +end + +local escapechars = { + ["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f", + ["n"] = "\n", ["r"] = "\r", ["t"] = "\t" +} + +local function unichar (value) + if value < 0 then + return nil + elseif value <= 0x007f then + return strchar (value) + elseif value <= 0x07ff then + return strchar (0xc0 + floor(value/0x40), + 0x80 + (floor(value) % 0x40)) + elseif value <= 0xffff then + return strchar (0xe0 + floor(value/0x1000), + 0x80 + (floor(value/0x40) % 0x40), + 0x80 + (floor(value) % 0x40)) + elseif value <= 0x10ffff then + return strchar (0xf0 + floor(value/0x40000), + 0x80 + (floor(value/0x1000) % 0x40), + 0x80 + (floor(value/0x40) % 0x40), + 0x80 + (floor(value) % 0x40)) + else + return nil + end +end + +local function scanstring (str, pos) + local lastpos = pos + 1 + local buffer, n = {}, 0 + while true do + local nextpos = strfind (str, "[\"\\]", lastpos) + if not nextpos then + return unterminated (str, "string", pos) + end + if nextpos > lastpos then + n = n + 1 + buffer[n] = strsub (str, lastpos, nextpos - 1) + end + if strsub (str, nextpos, nextpos) == "\"" then + lastpos = nextpos + 1 + break + else + local escchar = strsub (str, nextpos + 1, nextpos + 1) + local value + if escchar == "u" then + value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16) + if value then + local value2 + if 0xD800 <= value and value <= 0xDBff then + -- we have the high surrogate of UTF-16. Check if there is a + -- low surrogate escaped nearby to combine them. + if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then + value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16) + if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then + value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000 + else + value2 = nil -- in case it was out of range for a low surrogate + end + end + end + value = value and unichar (value) + if value then + if value2 then + lastpos = nextpos + 12 + else + lastpos = nextpos + 6 + end + end + end + end + if not value then + value = escapechars[escchar] or escchar + lastpos = nextpos + 2 + end + n = n + 1 + buffer[n] = value + end + end + if n == 1 then + return buffer[1], lastpos + elseif n > 1 then + return concat (buffer), lastpos + else + return "", lastpos + end +end + +local scanvalue -- forward declaration + +local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta) + local tbl, n = {}, 0 + local pos = startpos + 1 + if what == 'object' then + setmetatable (tbl, objectmeta) + else + setmetatable (tbl, arraymeta) + end + while true do + pos = scanwhite (str, pos) + if not pos then return unterminated (str, what, startpos) end + local char = strsub (str, pos, pos) + if char == closechar then + return tbl, pos + 1 + end + local val1, err + val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) + if err then return nil, pos, err end + pos = scanwhite (str, pos) + if not pos then return unterminated (str, what, startpos) end + char = strsub (str, pos, pos) + if char == ":" then + if val1 == nil then + return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")" + end + pos = scanwhite (str, pos + 1) + if not pos then return unterminated (str, what, startpos) end + local val2 + val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) + if err then return nil, pos, err end + tbl[val1] = val2 + pos = scanwhite (str, pos) + if not pos then return unterminated (str, what, startpos) end + char = strsub (str, pos, pos) + else + n = n + 1 + tbl[n] = val1 + end + if char == "," then + pos = pos + 1 + end + end +end + +scanvalue = function (str, pos, nullval, objectmeta, arraymeta) + pos = pos or 1 + pos = scanwhite (str, pos) + if not pos then + return nil, strlen (str) + 1, "no valid JSON value (reached the end)" + end + local char = strsub (str, pos, pos) + if char == "{" then + return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta) + elseif char == "[" then + return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta) + elseif char == "\"" then + return scanstring (str, pos) + else + local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos) + if pstart then + local number = str2num (strsub (str, pstart, pend)) + if number then + return number, pend + 1 + end + end + pstart, pend = strfind (str, "^%a%w*", pos) + if pstart then + local name = strsub (str, pstart, pend) + if name == "true" then + return true, pend + 1 + elseif name == "false" then + return false, pend + 1 + elseif name == "null" then + return nullval, pend + 1 + end + end + return nil, pos, "no valid JSON value at " .. loc (str, pos) + end +end + +local function optionalmetatables(...) + if select("#", ...) > 0 then + return ... + else + return {__jsontype = 'object'}, {__jsontype = 'array'} + end +end + +function json.decode (str, pos, nullval, ...) + local objectmeta, arraymeta = optionalmetatables(...) + return scanvalue (str, pos, nullval, objectmeta, arraymeta) +end + +function json.use_lpeg () + local g = require ("lpeg") + + if g.version() == "0.11" then + error "due to a bug in LPeg 0.11, it cannot be used for JSON matching" + end + + local pegmatch = g.match + local P, S, R = g.P, g.S, g.R + + local function ErrorCall (str, pos, msg, state) + if not state.msg then + state.msg = msg .. " at " .. loc (str, pos) + state.pos = pos + end + return false + end + + local function Err (msg) + return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall) + end + + local function ErrorUnterminatedCall (str, pos, what, state) + return ErrorCall (str, pos - 1, "unterminated " .. what, state) + end + + local SingleLineComment = P"//" * (1 - S"\n\r")^0 + local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/" + local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0 + + local function ErrUnterminated (what) + return g.Cmt (g.Cc (what) * g.Carg (2), ErrorUnterminatedCall) + end + + local PlainChar = 1 - S"\"\\\n\r" + local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars + local HexDigit = R("09", "af", "AF") + local function UTF16Surrogate (_match, _pos, high, low) + high, low = tonumber (high, 16), tonumber (low, 16) + if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then + return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000) + else + return false + end + end + local function UTF16BMP (hex) + return unichar (tonumber (hex, 16)) + end + local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit)) + local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP + local Char = UnicodeEscape + EscapeSequence + PlainChar + local String = P"\"" * (g.Cs (Char ^ 0) * P"\"" + ErrUnterminated "string") + local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0)) + local Fractal = P"." * R"09"^0 + local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1 + local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num + local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1) + local SimpleValue = Number + String + Constant + local ArrayContent, ObjectContent + + -- The functions parsearray and parseobject parse only a single value/pair + -- at a time and store them directly to avoid hitting the LPeg limits. + local function parsearray (str, pos, nullval, state) + local obj, cont + local start = pos + local npos + local t, nt = {}, 0 + repeat + obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state) + if cont == 'end' then + return ErrorUnterminatedCall (str, start, "array", state) + end + pos = npos + if cont == 'cont' or cont == 'last' then + nt = nt + 1 + t[nt] = obj + end + until cont ~= 'cont' + return pos, setmetatable (t, state.arraymeta) + end + + local function parseobject (str, pos, nullval, state) + local obj, key, cont + local start = pos + local npos + local t = {} + repeat + key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state) + if cont == 'end' then + return ErrorUnterminatedCall (str, start, "object", state) + end + pos = npos + if cont == 'cont' or cont == 'last' then + t[key] = obj + end + until cont ~= 'cont' + return pos, setmetatable (t, state.objectmeta) + end + + local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) + local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) + local Value = Space * (Array + Object + SimpleValue) + local ExpectedValue = Value + Space * Err "value expected" + local ExpectedKey = String + Err "key expected" + local End = P(-1) * g.Cc'end' + local ErrInvalid = Err "invalid JSON" + ArrayContent = (Value * Space * (P"," * g.Cc'cont' + P"]" * g.Cc'last'+ End + ErrInvalid) + g.Cc(nil) * (P"]" * g.Cc'empty' + End + ErrInvalid)) * g.Cp() + local Pair = g.Cg (Space * ExpectedKey * Space * (P":" + Err "colon expected") * ExpectedValue) + ObjectContent = (g.Cc(nil) * g.Cc(nil) * P"}" * g.Cc'empty' + End + (Pair * Space * (P"," * g.Cc'cont' + P"}" * g.Cc'last' + End + ErrInvalid) + ErrInvalid)) * g.Cp() + local DecodeValue = ExpectedValue * g.Cp () + + jsonlpeg.version = json.version + jsonlpeg.encode = json.encode + jsonlpeg.null = json.null + jsonlpeg.quotestring = json.quotestring + jsonlpeg.addnewline = json.addnewline + jsonlpeg.encodeexception = json.encodeexception + jsonlpeg.using_lpeg = true + + function jsonlpeg.decode (str, pos, nullval, ...) + local state = {} + state.objectmeta, state.arraymeta = optionalmetatables(...) + local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state) + if state.msg then + return nil, state.pos, state.msg + else + return obj, retpos + end + end + + -- cache result of this function: + json.use_lpeg = function () return jsonlpeg end + jsonlpeg.use_lpeg = json.use_lpeg + + return jsonlpeg +end + +if always_use_lpeg then + return json.use_lpeg() +end + +return json + diff --git a/home-modules/explicit-configs/awesome/lain/util/init.lua b/home-modules/explicit-configs/awesome/lain/util/init.lua new file mode 100644 index 0000000..5ae0523 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/util/init.lua @@ -0,0 +1,190 @@ +--[[ + + Lain + Layouts, widgets and utilities for Awesome WM + + Utilities section + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + * (c) 2010-2012, Peter Hofmann + +--]] + +local awful = require("awful") +local sqrt = math.sqrt +local pairs = pairs +local client = client +local tonumber = tonumber +local wrequire = require("lain.helpers").wrequire +local setmetatable = setmetatable + +-- Lain utilities submodule +-- lain.util +local util = { _NAME = "lain.util" } + +-- Like awful.menu.clients, but only show clients of currently selected tags +function util.menu_clients_current_tags(menu, args) + -- List of currently selected tags. + local cls_tags = awful.screen.focused().selected_tags + + if cls_tags == nil then return nil end + + -- Final list of menu items. + local cls_t = {} + + -- For each selected tag get all clients of that tag and add them to + -- the menu. A click on a menu item will raise that client. + for i = 1,#cls_tags do + local t = cls_tags[i] + local cls = t:clients() + + for _, c in pairs(cls) do + cls_t[#cls_t + 1] = { awful.util.escape(c.name) or "", + function () + c.minimized = false + client.focus = c + c:raise() + end, + c.icon } + end + end + + -- No clients? Then quit. + if #cls_t <= 0 then return nil end + + -- menu may contain some predefined values, otherwise start with a + -- fresh menu. + if not menu then menu = {} end + + -- Set the list of items and show the menu. + menu.items = cls_t + local m = awful.menu(menu) + m:show(args) + + return m +end + +-- Magnify a client: set it to "float" and resize it. +function util.magnify_client(c, width_f, height_f) + if c and not c.floating then + util.magnified_client = c + util.mc(c, width_f, height_f) + else + util.magnified_client = nil + c.floating = false + end +end + +-- https://github.com/lcpz/lain/issues/195 +function util.mc(c, width_f, height_f) + c = c or util.magnified_client + if not c then return end + + c.floating = true + local s = awful.screen.focused() + local mg = s.workarea + local g = {} + local mwfact = width_f or s.selected_tag.master_width_factor or 0.5 + g.width = sqrt(mwfact) * mg.width + g.height = sqrt(height_f or mwfact) * mg.height + g.x = mg.x + (mg.width - g.width) / 2 + g.y = mg.y + (mg.height - g.height) / 2 + + if c then c:geometry(g) end -- if c is still a valid object +end + +-- Non-empty tag browsing +-- direction in {-1, 1} <-> {previous, next} non-empty tag +function util.tag_view_nonempty(direction,sc) + direction = direction or 1 + local s = sc or awful.screen.focused() + local tags = s.tags + local sel = s.selected_tag + + local i = sel.index + repeat + i = i + direction + + -- Wrap around when we reach one of the bounds + if i > #tags then + i = i - #tags + end + if i < 1 then + i = i + #tags + end + + local t = tags[i] + + -- Stop when we get back to where we started + if t == sel then + break + end + + -- If it's The One, view it. + if #t:clients() > 0 then + t:view_only() + return + end + until false +end + +-- {{{ Dynamic tagging + +-- Add a new tag +function util.add_tag(layout) + awful.prompt.run { + prompt = "New tag name: ", + textbox = awful.screen.focused().mypromptbox.widget, + exe_callback = function(name) + if not name or #name == 0 then return end + awful.tag.add(name, { screen = awful.screen.focused(), layout = layout or awful.layout.suit.tile }):view_only() + end + } +end + +-- Rename current tag +function util.rename_tag() + awful.prompt.run { + prompt = "Rename tag: ", + textbox = awful.screen.focused().mypromptbox.widget, + exe_callback = function(new_name) + if not new_name or #new_name == 0 then return end + local t = awful.screen.focused().selected_tag + if t then + t.name = new_name + end + end + } +end + +-- Move current tag +-- pos in {-1, 1} <-> {previous, next} tag position +function util.move_tag(pos) + local tag = awful.screen.focused().selected_tag + if tonumber(pos) <= -1 then + awful.tag.move(tag.index - 1, tag) + else + awful.tag.move(tag.index + 1, tag) + end +end + +-- Delete current tag +-- Any rule set on the tag shall be broken +function util.delete_tag() + local t = awful.screen.focused().selected_tag + if not t then return end + t:delete() +end + +-- }}} + +-- On the fly useless gaps change +function util.useless_gaps_resize(thatmuch, s, t) + local scr = s or awful.screen.focused() + local tag = t or scr.selected_tag + tag.gap = tag.gap + tonumber(thatmuch) + awful.layout.arrange(scr) +end + +return setmetatable(util, { __index = wrequire }) diff --git a/home-modules/explicit-configs/awesome/lain/util/markup.lua b/home-modules/explicit-configs/awesome/lain/util/markup.lua new file mode 100644 index 0000000..63f9486 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/util/markup.lua @@ -0,0 +1,66 @@ +--[[ + + Licensed under MIT License + * (c) 2013, Luca CPZ + * (c) 2009, Uli Schlachter + * (c) 2009, Majic + +--]] + +local format = string.format +local setmetatable = setmetatable + +-- Lain markup util submodule +-- lain.util.markup +local markup = { fg = {}, bg = {} } + +-- Convenience tags +function markup.bold(text) return format("%s", text) end +function markup.italic(text) return format("%s", text) end +function markup.strike(text) return format("%s", text) end +function markup.underline(text) return format("%s", text) end +function markup.monospace(text) return format("%s", text) end +function markup.big(text) return format("%s", text) end +function markup.small(text) return format("%s", text) end + +-- Set the font +function markup.font(font, text) + return format("%s", font, text) +end + +-- Set the foreground +function markup.fg.color(color, text) + return format("%s", color, text) +end + +-- Set the background +function markup.bg.color(color, text) + return format("%s", color, text) +end + +-- Set foreground and background +function markup.color(fg, bg, text) + return format("%s", fg, bg, text) +end + +-- Set font and foreground +function markup.fontfg(font, fg, text) + return format("%s", font, fg, text) +end + +-- Set font and background +function markup.fontbg(font, bg, text) + return format("%s", font, bg, text) +end + +-- Set font, foreground and background +function markup.fontcolor(font, fg, bg, text) + return format("%s", font, fg, bg, text) +end + +-- link markup.{fg,bg}(...) calls to markup.{fg,bg}.color(...) +setmetatable(markup.fg, { __call = function(_, ...) return markup.fg.color(...) end }) +setmetatable(markup.bg, { __call = function(_, ...) return markup.bg.color(...) end }) + +-- link markup(...) calls to markup.fg.color(...) +return setmetatable(markup, { __call = function(_, ...) return markup.fg.color(...) end }) diff --git a/home-modules/explicit-configs/awesome/lain/util/menu_iterator.lua b/home-modules/explicit-configs/awesome/lain/util/menu_iterator.lua new file mode 100644 index 0000000..d457473 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/util/menu_iterator.lua @@ -0,0 +1,144 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2017, Simon Désaulniers + * (c) 2017, Uli Schlachter + * (c) 2017, Jeferson Siqueira + +--]] + +-- Menu iterator with Naughty notifications +-- lain.util.menu_iterator + +local naughty = require("naughty") +local helpers = require("lain.helpers") +local atable = require("awful.util").table +local assert = assert +local pairs = pairs +local tconcat = table.concat +local unpack = unpack or table.unpack -- lua 5.1 retro-compatibility + +local state = { cid = nil } + +local function naughty_destroy_callback(reason) + local closed = naughty.notificationClosedReason + if reason == closed.expired or reason == closed.dismissedByUser then + local actions = state.index and state.menu[state.index - 1][2] + if actions then + for _,action in pairs(actions) do + -- don't try to call nil callbacks + if action then action() end + end + state.index = nil + end + end +end + +-- Iterates over a menu. +-- After the timeout, callbacks associated to the last visited choice are +-- executed. Inputs: +-- * menu: a list of {label, {callbacks}} pairs +-- * timeout: time to wait before confirming the menu selection +-- * icon: icon to display in the notification of the chosen label +local function iterate(menu, timeout, icon) + timeout = timeout or 4 -- default timeout for each menu entry + icon = icon or nil -- icon to display on the menu + + -- Build the list of choices + if not state.index then + state.menu = menu + state.index = 1 + end + + -- Select one and display the appropriate notification + local label + local next = state.menu[state.index] + state.index = state.index + 1 + + if not next then + label = "Cancel" + state.index = nil + else + label, _ = unpack(next) + end + + state.cid = naughty.notify({ + text = label, + icon = icon, + timeout = timeout, + screen = mouse.screen, + replaces_id = state.cid, + destroy = naughty_destroy_callback + }).id +end + +-- Generates a menu compatible with the first argument of `iterate` function and +-- suitable for the following cases: +-- * all possible choices individually (partition of singletons); +-- * all possible subsets of the set of choices (powerset). +-- +-- Inputs: +-- * args: an array containing the following members: +-- * choices: Array of choices (string) on which the menu will be +-- generated. +-- * name: Displayed name of the menu (in the form "name: choices"). +-- * selected_cb: Callback to execute for each selected choice. Takes +-- the choice as a string argument. Can be `nil` (no action +-- to execute). +-- * rejected_cb: Callback to execute for each rejected choice (possible +-- choices which are not selected). Takes the choice as a +-- string argument. Can be `nil` (no action to execute). +-- * extra_choices: An array of extra { choice_str, callback_fun } pairs to be +-- added to the menu. Each callback_fun can be `nil`. +-- * combination: The combination of choices to generate. Possible values: +-- "powerset" and "single" (default). +-- Output: +-- * m: menu to be iterated over. +local function menu(args) + local choices = assert(args.choices or args[1]) + local name = assert(args.name or args[2]) + local selected_cb = args.selected_cb + local rejected_cb = args.rejected_cb + local extra_choices = args.extra_choices or {} + + local ch_combinations = args.combination == "powerset" and helpers.powerset(choices) or helpers.trivial_partition_set(choices) + + for _, c in pairs(extra_choices) do + ch_combinations = atable.join(ch_combinations, {{c[1]}}) + end + + local m = {} -- the menu + + for _,c in pairs(ch_combinations) do + if #c > 0 then + local cbs = {} + + -- selected choices + for _,ch in pairs(c) do + if atable.hasitem(choices, ch) then + cbs[#cbs + 1] = selected_cb and function() selected_cb(ch) end or nil + end + end + + -- rejected choices + for _,ch in pairs(choices) do + if not atable.hasitem(c, ch) and atable.hasitem(choices, ch) then + cbs[#cbs + 1] = rejected_cb and function() rejected_cb(ch) end or nil + end + end + + -- add user extra choices (like the choice "None" for example) + for _,x in pairs(extra_choices) do + if x[1] == c[1] then + cbs[#cbs + 1] = x[2] + end + end + + m[#m + 1] = { name .. ": " .. tconcat(c, " + "), cbs } + end + end + + return m +end + +return { iterate = iterate, menu = menu } diff --git a/home-modules/explicit-configs/awesome/lain/util/quake.lua b/home-modules/explicit-configs/awesome/lain/util/quake.lua new file mode 100644 index 0000000..8bc68a7 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/util/quake.lua @@ -0,0 +1,179 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2016, Luca CPZ + * (c) 2015, unknown + +--]] + +local awful = require("awful") +local capi = { client = client } +local math = math +local string = string +local pairs = pairs +local screen = screen +local setmetatable = setmetatable + +-- Quake-like Dropdown application spawn +local quake = {} + +-- If you have a rule like "awful.client.setslave" for your terminals, +-- ensure you use an exception for QuakeDD. Otherwise, you may +-- run into problems with focus. + +function quake:display() + if self.followtag then self.screen = awful.screen.focused() end + + -- First, we locate the client + local client = nil + local i = 0 + for c in awful.client.iterate(function (c) + -- c.name may be changed! + return c.instance == self.name + end) + do + i = i + 1 + if i == 1 then + client = c + else + -- Additional matching clients, let's remove the sticky bit + -- which may persist between awesome restarts. We don't close + -- them as they may be valuable. They will just turn into + -- normal clients. + c.sticky = false + c.ontop = false + c.above = false + end + end + + if not client and not self.visible then return end + + if not client then + -- The client does not exist, we spawn it + local cmd = string.format("%s %s %s", self.app, + string.format(self.argname, self.name), self.extra) + awful.spawn(cmd, { tag = self.screen.selected_tag }) + return + end + + -- Set geometry + client.floating = true + client.border_width = self.border + client.size_hints_honor = false + local maximized = client.maximized + local fullscreen = client.fullscreen + client:geometry(self.geometry[self.screen.index] or self:compute_size()) + + -- Set not sticky and on top + client.sticky = false + client.ontop = true + client.above = true + client.skip_taskbar = true + + -- Additional user settings + if self.settings then self.settings(client) end + + -- Toggle display + if self.visible then + client.hidden = false + client.maximized = self.maximized + client.fullscreen = self.fullscreen + client:raise() + self.last_tag = self.screen.selected_tag + client:tags({self.screen.selected_tag}) + capi.client.focus = client + else + self.maximized = maximized + self.fullscreen = fullscreen + client.maximized = false + client.fullscreen = false + client.hidden = true + local ctags = client:tags() + for j, _ in pairs(ctags) do + ctags[j] = nil + end + client:tags(ctags) + end + + return client +end + +function quake:compute_size() + -- skip if we already have a geometry for this screen + if not self.geometry[self.screen.index] then + local geom + if not self.overlap then + geom = screen[self.screen.index].workarea + else + geom = screen[self.screen.index].geometry + end + local width, height = self.width, self.height + if width <= 1 then width = math.floor(geom.width * width) - 2 * self.border end + if height <= 1 then height = math.floor(geom.height * height) end + local x, y + if self.horiz == "left" then x = geom.x + elseif self.horiz == "right" then x = geom.width + geom.x - width + else x = geom.x + (geom.width - width)/2 end + if self.vert == "top" then y = geom.y + elseif self.vert == "bottom" then y = geom.height + geom.y - height + else y = geom.y + (geom.height - height)/2 end + self.geometry[self.screen.index] = { x = x, y = y, width = width, height = height } + end + return self.geometry[self.screen.index] +end + +function quake:toggle() + if self.followtag then self.screen = awful.screen.focused() end + local current_tag = self.screen.selected_tag + if current_tag and self.last_tag ~= current_tag and self.visible then + local c=self:display() + if c then + c:move_to_tag(current_tag) + end + else + self.visible = not self.visible + self:display() + end +end + +function quake.new(conf) + conf = conf or {} + + conf.app = conf.app or "xterm" -- application to spawn + conf.name = conf.name or "QuakeDD" -- window name + conf.argname = conf.argname or "-name %s" -- how to specify window name + conf.extra = conf.extra or "" -- extra arguments + conf.border = conf.border or 1 -- client border width + conf.visible = conf.visible or false -- initially not visible + conf.followtag = conf.followtag or false -- spawn on currently focused screen + conf.overlap = conf.overlap or false -- overlap wibox + conf.screen = conf.screen or awful.screen.focused() + conf.settings = conf.settings + + -- If width or height <= 1 this is a proportion of the workspace + conf.height = conf.height or 0.25 -- height + conf.width = conf.width or 1 -- width + conf.vert = conf.vert or "top" -- top, bottom or center + conf.horiz = conf.horiz or "left" -- left, right or center + conf.geometry = {} -- internal use + + conf.maximized = false + conf.fullscreen = false + + local dropdown = setmetatable(conf, { __index = quake }) + + capi.client.connect_signal("manage", function(c) + if c.instance == dropdown.name and c.screen == dropdown.screen then + dropdown:display() + end + end) + capi.client.connect_signal("unmanage", function(c) + if c.instance == dropdown.name and c.screen == dropdown.screen then + dropdown.visible = false + end + end) + + return dropdown +end + +return setmetatable(quake, { __call = function(_, ...) return quake.new(...) end }) diff --git a/home-modules/explicit-configs/awesome/lain/util/separators.lua b/home-modules/explicit-configs/awesome/lain/util/separators.lua new file mode 100644 index 0000000..04402bb --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/util/separators.lua @@ -0,0 +1,118 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2015, Luca CPZ + * (c) 2015, plotnikovanton + +--]] + +local wibox = require("wibox") +local gears = require("gears") +local beautiful = require("beautiful") + +-- Lain Cairo separators util submodule +-- lain.util.separators +local separators = { height = beautiful.separators_height or 0, width = beautiful.separators_width or 9 } + +-- [[ Arrow + +-- Right +function separators.arrow_right(col1, col2) + local widget = wibox.widget.base.make_widget() + widget.col1 = col1 + widget.col2 = col2 + + widget.fit = function(_, _, _) + return separators.width, separators.height + end + + widget.update = function(_, _) + widget.col1 = col1 + widget.col2 = col2 + widget:emit_signal("widget::redraw_needed") + end + + widget.draw = function(_, _, cr, width, height) + if widget.col2 ~= "alpha" then + cr:set_source_rgba(gears.color.parse_color(widget.col2)) + cr:new_path() + cr:move_to(0, 0) + cr:line_to(width, height/2) + cr:line_to(width, 0) + cr:close_path() + cr:fill() + + cr:new_path() + cr:move_to(0, height) + cr:line_to(width, height/2) + cr:line_to(width, height) + cr:close_path() + cr:fill() + end + + if widget.col1 ~= "alpha" then + cr:set_source_rgba(gears.color.parse_color(widget.col1)) + cr:new_path() + cr:move_to(0, 0) + cr:line_to(width, height/2) + cr:line_to(0, height) + cr:close_path() + cr:fill() + end + end + + return widget +end + +-- Left +function separators.arrow_left(col1, col2) + local widget = wibox.widget.base.make_widget() + widget.col1 = col1 + widget.col2 = col2 + + widget.fit = function(_, _, _) + return separators.width, separators.height + end + + widget.update = function(c1, c2) + widget.col1 = c1 + widget.col2 = c2 + widget:emit_signal("widget::redraw_needed") + end + + widget.draw = function(_, _, cr, width, height) + if widget.col1 ~= "alpha" then + cr:set_source_rgba(gears.color.parse_color(widget.col1)) + cr:new_path() + cr:move_to(width, 0) + cr:line_to(0, height/2) + cr:line_to(0, 0) + cr:close_path() + cr:fill() + + cr:new_path() + cr:move_to(width, height) + cr:line_to(0, height/2) + cr:line_to(0, height) + cr:close_path() + cr:fill() + end + + if widget.col2 ~= "alpha" then + cr:new_path() + cr:move_to(width, 0) + cr:line_to(0, height/2) + cr:line_to(width, height) + cr:close_path() + + cr:set_source_rgba(gears.color.parse_color(widget.col2)) + cr:fill() + end + end + + return widget +end + +-- ]] + +return separators diff --git a/home-modules/explicit-configs/awesome/lain/widget/alsa.lua b/home-modules/explicit-configs/awesome/lain/widget/alsa.lua new file mode 100644 index 0000000..202dc98 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/alsa.lua @@ -0,0 +1,54 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + * (c) 2010, Adrian C. + +--]] + +local helpers = require("lain.helpers") +local shell = require("awful.util").shell +local wibox = require("wibox") +local string = string + +-- ALSA volume +-- lain.widget.alsa + +local function factory(args) + args = args or {} + local alsa = { widget = args.widget or wibox.widget.textbox() } + local timeout = args.timeout or 5 + local settings = args.settings or function() end + + alsa.cmd = args.cmd or "amixer" + alsa.channel = args.channel or "Master" + alsa.togglechannel = args.togglechannel + + local format_cmd = string.format("%s get %s", alsa.cmd, alsa.channel) + + if alsa.togglechannel then + format_cmd = { shell, "-c", string.format("%s get %s; %s get %s", + alsa.cmd, alsa.channel, alsa.cmd, alsa.togglechannel) } + end + + alsa.last = {} + + function alsa.update() + helpers.async(format_cmd, function(mixer) + local l,s = string.match(mixer, "([%d]+)%%.*%[([%l]*)") + l = tonumber(l) + if alsa.last.level ~= l or alsa.last.status ~= s then + volume_now = { level = l, status = s } + widget = alsa.widget + settings() + alsa.last = volume_now + end + end) + end + + helpers.newtimer(string.format("alsa-%s-%s", alsa.cmd, alsa.channel), timeout, alsa.update) + + return alsa +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/alsabar.lua b/home-modules/explicit-configs/awesome/lain/widget/alsabar.lua new file mode 100644 index 0000000..8e8cd3a --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/alsabar.lua @@ -0,0 +1,166 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + * (c) 2013, Rman + +--]] + +local helpers = require("lain.helpers") +local awful = require("awful") +local naughty = require("naughty") +local wibox = require("wibox") +local math = math +local string = string +local type = type +local tonumber = tonumber + +-- ALSA volume bar +-- lain.widget.alsabar + +local function factory(args) + local alsabar = { + colors = { + background = "#000000", + mute = "#EB8F8F", + unmute = "#A4CE8A" + }, + + _current_level = 0, + _playback = "off" + } + + args = args or {} + + local timeout = args.timeout or 5 + local settings = args.settings or function() end + local width = args.width or 63 + local height = args.height or 1 + local margins = args.margins or 1 + local ticks = args.ticks or false + local ticks_size = args.ticks_size or 7 + local tick = args.tick or "|" + local tick_pre = args.tick_pre or "[" + local tick_post = args.tick_post or "]" + local tick_none = args.tick_none or " " + + alsabar.cmd = args.cmd or "amixer" + alsabar.channel = args.channel or "Master" + alsabar.togglechannel = args.togglechannel + alsabar.colors = args.colors or alsabar.colors + alsabar.followtag = args.followtag or false + alsabar.notification_preset = args.notification_preset + + if not alsabar.notification_preset then + alsabar.notification_preset = { font = "Monospace 10" } + end + + local format_cmd = string.format("%s get %s", alsabar.cmd, alsabar.channel) + + if alsabar.togglechannel then + format_cmd = { awful.util.shell, "-c", string.format("%s get %s; %s get %s", + alsabar.cmd, alsabar.channel, alsabar.cmd, alsabar.togglechannel) } + end + + alsabar.bar = wibox.widget { + color = alsabar.colors.unmute, + background_color = alsabar.colors.background, + forced_height = height, + forced_width = width, + margins = margins, + paddings = margins, + ticks = ticks, + ticks_size = ticks_size, + widget = wibox.widget.progressbar + } + + alsabar.tooltip = awful.tooltip({ objects = { alsabar.bar } }) + + function alsabar.update(callback) + helpers.async(format_cmd, function(mixer) + local vol, playback = string.match(mixer, "([%d]+)%%.*%[([%l]*)") + + if not vol or not playback then return end + + if vol ~= alsabar._current_level or playback ~= alsabar._playback then + alsabar._current_level = tonumber(vol) + alsabar.bar:set_value(alsabar._current_level / 100) + if alsabar._current_level == 0 or playback == "off" then + alsabar._playback = playback + alsabar.tooltip:set_text("[Muted]") + alsabar.bar.color = alsabar.colors.mute + else + alsabar._playback = "on" + alsabar.tooltip:set_text(string.format("%s: %s", alsabar.channel, vol)) + alsabar.bar.color = alsabar.colors.unmute + end + + volume_now = { + level = alsabar._current_level, + status = alsabar._playback + } + + settings() + + if type(callback) == "function" then callback() end + end + end) + end + + function alsabar.notify() + alsabar.update(function() + local preset = alsabar.notification_preset + + preset.title = string.format("%s - %s%%", alsabar.channel, alsabar._current_level) + + if alsabar._playback == "off" then + preset.title = preset.title .. " Muted" + end + + -- tot is the maximum number of ticks to display in the notification + local tot = alsabar.notification_preset.max_ticks + + if not tot then + local wib = awful.screen.focused().mywibox + -- if we can grab mywibox, tot is defined as its height if + -- horizontal, or width otherwise + if wib then + if wib.position == "left" or wib.position == "right" then + tot = wib.width + else + tot = wib.height + end + -- fallback: default horizontal wibox height + else + tot = 20 + end + end + + local int = math.modf((alsabar._current_level / 100) * tot) + preset.text = string.format( + "%s%s%s%s", + tick_pre, + string.rep(tick, int), + string.rep(tick_none, tot - int), + tick_post + ) + + if alsabar.followtag then preset.screen = awful.screen.focused() end + + if not alsabar.notification then + alsabar.notification = naughty.notify { + preset = preset, + destroy = function() alsabar.notification = nil end + } + else + naughty.replace_text(alsabar.notification, preset.title, preset.text) + end + end) + end + + helpers.newtimer(string.format("alsabar-%s-%s", alsabar.cmd, alsabar.channel), timeout, alsabar.update) + + return alsabar +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/bat.lua b/home-modules/explicit-configs/awesome/lain/widget/bat.lua new file mode 100644 index 0000000..260f4b9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/bat.lua @@ -0,0 +1,236 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + * (c) 2010-2012, Peter Hofmann + +--]] + +local helpers = require("lain.helpers") +local fs = require("gears.filesystem") +local naughty = require("naughty") +local wibox = require("wibox") +local math = math +local string = string +local ipairs = ipairs +local tonumber = tonumber + +-- Battery infos +-- lain.widget.bat + +local function factory(args) + local pspath = args.pspath or "/sys/class/power_supply/" + + if not fs.is_dir(pspath) then + naughty.notify { text = "lain.widget.bat: invalid power supply path", timeout = 0 } + return + end + + args = args or {} + + local bat = { widget = args.widget or wibox.widget.textbox() } + local timeout = args.timeout or 30 + local notify = args.notify or "on" + local full_notify = args.full_notify or notify + local n_perc = args.n_perc or { 5, 15 } + local batteries = args.batteries or (args.battery and {args.battery}) or {} + local ac = args.ac or "AC0" + local settings = args.settings or function() end + + function bat.get_batteries() + helpers.line_callback("ls -1 " .. pspath, function(line) + local bstr = string.match(line, "BAT%w+") + if bstr then + batteries[#batteries + 1] = bstr + else + ac = string.match(line, "A%w+") or ac + end + end) + end + + if #batteries == 0 then bat.get_batteries() end + + bat_notification_critical_preset = { + title = "Battery exhausted", + text = "Shutdown imminent", + timeout = 15, + fg = "#000000", + bg = "#FFFFFF" + } + + bat_notification_low_preset = { + title = "Battery low", + text = "Plug the cable!", + timeout = 15, + fg = "#202020", + bg = "#CDCDCD" + } + + bat_notification_charged_preset = { + title = "Battery full", + text = "You can unplug the cable", + timeout = 15, + fg = "#202020", + bg = "#CDCDCD" + } + + bat_now = { + status = "N/A", + ac_status = "N/A", + perc = "N/A", + time = "N/A", + watt = "N/A", + capacity = "N/A" + } + + bat_now.n_status = {} + bat_now.n_perc = {} + bat_now.n_capacity = {} + for i = 1, #batteries do + bat_now.n_status[i] = "N/A" + bat_now.n_perc[i] = 0 + bat_now.n_capacity[i] = 0 + end + + -- used to notify full charge only once before discharging + local fullnotification = false + + function bat.update() + -- luacheck: globals bat_now + local sum_rate_current = 0 + local sum_rate_voltage = 0 + local sum_rate_power = 0 + local sum_rate_energy = 0 + local sum_energy_now = 0 + local sum_energy_full = 0 + local sum_charge_full = 0 + local sum_charge_design = 0 + + for i, battery in ipairs(batteries) do + local bstr = pspath .. battery + local present = helpers.first_line(bstr .. "/present") + + if tonumber(present) == 1 then + -- current_now(I)[uA], voltage_now(U)[uV], power_now(P)[uW] + local rate_current = tonumber(helpers.first_line(bstr .. "/current_now")) + local rate_voltage = tonumber(helpers.first_line(bstr .. "/voltage_now")) + local rate_power = tonumber(helpers.first_line(bstr .. "/power_now")) + local charge_full = tonumber(helpers.first_line(bstr .. "/charge_full")) + local charge_design = tonumber(helpers.first_line(bstr .. "/charge_full_design")) + + -- energy_now(P)[uWh], charge_now(I)[uAh] + local energy_now = tonumber(helpers.first_line(bstr .. "/energy_now") or + helpers.first_line(bstr .. "/charge_now")) + + -- energy_full(P)[uWh], charge_full(I)[uAh] + local energy_full = tonumber(helpers.first_line(bstr .. "/energy_full") or + charge_full) + + local energy_percentage = tonumber(helpers.first_line(bstr .. "/capacity")) or + math.floor((energy_now / energy_full) * 100) + + bat_now.n_status[i] = helpers.first_line(bstr .. "/status") or "N/A" + bat_now.n_perc[i] = energy_percentage or bat_now.n_perc[i] + + if not charge_design or charge_design == 0 then + bat_now.n_capacity[i] = 0 + else + bat_now.n_capacity[i] = math.floor((charge_full / charge_design) * 100) + end + + sum_rate_current = sum_rate_current + (rate_current or 0) + sum_rate_voltage = sum_rate_voltage + (rate_voltage or 0) + sum_rate_power = sum_rate_power + (rate_power or 0) + sum_rate_energy = sum_rate_energy + (rate_power or (((rate_voltage or 0) * (rate_current or 0)) / 1e6)) + sum_energy_now = sum_energy_now + (energy_now or 0) + sum_energy_full = sum_energy_full + (energy_full or 0) + sum_charge_full = sum_charge_full + (charge_full or 0) + sum_charge_design = sum_charge_design + (charge_design or 0) + end + end + + bat_now.capacity = math.floor(math.min(100, (sum_charge_full / sum_charge_design) * 100)) + + -- When one of the battery is charging, others' status are either + -- "Full", "Unknown" or "Charging". When the laptop is not plugged in, + -- one or more of the batteries may be full, but only one battery + -- discharging suffices to set global status to "Discharging". + bat_now.status = bat_now.n_status[1] or "N/A" + for _,status in ipairs(bat_now.n_status) do + if status == "Discharging" or status == "Charging" then + bat_now.status = status + end + end + bat_now.ac_status = tonumber(helpers.first_line(string.format("%s%s/online", pspath, ac))) or "N/A" + + if bat_now.status ~= "N/A" then + if bat_now.status ~= "Full" and sum_rate_power == 0 and bat_now.ac_status == 1 then + bat_now.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100)) + bat_now.time = "00:00" + bat_now.watt = 0 + + -- update {perc,time,watt} iff battery not full and rate > 0 + elseif bat_now.status ~= "Full" then + local rate_time = 0 + -- Calculate time and watt if rates are greater then 0 + if (sum_rate_power > 0 or sum_rate_current > 0) then + local div = (sum_rate_power > 0 and sum_rate_power) or sum_rate_current + + if bat_now.status == "Charging" then + rate_time = (sum_energy_full - sum_energy_now) / div + else -- Discharging + rate_time = sum_energy_now / div + end + + if 0 < rate_time and rate_time < 0.01 then -- check for magnitude discrepancies (#199) + rate_time_magnitude = math.abs(math.floor(math.log10(rate_time))) + rate_time = rate_time * 10^(rate_time_magnitude - 2) + end + end + + local hours = math.floor(rate_time) + local minutes = math.floor((rate_time - hours) * 60) + bat_now.perc = math.floor(math.min(100, (sum_energy_now / sum_energy_full) * 100)) + bat_now.time = string.format("%02d:%02d", hours, minutes) + bat_now.watt = tonumber(string.format("%.2f", sum_rate_energy / 1e6)) + elseif bat_now.status == "Full" then + bat_now.perc = 100 + bat_now.time = "00:00" + bat_now.watt = 0 + end + end + + widget = bat.widget + settings() + + -- notifications for critical, low, and full levels + if notify == "on" then + if bat_now.status == "Discharging" then + if tonumber(bat_now.perc) <= n_perc[1] then + bat.id = naughty.notify({ + preset = bat_notification_critical_preset, + replaces_id = bat.id + }).id + elseif tonumber(bat_now.perc) <= n_perc[2] then + bat.id = naughty.notify({ + preset = bat_notification_low_preset, + replaces_id = bat.id + }).id + end + fullnotification = false + elseif bat_now.status == "Full" and full_notify == "on" and not fullnotification then + bat.id = naughty.notify({ + preset = bat_notification_charged_preset, + replaces_id = bat.id + }).id + fullnotification = true + end + end + end + + helpers.newtimer("batteries", timeout, bat.update) + + return bat +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/cal.lua b/home-modules/explicit-configs/awesome/lain/widget/cal.lua new file mode 100644 index 0000000..97dced8 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/cal.lua @@ -0,0 +1,191 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2018, Luca CPZ + +--]] + +local helpers = require("lain.helpers") +local markup = require("lain.util.markup") +local awful = require("awful") +local naughty = require("naughty") +local floor = math.floor +local os = os +local pairs = pairs +local string = string +local tconcat = table.concat +local type = type +local tonumber = tonumber +local tostring = tostring + +-- Calendar notification +-- lain.widget.cal + +local function factory(args) + args = args or {} + local cal = { + attach_to = args.attach_to or {}, + week_start = args.week_start or 2, + three = args.three or false, + followtag = args.followtag or false, + week_number = args.week_number or "none", + week_number_format = args.week_number_format or args.week_number == "left" and "%3d | " or "| %-3d", + icons = args.icons or helpers.icons_dir .. "cal/white/", + notification_preset = args.notification_preset or { + font = "Monospace 10", fg = "#FFFFFF", bg = "#000000" + } + } + + function cal.get_week_number(m, st_day, x) + local date = os.date("*t", m) + + local week_step = (x ~= 0 and floor((x + st_day) / 7) - 1 or 0); + + local display_time = os.time { + year = date.year, month = date.month, day = date.day + 7 * week_step + } + + return string.format(cal.week_number_format, os.date("%V", display_time)) + end + + function cal.sum_week_days(x, y) + return (x + y) % 7 + end + + function cal.build(month, year) + local current_month, current_year = tonumber(os.date("%m")), tonumber(os.date("%Y")) + local is_current_month = (not month or not year) or (month == current_month and year == current_year) + local today = is_current_month and tonumber(os.date("%d")) -- otherwise nil and not highlighted + local t = os.time { year = year or current_year, month = month and month+1 or current_month+1, day = 0 } + local d = os.date("*t", t) + local mth_days, st_day, this_month = d.day, (d.wday-d.day-cal.week_start+1)%7, os.date("%B %Y", t) + local notifytable = { [1] = string.format("%s%s\n", string.rep(" ", floor((28 - this_month:len())/2)), markup.bold(this_month)) } + for day_num = 0, 6 do + notifytable[#notifytable+1] = string.format("%3s ", os.date("%a", os.time { year = 2006, month = 1, day = day_num + cal.week_start })) + end + notifytable[#notifytable] = string.format("%s\n%s", notifytable[#notifytable]:sub(1, -2), string.rep(" ", st_day*4)) + local strx + for x = 1,mth_days do + strx = x + if x == today then + if x < 10 then x = " " .. x end + strx = markup.bold(markup.color(cal.notification_preset.bg, cal.notification_preset.fg, x) .. " ") + end + strx = string.format("%s%s", string.rep(" ", 3 - tostring(x):len()), strx) + notifytable[#notifytable+1] = string.format("%-4s%s", strx, (x+st_day)%7==0 and x ~= mth_days and "\n" or "") + end + if string.len(cal.icons or "") > 0 and today then cal.icon = cal.icons .. today .. ".png" end + cal.month, cal.year = d.month, d.year + + if cal.week_number ~= "none" then + local m = os.time { year = year or current_year, month = month and month or current_month, day = 1 } + local head_prepend = string.rep(" ", tostring(string.format(cal.week_number_format, 0)):len()) + + if cal.week_number == "left" then + notifytable[1] = head_prepend .. notifytable[1] -- month-year row + notifytable[2] = head_prepend .. notifytable[2] -- weekdays row + notifytable[8] = notifytable[8]:gsub("\n", "\n" .. cal.get_week_number(m, st_day, 0)) -- first week of the month + + for x = 10,#notifytable do + if cal.sum_week_days(st_day, x) == 2 then + notifytable[x] = cal.get_week_number(m, st_day, x) .. notifytable[x] + end + end + elseif cal.week_number == "right" then + notifytable[8] = notifytable[8]:gsub("\n", head_prepend .. "\n") -- weekdays row + for x = 9,#notifytable do + if cal.sum_week_days(st_day, x) == 1 then + notifytable[x] = notifytable[x]:gsub("\n", cal.get_week_number(m, st_day, x - 7) .. "\n") + end + end + -- last week of the month + local end_days = cal.sum_week_days(st_day, mth_days) + if end_days ~= 0 then end_days = 7 - end_days end + notifytable[#notifytable] = notifytable[#notifytable] .. string.rep(" ", 4 * end_days) .. cal.get_week_number(m, st_day, mth_days + end_days) + end + end + + return notifytable + end + + function cal.getdate(month, year, offset) + if not month or not year then + month = tonumber(os.date("%m")) + year = tonumber(os.date("%Y")) + end + + month = month + offset + + while month > 12 do + month = month - 12 + year = year + 1 + end + + while month < 1 do + month = month + 12 + year = year - 1 + end + + return month, year + end + + function cal.hide() + if not cal.notification then return end + naughty.destroy(cal.notification) + cal.notification = nil + end + + function cal.show(seconds, month, year, scr) + local text = tconcat(cal.build(month, year)) + + if cal.three then + local current_month, current_year = cal.month, cal.year + local prev_month, prev_year = cal.getdate(cal.month, cal.year, -1) + local next_month, next_year = cal.getdate(cal.month, cal.year, 1) + text = string.format("%s\n\n%s\n\n%s", + tconcat(cal.build(prev_month, prev_year)), text, + tconcat(cal.build(next_month, next_year))) + cal.month, cal.year = current_month, current_year + end + + if cal.notification then + local title = cal.notification_preset.title or nil + naughty.replace_text(cal.notification, title, text) + return + end + + cal.notification = naughty.notify { + preset = cal.notification_preset, + screen = cal.followtag and awful.screen.focused() or scr or 1, + icon = cal.icon, + timeout = type(seconds) == "number" and seconds or cal.notification_preset.timeout or 5, + text = text + } + end + + function cal.hover_on() cal.show(0) end + function cal.move(offset) + offset = offset or 0 + cal.month, cal.year = cal.getdate(cal.month, cal.year, offset) + cal.show(0, cal.month, cal.year) + end + function cal.prev() cal.move(-1) end + function cal.next() cal.move( 1) end + + function cal.attach(widget) + widget:connect_signal("mouse::enter", cal.hover_on) + widget:connect_signal("mouse::leave", cal.hide) + widget:buttons(awful.util.table.join( + awful.button({}, 1, cal.prev), + awful.button({}, 3, cal.next), + awful.button({}, 2, cal.hover_on), + awful.button({}, 5, cal.prev), + awful.button({}, 4, cal.next))) + end + + for _, widget in pairs(cal.attach_to) do cal.attach(widget) end + + return cal +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/contrib/init.lua b/home-modules/explicit-configs/awesome/lain/widget/contrib/init.lua new file mode 100644 index 0000000..9e863a5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/contrib/init.lua @@ -0,0 +1,18 @@ +--[[ + + Lain + Layouts, widgets and utilities for Awesome WM + + Users contributed widgets section + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + +--]] + +local wrequire = require("lain.helpers").wrequire +local setmetatable = setmetatable + +local widget = { _NAME = "lain.widget.contrib" } + +return setmetatable(widget, { __index = wrequire }) diff --git a/home-modules/explicit-configs/awesome/lain/widget/contrib/moc.lua b/home-modules/explicit-configs/awesome/lain/widget/contrib/moc.lua new file mode 100644 index 0000000..ad6452e --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/contrib/moc.lua @@ -0,0 +1,97 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2014, anticlockwise + +--]] + +local helpers = require("lain.helpers") +local shell = require("awful.util").shell +local focused = require("awful.screen").focused +local escape_f = require("awful.util").escape +local naughty = require("naughty") +local wibox = require("wibox") +local os = os +local string = string + +-- MOC audio player +-- lain.widget.contrib.moc + +local function factory(args) + args = args or {} + + local moc = { widget = args.widget or wibox.widget.textbox() } + local timeout = args.timeout or 2 + local music_dir = args.music_dir or os.getenv("HOME") .. "/Music" + local cover_pattern = args.cover_pattern or "*\\.(jpg|jpeg|png|gif)$" + local cover_size = args.cover_size or 100 + local default_art = args.default_art or "" + local followtag = args.followtag or false + local settings = args.settings or function() end + + moc_notification_preset = { title = "Now playing", timeout = 6 } + + helpers.set_map("current moc track", nil) + + function moc.update() + helpers.async("mocp -i", function(f) + moc_now = { + state = "N/A", + file = "N/A", + artist = "N/A", + title = "N/A", + album = "N/A", + elapsed = "N/A", + total = "N/A" + } + + for line in string.gmatch(f, "[^\n]+") do + for k, v in string.gmatch(line, "([%w]+):[%s](.*)$") do + if k == "State" then moc_now.state = v + elseif k == "File" then moc_now.file = v + elseif k == "Artist" then moc_now.artist = escape_f(v) + elseif k == "SongTitle" then moc_now.title = escape_f(v) + elseif k == "Album" then moc_now.album = escape_f(v) + elseif k == "CurrentTime" then moc_now.elapsed = escape_f(v) + elseif k == "TotalTime" then moc_now.total = escape_f(v) + end + end + end + + moc_notification_preset.text = string.format("%s (%s) - %s\n%s", moc_now.artist, + moc_now.album, moc_now.total, moc_now.title) + widget = moc.widget + settings() + + if moc_now.state == "PLAY" then + if moc_now.title ~= helpers.get_map("current moc track") then + helpers.set_map("current moc track", moc_now.title) + + if followtag then moc_notification_preset.screen = focused() end + + local common = { + preset = moc_notification_preset, + icon = default_art, + icon_size = cover_size, + replaces_id = moc.id, + } + + local path = string.format("%s/%s", music_dir, string.match(moc_now.file, ".*/")) + local cover = string.format("find '%s' -maxdepth 1 -type f | egrep -i -m1 '%s'", path, cover_pattern) + helpers.async({ shell, "-c", cover }, function(current_icon) + common.icon = current_icon:gsub("\n", "") + moc.id = naughty.notify(common).id + end) + end + elseif moc_now.state ~= "PAUSE" then + helpers.set_map("current moc track", nil) + end + end) + end + + moc.timer = helpers.newtimer("moc", timeout, moc.update, true, true) + + return moc +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/contrib/redshift.lua b/home-modules/explicit-configs/awesome/lain/widget/contrib/redshift.lua new file mode 100644 index 0000000..d91d941 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/contrib/redshift.lua @@ -0,0 +1,54 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2017, Luca CPZ + * (c) 2014, blueluke + +--]] + +local async = require("lain.helpers").async +local awful = require("awful") +local execute = os.execute +local type = type + +-- Redshift +-- lain.widget.contrib.redshift +local redshift = { active = false, pid = nil } + +function redshift.start() + execute("pkill redshift") + awful.spawn.with_shell("redshift -x") -- clear adjustments + redshift.pid = awful.spawn.with_shell("redshift") + redshift.active = true + if type(redshift.update_fun) == "function" then + redshift.update_fun(redshift.active) + end +end + +function redshift.toggle() + async({ awful.util.shell, "-c", string.format("ps -p %d -o pid=", redshift.pid) }, function(f) + if f and #f > 0 then -- redshift is running + -- Sending -USR1 toggles redshift (See project website) + execute("pkill -USR1 redshift") + redshift.active = not redshift.active + else -- not started or killed, (re)start it + redshift.start() + end + redshift.update_fun(redshift.active) + end) +end + +-- Attach to a widget +-- Provides a button which toggles redshift on/off on click +-- @param widget: Widget to attach to. +-- @param fun: Function to be run each time redshift is toggled (optional). +-- Use it to update widget text or icons on status change. +function redshift.attach(widget, fun) + redshift.update_fun = fun or function() end + if not redshift.pid then redshift.start() end + if widget then + widget:buttons(awful.util.table.join(awful.button({}, 1, function () redshift.toggle() end))) + end +end + +return redshift diff --git a/home-modules/explicit-configs/awesome/lain/widget/contrib/task.lua b/home-modules/explicit-configs/awesome/lain/widget/contrib/task.lua new file mode 100644 index 0000000..2311996 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/contrib/task.lua @@ -0,0 +1,92 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Jan Xie + +--]] + +local helpers = require("lain.helpers") +local markup = require("lain.util").markup +local awful = require("awful") +local naughty = require("naughty") +local mouse = mouse + +-- Taskwarrior notification +-- lain.widget.contrib.task +local task = {} + +function task.hide() + if not task.notification then return end + naughty.destroy(task.notification) + task.notification = nil +end + +function task.show(scr) + task.notification_preset.screen = task.followtag and awful.screen.focused() or scr or 1 + + helpers.async({ awful.util.shell, "-c", task.show_cmd }, function(f) + local widget_focused = true + + if mouse.current_widgets then + widget_focused = false + for _,v in ipairs(mouse.current_widgets) do + if task.widget == v then + widget_focused = true + break + end + end + end + + if widget_focused then + task.hide() + task.notification = naughty.notify { + preset = task.notification_preset, + title = "task next", + text = markup.font(task.notification_preset.font, + awful.util.escape(f:gsub("\n*$", ""))) + } + end + end) +end + +function task.prompt() + awful.prompt.run { + prompt = task.prompt_text, + textbox = awful.screen.focused().mypromptbox.widget, + exe_callback = function(t) + helpers.async(t, function(f) + naughty.notify { + preset = task.notification_preset, + title = t, + text = markup.font(task.notification_preset.font, + awful.util.escape(f:gsub("\n*$", ""))) + } + end) + end, + history_path = awful.util.getdir("cache") .. "/history_task" + } +end + +function task.attach(widget, args) + args = args or {} + + task.show_cmd = args.show_cmd or "task next" + task.prompt_text = args.prompt_text or "Enter task command: " + task.followtag = args.followtag or false + task.notification_preset = args.notification_preset + task.widget = widget + + if not task.notification_preset then + task.notification_preset = { + font = "Monospace 10", + icon = helpers.icons_dir .. "/taskwarrior.png" + } + end + + if widget then + widget:connect_signal("mouse::enter", function () task.show() end) + widget:connect_signal("mouse::leave", function () task.hide() end) + end +end + +return task diff --git a/home-modules/explicit-configs/awesome/lain/widget/contrib/tp_smapi.lua b/home-modules/explicit-configs/awesome/lain/widget/contrib/tp_smapi.lua new file mode 100644 index 0000000..87c5e51 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/contrib/tp_smapi.lua @@ -0,0 +1,147 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2018, Luca CPZ + * (c) 2013, Conor Heine + +--]] + +local helpers = require("lain.helpers") +local focused = require("awful.screen").focused +local naughty = require("naughty") +local wibox = require("wibox") +local string = string +local type = type + +-- ThinkPad battery infos and widget creator +-- http://www.thinkwiki.org/wiki/Tp_smapi +-- lain.widget.contrib.tp_smapi + +local function factory(apipath) + local tp_smapi = { + path = apipath or "/sys/devices/platform/smapi" + } + + function tp_smapi.get(batid, feature) + return helpers.first_line(string.format("%s/%s/%s", tp_smapi.path, batid or "BAT0", feature or "")) + end + + function tp_smapi.installed(batid) + return tp_smapi.get(batid, "installed") == "1" + end + + function tp_smapi.status(batid) + return tp_smapi.get(batid, "state") + end + + function tp_smapi.percentage(batid) + return tp_smapi.get(batid, "remaining_percent") + end + + -- either running or charging time + function tp_smapi.time(batid) + local status = tp_smapi.status(batid) + local mins_left = tp_smapi.get(batid, string.match(string.lower(status), "discharging") and "remaining_running_time" or "remaining_charging_time") + if not string.find(mins_left, "^%d+") then return "N/A" end + return string.format("%02d:%02d", math.floor(mins_left / 60), mins_left % 60) -- HH:mm + end + + function tp_smapi.hide() + if not tp_smapi.notification then return end + naughty.destroy(tp_smapi.notification) + tp_smapi.notification = nil + end + + function tp_smapi.show(batid, seconds, scr) + if not tp_smapi.installed(batid) then return end + + local mfgr = tp_smapi.get(batid, "manufacturer") or "no_mfgr" + local model = tp_smapi.get(batid, "model") or "no_model" + local chem = tp_smapi.get(batid, "chemistry") or "no_chem" + local status = tp_smapi.get(batid, "state") + local time = tp_smapi.time(batid) + local msg + + if status and status ~= "idle" then + msg = string.format("[%s] %s %s", status, time ~= "N/A" and time or "unknown remaining time", + string.lower(status):gsub(" ", ""):gsub("\n", "") == "charging" and " until charged" or " remaining") + else + msg = "On AC power" + end + + tp_smapi.hide() + tp_smapi.notification = naughty.notify { + title = string.format("%s: %s %s (%s)", batid, mfgr, model, chem), + text = msg, + timeout = type(seconds) == "number" and seconds or 0, + screen = scr or focused() + } + end + + function tp_smapi.create_widget(args) + args = args or {} + + local pspath = args.pspath or "/sys/class/power_supply/" + local batteries = args.batteries or (args.battery and {args.battery}) or {} + local timeout = args.timeout or 30 + local settings = args.settings or function() end + + if #batteries == 0 then + helpers.line_callback("ls -1 " .. pspath, function(line) + local bstr = string.match(line, "BAT%w+") + if bstr then batteries[#batteries + 1] = bstr end + end) + end + + local all_batteries_installed = true + + for _, battery in ipairs(batteries) do + if not tp_smapi.installed(battery) then + naughty.notify { + preset = naughty.config.critical, + title = "tp_smapi: error while creating widget", + text = string.format("battery %s is not installed", battery) + } + all_batteries_installed = false + break + end + end + + if not all_batteries_installed then return end + + tpbat = { + batteries = batteries, + widget = args.widget or wibox.widget.textbox() + } + + function tpbat.update() + tpbat_now = { + n_status = {}, + n_perc = {}, + n_time = {}, + status = "N/A" + } + + for i = 1, #batteries do + tpbat_now.n_status[i] = tp_smapi.status(batteries[i]) or "N/A" + tpbat_now.n_perc[i] = tp_smapi.percentage(batteries[i]) + tpbat_now.n_time[i] = tp_smapi.time(batteries[i]) or "N/A" + + if not tpbat_now.n_status[i]:lower():match("full") then + tpbat_now.status = tpbat_now.n_status[i] + end + end + + widget = tpbat.widget -- backwards compatibility + settings() + end + + helpers.newtimer("thinkpad-batteries", timeout, tpbat.update) + + return tpbat + end + + return tp_smapi +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/cpu.lua b/home-modules/explicit-configs/awesome/lain/widget/cpu.lua new file mode 100644 index 0000000..6c51115 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/cpu.lua @@ -0,0 +1,75 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + * (c) 2010-2012, Peter Hofmann + +--]] + +local helpers = require("lain.helpers") +local wibox = require("wibox") +local math = math +local string = string + +-- CPU usage +-- lain.widget.cpu + +local function factory(args) + args = args or {} + + local cpu = { core = {}, widget = args.widget or wibox.widget.textbox() } + local timeout = args.timeout or 2 + local settings = args.settings or function() end + + function cpu.update() + -- Read the amount of time the CPUs have spent performing + -- different kinds of work. Read the first line of /proc/stat + -- which is the sum of all CPUs. + for index,time in pairs(helpers.lines_match("cpu","/proc/stat")) do + local coreid = index - 1 + local core = cpu.core[coreid] or + { last_active = 0 , last_total = 0, usage = 0 } + local at = 1 + local idle = 0 + local total = 0 + + for field in string.gmatch(time, "[%s]+([^%s]+)") do + -- 4 = idle, 5 = ioWait. Essentially, the CPUs have done + -- nothing during these times. + if at == 4 or at == 5 then + idle = idle + field + end + total = total + field + at = at + 1 + end + + local active = total - idle + + if core.last_active ~= active or core.last_total ~= total then + -- Read current data and calculate relative values. + local dactive = active - core.last_active + local dtotal = total - core.last_total + local usage = math.ceil(math.abs((dactive / dtotal) * 100)) + + core.last_active = active + core.last_total = total + core.usage = usage + + -- Save current data for the next run. + cpu.core[coreid] = core + end + end + + cpu_now = cpu.core + cpu_now.usage = cpu_now[0].usage + widget = cpu.widget + + settings() + end + + helpers.newtimer("cpu", timeout, cpu.update) + + return cpu +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/fs.lua b/home-modules/explicit-configs/awesome/lain/widget/fs.lua new file mode 100644 index 0000000..b3a2dad --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/fs.lua @@ -0,0 +1,156 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2018, Uli Schlacter + * (c) 2018, Otto Modinos + * (c) 2013, Luca CPZ + +--]] + +local helpers = require("lain.helpers") +local Gio = require("lgi").Gio +local focused = require("awful.screen").focused +local wibox = require("wibox") +local naughty = require("naughty") +local gears = require("gears") +local math = math +local string = string +local tconcat = table.concat +local type = type +local query_size = Gio.FILE_ATTRIBUTE_FILESYSTEM_SIZE +local query_free = Gio.FILE_ATTRIBUTE_FILESYSTEM_FREE +local query_used = Gio.FILE_ATTRIBUTE_FILESYSTEM_USED +local query = query_size .. "," .. query_free .. "," .. query_used + +-- File systems info +-- lain.widget.fs + +local function factory(args) + args = args or {} + + local fs = { + widget = args.widget or wibox.widget.textbox(), + units = { + [1] = "Kb", [2] = "Mb", [3] = "Gb", + [4] = "Tb", [5] = "Pb", [6] = "Eb", + [7] = "Zb", [8] = "Yb" + } + } + + function fs.hide() + if not fs.notification then return end + naughty.destroy(fs.notification) + fs.notification = nil + end + + function fs.show(seconds, scr) + fs.hide() + fs.update(function() + fs.notification_preset.screen = fs.followtag and focused() or scr or 1 + fs.notification = naughty.notify { + preset = fs.notification_preset, + timeout = type(seconds) == "number" and seconds or 5 + } + end) + end + + local timeout = args.timeout or 600 + local partition = args.partition + local threshold = args.threshold or 99 + local showpopup = args.showpopup or "on" + local settings = args.settings or function() end + + fs.followtag = args.followtag or false + fs.notification_preset = args.notification_preset + + if not fs.notification_preset then + fs.notification_preset = { + font = "Monospace 10", + fg = "#FFFFFF", + bg = "#000000" + } + end + + local function update_synced() + local pathlen = 10 + fs_now = {} + + local notifypaths = {} + for _, mount in ipairs(Gio.unix_mounts_get()) do + local path = Gio.unix_mount_get_mount_path(mount) + local root = Gio.File.new_for_path(path) + local info = root:query_filesystem_info(query) + + if info then + local size = info:get_attribute_uint64(query_size) + local used = info:get_attribute_uint64(query_used) + local free = info:get_attribute_uint64(query_free) + + if size > 0 then + local units = math.floor(math.log(size)/math.log(1024)) + + fs_now[path] = { + units = fs.units[units], + percentage = math.floor(100 * used / size), -- used percentage + size = size / math.pow(1024, units), + used = used / math.pow(1024, units), + free = free / math.pow(1024, units) + } + + if fs_now[path].percentage > 0 then -- don't notify unused file systems + notifypaths[#notifypaths+1] = path + + if #path > pathlen then + pathlen = #path + end + end + end + end + end + + widget = fs.widget + settings() + + if partition and fs_now[partition] and fs_now[partition].percentage >= threshold then + if not helpers.get_map(partition) then + naughty.notify { + preset = naughty.config.presets.critical, + title = "Warning", + text = string.format("%s is above %d%% (%d%%)", partition, threshold, fs_now[partition].percentage) + } + helpers.set_map(partition, true) + else + helpers.set_map(partition, false) + end + end + + local fmt = "%-" .. tostring(pathlen) .. "s %4s\t%6s\t%6s\n" + local notifytable = { [1] = string.format(fmt, "path", "used", "free", "size") } + fmt = "\n%-" .. tostring(pathlen) .. "s %3s%%\t%6.2f\t%6.2f %s" + for _, path in ipairs(notifypaths) do + notifytable[#notifytable+1] = string.format(fmt, path, fs_now[path].percentage, fs_now[path].free, fs_now[path].size, fs_now[path].units) + end + + fs.notification_preset.text = tconcat(notifytable) + end + + function fs.update(callback) + Gio.Async.start(gears.protected_call.call)(function() + update_synced() + if type(callback) == "function" and callback then + callback() + end + end) + end + + if showpopup == "on" then + fs.widget:connect_signal('mouse::enter', function () fs.show(0) end) + fs.widget:connect_signal('mouse::leave', function () fs.hide() end) + end + + helpers.newtimer(partition or "fs", timeout, fs.update) + + return fs +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/imap.lua b/home-modules/explicit-configs/awesome/lain/widget/imap.lua new file mode 100644 index 0000000..e3f7baa --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/imap.lua @@ -0,0 +1,94 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + +--]] + +local helpers = require("lain.helpers") +local naughty = require("naughty") +local wibox = require("wibox") +local awful = require("awful") +local string = string +local type = type +local tonumber = tonumber + +-- Mail IMAP check +-- lain.widget.imap + +local function factory(args) + args = args or {} + + local imap = { widget = args.widget or wibox.widget.textbox() } + local server = args.server + local mail = args.mail + local password = args.password + local port = args.port or 993 + local timeout = args.timeout or 60 + local pwdtimeout = args.pwdtimeout or 10 + local is_plain = args.is_plain or false + local followtag = args.followtag or false + local notify = args.notify or "on" + local settings = args.settings or function() end + + local head_command = "curl --connect-timeout 3 -fsm 3" + local request = "-X 'STATUS INBOX (MESSAGES RECENT UNSEEN)'" + + if not server or not mail or not password then return end + + mail_notification_preset = { + icon = helpers.icons_dir .. "mail.png", + position = "top_left" + } + + helpers.set_map(mail, 0) + + if not is_plain then + if type(password) == "string" or type(password) == "table" then + helpers.async(password, function(f) password = f:gsub("\n", "") end) + elseif type(password) == "function" then + imap.pwdtimer = helpers.newtimer(mail .. "-password", pwdtimeout, function() + local retrieved_password, try_again = password() + if not try_again then + imap.pwdtimer:stop() -- stop trying to retrieve + password = retrieved_password or "" -- failsafe + end + end, true, true) + end + end + + function imap.update() + -- do not update if the password has not been retrieved yet + if type(password) ~= "string" then return end + + local curl = string.format("%s --url imaps://%s:%s/INBOX -u %s:'%s' %s -k", + head_command, server, port, mail, password, request) + + helpers.async(curl, function(f) + imap_now = { ["MESSAGES"] = 0, ["RECENT"] = 0, ["UNSEEN"] = 0 } + + for s,d in f:gmatch("(%w+)%s+(%d+)") do imap_now[s] = tonumber(d) end + mailcount = imap_now["UNSEEN"] -- backwards compatibility + widget = imap.widget + + settings() + + if notify == "on" and mailcount and mailcount >= 1 and mailcount > helpers.get_map(mail) then + if followtag then mail_notification_preset.screen = awful.screen.focused() end + naughty.notify { + preset = mail_notification_preset, + text = string.format("%s has %d new message%s", mail, mailcount, mailcount == 1 and "" or "s") + } + end + + helpers.set_map(mail, imap_now["UNSEEN"]) + end) + + end + + imap.timer = helpers.newtimer(mail, timeout, imap.update, true, true) + + return imap +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/init.lua b/home-modules/explicit-configs/awesome/lain/widget/init.lua new file mode 100644 index 0000000..57b86bb --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/init.lua @@ -0,0 +1,19 @@ +--[[ + + Lain + Layouts, widgets and utilities for Awesome WM + + Widgets section + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + * (c) 2010-2012, Peter Hofmann + +--]] + +local wrequire = require("lain.helpers").wrequire +local setmetatable = setmetatable + +local widget = { _NAME = "lain.widget" } + +return setmetatable(widget, { __index = wrequire }) diff --git a/home-modules/explicit-configs/awesome/lain/widget/mem.lua b/home-modules/explicit-configs/awesome/lain/widget/mem.lua new file mode 100644 index 0000000..0318494 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/mem.lua @@ -0,0 +1,51 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + * (c) 2010-2012, Peter Hofmann + +--]] + +local helpers = require("lain.helpers") +local wibox = require("wibox") +local gmatch, lines, floor = string.gmatch, io.lines, math.floor + +-- Memory usage (ignoring caches) +-- lain.widget.mem + +local function factory(args) + args = args or {} + + local mem = { widget = args.widget or wibox.widget.textbox() } + local timeout = args.timeout or 2 + local settings = args.settings or function() end + + function mem.update() + mem_now = {} + for line in lines("/proc/meminfo") do + for k, v in gmatch(line, "([%a]+):[%s]+([%d]+).+") do + if k == "MemTotal" then mem_now.total = floor(v / 1024 + 0.5) + elseif k == "MemFree" then mem_now.free = floor(v / 1024 + 0.5) + elseif k == "Buffers" then mem_now.buf = floor(v / 1024 + 0.5) + elseif k == "Cached" then mem_now.cache = floor(v / 1024 + 0.5) + elseif k == "SwapTotal" then mem_now.swap = floor(v / 1024 + 0.5) + elseif k == "SwapFree" then mem_now.swapf = floor(v / 1024 + 0.5) + elseif k == "SReclaimable" then mem_now.srec = floor(v / 1024 + 0.5) + end + end + end + + mem_now.used = mem_now.total - mem_now.free - mem_now.buf - mem_now.cache - mem_now.srec + mem_now.swapused = mem_now.swap - mem_now.swapf + mem_now.perc = math.floor(mem_now.used / mem_now.total * 100) + + widget = mem.widget + settings() + end + + helpers.newtimer("mem", timeout, mem.update) + + return mem +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/mpd.lua b/home-modules/explicit-configs/awesome/lain/widget/mpd.lua new file mode 100644 index 0000000..55d3649 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/mpd.lua @@ -0,0 +1,135 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + * (c) 2010, Adrian C. + +--]] + +local helpers = require("lain.helpers") +local shell = require("awful.util").shell +local escape_f = require("awful.util").escape +local focused = require("awful.screen").focused +local naughty = require("naughty") +local wibox = require("wibox") +local os = os +local string = string + +-- MPD infos +-- lain.widget.mpd + +local function factory(args) + args = args or {} + + local mpd = { widget = args.widget or wibox.widget.textbox() } + local timeout = args.timeout or 2 + local password = (args.password and #args.password > 0 and string.format("password %s\\n", args.password)) or "" + local host = args.host or os.getenv("MPD_HOST") or "127.0.0.1" + local port = args.port or os.getenv("MPD_PORT") or "6600" + local music_dir = args.music_dir or os.getenv("HOME") .. "/Music" + local cover_pattern = args.cover_pattern or "*\\.(jpg|jpeg|png|gif)$" + local cover_size = args.cover_size or 100 + local default_art = args.default_art + local notify = args.notify or "on" + local followtag = args.followtag or false + local settings = args.settings or function() end + + local mpdh = string.format("telnet://%s:%s", host, port) + local echo = string.format("printf \"%sstatus\\ncurrentsong\\nclose\\n\"", password) + local cmd = string.format("%s | curl --connect-timeout 1 -fsm 3 %s", echo, mpdh) + + mpd_notification_preset = { title = "Now playing", timeout = 6 } + + helpers.set_map("current mpd track", nil) + + function mpd.update() + helpers.async({ shell, "-c", cmd }, function(f) + mpd_now = { + random_mode = false, + single_mode = false, + repeat_mode = false, + consume_mode = false, + pls_pos = "N/A", + pls_len = "N/A", + state = "N/A", + file = "N/A", + name = "N/A", + artist = "N/A", + title = "N/A", + album = "N/A", + genre = "N/A", + track = "N/A", + date = "N/A", + time = "N/A", + elapsed = "N/A", + volume = "N/A" + } + + for line in string.gmatch(f, "[^\n]+") do + for k, v in string.gmatch(line, "([%w]+):[%s](.*)$") do + if k == "state" then mpd_now.state = v + elseif k == "file" then mpd_now.file = v + elseif k == "Name" then mpd_now.name = escape_f(v) + elseif k == "Artist" then mpd_now.artist = escape_f(v) + elseif k == "Title" then mpd_now.title = escape_f(v) + elseif k == "Album" then mpd_now.album = escape_f(v) + elseif k == "Genre" then mpd_now.genre = escape_f(v) + elseif k == "Track" then mpd_now.track = escape_f(v) + elseif k == "Date" then mpd_now.date = escape_f(v) + elseif k == "Time" then mpd_now.time = v + elseif k == "elapsed" then mpd_now.elapsed = string.match(v, "%d+") + elseif k == "song" then mpd_now.pls_pos = v + elseif k == "playlistlength" then mpd_now.pls_len = v + elseif k == "repeat" then mpd_now.repeat_mode = v ~= "0" + elseif k == "single" then mpd_now.single_mode = v ~= "0" + elseif k == "random" then mpd_now.random_mode = v ~= "0" + elseif k == "consume" then mpd_now.consume_mode = v ~= "0" + elseif k == "volume" then mpd_now.volume = v + end + end + end + + mpd_notification_preset.text = string.format("%s (%s) - %s\n%s", mpd_now.artist, + mpd_now.album, mpd_now.date, mpd_now.title) + widget = mpd.widget + settings() + + if mpd_now.state == "play" then + if notify == "on" and mpd_now.title ~= helpers.get_map("current mpd track") then + helpers.set_map("current mpd track", mpd_now.title) + + if followtag then mpd_notification_preset.screen = focused() end + + local common = { + preset = mpd_notification_preset, + icon = default_art, + icon_size = cover_size, + replaces_id = mpd.id + } + + if not string.match(mpd_now.file, "http.*://") then -- local file instead of http stream + local path = string.format("%s/%s", music_dir, string.match(mpd_now.file, ".*/")) + local cover = string.format("find '%s' -maxdepth 1 -type f | egrep -i -m1 '%s'", + path:gsub("'", "'\\''"), cover_pattern) + helpers.async({ shell, "-c", cover }, function(current_icon) + common.icon = current_icon:gsub("\n", "") + if #common.icon == 0 then common.icon = nil end + mpd.id = naughty.notify(common).id + end) + else + mpd.id = naughty.notify(common).id + end + + end + elseif mpd_now.state ~= "pause" then + helpers.set_map("current mpd track", nil) + end + end) + end + + mpd.timer = helpers.newtimer("mpd", timeout, mpd.update, true, true) + + return mpd +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/net.lua b/home-modules/explicit-configs/awesome/lain/widget/net.lua new file mode 100644 index 0000000..9b7b165 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/net.lua @@ -0,0 +1,122 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + * (c) 2010-2012, Peter Hofmann + +--]] + +local helpers = require("lain.helpers") +local naughty = require("naughty") +local wibox = require("wibox") +local string = string + +-- Network infos +-- lain.widget.net + +local function factory(args) + args = args or {} + + local net = { widget = args.widget or wibox.widget.textbox(), devices = {} } + local timeout = args.timeout or 2 + local units = args.units or 1024 -- KB + local notify = args.notify or "on" + local wifi_state = args.wifi_state or "off" + local eth_state = args.eth_state or "off" + local screen = args.screen or 1 + local format = args.format or "%.1f" + local settings = args.settings or function() end + + -- Compatibility with old API where iface was a string corresponding to 1 interface + net.iface = (args.iface and (type(args.iface) == "string" and {args.iface}) or + (type(args.iface) == "table" and args.iface)) or {} + + function net.get_devices() + net.iface = {} -- reset at every call + helpers.line_callback("ip link", function(line) + net.iface[#net.iface + 1] = not string.match(line, "LOOPBACK") and string.match(line, "(%w+): <") or nil + end) + end + + if #net.iface == 0 then net.get_devices() end + + function net.update() + -- These are the totals over all specified interfaces + net_now = { + devices = {}, + -- Bytes since last iteration + sent = 0, + received = 0 + } + + for _, dev in ipairs(net.iface) do + local dev_now = {} + local dev_before = net.devices[dev] or { last_t = 0, last_r = 0 } + local now_t = tonumber(helpers.first_line(string.format("/sys/class/net/%s/statistics/tx_bytes", dev)) or 0) + local now_r = tonumber(helpers.first_line(string.format("/sys/class/net/%s/statistics/rx_bytes", dev)) or 0) + + dev_now.carrier = helpers.first_line(string.format("/sys/class/net/%s/carrier", dev)) or "0" + dev_now.state = helpers.first_line(string.format("/sys/class/net/%s/operstate", dev)) or "down" + + dev_now.sent = (now_t - dev_before.last_t) / timeout / units + dev_now.received = (now_r - dev_before.last_r) / timeout / units + + net_now.sent = net_now.sent + dev_now.sent + net_now.received = net_now.received + dev_now.received + + dev_now.sent = string.format(format, dev_now.sent) + dev_now.received = string.format(format, dev_now.received) + + dev_now.last_t = now_t + dev_now.last_r = now_r + + if wifi_state == "on" and helpers.first_line(string.format("/sys/class/net/%s/uevent", dev)) == "DEVTYPE=wlan" then + dev_now.wifi = true + if string.match(dev_now.carrier, "1") then + dev_now.signal = tonumber(string.match(helpers.lines_from("/proc/net/wireless")[3], "(%-%d+%.)")) or nil + end + else + dev_now.wifi = false + end + + if eth_state == "on" and helpers.first_line(string.format("/sys/class/net/%s/uevent", dev)) ~= "DEVTYPE=wlan" then + dev_now.ethernet = true + else + dev_now.ethernet = false + end + + net.devices[dev] = dev_now + + -- Notify only once when connection is lost + if string.match(dev_now.carrier, "0") and notify == "on" and helpers.get_map(dev) then + naughty.notify { + title = dev, + text = "No carrier", + icon = helpers.icons_dir .. "no_net.png", + screen = screen + } + helpers.set_map(dev, false) + elseif string.match(dev_now.carrier, "1") then + helpers.set_map(dev, true) + end + + net_now.carrier = dev_now.carrier + net_now.state = dev_now.state + net_now.devices[dev] = dev_now + -- net_now.sent and net_now.received will be + -- the totals across all specified devices + end + + net_now.sent = string.format(format, net_now.sent) + net_now.received = string.format(format, net_now.received) + + widget = net.widget + settings() + end + + helpers.newtimer("network", timeout, net.update) + + return net +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/pulse.lua b/home-modules/explicit-configs/awesome/lain/widget/pulse.lua new file mode 100644 index 0000000..69f4d70 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/pulse.lua @@ -0,0 +1,58 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2016, Luca CPZ + +--]] + +local helpers = require("lain.helpers") +local shell = require("awful.util").shell +local wibox = require("wibox") +local string = string +local type = type + +-- PulseAudio volume +-- lain.widget.pulse + +local function factory(args) + args = args or {} + + local pulse = { widget = args.widget or wibox.widget.textbox(), device = "N/A" } + local timeout = args.timeout or 5 + local settings = args.settings or function() end + + pulse.devicetype = args.devicetype or "sink" + pulse.cmd = args.cmd or "pacmd list-" .. pulse.devicetype .. "s | sed -n -e '/*/,$!d' -e '/index/p' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'" + + function pulse.update() + helpers.async({ shell, "-c", type(pulse.cmd) == "string" and pulse.cmd or pulse.cmd() }, + function(s) + volume_now = { + index = string.match(s, "index: (%S+)") or "N/A", + device = string.match(s, "device.string = \"(%S+)\"") or "N/A", + muted = string.match(s, "muted: (%S+)") or "N/A" + } + + pulse.device = volume_now.index + + local ch = 1 + volume_now.channel = {} + for v in string.gmatch(s, ":.-(%d+)%%") do + volume_now.channel[ch] = v + ch = ch + 1 + end + + volume_now.left = volume_now.channel[1] or "N/A" + volume_now.right = volume_now.channel[2] or "N/A" + + widget = pulse.widget + settings() + end) + end + + helpers.newtimer("pulse", timeout, pulse.update) + + return pulse +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/pulsebar.lua b/home-modules/explicit-configs/awesome/lain/widget/pulsebar.lua new file mode 100644 index 0000000..19e73b9 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/pulsebar.lua @@ -0,0 +1,175 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + * (c) 2013, Rman + +--]] + +local helpers = require("lain.helpers") +local awful = require("awful") +local naughty = require("naughty") +local wibox = require("wibox") +local math = math +local string = string +local type = type +local tonumber = tonumber + +-- PulseAudio volume bar +-- lain.widget.pulsebar + +local function factory(args) + local pulsebar = { + colors = { + background = "#000000", + mute_background = "#000000", + mute = "#EB8F8F", + unmute = "#A4CE8A" + }, + + _current_level = 0, + _mute = "no", + device = "N/A" + } + + args = args or {} + + local timeout = args.timeout or 5 + local settings = args.settings or function() end + local width = args.width or 63 + local height = args.height or 1 + local margins = args.margins or 1 + local paddings = args.paddings or 1 + local ticks = args.ticks or false + local ticks_size = args.ticks_size or 7 + local tick = args.tick or "|" + local tick_pre = args.tick_pre or "[" + local tick_post = args.tick_post or "]" + local tick_none = args.tick_none or " " + + pulsebar.colors = args.colors or pulsebar.colors + pulsebar.followtag = args.followtag or false + pulsebar.notification_preset = args.notification_preset + pulsebar.devicetype = args.devicetype or "sink" + pulsebar.cmd = args.cmd or "pacmd list-" .. pulsebar.devicetype .. "s | sed -n -e '/*/,$!d' -e '/index/p' -e '/base volume/d' -e '/volume:/p' -e '/muted:/p' -e '/device\\.string/p'" + + if not pulsebar.notification_preset then + pulsebar.notification_preset = { + font = "Monospace 10" + } + end + + pulsebar.bar = wibox.widget { + color = pulsebar.colors.unmute, + background_color = pulsebar.colors.background, + forced_height = height, + forced_width = width, + margins = margins, + paddings = paddings, + ticks = ticks, + ticks_size = ticks_size, + widget = wibox.widget.progressbar, + } + + pulsebar.tooltip = awful.tooltip({ objects = { pulsebar.bar } }) + + function pulsebar.update(callback) + helpers.async({ awful.util.shell, "-c", type(pulsebar.cmd) == "string" and pulsebar.cmd or pulsebar.cmd() }, + function(s) + volume_now = { + index = string.match(s, "index: (%S+)") or "N/A", + device = string.match(s, "device.string = \"(%S+)\"") or "N/A", + muted = string.match(s, "muted: (%S+)") or "N/A" + } + + pulsebar.device = volume_now.index + + local ch = 1 + volume_now.channel = {} + for v in string.gmatch(s, ":.-(%d+)%%") do + volume_now.channel[ch] = v + ch = ch + 1 + end + + volume_now.left = volume_now.channel[1] or "N/A" + volume_now.right = volume_now.channel[2] or "N/A" + + local volu = volume_now.left + local mute = volume_now.muted + + if volu:match("N/A") or mute:match("N/A") then return end + + if volu ~= pulsebar._current_level or mute ~= pulsebar._mute then + pulsebar._current_level = tonumber(volu) + pulsebar.bar:set_value(pulsebar._current_level / 100) + if pulsebar._current_level == 0 or mute == "yes" then + pulsebar._mute = mute + pulsebar.tooltip:set_text ("[muted]") + pulsebar.bar.color = pulsebar.colors.mute + pulsebar.bar.background_color = pulsebar.colors.mute_background + else + pulsebar._mute = "no" + pulsebar.tooltip:set_text(string.format("%s %s: %s", pulsebar.devicetype, pulsebar.device, volu)) + pulsebar.bar.color = pulsebar.colors.unmute + pulsebar.bar.background_color = pulsebar.colors.background + end + + settings() + + if type(callback) == "function" then callback() end + end + end) + end + + function pulsebar.notify() + pulsebar.update(function() + local preset = pulsebar.notification_preset + + preset.title = string.format("%s %s - %s%%", pulsebar.devicetype, pulsebar.device, pulsebar._current_level) + + if pulsebar._mute == "yes" then + preset.title = preset.title .. " muted" + end + + -- tot is the maximum number of ticks to display in the notification + -- fallback: default horizontal wibox height + local wib, tot = awful.screen.focused().mywibox, 20 + + -- if we can grab mywibox, tot is defined as its height if + -- horizontal, or width otherwise + if wib then + if wib.position == "left" or wib.position == "right" then + tot = wib.width + else + tot = wib.height + end + end + + local int = math.modf((pulsebar._current_level / 100) * tot) + preset.text = string.format( + "%s%s%s%s", + tick_pre, + string.rep(tick, int), + string.rep(tick_none, tot - int), + tick_post + ) + + if pulsebar.followtag then preset.screen = awful.screen.focused() end + + if not pulsebar.notification then + pulsebar.notification = naughty.notify { + preset = preset, + destroy = function() pulsebar.notification = nil end + } + else + naughty.replace_text(pulsebar.notification, preset.title, preset.text) + end + end) + end + + helpers.newtimer(string.format("pulsebar-%s-%s", pulsebar.devicetype, pulsebar.device), timeout, pulsebar.update) + + return pulsebar +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/sysload.lua b/home-modules/explicit-configs/awesome/lain/widget/sysload.lua new file mode 100644 index 0000000..7260524 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/sysload.lua @@ -0,0 +1,39 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + * (c) 2010-2012, Peter Hofmann + +--]] + +local helpers = require("lain.helpers") +local wibox = require("wibox") +local open, match = io.open, string.match + +-- System load +-- lain.widget.sysload + +local function factory(args) + args = args or {} + + local sysload = { widget = args.widget or wibox.widget.textbox() } + local timeout = args.timeout or 2 + local settings = args.settings or function() end + + function sysload.update() + local f = open("/proc/loadavg") + local ret = f:read("*all") + f:close() + + load_1, load_5, load_15 = match(ret, "([^%s]+) ([^%s]+) ([^%s]+)") + + widget = sysload.widget + settings() + end + + helpers.newtimer("sysload", timeout, sysload.update) + + return sysload +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/temp.lua b/home-modules/explicit-configs/awesome/lain/widget/temp.lua new file mode 100644 index 0000000..99f8700 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/temp.lua @@ -0,0 +1,50 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2013, Luca CPZ + +--]] + +local helpers = require("lain.helpers") +local wibox = require("wibox") +local tonumber = tonumber + +-- {thermal,core} temperature info +-- lain.widget.temp + +local function factory(args) + args = args or {} + + local temp = { widget = args.widget or wibox.widget.textbox() } + local timeout = args.timeout or 30 + local tempfile = args.tempfile or "/sys/devices/virtual/thermal/thermal_zone0/temp" + local format = args.format or "%.1f" + local settings = args.settings or function() end + + function temp.update() + helpers.async({"find", "/sys/devices", "-type", "f", "-name", "*temp*"}, function(f) + temp_now = {} + local temp_fl, temp_value + for t in f:gmatch("[^\n]+") do + temp_fl = helpers.first_line(t) + if temp_fl then + temp_value = tonumber(temp_fl) + temp_now[t] = temp_value and temp_value/1e3 or temp_fl + end + end + if temp_now[tempfile] then + coretemp_now = string.format(format, temp_now[tempfile]) + else + coretemp_now = "N/A" + end + widget = temp.widget + settings() + end) + end + + helpers.newtimer("thermal", timeout, temp.update) + + return temp +end + +return factory diff --git a/home-modules/explicit-configs/awesome/lain/widget/weather.lua b/home-modules/explicit-configs/awesome/lain/widget/weather.lua new file mode 100644 index 0000000..93028b5 --- /dev/null +++ b/home-modules/explicit-configs/awesome/lain/widget/weather.lua @@ -0,0 +1,149 @@ +--[[ + + Licensed under GNU General Public License v2 + * (c) 2015, Luca CPZ + +--]] + +local helpers = require("lain.helpers") +local json = require("lain.util").dkjson +local focused = require("awful.screen").focused +local naughty = require("naughty") +local wibox = require("wibox") +local math = math +local os = os +local string = string +local type = type +local tonumber = tonumber + +-- OpenWeatherMap +-- current weather and X-days forecast +-- lain.widget.weather + +local function factory(args) + args = args or {} + + local weather = { widget = args.widget or wibox.widget.textbox() } + local APPID = args.APPID -- mandatory api key + local timeout = args.timeout or 900 -- 15 min + local current_call = args.current_call or "curl -s 'https://api.openweathermap.org/data/2.5/weather?lat=%s&lon=%s&APPID=%s&units=%s&lang=%s'" + local forecast_call = args.forecast_call or "curl -s 'https://api.openweathermap.org/data/2.5/forecast?lat=%s&lon=%s&APPID=%s&cnt=%s&units=%s&lang=%s'" + local lat = args.lat or 0 -- placeholder + local lon = args.lon or 0 -- placeholder + local units = args.units or "metric" + local lang = args.lang or "en" + local cnt = args.cnt or 5 + local icons_path = args.icons_path or helpers.icons_dir .. "openweathermap/" + local notification_preset = args.notification_preset or {} + local notification_text_fun = args.notification_text_fun or + function (wn) + local day = os.date("%a %d", wn["dt"]) + local temp = math.floor(wn["main"]["temp"]) + local desc = wn["weather"][1]["description"] + return string.format("%s: %s, %d ", day, desc, temp) + end + local weather_na_markup = args.weather_na_markup or " N/A " + local followtag = args.followtag or false + local showpopup = args.showpopup or "on" + local settings = args.settings or function() end + + weather.widget:set_markup(weather_na_markup) + weather.icon_path = icons_path .. "na.png" + weather.icon = wibox.widget.imagebox(weather.icon_path) + + function weather.show(seconds) + weather.hide() + + if followtag then + notification_preset.screen = focused() + end + + if not weather.notification_text then + weather.update() + weather.forecast_update() + end + + weather.notification = naughty.notify { + preset = notification_preset, + text = weather.notification_text, + icon = weather.icon_path, + timeout = type(seconds) == "number" and seconds or notification_preset.timeout + } + end + + function weather.hide() + if weather.notification then + naughty.destroy(weather.notification) + weather.notification = nil + end + end + + function weather.attach(obj) + obj:connect_signal("mouse::enter", function() + weather.show(0) + end) + obj:connect_signal("mouse::leave", function() + weather.hide() + end) + end + + function weather.forecast_update() + local cmd = string.format(forecast_call, lat, lon, APPID, cnt, units, lang) + + helpers.async(cmd, function(f) + local err + weather_now, _, err = json.decode(f, 1, nil) + + if not err and type(weather_now) == "table" and tonumber(weather_now["cod"]) == 200 then + weather.notification_text = "" + for i = 1, weather_now["cnt"], math.floor(weather_now["cnt"] / cnt) do + weather.notification_text = weather.notification_text .. + notification_text_fun(weather_now["list"][i]) + if i < weather_now["cnt"] then + weather.notification_text = weather.notification_text .. "\n" + end + end + end + end) + end + + function weather.update() + local cmd = string.format(current_call, lat, lon, APPID, units, lang) + + helpers.async(cmd, function(f) + local err + weather_now, _, err = json.decode(f, 1, nil) + + if not err and type(weather_now) == "table" and tonumber(weather_now["cod"]) == 200 then + local sunrise = tonumber(weather_now["sys"]["sunrise"]) + local sunset = tonumber(weather_now["sys"]["sunset"]) + local icon = weather_now["weather"][1]["icon"] + local loc_now = os.time() + + if sunrise <= loc_now and loc_now <= sunset then + icon = string.gsub(icon, "n", "d") + else + icon = string.gsub(icon, "d", "n") + end + + weather.icon_path = icons_path .. icon .. ".png" + widget = weather.widget + settings() + else + weather.icon_path = icons_path .. "na.png" + weather.widget:set_markup(weather_na_markup) + end + + weather.icon:set_image(weather.icon_path) + end) + end + + if showpopup == "on" then weather.attach(weather.widget) end + + weather.timer = helpers.newtimer("weather-" .. lat .. ":" .. lon, timeout, weather.update, false, true) + weather.timer_forecast = helpers.newtimer("weather_forecast-" .. lat .. ":" .. lon, timeout, weather.forecast_update, false, true) + + return weather +end + +return factory diff --git a/home-modules/explicit-configs/awesome/rc.lua b/home-modules/explicit-configs/awesome/rc.lua new file mode 100644 index 0000000..e2e2f5a --- /dev/null +++ b/home-modules/explicit-configs/awesome/rc.lua @@ -0,0 +1,665 @@ +-- If LuaRocks is installed, make sure that packages installed through it are +-- found (e.g. lgi). If LuaRocks is not installed, do nothing. +pcall(require, "luarocks.loader") + +-- Standard awesome library +local gears = require("gears") +local awful = require("awful") + +awful.screen.set_auto_dpi_enabled(true) + +require("awful.autofocus") +-- Widget and layout library +local wibox = require("wibox") +-- Theme handling library +local beautiful = require("beautiful") +-- Notification library +local naughty = require("naughty") +local menubar = require("menubar") +local hotkeys_popup = require("awful.hotkeys_popup") + +-- External deps. +local deficient = require("deficient") +local battery_widget = deficient.battery_widget {} +local logout_popup = require("awesome-wm-widgets.logout-popup-widget.logout-popup") +local lain = require("lain") + + +-- Enable hotkeys help widget for VIM and other apps +-- when client with a matching name is opened: +require("awful.hotkeys_popup.keys") + +-- {{{ Error handling +-- Check if awesome encountered an error during startup and fell back to +-- another config (This code will only ever execute for the fallback config) +if awesome.startup_errors then + naughty.notify({ preset = naughty.config.presets.critical, + title = "Oops, there were errors during startup!", + text = awesome.startup_errors }) +end + +-- Handle runtime errors after startup +do + local in_error = false + awesome.connect_signal("debug::error", function (err) + -- Make sure we don't go into an endless error loop + if in_error then return end + in_error = true + + naughty.notify({ preset = naughty.config.presets.critical, + title = "Oops, an error happened!", + text = tostring(err) }) + in_error = false + end) +end +-- }}} + +-- {{{ Variable definitions +-- Themes define colours, icons, font and wallpapers. +beautiful.init(gears.filesystem.get_themes_dir() .. "default/theme.lua") + +-- This is used later as the default terminal and editor to run. +terminal = "ghostty" +editor = os.getenv("EDITOR") or "nvim" +editor_cmd = terminal .. " -e " .. editor + +-- Default modkey. +-- Usually, Mod4 is the key with a logo between Control and Alt. +-- If you do not like this or do not have such a key, +-- I suggest you to remap Mod4 to another key using xmodmap or other tools. +-- However, you can use another modifier like Mod1, but it may interact with others. +modkey = "Mod4" + +-- Table of layouts to cover with awful.layout.inc, order matters. +awful.layout.layouts = { + awful.layout.suit.tile, + awful.layout.suit.max, + lain.layout.centerwork, +} +-- }}} + +-- {{{ Menu +-- Create a launcher widget and a main menu +myawesomemenu = { + { "hotkeys", function() hotkeys_popup.show_help(nil, awful.screen.focused()) end }, + { "manual", terminal .. " -e man awesome" }, + { "edit config", editor_cmd .. " " .. awesome.conffile }, + { "restart", awesome.restart }, + { "quit", function() awesome.quit() end }, +} + +mymainmenu = awful.menu({ items = { { "awesome", myawesomemenu, beautiful.awesome_icon }, + { "open terminal", terminal } + } + }) + +mylauncher = awful.widget.launcher({ image = beautiful.awesome_icon, + menu = mymainmenu }) + +-- Menubar configuration +menubar.utils.terminal = terminal -- Set the terminal for applications that require it +-- }}} + +-- {{{ Wibar +-- Create a textclock widget +mytextclock = wibox.widget.textclock() + +-- Create a wibox for each screen and add it +local taglist_buttons = gears.table.join( + awful.button({ }, 1, function(t) t:view_only() end), + awful.button({ modkey }, 1, function(t) + if client.focus then + client.focus:move_to_tag(t) + end + end), + awful.button({ }, 3, awful.tag.viewtoggle), + awful.button({ modkey }, 3, function(t) + if client.focus then + client.focus:toggle_tag(t) + end + end), + awful.button({ }, 4, function(t) awful.tag.viewnext(t.screen) end), + awful.button({ }, 5, function(t) awful.tag.viewprev(t.screen) end) + ) + +local tasklist_buttons = gears.table.join( + awful.button({ }, 1, function (c) + if c == client.focus then + c.minimized = true + else + c:emit_signal( + "request::activate", + "tasklist", + {raise = true} + ) + end + end), + awful.button({ }, 3, function() + awful.menu.client_list({ theme = { width = 250 } }) + end), + awful.button({ }, 4, function () + awful.client.focus.byidx(1) + end), + awful.button({ }, 5, function () + awful.client.focus.byidx(-1) + end)) + +local function set_wallpaper(_) + -- Wallpaper + awful.spawn.with_shell("nitrogen --restore") +end + +-- Re-set wallpaper when a screen's geometry changes (e.g. different resolution) +screen.connect_signal("property::geometry", set_wallpaper) + +awful.screen.connect_for_each_screen(function(s) + -- Wallpaper + set_wallpaper(s) + + local layouts = awful.layout.layouts + local tags = { + names = { "www", "term", "edt", "eda", "5", "6", "music", "todo", "com" }, + layouts = { layouts[1], layouts[1], layouts[1], layouts[2], layouts[1], + layouts[1], layouts[1], layouts[1], layouts[1] } + } + + -- Each screen has its own tag table. + awful.tag(tags.names, s, tags.layouts) + -- Make reading + -- comfortable on a wide monitor with the centerwork layout. + s.tags[1].master_width_factor = 0.7 + s.tags[2].master_width_factor = 0.7 + + -- Create a promptbox for each screen + -- s.mypromptbox = awful.widget.prompt() + -- Create an imagebox widget which will contain an icon indicating which layout we're using. + -- We need one layoutbox per screen. + s.mylayoutbox = awful.widget.layoutbox(s) + s.mylayoutbox:buttons(gears.table.join( + awful.button({ }, 1, function () awful.layout.inc( 1) end), + awful.button({ }, 3, function () awful.layout.inc(-1) end), + awful.button({ }, 4, function () awful.layout.inc( 1) end), + awful.button({ }, 5, function () awful.layout.inc(-1) end))) + + -- Create a taglist widget + s.mytaglist = awful.widget.taglist { + screen = s, + filter = awful.widget.taglist.filter.all, + buttons = taglist_buttons + } + + -- Create a tasklist widget + s.mytasklist = awful.widget.tasklist { + screen = s, + filter = awful.widget.tasklist.filter.currenttags, + buttons = tasklist_buttons + } + + -- Create the wibox + s.mywibox = awful.wibar({ position = "top", screen = s }) + + -- Add widgets to the wibox + s.mywibox:setup { + layout = wibox.layout.align.horizontal, + { -- Left widgets + layout = wibox.layout.fixed.horizontal, + mylauncher, + s.mytaglist, + s.mypromptbox, + }, + s.mytasklist, -- Middle widget + { -- Right widgets + layout = wibox.layout.fixed.horizontal, + wibox.widget.systray(), + battery_widget, + mytextclock, + s.mylayoutbox, + }, + } +end) +-- }}} + +-- {{{ Mouse bindings +root.buttons(gears.table.join( + awful.button({ }, 3, function () mymainmenu:toggle() end), + awful.button({ }, 4, awful.tag.viewnext), + awful.button({ }, 5, awful.tag.viewprev) +)) +-- }}} + +-- {{{ Key bindings +globalkeys = gears.table.join( + awful.key({ modkey, }, "s", hotkeys_popup.show_help, + {description="show help", group="awesome"}), + awful.key({ modkey, }, "Left", awful.tag.viewprev, + {description = "view previous", group = "tag"}), + awful.key({ modkey, }, "Right", awful.tag.viewnext, + {description = "view next", group = "tag"}), + awful.key({ modkey, }, "Escape", awful.tag.history.restore, + {description = "go back", group = "tag"}), + ------------------------ Focus -------------------------- + awful.key({ modkey, }, "e", + function () + awful.client.focus.byidx( 1) + end, + {description = "focus next by index", group = "client"} + ), + awful.key({ modkey, }, "n", + function () + awful.client.focus.byidx(-1) + end, + {description = "focus previous by index", group = "client"} + ), + awful.key({ modkey, }, "w", function () mymainmenu:show() end, + {description = "show main menu", group = "awesome"}), + + -- Layout manipulation + awful.key({ modkey, "Shift" }, "e", function () awful.client.swap.byidx( 1) end, + {description = "swap with next client by index", group = "client"}), + awful.key({ modkey, "Shift" }, "n", function () awful.client.swap.byidx( -1) end, + {description = "swap with previous client by index", group = "client"}), + + awful.key({ modkey, "Control" }, "e", function () awful.screen.focus_relative( 1) end, -- Possibly has conflicts + {description = "focus the next screen", group = "screen"}), + awful.key({ modkey, "Control" }, "n", function () awful.screen.focus_relative(-1) end, + {description = "focus the previous screen", group = "screen"}), + + -- awful.key({ modkey, }, "u", awful.client.urgent.jumpto, + -- {description = "jump to urgent client", group = "client"}), + awful.key({ modkey, }, "Tab", + function () + awful.client.focus.history.previous() + if client.focus then + client.focus:raise() + end + end, + {description = "go back", group = "client"}), + + -- Standard program + awful.key({ modkey, }, "Return", function () awful.spawn(terminal) end, + {description = "open a terminal", group = "launcher"}), + awful.key({ modkey, "Control" }, "r", awesome.restart, + {description = "reload awesome", group = "awesome"}), + awful.key({ modkey, "Control" }, "q", awesome.quit, + {description = "quit awesome", group = "awesome"}), + + awful.key({ modkey, }, "i", function () awful.tag.incmwfact( 0.05) end, + {description = "increase master width factor", group = "layout"}), + awful.key({ modkey, }, "m", function () awful.tag.incmwfact(-0.05) end, + {description = "decrease master width factor", group = "layout"}), + + awful.key({ modkey, "Shift" }, "i", function () awful.tag.incnmaster( 1, nil, true) end, + {description = "increase the number of master clients", group = "layout"}), + awful.key({ modkey, "Shift" }, "m", function () awful.tag.incnmaster(-1, nil, true) end, + {description = "decrease the number of master clients", group = "layout"}), + + awful.key({ modkey, "Control" }, "i", function () awful.tag.incncol( 1, nil, true) end, + {description = "increase the number of columns", group = "layout"}), + awful.key({ modkey, "Control" }, "m", function () awful.tag.incncol(-1, nil, true) end, + {description = "decrease the number of columns", group = "layout"}), + + awful.key({ modkey, }, "l", function () awful.layout.inc( 1) end, + {description = "select next", group = "layout"}), + awful.key({ modkey, "Shift" }, "l", function () awful.layout.inc(-1) end, + {description = "select previous", group = "layout"}), + + -- awful.key({ modkey, "Control" }, "n", + -- function () + -- local c = awful.client.restore() + -- -- Focus restored client + -- if c then + -- c:emit_signal( + -- "request::activate", "key.unminimize", {raise = true} + -- ) + -- end + -- end, + -- {description = "restore minimized", group = "client"}), + + -- awful.key({ modkey }, "x", + -- function () + -- awful.prompt.run { + -- prompt = "Run Lua code: ", + -- textbox = awful.screen.focused().mypromptbox.widget, + -- exe_callback = awful.util.eval, + -- history_path = awful.util.get_cache_dir() .. "/history_eval" + -- } + -- end, + -- {description = "lua execute prompt", group = "awesome"}), + -- Menubar + -- awful.key({ modkey }, "p", function() menubar.show() end, + -- {description = "show the menubar", group = "launcher"}), + -- Brightness control + awful.key({}, "XF86MonBrightnessUp", function () + awful.util.spawn("brightnessctl -c backlight s \"10%+\"") + end), + awful.key({}, "XF86MonBrightnessDown", function () + awful.util.spawn("brightnessctl -c backlight s \"10%-\"") + end), + -- Volume control + awful.key({}, "XF86AudioRaiseVolume", function () + awful.util.spawn("wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%+") + end), + awful.key({}, "XF86AudioLowerVolume", function () + awful.util.spawn("wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 5%-") + end), + awful.key({}, "XF86AudioMute", function () + awful.util.spawn("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle") + end), + -- Lock control + awful.key({ modkey }, "`", function() + logout_popup.launch() + end) +) + +clientkeys = gears.table.join( + awful.key({ modkey, }, "f", + function (c) + c.fullscreen = not c.fullscreen + c:raise() + end, + {description = "toggle fullscreen", group = "client"}), + awful.key({ modkey, }, "q", function (c) c:kill() end, + {description = "close", group = "client"}), + awful.key({ modkey, "Control" }, "space", awful.client.floating.toggle , + {description = "toggle floating", group = "client"}), + awful.key({ modkey, "Control" }, "Return", function (c) c:swap(awful.client.getmaster()) end, + {description = "move to master", group = "client"}), + awful.key({ modkey, }, "o", function (c) c:move_to_screen() end, + {description = "move to screen", group = "client"}), + awful.key({ modkey, }, "t", function (c) c.ontop = not c.ontop end, + {description = "toggle keep on top", group = "client"}), + -- awful.key({ modkey, }, "n", + -- function (c) + -- -- The client currently has the input focus, so it cannot be + -- -- minimized, since minimized clients can't have the focus. + -- c.minimized = true + -- end , + -- {description = "minimize", group = "client"}), + awful.key({ modkey, }, "=", + function (c) + c.maximized = not c.maximized + c:raise() + end , + {description = "(un)maximize", group = "client"}), + awful.key({ modkey, "Control" }, "=", + function (c) + c.maximized_vertical = not c.maximized_vertical + c:raise() + end , + {description = "(un)maximize vertically", group = "client"}), + awful.key({ modkey, "Shift" }, "=", + function (c) + c.maximized_horizontal = not c.maximized_horizontal + c:raise() + end , + {description = "(un)maximize horizontally", group = "client"}) +) + +-- Bind all key numbers to tags. +-- Be careful: we use keycodes to make it work on any keyboard layout. +-- This should map on the top row of your keyboard, usually 1 to 9. +for i = 1, 9 do + globalkeys = gears.table.join(globalkeys, + -- View tag only. + awful.key({ modkey }, "#" .. i + 9, + function () + local screen = awful.screen.focused() + local tag = screen.tags[i] + if tag then + tag:view_only() + end + end, + {description = "view tag #"..i, group = "tag"}), + -- Toggle tag display. + awful.key({ modkey, "Control" }, "#" .. i + 9, + function () + local screen = awful.screen.focused() + local tag = screen.tags[i] + if tag then + awful.tag.viewtoggle(tag) + end + end, + {description = "toggle tag #" .. i, group = "tag"}), + -- Move client to tag. + awful.key({ modkey, "Shift" }, "#" .. i + 9, + function () + if client.focus then + local tag = client.focus.screen.tags[i] + if tag then + client.focus:move_to_tag(tag) + end + end + end, + {description = "move focused client to tag #"..i, group = "tag"}), + -- Toggle tag on focused client. + awful.key({ modkey, "Control", "Shift" }, "#" .. i + 9, + function () + if client.focus then + local tag = client.focus.screen.tags[i] + if tag then + client.focus:toggle_tag(tag) + end + end + end, + {description = "toggle focused client on tag #" .. i, group = "tag"}) + ) +end + +clientbuttons = gears.table.join( + awful.button({ }, 1, function (c) + c:emit_signal("request::activate", "mouse_click", {raise = true}) + end), + awful.button({ modkey }, 1, function (c) + c:emit_signal("request::activate", "mouse_click", {raise = true}) + awful.mouse.client.move(c) + end), + awful.button({ modkey }, 3, function (c) + c:emit_signal("request::activate", "mouse_click", {raise = true}) + awful.mouse.client.resize(c) + end) +) + +-- Set keys +root.keys(globalkeys) +-- }}} + +-- {{{ Rules +-- Rules to apply to new clients (through the "manage" signal). +awful.rules.rules = { + -- All clients will match this rule. + { rule = { }, + except = { instance = "albert" }, + properties = { border_width = beautiful.border_width, + border_color = beautiful.border_normal, + focus = awful.client.focus.filter, + raise = true, + keys = clientkeys, + buttons = clientbuttons, + screen = awful.screen.preferred, + placement = awful.placement.no_overlap+awful.placement.no_offscreen + } + }, + + -- Floating clients. + { rule_any = { + instance = { + "galculator", + "DTA", -- Firefox addon DownThemAll. + "copyq", -- Includes session name in class. + "pinentry", + "yubioath-flutter" + }, + class = { + "Arandr", + "Blueman-manager", + "Gpick", + "Kruler", + "MessageWin", -- kalarm. + "Sxiv", + "Tor Browser", -- Needs a fixed window size to avoid fingerprinting by screen size. + "Wpa_gui", + "veromix", + "xtightvncviewer"}, + + -- Note that the name property shown in xprop might be set slightly after creation of the client + -- and the name shown there might not match defined rules here. + name = { + "Event Tester", -- xev. + }, + role = { + "AlarmWindow", -- Thunderbird's calendar. + "ConfigManager", -- Thunderbird's about:config. + "pop-up", -- e.g. Google Chrome's (detached) Developer Tools. + } + }, properties = { floating = true }}, + { rule = { instance = ".blueman-manager-wrapped" }, + properties = { + floating = true, + width = 800, + height = 600, + }, + }, + { rule_any = { + class = {"firefox", "librewolf"} + }, + properties = { + tag = "www" + }, + }, + { rule_any = { + class = { "Alacritty" }, + instance = { "ghostty" } + }, + properties = { + tag = "term" + }, + }, + { rule_any = { + class = { "VSCodium", "Code" } + }, + properties = { + tag = "edt" + }, + }, + { rule = { class = "KiCad" }, + properties = { + tag = "eda" + }, + }, + { rule = { instance = "spotify" }, + properties = { + tag = "music" + }, + }, + { rule = { instance = "ticktick" }, + properties = { + tag = "todo" + }, + }, + { rule_any = { + class = {"Slack", "discord" } + }, + properties = { + tag = "com" + }, + }, + + -- Add titlebars to dialogs (maybe they should be added to "normal" type clients as well) + { rule_any = { + type = { "dialog" }, + instance = { ".blueman-manager-wrapped" } + }, properties = { titlebars_enabled = true } + }, +} +-- }}} + +-- {{{ Signals +-- Signal function to execute when a new client appears. +client.connect_signal("manage", function (c) + -- Set the windows at the slave, + -- i.e. put it at the end of others instead of setting it master. + -- if not awesome.startup then awful.client.setslave(c) end + + if awesome.startup + and not c.size_hints.user_position + and not c.size_hints.program_position then + -- Prevent clients from being unreachable after screen count changes. + awful.placement.no_offscreen(c) + end +end) + +-- Add a titlebar if titlebars_enabled is set to true in the rules. +client.connect_signal("request::titlebars", function(c) + -- buttons for the titlebar + local buttons = gears.table.join( + awful.button({ }, 1, function() + c:emit_signal("request::activate", "titlebar", {raise = true}) + awful.mouse.client.move(c) + end), + awful.button({ }, 3, function() + c:emit_signal("request::activate", "titlebar", {raise = true}) + awful.mouse.client.resize(c) + end) + ) + + awful.titlebar(c) : setup { + { -- Left + awful.titlebar.widget.iconwidget(c), + buttons = buttons, + layout = wibox.layout.fixed.horizontal + }, + { -- Middle + { -- Title + align = "center", + widget = awful.titlebar.widget.titlewidget(c) + }, + buttons = buttons, + layout = wibox.layout.flex.horizontal + }, + { -- Right + awful.titlebar.widget.floatingbutton (c), + awful.titlebar.widget.maximizedbutton(c), + awful.titlebar.widget.stickybutton (c), + awful.titlebar.widget.ontopbutton (c), + awful.titlebar.widget.closebutton (c), + layout = wibox.layout.fixed.horizontal() + }, + layout = wibox.layout.align.horizontal + } +end) + +-- Enable sloppy focus, so that focus follows mouse. +client.connect_signal("mouse::enter", function(c) + c:emit_signal("request::activate", "mouse_enter", {raise = false}) +end) + +client.connect_signal("focus", function(c) c.border_color = beautiful.border_focus end) +client.connect_signal("unfocus", function(c) c.border_color = beautiful.border_normal end) +-- }}} + +-- Move all clients to the correct tag when a screen is removed. +tag.connect_signal("request::screen", function(t) + -- Skip empty tags. + local clients = t:clients() + if clients == nil or next(clients) == nil then + return + end + + for s in screen do + -- Find a screen that's not being deleted. + if s ~= t.screen then + -- Find a tag in said screen with the same name. + local target_t = awful.tag.find_by_name(s, t.name) + for _, c in ipairs(clients) do + c:move_to_tag(target_t) + end + return + end + end +end); + +beautiful.useless_gap = 5; +beautiful.gap_single_client = false; + +-- Restore wallpapers +awful.spawn.with_shell("nitrogen --restore") diff --git a/home-modules/explicit-configs/nvim/after/queries/roc/highlights.scm b/home-modules/explicit-configs/nvim/after/queries/roc/highlights.scm new file mode 100644 index 0000000..98bd306 --- /dev/null +++ b/home-modules/explicit-configs/nvim/after/queries/roc/highlights.scm @@ -0,0 +1,177 @@ +;---Most generic types--- +(module) @module + +(identifier) @variable + +(concrete_type) @type + +;---annotations---- +(annotation_type_def + (annotation_pre_colon + (identifier) @type)) + +(annotation_type_def + (annotation_pre_colon + (identifier) @function) + (function_type)) + +;----decleration types---- +(value_declaration + (decl_left + (identifier_pattern + (identifier) @variable.parameter))) + +;---records---- +(field_name) @variable.member + +(record_field_pattern + (_ + (identifier) @variable)) + +;matches the second identifier and all subsequent ones +(field_access_expr + (identifier) @variable.member) + +;highlight module members as records instead of free variables +; avoids highlighting them as out-of-scope vars +(variable_expr + (module) (identifier) @variable.member) + +;----comments---- +(line_comment) @comment @spell + +(doc_comment) @comment.documentation @spell + +;-----Punctuation---- +[ + "?" + (arrow) + (fat_arrow) + "|" + "," + ":" +] @punctuation.delimiter + +[ + "(" + ")" + "{" + "}" + "[" + "]" +] @punctuation.bracket + +[ + "|" + "&" + "<-" + ".." + (operator) +] @operator + +(wildcard_pattern) @constant.builtin + +[ + "if" + "then" + "else" +] @keyword.conditional + +[ + (implements) + (when) + (is) + "as" + (to) +] @keyword + +;----headers----- + +[ + "app" + "expect" + "module" + "package" + "import" +] @keyword + +[ + (import_as) + "imports" +] @keyword.import + + +(value_declaration + (decl_left + (identifier_pattern + (identifier) @function)) + (expr_body + (anon_fun_expr))) + +;----tags---- +(tags_type + (tag_type) @constructor) + +[ + (tag) + (opaque_tag) +] @constructor + +;-----builtins---- +(variable_expr + (module) @module + (identifier) @boolean + (#any-of? @boolean "true" "false") + (#eq? @module "Bool")) + +"dbg" @keyword.debug + +;----function invocations ---- +(function_call_pnc_expr + caller: (variable_expr + (identifier) @function.call)) + +(function_call_pnc_expr + caller: (field_access_expr + (identifier) @function.call .)) + +(bin_op_expr + (operator + "|>") @operator + (variable_expr + (identifier) @function)) + +;----function arguments---- +(argument_patterns + (identifier_pattern + (identifier) @variable.parameter)) + +(argument_patterns + (_ + (identifier_pattern + (identifier) @variable.parameter))) + +(argument_patterns + (_ + (_ + (identifier_pattern + (identifier) @variable.parameter)))) + +;-----consts----- +[ + (int) + (uint) + (iint) + (xint) + (natural) +] @number + +[ + (decimal) + (float) +] @number.float + +(string) @string +(multiline_string) @string + +(char) @character diff --git a/home-modules/explicit-configs/nvim/after/queries/roc/indents.scm b/home-modules/explicit-configs/nvim/after/queries/roc/indents.scm new file mode 100644 index 0000000..526b90c --- /dev/null +++ b/home-modules/explicit-configs/nvim/after/queries/roc/indents.scm @@ -0,0 +1,61 @@ +; (value_declaration(expr_body(anon_fun_expr)))@indent.ignore +[ + (when_is_expr) + (when_is_branch) + (record_expr) + (anon_fun_expr) + (list_expr) + (parenthesized_expr) + (function_call_pnc_expr) + (tuple_expr) + (backpassing_expr) + (imports) + (exposes) + (exposes_list) + (exposing) + ;;patterns + (record_pattern) + (tuple_pattern) + (list_pattern) + + ;;ability stuff + (ability_implementation) + (opaque_type_def) + ;;types + (record_type) + (tags_type) + (record_expr) + (implements_implementation) + "{" + "(" + "[" + ] +@indent.begin + +; ((record_type) +; @indent.align +; (#set! indent.open_delimiter "{") +; (#set! indent.close_delimiter "}")) +; ((record_expr) +; @indent.align +; (#set! indent.open_delimiter "{") +; (#set! indent.close_delimiter "}")) +; ((tags_type) @indent.align +; (#set! indent.open_delimiter "[") +; (#set! indent.close_delimiter "]")) +; ((implements_implementation) @indent.align +; (#set! indent.open_delimiter "[") +; (#set! indent.close_delimiter "]")) + + +(expr_body)@indent.begin + +(ERROR "=")@indent.begin +(then )@indent.begin +(else)@indent.begin +[ + ; result:(_) + "]" + "}" + ")" +]@indent.branch diff --git a/home-modules/explicit-configs/nvim/after/queries/roc/injections.scm b/home-modules/explicit-configs/nvim/after/queries/roc/injections.scm new file mode 100644 index 0000000..2e3732b --- /dev/null +++ b/home-modules/explicit-configs/nvim/after/queries/roc/injections.scm @@ -0,0 +1,57 @@ +;injection from function calls +(function_call_pnc_expr + (variable_expr (identifier) @injection.language) + (const [(multiline_string) (string)] @injection.content) + (#any-eq? @injection.language + "json" + "toml" + "yaml" + "xml" + "sql" + "lua" + "js" + "html" + "css" + "http" + "jq" + "latex" + "md" + "nix" + "regex" + ) +) +;injection from piping function calls +(bin_op_expr + part: (const + [(multiline_string) (string)] @injection.content + ) + part: (operator) + part: (variable_expr + (identifier) @injection.language + ) + (#any-eq? @injection.language + "json" + "toml" + "yaml" + "xml" + "sql" + "lua" + "js" + "html" + "css" + "http" + "jq" + "latex" + "md" + "nix" + "regex" + ) +) + +( + [ + (line_comment) + (doc_comment) + ] @injection.content + (#set! injection.language "comment") +) diff --git a/home-modules/explicit-configs/nvim/after/queries/roc/locals.scm b/home-modules/explicit-configs/nvim/after/queries/roc/locals.scm new file mode 100644 index 0000000..50f0177 --- /dev/null +++ b/home-modules/explicit-configs/nvim/after/queries/roc/locals.scm @@ -0,0 +1,42 @@ +(expr_body) @local.scope + +(argument_patterns + (identifier_pattern + (identifier) @local.definition)) + +; (argument_patterns(long_identifier)@local.definition) +(exposes_list + (identifier) @local.reference) + +(import_expr(as)(module)@local.definition) + +(opaque_type_def + (apply_type + (concrete_type) @local.definition.type)) + +(alias_type_def + (apply_type + (concrete_type) @local.definition.type)) + +(value_declaration + (decl_left + (identifier_pattern + (identifier) @local.definition.function)) + (expr_body + (anon_fun_expr))) + +(value_declaration + (decl_left + (identifier_pattern + (identifier) @local.definition.var))) + +(identifier_pattern + (identifier) @local.definition) + +(when_is_branch pattern: (_ (identifier_pattern (identifier) @local.definition))) +(spread_pattern (identifier) @local.definition) + +(identifier) @local.reference + +(tag_expr + (tag)) @local.reference diff --git a/home-modules/explicit-configs/nvim/after/queries/roc/textobjects.scm b/home-modules/explicit-configs/nvim/after/queries/roc/textobjects.scm new file mode 100644 index 0000000..613be1b --- /dev/null +++ b/home-modules/explicit-configs/nvim/after/queries/roc/textobjects.scm @@ -0,0 +1,33 @@ +(anon_fun_expr + (expr_body) @function.inner +) @function.outer + +(argument_patterns + ((_) @parameter.inner . ","? @parameter.outer) @parameter.outer +) + +(function_type + ((_) @parameter.inner . ","? @parameter.outer) @parameter.outer(#not-eq? @parameter.inner "->") +) + +(function_pnc_expr + . + (_) + (parenthesized_expr (expr_body) @parameter.inner) @parameter.outer +) + +(function_pnc_expr + . + (_) ((_) @parameter.inner) @parameter.outer +) + +[ + (annotation_type_def ) @class.inner + (alias_type_def ) @class.inner + (opaque_type_def ) @class.inner +] @class.outer + +(apply_type_arg) @parameter.inner + +(line_comment) @comment.outer +(doc_comment) @comment.outer diff --git a/home-modules/explicit-configs/nvim/init-vsc.lua b/home-modules/explicit-configs/nvim/init-vsc.lua new file mode 100644 index 0000000..79a6569 --- /dev/null +++ b/home-modules/explicit-configs/nvim/init-vsc.lua @@ -0,0 +1,54 @@ +----------------------------------------------------- +----------------------------------------------------- +----------------------------------------------------- +------------- VSCode specific config ---------------- +----------------------------------------------------- +----------------------------------------------------- +----------------------------------------------------- +----------------------------------------------------- + +vim.api.nvim_set_keymap('n', '', '', {noremap = true, silent = true}) +vim.g.mapleader = ' ' + +-- Exit insert mode with ,h. +vim.api.nvim_set_keymap('i', ',h', '', {noremap = true, silent = true}) + +-- Quick save. +vim.api.nvim_set_keymap('n', 'fw', ':w', {silent = true}) + +-- Remap for colemak-dhm. +-- Left. +vim.api.nvim_set_keymap('', 'm', 'h', {noremap = true, silent = true}) +vim.api.nvim_set_keymap('', 'h', 'm', {noremap = true, silent = true}) +-- Down. +vim.api.nvim_set_keymap('', 'j', 'n', {noremap = true, silent = true}) +vim.api.nvim_set_keymap('', 'n', 'j', {noremap = true, silent = true}) +-- Up. +vim.api.nvim_set_keymap('', 'k', 'e', {noremap = true, silent = true}) +vim.api.nvim_set_keymap('', 'e', 'k', {noremap = true, silent = true}) +-- Right. +vim.api.nvim_set_keymap('', 'l', 'i', {noremap = true, silent = true}) +vim.api.nvim_set_keymap('', 'i', 'l', {noremap = true, silent = true}) + +-- C-g as ESC +vim.api.nvim_set_keymap('n', '', '', {silent = true}) +vim.api.nvim_set_keymap('i', '', '', {silent = true}) +vim.api.nvim_set_keymap('v', '', '', {silent = true}) +vim.api.nvim_set_keymap('s', '', '', {silent = true}) +vim.api.nvim_set_keymap('x', '', '', {silent = true}) +vim.api.nvim_set_keymap('c', '', '', {silent = true}) +vim.api.nvim_set_keymap('o', '', '', {silent = true}) +vim.api.nvim_set_keymap('l', '', '', {silent = true}) +vim.api.nvim_set_keymap('t', '', '', {silent = true}) + +-- Move right when in insert mode. +vim.api.nvim_set_keymap("i", '', '', {noremap = true, silent = true}) + +-- Lsp +vim.api.nvim_set_keymap('n', 'lgd', "lua require('vscode-neovim').call('editor.action.goToDeclaration')", {noremap = true, silent = true}) +vim.api.nvim_set_keymap('n', 'lgr', "lua require('vscode-neovim').call('editor.action.referenceSearch.trigger')", {noremap = true, silent = true}) +vim.api.nvim_set_keymap('n', 'lr', "lua require('vscode-neovim').call('editor.action.rename')", {noremap = true, silent = true}) + +-- Tab movement. +vim.api.nvim_set_keymap('n', 'm', "lua require('vscode-neovim').call('workbench.action.previousEditorInGroup')", {noremap = true, silent = true}) +vim.api.nvim_set_keymap('n', 'i', "lua require('vscode-neovim').call('workbench.action.nextEditorInGroup')", {noremap = true, silent = true}) diff --git a/home-modules/explicit-configs/nvim/init.lua b/home-modules/explicit-configs/nvim/init.lua new file mode 100644 index 0000000..89d3c5c --- /dev/null +++ b/home-modules/explicit-configs/nvim/init.lua @@ -0,0 +1,32 @@ +require('settings') +require('nv-globals') +require('plugins') +require('colorscheme') +require('settings') + +-- Must go after plugins. +require("oil").setup() + +-- LSP +require('lsp') +require('lsp.typescript-ls') +require('lsp.python-ls') +require('lsp.lua-ls') +require('lsp.go-ls') +require('lsp.ocaml-ls') +require('lsp.sml-ls') +require('lsp.arduino-ls') +require('lsp.roc-ls') +require('lsp.zls-ls') + +-- Completion +require('completion') + +require('nv-prettier') + +require('nv-telescope') + +require('langs.roc') + +-- Set up keymaps after everything is configured. +require('keymappings') diff --git a/home-modules/explicit-configs/nvim/lua/colorscheme.lua b/home-modules/explicit-configs/nvim/lua/colorscheme.lua new file mode 100644 index 0000000..6456c29 --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/colorscheme.lua @@ -0,0 +1,13 @@ +vim.api.nvim_set_option('background', 'dark') +vim.api.nvim_set_option('termguicolors', true) + +-- colorscheme +vim.cmd([[ + colorscheme palenight + highlight ColorColumn ctermbg=0 guibg=grey + hi CurrentWord guibg=#444444 + hi CurrentWordTwins guibg=#606060 + hi CursorLine guibg=#32384d +]]) + +-- BG color is #282D3F diff --git a/home-modules/explicit-configs/nvim/lua/completion/init.lua b/home-modules/explicit-configs/nvim/lua/completion/init.lua new file mode 100644 index 0000000..5e1c4ba --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/completion/init.lua @@ -0,0 +1,118 @@ +vim.o.completeopt = "menuone,noselect" +-- Avoid having a "Pattern not found" message +vim.cmd("set shortmess+=c") + +local cmp = require'cmp' +local snippy = require('snippy') + +cmp.setup({ + window = { + completion = cmp.config.window.bordered(), + documentation = cmp.config.window.bordered(), + }, + snippet = { + expand = function(args) + snippy.expand_snippet(args.body) + end, + }, + mapping = cmp.mapping.preset.insert({ + [''] = cmp.mapping.select_next_item(), + [''] = cmp.mapping.select_prev_item(), + [''] = cmp.mapping.confirm({ select = true }), + [''] = cmp.mapping.abort(), + }), + sources = cmp.config.sources({ + { name = 'nvim_lsp' }, + { name = 'async_path' }, + { name = 'snippy' }, + }, { + { name = 'buffer' }, + }) +}) + +snippy.setup({ + mappings = { + is = { + [''] = 'expand_or_advance', + [''] = 'previous', + }, + }, +}) + +require('lspsaga').setup({}) + +-- require'compe'.setup { +-- enabled = true; +-- autocomplete = true; +-- debug = false; +-- min_length = 1; +-- preselect = 'always'; +-- throttle_time = 80; +-- source_timeout = 200; +-- incomplete_delay = 400; +-- max_abbr_width = 100; +-- max_kind_width = 100; +-- max_menu_width = 100; +-- documentation = true; + +-- source = { +-- path = true; +-- buffer = false; +-- calc = false; +-- vsnip = false; -- Could be used if I want to start using snippets. +-- nvim_lsp = true; +-- nvim_lua = true; +-- tags = true; +-- snippets_nvim = true; +-- treesitter = true; +-- }; +-- } + +-- vim.cmd([[ +-- autocmd FileType ocaml call compe#setup({ +-- \ 'preselect': 'disable' +-- \ }, 0) +-- ]]) + +-- local t = function(str) +-- return vim.api.nvim_replace_termcodes(str, true, true, true) +-- end + +-- _G.comp_jump_if_avail = function() +-- if vim.fn.pumvisible() == 1 then +-- return t "" +-- end +-- end +-- _G.comp_jump_prev_if_avail = function() +-- if vim.fn.pumvisible() == 1 then +-- return t "" +-- end +-- end +-- _G.tab_complete = function() +-- if vim.fn.pumvisible() == 1 then +-- return vim.fn['compe#confirm']('') +-- else +-- return t "" +-- end +-- end +-- _G.enter_complete = function() +-- if vim.bo.filetype == "ocaml" then +-- return t "" +-- elseif vim.fn.pumvisible() == 1 then +-- return vim.fn['compe#confirm']('') +-- else +-- return t "" +-- end +-- end + +-- vim.api.nvim_set_keymap("i", "", "v:lua.comp_jump_if_avail()", {expr = true}) +-- vim.api.nvim_set_keymap("s", "", "v:lua.comp_jump_if_avail()", {expr = true}) +-- vim.api.nvim_set_keymap("i", "", "v:lua.comp_jump_prev_if_avail()", {expr = true}) +-- vim.api.nvim_set_keymap("s", "", "v:lua.comp_jump_prev_if_avail()", {expr = true}) +-- -- The next 4 funtcions don't work if compe doesn't automatically select the first option. +-- -- meaning the preselect option has to be set to alwasy. +-- vim.api.nvim_set_keymap("i", "", "v:lua.tab_complete()", {expr = true}) +-- vim.api.nvim_set_keymap("s", "", "v:lua.tab_complete()", {expr = true}) +-- vim.api.nvim_set_keymap("i", "", "v:lua.enter_complete()", {expr = true}) +-- vim.api.nvim_set_keymap("s", "", "v:lua.enter_complete()", {expr = true}) + diff --git a/home-modules/explicit-configs/nvim/lua/keymappings.lua b/home-modules/explicit-configs/nvim/lua/keymappings.lua new file mode 100644 index 0000000..e7252ff --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/keymappings.lua @@ -0,0 +1,95 @@ +local lsp = vim.lsp +local handlers = lsp.handlers +-- local saga = require('lspsaga') +-- saga.init_lsp_saga() + +-- Space as leader. +vim.api.nvim_set_keymap('n', '', '', {noremap = true, silent = true}) +vim.g.mapleader = ' ' + +-- Exit insert mode with ,h. +vim.api.nvim_set_keymap('i', ',h', '', {noremap = true, silent = true}) + +-- Remap for colemak-dhm. +-- Left. +vim.api.nvim_set_keymap('', 'm', 'h', {noremap = true, silent = true}) +vim.api.nvim_set_keymap('', 'h', 'm', {noremap = true, silent = true}) +-- Down. +vim.api.nvim_set_keymap('', 'j', 'n', {noremap = true, silent = true}) +vim.api.nvim_set_keymap('', 'n', 'j', {noremap = true, silent = true}) +-- Up. +vim.api.nvim_set_keymap('', 'k', 'e', {noremap = true, silent = true}) +vim.api.nvim_set_keymap('', 'e', 'k', {noremap = true, silent = true}) +-- Right. +vim.api.nvim_set_keymap('', 'l', 'i', {noremap = true, silent = true}) +vim.api.nvim_set_keymap('', 'i', 'l', {noremap = true, silent = true}) + +-- C-g as ESC +vim.api.nvim_set_keymap('n', '', '', {silent = true}) +vim.api.nvim_set_keymap('i', '', '', {silent = true}) +vim.api.nvim_set_keymap('v', '', '', {silent = true}) +vim.api.nvim_set_keymap('s', '', '', {silent = true}) +vim.api.nvim_set_keymap('x', '', '', {silent = true}) +vim.api.nvim_set_keymap('c', '', '', {silent = true}) +vim.api.nvim_set_keymap('o', '', '', {silent = true}) +vim.api.nvim_set_keymap('l', '', '', {silent = true}) +vim.api.nvim_set_keymap('t', '', '', {silent = true}) + +-- Move right when in insert mode. +vim.api.nvim_set_keymap("i", '', '', {noremap = true, silent = true}) + +-- Leader window movement. +vim.api.nvim_set_keymap("n", 'wm', 'h', {noremap = true, silent = true}) +vim.api.nvim_set_keymap("n", 'wn', 'j', {noremap = true, silent = true}) +vim.api.nvim_set_keymap("n", 'we', 'k', {noremap = true, silent = true}) +vim.api.nvim_set_keymap("n", 'wi', 'l', {noremap = true, silent = true}) + +-- Quick save. +vim.api.nvim_set_keymap('n', 'fw', ':w', {silent = true}) + +-- Toggle between buffers. +vim.api.nvim_set_keymap('n', '', '', {noremap = true, silent = true}) + +-- Find files inside the current folder. +vim.api.nvim_set_keymap('n', 'ff', 'Telescope find_files',{}) +-- Find files and folders allowing creation. +vim.api.nvim_set_keymap('n', 'fb', 'Telescope file_browser', {noremap = true}) +-- File brosers starting at the cwd. +vim.api.nvim_set_keymap('n', 'fc', 'Telescope file_browser path=%:p:h', {noremap = true}) +-- File browser that allows edits in-buffer. +vim.keymap.set("n", "fo", "Oil", { desc = "Open parent directory" }) +-- Do a grep search in the current folder. +vim.api.nvim_set_keymap('n', 'ps', ':Rg',{}) +-- Search among the currently open buffers. +vim.api.nvim_set_keymap('n', 'bs', 'Telescope buffers',{}) +-- Code actions. +vim.api.nvim_set_keymap('n', 'ca', "lua vim.lsp.buf.code_action()",{silent = true, noremap = true}) +vim.api.nvim_set_keymap('x', 'ca', "lua vim.lsp.buf.range_code_action()",{silent = true, noremap = true}) +-- Flutter commands. +vim.api.nvim_set_keymap('n', 'fl', "lua require('telescope').extensions.flutter.commands()",{silent = true, noremap = true}) + + +-- Hover doc popup +local pop_opts = { border = "rounded", max_width = 80 } +handlers["textDocument/hover"] = lsp.with(handlers.hover, pop_opts) +handlers["textDocument/signatureHelp"] = lsp.with(handlers.signature_help, pop_opts) + +vim.api.nvim_set_keymap('n', 'lgd', 'lua vim.lsp.buf.definition()', {noremap = true, silent = true}) +vim.api.nvim_set_keymap('n', 'lgD', 'lua vim.lsp.buf.declaration()', {noremap = true, silent = true}) +vim.api.nvim_set_keymap('n', 'lgr', 'lua vim.lsp.buf.references()', {noremap = true, silent = true}) +vim.api.nvim_set_keymap('n', 'lgi', 'lua vim.lsp.buf.implementation()', {noremap = true, silent = true}) +vim.api.nvim_set_keymap('n', 'lf', 'lua vim.lsp.buf.format()', {noremap = true, silent = true}) +vim.keymap.set('v', 'lrf', vim.lsp.buf.format) +vim.api.nvim_set_keymap('n', 'lr', 'Lspsaga rename', {silent = true}) +vim.api.nvim_set_keymap('n', 'K', 'lua vim.lsp.buf.hover()', {noremap = true, silent = true}) + +------ Trouble +vim.api.nvim_set_keymap("n", "lld", 'lua vim.diagnostic.open_float({scope="line"})', + {silent = true, noremap = true} +) -- Mnemonic "lsp line diagnostics" +vim.api.nvim_set_keymap("n", "lwd", "Trouble workspace_diagnostics", + {silent = true, noremap = true} +) -- Mnemonic "lsp workspace diagnostics" +vim.api.nvim_set_keymap("n", "ldd", "Trouble document_diagnostics", + {silent = true, noremap = true} +) -- Mnemonic "lsp document diagnostics" diff --git a/home-modules/explicit-configs/nvim/lua/langs/init.lua b/home-modules/explicit-configs/nvim/lua/langs/init.lua new file mode 100644 index 0000000..e69de29 diff --git a/home-modules/explicit-configs/nvim/lua/langs/roc.lua b/home-modules/explicit-configs/nvim/lua/langs/roc.lua new file mode 100644 index 0000000..5bdfade --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/langs/roc.lua @@ -0,0 +1,26 @@ +-- make .roc files have the correct filetype +vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, { + pattern = { "*.roc" }, + command = "set filetype=roc", +}) + +-- add roc tree-sitter +local parsers = require("nvim-treesitter.parsers").get_parser_configs() + +parsers.roc = { + install_info = { + url = "https://github.com/faldor20/tree-sitter-roc", + files = { "src/parser.c", "src/scanner.c" }, + }, +} + +require'nvim-treesitter.configs'.setup { + highlight = { + enable = true, + -- Setting this to true will run `:h syntax` and tree-sitter at the same time. + -- Set this to `true` if you depend on 'syntax' being enabled (like for indentation). + -- Using this option may slow down your editor, and you may see some duplicate highlights. + -- Instead of true it can also be a list of languages + -- additional_vim_regex_highlighting = false, + }, +} diff --git a/home-modules/explicit-configs/nvim/lua/lsp/arduino-ls.lua b/home-modules/explicit-configs/nvim/lua/lsp/arduino-ls.lua new file mode 100644 index 0000000..ac22b2e --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/lsp/arduino-ls.lua @@ -0,0 +1,9 @@ +require'lspconfig'.arduino_language_server.setup { + cmd = { + "arduino-language-server", + "-cli-config", "/Users/aym/Library/Arduino15/arduino-cli.yaml", + "-fqbn", "adafruit:nrf52:feather52841", + "-cli", "arduino-cli", + "-clangd", "clangd" + } +} diff --git a/home-modules/explicit-configs/nvim/lua/lsp/elixir-ls.lua b/home-modules/explicit-configs/nvim/lua/lsp/elixir-ls.lua new file mode 100644 index 0000000..f111c8b --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/lsp/elixir-ls.lua @@ -0,0 +1,8 @@ +local elixir_ls_binary = "/Users/" .. USER .. "/.config/nvim/lang-srvrs/elixir-ls/language_server.sh" + +require'lspconfig'.elixirls.setup{ + cmd = { elixir_ls_binary }; +} + +vim.api.nvim_command('autocmd BufWritePost *.ex :lua vim.lsp.buf.formatting()') +vim.api.nvim_command('autocmd BufWritePost *.exs :lua vim.lsp.buf.formatting()') diff --git a/home-modules/explicit-configs/nvim/lua/lsp/flutter-ls.lua b/home-modules/explicit-configs/nvim/lua/lsp/flutter-ls.lua new file mode 100644 index 0000000..7d664a7 --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/lsp/flutter-ls.lua @@ -0,0 +1,3 @@ +require("flutter-tools").setup{ +} -- use defaults + diff --git a/home-modules/explicit-configs/nvim/lua/lsp/go-ls.lua b/home-modules/explicit-configs/nvim/lua/lsp/go-ls.lua new file mode 100644 index 0000000..5d1db44 --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/lsp/go-ls.lua @@ -0,0 +1,74 @@ +local lspconfig = require "lspconfig" +-- local capabilities = require('cmp_nvim_lsp').default_capabilities() +lspconfig.gopls.setup { + on_attach = function(client, bufnr) + require "lsp_signature".on_attach({ + bind = true, + -- floating_window = true, + --hint_enable = true, + fix_pos = true, + use_lspsaga = true, + handler_opts = { + border = "rounded" -- double, single, shadow, none + }, + }) + end, + cmd = {"gopls", "serve"}, + settings = { + autostart = true, + gopls = { + analyses = { + unusedparams = true, + }, + staticcheck = true, + }, + }, + autostart = true, + root_dir = lspconfig.util.root_pattern(".git","go.mod"), + init_options = {usePlaceholders = true, completeUnimported = true}, + -- capabilities = capabilities, + single_file_support = true; +} + + +------ Configre vim-go +-- Disable autocomplete since we already have it with native lsp. +vim.g.go_code_completion_enabled = 0 +-- Disable gopls for the same reasons as above. +vim.g.go_gopls_enabled = 0 + +-- Simplify code when formatting. +vim.g.go_fmt_command = 'gofmt' +vim.g.go_fmt_options = { + gofmt = '-s', +} + +vim.g.go_imports_mode = 'goimports' + +-- Could confilg with lsp saga, should experiment. +vim.g.go_doc_keywordprg_enabled = 0 + +-- Use my own universal mappings for lsp. +vim.g.go_def_mapping_enabled = 0 + +-- Use globally installed binaries first. (gopls) installed through brew. +vim.g.go_search_bin_path_first = 0 + +-- When auto-creating a template file, use the package rather than a file. +vim.g.go_template_use_pkg = 1 + +-- Configure folding. +-- vim.cmd("autocmd FileType go setlocal foldmethod=syntax") +-- vim.g.go_fmt_experimental = 1 + +-- Use a ruler for go files. +vim.cmd("autocmd FileType go setlocal colorcolumn=80") +-- Automatically add comment symbol on next line and wrap it. +vim.cmd("autocmd FileType go setlocal fo+=c fo+=r") + +-- Highlights. +vim.g.go_highlight_operators = 1 +vim.g.go_highlight_types = 1 +vim.g.go_highlight_extra_types = 1 +vim.g.go_highlight_functions = 1 +vim.g.go_highlight_function_calls = 1 diff --git a/home-modules/explicit-configs/nvim/lua/lsp/init.lua b/home-modules/explicit-configs/nvim/lua/lsp/init.lua new file mode 100644 index 0000000..e69de29 diff --git a/home-modules/explicit-configs/nvim/lua/lsp/lua-ls.lua b/home-modules/explicit-configs/nvim/lua/lsp/lua-ls.lua new file mode 100644 index 0000000..f1590f0 --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/lsp/lua-ls.lua @@ -0,0 +1,35 @@ +local capabilities = require('cmp_nvim_lsp').default_capabilities() +require'lspconfig'.lua_ls.setup { + on_init = function(client) + if client.workspace_folders then + local path = client.workspace_folders[1].name + if vim.uv.fs_stat(path..'/.luarc.json') or vim.uv.fs_stat(path..'/.luarc.jsonc') then + return + end + end + + client.config.settings.Lua = vim.tbl_deep_extend('force', client.config.settings.Lua, { + runtime = { + -- Tell the language server which version of Lua you're using + -- (most likely LuaJIT in the case of Neovim) + version = 'LuaJIT' + }, + -- Make the server aware of Neovim runtime files + workspace = { + checkThirdParty = false, + library = { + vim.env.VIMRUNTIME + -- Depending on the usage, you might want to add additional paths here. + -- "${3rd}/luv/library" + -- "${3rd}/busted/library", + } + -- or pull in all of 'runtimepath'. NOTE: this is a lot slower and will cause issues when working on your own configuration (see https://github.com/neovim/nvim-lspconfig/issues/3189) + -- library = vim.api.nvim_get_runtime_file("", true) + } + }) + end, + settings = { + Lua = {} + }, + capabilities = capabilities, +} diff --git a/home-modules/explicit-configs/nvim/lua/lsp/ocaml-ls.lua b/home-modules/explicit-configs/nvim/lua/lsp/ocaml-ls.lua new file mode 100644 index 0000000..41b1dff --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/lsp/ocaml-ls.lua @@ -0,0 +1,4 @@ +require("lsp-format").setup{} +require'lspconfig'.ocamllsp.setup{ + on_attach = require("lsp-format").on_attach +} diff --git a/home-modules/explicit-configs/nvim/lua/lsp/python-ls.lua b/home-modules/explicit-configs/nvim/lua/lsp/python-ls.lua new file mode 100644 index 0000000..0fd7378 --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/lsp/python-ls.lua @@ -0,0 +1,5 @@ +-- require'lspconfig'.pyright.setup{} + +-- Autoformat on save. +-- vim.cmd("autocmd BufWritePre *.py execute ':Black'") + diff --git a/home-modules/explicit-configs/nvim/lua/lsp/roc-ls.lua b/home-modules/explicit-configs/nvim/lua/lsp/roc-ls.lua new file mode 100644 index 0000000..7c2d355 --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/lsp/roc-ls.lua @@ -0,0 +1,12 @@ +require'lspconfig'.roc_ls.setup{ + offset_encoding = "utf-8", +} + +-- Should get rid of this once semantic highlighting gets better! +vim.api.nvim_create_autocmd('LspAttach', { + group = vim.api.nvim_create_augroup('UserLspConfig', {}), + callback = function(ev) + local client = vim.lsp.get_client_by_id(ev.data.client_id) + client.server_capabilities.semanticTokensProvider = nil + end, +}) diff --git a/home-modules/explicit-configs/nvim/lua/lsp/sml-ls.lua b/home-modules/explicit-configs/nvim/lua/lsp/sml-ls.lua new file mode 100644 index 0000000..1fb0042 --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/lsp/sml-ls.lua @@ -0,0 +1,7 @@ +local lspconfig = require "lspconfig" +local capabilities = require('cmp_nvim_lsp').default_capabilities() + +lspconfig.millet.setup{ + cmd = { "millet-ls" }, + capabilities = capabilities, +} diff --git a/home-modules/explicit-configs/nvim/lua/lsp/typescript-ls.lua b/home-modules/explicit-configs/nvim/lua/lsp/typescript-ls.lua new file mode 100644 index 0000000..0506d00 --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/lsp/typescript-ls.lua @@ -0,0 +1,4 @@ +require'lspconfig'.ts_ls.setup{ + filetypes = { "javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx" }, + root_dir = require('lspconfig/util').root_pattern("package.json", "tsconfig.json", "jsconfig.json", ".git") +} diff --git a/home-modules/explicit-configs/nvim/lua/lsp/zls-ls.lua b/home-modules/explicit-configs/nvim/lua/lsp/zls-ls.lua new file mode 100644 index 0000000..266805a --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/lsp/zls-ls.lua @@ -0,0 +1 @@ + require'lspconfig'.zls.setup{} diff --git a/home-modules/explicit-configs/nvim/lua/nv-globals.lua b/home-modules/explicit-configs/nvim/lua/nv-globals.lua new file mode 100644 index 0000000..60fadff --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/nv-globals.lua @@ -0,0 +1,2 @@ +DATA_PATH = vim.fn.stdpath('data') +CACHE_PATH = vim.fn.stdpath('cache') diff --git a/home-modules/explicit-configs/nvim/lua/nv-prettier/init.lua b/home-modules/explicit-configs/nvim/lua/nv-prettier/init.lua new file mode 100644 index 0000000..3ee31a0 --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/nv-prettier/init.lua @@ -0,0 +1,4 @@ +vim.g["prettier#autoformat"] = 1 +vim.g["prettier#autoformat_require_pragma"] = 0 +vim.g["prettier#exec_cmd_async"] = 1 + diff --git a/home-modules/explicit-configs/nvim/lua/nv-telescope/init.lua b/home-modules/explicit-configs/nvim/lua/nv-telescope/init.lua new file mode 100644 index 0000000..874064c --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/nv-telescope/init.lua @@ -0,0 +1,11 @@ +-- This is your opts table +require("telescope").setup { + extensions = { + ["ui-select"] = { + require("telescope.themes").get_dropdown {} + } + } +} + +require("telescope").load_extension("ui-select") +require("telescope").load_extension "file_browser" diff --git a/home-modules/explicit-configs/nvim/lua/plugins.lua b/home-modules/explicit-configs/nvim/lua/plugins.lua new file mode 100644 index 0000000..7d19653 --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/plugins.lua @@ -0,0 +1,137 @@ +local execute = vim.api.nvim_command +local fn = vim.fn + +local install_path = fn.stdpath('data') .. '/site/pack/packer/start/packer.nvim' + +if fn.empty(fn.glob(install_path)) > 0 then + fn.system({'git', 'clone', 'https://github.com/wbthomason/packer.nvim', install_path}) + execute 'packadd packer.nvim' +end + +return require('packer').startup(function(use) + -- Packer can manage itself as an optional plugin + use 'wbthomason/packer.nvim' + + -- LSP + use 'neovim/nvim-lspconfig' + use ({ + 'nvimdev/lspsaga.nvim', + config = function() + require('lspsaga').setup({}) + end, + }) + use { + 'nvim-treesitter/nvim-treesitter', + run = ':TSUpdate' + } + -- Autocomplete + use 'hrsh7th/cmp-nvim-lsp' + use 'hrsh7th/cmp-buffer' + use 'https://codeberg.org/FelipeLema/cmp-async-path' + use 'dcampos/nvim-snippy' + use 'dcampos/cmp-snippy' + use 'hrsh7th/nvim-cmp' + + -- Function signatures + use "ray-x/lsp_signature.nvim" + + -- Theme + use 'drewtempelmeyer/palenight.vim' + use 'kyazdani42/nvim-web-devicons' + + -- Syntax + use 'sheerun/vim-polyglot' + -- use { + --- 'prettier/vim-prettier', + -- run = "yarn install", + -- ft = {"javascript", "typescript", "json"} + -- } + use {'styled-components/vim-styled-components', branch = "main"} + use 'dominikduda/vim_current_word' + use { + 'lukas-reineke/indent-blankline.nvim', + ft = {'yaml', 'yml', 'helm'}, + config = function() + vim.opt.list = true + vim.opt.listchars:append("space:⋅") + vim.opt.listchars:append("eol:↴") + + require("indent_blankline").setup { + show_end_of_line = true, + space_char_blankline = " ", + } + end + } + + use 'airblade/vim-rooter' + + -- Find tools. + use { "nvim-telescope/telescope-file-browser.nvim" } + use {'nvim-telescope/telescope-ui-select.nvim' } + use { + 'nvim-telescope/telescope.nvim', + requires = {{'nvim-lua/popup.nvim'}, {'nvim-lua/plenary.nvim'}}, + } + use { "junegunn/fzf" } + use { "junegunn/fzf.vim" } + + + -- Diagnostics + -- Lua + use { + "folke/trouble.nvim", + requires = "kyazdani42/nvim-web-devicons", + config = function() + require("trouble").setup { + -- your configuration comes here + -- or leave it empty to use the default settings + } + end + } + + -- Git symobls. + use { + 'lewis6991/gitsigns.nvim', + tag = 'release', + config = function() + require('gitsigns').setup() + end + } + + use 'jiangmiao/auto-pairs' + use 'itchyny/lightline.vim' + use 'itchyny/vim-gitbranch' + + -- Golang + use 'fatih/vim-go' + + -- Flutter/Dart + use {'akinsho/flutter-tools.nvim', requires = 'nvim-lua/plenary.nvim'} + + -- Pyhton + -- use 'a-vrma/black-nvim' + use { + 'psf/black', branch = "stable" + } + + -- Ocaml + use "lukas-reineke/lsp-format.nvim" + + -- Comments + use 'tpope/vim-commentary' + + -- Smooth scrolling + use 'psliwka/vim-smoothie' + + -- Yuck (Eww config language). + -- use 'elkowar/yuck.vim' + + -- Better file viewer + use({ + "stevearc/oil.nvim", + config = function() + require("oil").setup() + end, + }) + + end) diff --git a/home-modules/explicit-configs/nvim/lua/settings.lua b/home-modules/explicit-configs/nvim/lua/settings.lua new file mode 100644 index 0000000..505365b --- /dev/null +++ b/home-modules/explicit-configs/nvim/lua/settings.lua @@ -0,0 +1,81 @@ +-- Comes from the Chris@Machine config. + +vim.cmd('set iskeyword+=-') -- treat dash separated words as a word text object" +vim.o.hidden = true -- Required to keep multiple buffers open multiple buffers +vim.wo.wrap = false -- Display long lines as just one line +vim.o.fileencoding = "utf-8" -- The encoding written to file +vim.o.splitbelow = true -- Horizontal splits will automatically be below +vim.o.splitright = true -- Vertical splits will automatically be to the right +vim.o.conceallevel = 0 -- So that I can see `` in markdown files +vim.cmd('set ts=4') -- Insert 2 spaces for a tab +vim.cmd('set sw=4') -- Change the number of space characters inserted for indentation +vim.bo.expandtab = true -- Converts tabs to spaces +vim.bo.smartindent = true -- Makes indenting smart +vim.wo.number = true -- set numbered lines +vim.wo.cursorline = true -- Enable highlighting of the current line +--vim.o.showtabline = 2 -- Always show tabs +vim.o.showmode = false -- We don't need to see things like -- INSERT -- anymore +vim.o.backup = false -- This is recommended by coc +vim.o.writebackup = false -- This is recommended by coc +vim.wo.signcolumn = "yes" -- Always show the signcolumn, otherwise it would shift the text each time +vim.o.updatetime = 300 -- Faster completion +vim.o.timeoutlen = 500 -- By default timeoutlen is 1000 ms + +vim.cmd(":set number relativenumber") + + +vim.cmd("autocmd FileType helm set nofixendofline") +vim.cmd("autocmd FileType yaml set nofixendofline") +vim.cmd("autocmd FileType yml set nofixendofline") +vim.cmd("autocmd BufReadPost * :lua require('gitsigns').setup()") + +------ Folding +-- vim.o.foldlevel = 99 +-- Persist folds. +-- Ignore errors. +-- vim.cmd([[ +-- augroup AutoSaveFolds +-- autocmd! +-- autocmd BufWinLeave * silent! mkview +-- autocmd BufWinEnter * silent! loadview +-- augroup END +-- ]]) + +-- Show full file path in lightline. +vim.cmd([[ +let g:lightline = { + \ 'colorscheme' : 'palenight', + \ 'active': { + \ 'left': [ [ 'mode', 'paste' ], + \ [ 'gitbranch', 'readonly', 'filename', 'modified' ] ] + \ }, + \ 'component_function': { + \ 'filename': 'FilenameForLightline', + \ 'gitbranch': 'gitbranch#name' + \ } + \ } + +function! FilenameForLightline() + let root = fnamemodify(get(b:, 'gitbranch_path'), ':h:h') + let path = expand('%:p') + if path[:len(root)-1] ==# root + return path[len(root)+1:] + endif + return expand('%') +endfunction +]]) + +-- Custom command for ripgrep. +-- vim.cmd([[ +-- function! RipgrepFzf(query, fullscreen) +-- let command_fmt = 'rg --column --line-number --no-heading --color=always --smart-case -- %s || true' +-- let initial_command = printf(command_fmt, shellescape(a:query)) +-- let reload_command = printf(command_fmt, '{q}') +-- let spec = {'options': ['--phony', '--query', a:query, '--bind', 'change:reload:'.reload_command]} +-- call fzf#vim#grep(initial_command, 1, fzf#vim#with_preview(spec), a:fullscreen) +-- endfunction + +-- command! -nargs=* -bang RG call RipgrepFzf(, 0) +-- ]]) + + diff --git a/home-modules/ghostty-config.nix b/home-modules/ghostty-config.nix new file mode 100644 index 0000000..005f122 --- /dev/null +++ b/home-modules/ghostty-config.nix @@ -0,0 +1,42 @@ +{ ... }: { + home.file.".config/ghostty/config" = { + text = '' + # If not preset, this causes issues when sshing into linux servers. + term = xterm-256color + font-family = "BigBlueTermPlus Nerd Font" + font-size = 17.2 + theme = "PaleNight" + background-opacity = 0.9 + window-decoration = false + + # This is the default to open the configuration + # I type this by accident way to frequently... + keybind = ctrl+comma=unbind + ''; + }; + home.file.".config/ghostty/themes/PaleNight" = { + text = '' + palette = 0=#000000 + palette = 1=#ff5555 + palette = 2=#50fa7b + palette = 3=#f1fa8c + palette = 4=#caa9fa + palette = 5=#ff79c6 + palette = 6=#8be9fd + palette = 7=#bfbfbf + palette = 8=#575b70 + palette = 9=#ff6e67 + palette = 10=#5af78e + palette = 11=#f4f99d + palette = 12=#caa9fa + palette = 13=#ff92d0 + palette = 14=#9aedfe + palette = 15=#e6e6e6 + background = 282a36 + foreground = f8f8f2 + cursor-color = #ffcb6b + selection-background = #717cb4 + selection-foreground = #80cbc4 + ''; + }; +} diff --git a/home-modules/ghostty-mac-config.nix b/home-modules/ghostty-mac-config.nix new file mode 100644 index 0000000..c61ecdf --- /dev/null +++ b/home-modules/ghostty-mac-config.nix @@ -0,0 +1,42 @@ +{ ... }: { + home.file.".config/ghostty/config" = { + text = '' + # If not preset, this causes issues when sshing into linux servers. + term = xterm-256color + font-family = "BigBlueTermPlus Nerd Font" + font-size = 17.2 + theme = "PaleNight" + background-opacity = 0.9 + window-decoration = true + + # This is the default to open the configuration + # I type this by accident way to frequently... + keybind = ctrl+comma=unbind + ''; + }; + home.file.".config/ghostty/themes/PaleNight" = { + text = '' + palette = 0=#000000 + palette = 1=#ff5555 + palette = 2=#50fa7b + palette = 3=#f1fa8c + palette = 4=#caa9fa + palette = 5=#ff79c6 + palette = 6=#8be9fd + palette = 7=#bfbfbf + palette = 8=#575b70 + palette = 9=#ff6e67 + palette = 10=#5af78e + palette = 11=#f4f99d + palette = 12=#caa9fa + palette = 13=#ff92d0 + palette = 14=#9aedfe + palette = 15=#e6e6e6 + background = 282a36 + foreground = f8f8f2 + cursor-color = #ffcb6b + selection-background = #717cb4 + selection-foreground = #80cbc4 + ''; + }; +} diff --git a/home-modules/git.nix b/home-modules/git.nix new file mode 100644 index 0000000..dea4c2c --- /dev/null +++ b/home-modules/git.nix @@ -0,0 +1,15 @@ +{ pkgs, ... } : { + + home.packages = with pkgs; [ + git + ]; + + programs.git = { + enable = true; + userName = "jmug"; + userEmail = "u.g.a.mariano@gmail.com"; + extraConfig = { + init.defaultBranch = "main"; + }; + }; +} diff --git a/home-modules/lazygit.nix b/home-modules/lazygit.nix new file mode 100644 index 0000000..1160e30 --- /dev/null +++ b/home-modules/lazygit.nix @@ -0,0 +1,33 @@ +{ ... }: { + programs.lazygit = { + enable = true; + settings = { + git.commit.signOff = true; + git.autoFetch = false; + git.autoRefresh = false; + keybinding = { + universal = { + prevItem-alt = "e"; + nextItem-alt = "n"; + prevBlock-alt = "m"; + nextBlock-alt = "i"; + nextMatch = "@"; + prevMatch = "#"; + new = "+"; + edit = "~"; + nextScreenMode = "*"; + }; + files.ignoreFile = "-"; + branches.viewGitFlowOptions = "g"; + commits.startInteractiveRebase = "I"; + submodules.init = "I"; + }; + }; + }; + + programs.zsh = { + shellAliases = { + lg = "lazygit"; + }; + }; +} diff --git a/home-modules/nvim.nix b/home-modules/nvim.nix new file mode 100644 index 0000000..edafeec --- /dev/null +++ b/home-modules/nvim.nix @@ -0,0 +1,17 @@ +{ pkgs, ... } : { + + home.packages = with pkgs; [ + neovim + ]; + + programs.zsh = { + shellAliases = { + n = "nvim"; + }; + }; + + home.file.".config/nvim" = { + recursive = true; + source = ./explicit-configs/nvim; + }; +} diff --git a/home-modules/sops.nix b/home-modules/sops.nix new file mode 100644 index 0000000..e5b56c0 --- /dev/null +++ b/home-modules/sops.nix @@ -0,0 +1,22 @@ +{ inputs, ... }: +{ + imports = [ + inputs.sops-nix.homeManagerModules.sops + ]; + + sops = { + age.keyFile = "/home/jmug/.config/sops/age/keys.txt"; + + defaultSopsFile = ../secrets.yaml; + validateSopsFiles = false; + + secrets = { + "private_keys/jmug" = { + path = "/home/jmug/.ssh/id_jmug"; + }; + "private_keys/matcha" = { + path = "/home/jmug/.ssh/id_matcha"; + }; + }; + }; +} diff --git a/home-modules/starship.nix b/home-modules/starship.nix new file mode 100644 index 0000000..d741c9b --- /dev/null +++ b/home-modules/starship.nix @@ -0,0 +1,30 @@ +{ ... }: { + programs.starship = { + enable = true; + enableZshIntegration = true; + settings = { + username.format = "[$user](213) "; + hostname = { + disabled = false; + ssh_symbol = "🌐"; + style = "bold green"; + }; + nix_shell = { + format = "[$symbol$state nix-shell]($style) "; + symbol = "❄️ "; + pure_msg = "p"; + impure_msg = "i"; + }; + golang = { + format = "go [$symbol $version ]($style)"; + symbol = "🦫"; + }; + character = { + error_symbol = "[✖](bold red)"; + }; + directory = { + truncation_symbol = ".../"; + }; + }; + }; +} diff --git a/home-modules/tmux-darwin.nix b/home-modules/tmux-darwin.nix new file mode 100644 index 0000000..1f6d271 --- /dev/null +++ b/home-modules/tmux-darwin.nix @@ -0,0 +1,81 @@ +{ pkgs, ... }: { + + home = { + packages = with pkgs; [ + tmux + ]; + }; + + programs.zsh = { + shellAliases = { + t = "tmux"; + ta = "tmux attach -t"; + tns = "tmux new-session -t"; + tks = "tmux kill-session -t"; + }; + }; + + programs.tmux = { + enable = true; + mouse = true; + prefix = "C-a"; + terminal = "screen-256color"; + historyLimit = 5000; + baseIndex = 1; + escapeTime = 10; + customPaneNavigationAndResize = true; + sensibleOnTop = false; + extraConfig = '' + set -ga terminal-overrides ",xterm-256color*:Tc" + # Keep path when creating a new window. + bind c new-window -c "#{pane_current_path}" + + # Better splits + bind-key "|" split-window -h -c "#{pane_current_path}" + bind-key "\\" split-window -fh -c "#{pane_current_path}" + bind-key "-" split-window -v -c "#{pane_current_path}" + bind-key "_" split-window -fv -c "#{pane_current_path}" + + # Toggle last windows + bind Space last-window + bind -r p previous-window + bind -r f next-window + + # Movement + bind -r e select-pane -U + bind -r n select-pane -D + bind -r m select-pane -L + bind -r i select-pane -R + + # Resizing this will not work with colemak keymaps. + bind -r C-j resize-pane -D 15 + bind -r C-k resize-pane -U 15 + bind -r C-h resize-pane -L 15 + bind -r C-l resize-pane -R 15 + + # Kill panes and windows + bind w kill-pane + bind W kill-window + + # Pane sync + unbind s + unbind S + bind s setw synchronize-panes on + bind S setw synchronize-panes off + ''; + plugins = with pkgs; [ + { + plugin = tmuxPlugins.resurrect; + extraConfig = "set -g @resurrect-strategy-nvim 'session'"; + } + { + plugin = tmuxPlugins.continuum; + extraConfig = '' + set -g @continuum-restore 'on' + set -g @continuum-save-interval '1' # minutes + ''; + } + ]; + }; + +} diff --git a/home-modules/tmux.nix b/home-modules/tmux.nix new file mode 100644 index 0000000..31bea1c --- /dev/null +++ b/home-modules/tmux.nix @@ -0,0 +1,80 @@ +{ pkgs, ... }: { + + home = { + packages = with pkgs; [ + tmux + ]; + }; + + programs.zsh = { + shellAliases = { + t = "tmux"; + ta = "tmux attach -t"; + tns = "tmux new-session -t"; + tks = "tmux kill-session -t"; + }; + }; + + programs.tmux = { + enable = true; + mouse = true; + prefix = "C-a"; + terminal = "screen-256color"; + historyLimit = 5000; + baseIndex = 1; + escapeTime = 10; + customPaneNavigationAndResize = true; + extraConfig = '' + set -ga terminal-overrides ",xterm-256color*:Tc" + # Keep path when creating a new window. + bind c new-window -c "#{pane_current_path}" + + # Better splits + bind-key "|" split-window -h -c "#{pane_current_path}" + bind-key "\\" split-window -fh -c "#{pane_current_path}" + bind-key "-" split-window -v -c "#{pane_current_path}" + bind-key "_" split-window -fv -c "#{pane_current_path}" + + # Toggle last windows + bind Space last-window + bind -r p previous-window + bind -r f next-window + + # Movement + bind -r e select-pane -U + bind -r n select-pane -D + bind -r m select-pane -L + bind -r i select-pane -R + + # Resizing this will not work with colemak keymaps. + bind -r C-j resize-pane -D 15 + bind -r C-k resize-pane -U 15 + bind -r C-h resize-pane -L 15 + bind -r C-l resize-pane -R 15 + + # Kill panes and windows + bind w kill-pane + bind W kill-window + + # Pane sync + unbind s + unbind S + bind s setw synchronize-panes on + bind S setw synchronize-panes off + ''; + plugins = with pkgs; [ + { + plugin = tmuxPlugins.resurrect; + extraConfig = "set -g @resurrect-strategy-nvim 'session'"; + } + { + plugin = tmuxPlugins.continuum; + extraConfig = '' + set -g @continuum-restore 'on' + set -g @continuum-save-interval '1' # minutes + ''; + } + ]; + }; + +} diff --git a/home-modules/vscode.nix b/home-modules/vscode.nix new file mode 100644 index 0000000..c0cef9a --- /dev/null +++ b/home-modules/vscode.nix @@ -0,0 +1,5 @@ +{ pkgs, ... }: { + home.packages = with pkgs; [ + vscode-fhs + ]; +} diff --git a/home-modules/zsh.nix b/home-modules/zsh.nix new file mode 100644 index 0000000..85a84fd --- /dev/null +++ b/home-modules/zsh.nix @@ -0,0 +1,7 @@ +{ ... }: { + programs.zsh = { + enable = true; + autocd = true; + defaultKeymap = "emacs"; + }; +} diff --git a/hosts/common/core/default.nix b/hosts/common/core/default.nix new file mode 100644 index 0000000..4f1bd70 --- /dev/null +++ b/hosts/common/core/default.nix @@ -0,0 +1,8 @@ +{ ... }: +{ + imports = [ + ./sops.nix + + ../../../modules/nixos + ]; +} diff --git a/hosts/common/core/sops.nix b/hosts/common/core/sops.nix new file mode 100644 index 0000000..4c873e8 --- /dev/null +++ b/hosts/common/core/sops.nix @@ -0,0 +1,27 @@ +{ inputs, config, ... }: +{ + imports = [ + inputs.sops-nix.nixosModules.sops + ]; + + sops = { + defaultSopsFile = ../../../secrets.yaml; + validateSopsFiles = false; + + age = { + sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; + keyFile = "/var/lib/sops-nix/key.txt"; + generateKey = true; + }; + }; + + sops.secrets = { + "wireless.env" = {}; + + "yubico/u2f_keys/jmug" = { + owner = config.users.users.jmug.name; + inherit (config.users.users.jmug) group; + path = "/home/jmug/.config/Yubico/u2f_keys"; + }; + }; +} diff --git a/hosts/common/keys/id_jmug.pub b/hosts/common/keys/id_jmug.pub new file mode 100644 index 0000000..6da5ea3 --- /dev/null +++ b/hosts/common/keys/id_jmug.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFg9H/WWc1/jStVmgz6Gm4orTJ6yexHFWjBSYqDJyLvY jmug@nixlap diff --git a/hosts/common/keys/id_secretive.pub b/hosts/common/keys/id_secretive.pub new file mode 100644 index 0000000..175e670 --- /dev/null +++ b/hosts/common/keys/id_secretive.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCLJj/fefApUkXp79YPPk1O2L6CW6kqISXSWWmR8+MX0wk3lQv1NUp1p87sE57i6aUcYMuba0U9y+ppQq603uOc= code@secretive.Mariano’s-MacBook-Pro-(2).local diff --git a/hosts/common/keys/yubi/id_matcha.pub b/hosts/common/keys/yubi/id_matcha.pub new file mode 100644 index 0000000..70013ce --- /dev/null +++ b/hosts/common/keys/yubi/id_matcha.pub @@ -0,0 +1 @@ +sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIO4E2wFY8IJQYn16Xf1AN5UGXnhm/Ie22jZOX96aOcUuAAAABHNzaDo= jmug@nixlap diff --git a/hosts/common/optional/yubikey.nix b/hosts/common/optional/yubikey.nix new file mode 100644 index 0000000..f58db56 --- /dev/null +++ b/hosts/common/optional/yubikey.nix @@ -0,0 +1,8 @@ +{ ... }: { + yubikey = { + enable = true; + identifiers = { + matcha = 13164607; + }; + }; +} diff --git a/hosts/devbox/configuration.nix b/hosts/devbox/configuration.nix new file mode 100644 index 0000000..5810b5c --- /dev/null +++ b/hosts/devbox/configuration.nix @@ -0,0 +1,72 @@ +# Edit this configuration file to define what should be installed on +# your system. Help is available in the configuration.nix(5) man page, on +# https://search.nixos.org/options and in the NixOS manual (`nixos-help`). +{ config, lib, pkgs, ... }: +let + pubKeys = lib.filesystem.listFilesRecursive ../common/keys; +in +{ + imports = + [ # Include the results of the hardware scan. + ./hardware-configuration.nix + ]; + + # Use the systemd-boot EFI boot loader. + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + virtualisation.docker.enable = true; + + networking.hostName = "nixbox"; # Define your hostname. + + time.timeZone = "America/Los_Angeles"; + + # Select internationalisation properties. + i18n.defaultLocale = "en_US.UTF-8"; + + i18n.extraLocaleSettings = { + LC_ADDRESS = "en_US.UTF-8"; + LC_IDENTIFICATION = "en_US.UTF-8"; + LC_MEASUREMENT = "en_US.UTF-8"; + LC_MONETARY = "en_US.UTF-8"; + LC_NAME = "en_US.UTF-8"; + LC_NUMERIC = "en_US.UTF-8"; + LC_PAPER = "en_US.UTF-8"; + LC_TELEPHONE = "en_US.UTF-8"; + LC_TIME = "en_US.UTF-8"; + }; + + programs.zsh.enable = true; + users.users.jmug = { + isNormalUser = true; + description = "Mariano Uvalle"; + extraGroups = [ "wheel" "docker" ]; + shell = pkgs.zsh; + + openssh.authorizedKeys.keys = lib.lists.forEach pubKeys (key: builtins.readFile key); + }; + users.users.root = { + shell = pkgs.zsh; + }; + + # List services that you want to enable: + + # Enable the OpenSSH daemon. + services.openssh = { + enable = true; + ports = [ 69 ]; + settings = { + PasswordAuthentication = false; + PermitRootLogin = "no"; + }; + }; + + # Open ports in the firewall. + # networking.firewall.allowedTCPPorts = [ ... ]; + # networking.firewall.allowedUDPPorts = [ ... ]; + # Or disable the firewall altogether. + networking.firewall.enable = false; + + system.stateVersion = "24.11"; # Did you read the comment? +} diff --git a/hosts/devbox/hardware-configuration.nix b/hosts/devbox/hardware-configuration.nix new file mode 100644 index 0000000..69ee500 --- /dev/null +++ b/hosts/devbox/hardware-configuration.nix @@ -0,0 +1,41 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usb_storage" "sd_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/67dc8c71-37ca-4340-945a-cfd1befe2aa1"; + fsType = "ext4"; + }; + + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/9FBA-15AF"; + fsType = "vfat"; + options = [ "fmask=0077" "dmask=0077" ]; + }; + + swapDevices = + [ { device = "/dev/disk/by-uuid/16fa084b-0350-4416-9597-36010f16011d"; } + ]; + + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking + # (the default) this is the recommended approach. When using systemd-networkd it's + # still possible to use this option, but it's recommended to use it in conjunction + # with explicit per-interface declarations with `networking.interfaces..useDHCP`. + networking.useDHCP = lib.mkDefault true; + # networking.interfaces.enp2s0.useDHCP = lib.mkDefault true; + # networking.interfaces.wlo1.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/hosts/devbox/home-root.nix b/hosts/devbox/home-root.nix new file mode 100644 index 0000000..5b10995 --- /dev/null +++ b/hosts/devbox/home-root.nix @@ -0,0 +1,32 @@ +{ config, pkgs, ... } : + +{ + + imports = [ + ../../home-modules/default.nix + ../../home-modules/nvim.nix + ../../home-modules/git.nix + ../../home-modules/lazygit.nix + ../../home-modules/starship.nix + ../../home-modules/zsh.nix + ]; + + home = { + username = "root"; + homeDirectory = "/root"; + + stateVersion = "24.11"; + }; + + programs.zsh = { + shellAliases = { + lg = "lazygit"; + n = "nvim"; + # TODO: Interpolate the hostname here. + nrsw = "nixos-rebuild switch --flake /etc/nixos#devbox"; + }; + }; + + # Let Home Manager install and manage itself. + programs.home-manager.enable = true; +} diff --git a/hosts/devbox/home.nix b/hosts/devbox/home.nix new file mode 100644 index 0000000..25e8128 --- /dev/null +++ b/hosts/devbox/home.nix @@ -0,0 +1,80 @@ +{ config, pkgs, ... } : + +{ + + imports = [ + ../../home-modules/default.nix + ../../home-modules/nvim.nix + ../../home-modules/tmux.nix + ../../home-modules/git.nix + ../../home-modules/lazygit.nix + ../../home-modules/starship.nix + ../../home-modules/direnv.nix + ../../home-modules/zsh.nix + ]; + + home = { + username = "jmug"; + homeDirectory = "/home/jmug"; + + packages = with pkgs; [ + zig + neofetch + fzf + ripgrep + htop + git + wget + exercism + # Thin provisioning tools + thin-provisioning-tools + ]; + + stateVersion = "24.11"; + }; + + programs.zsh = { + shellAliases = { + # TODO BEGIN Interpolate the name of the host here. + flakeconf = "sudo nvim /etc/nixos/flake.nix"; + nosconf = "sudo nvim /etc/nixos/hosts/devbox/configuration.nix"; + homeconf = "sudo nvim /etc/nixos/hosts/devbox/home.nix"; + nvconf = "sudo nvim /etc/nixos/home-modules/explicit-configs/nvim/init.lua"; + # TODO END Interpolate the name of the host here. + rshellconf = "source ~/.zshrc"; + # TODO: Interpolate the name of the host here. + nrsw = "sudo nixos-rebuild switch --flake /home/jmug/nixos#devbox"; + }; + }; + + services.ssh-agent.enable = true; + programs.ssh = { + enable = true; + addKeysToAgent = "confirm"; + # matchBlocks = { + # alarm = { + # user = "alarm"; + # hostname = "alarm"; + # forwardAgent = true; + # identityFile = "/home/jmug/.ssh/id_ed25519"; + # }; + # wsl = { + # user = "aym"; + # hostname = "192.168.10.241"; + # port = 69; + # forwardAgent = true; + # identityFile = "/home/jmug/.ssh/id_ed25519"; + # }; + # ws = { + # user = "aym"; + # hostname = "73.118.150.68"; + # port = 69; + # forwardAgent = true; + # identityFile = "/home/jmug/.ssh/id_ed25519"; + # }; + # }; + }; + + # Let Home Manager install and manage itself. + programs.home-manager.enable = true; +} diff --git a/hosts/macbook/configuration.nix b/hosts/macbook/configuration.nix new file mode 100644 index 0000000..403e00e --- /dev/null +++ b/hosts/macbook/configuration.nix @@ -0,0 +1,55 @@ +{ self, pkgs, ... }: { + # List packages installed in system profile. To search by name, run: + # $ nix-env -qaP | grep wget + environment.systemPackages = with pkgs; [ + neofetch + ]; + + homebrew = { + enable = true; + onActivation = { + autoUpdate = true; + cleanup = "uninstall"; + upgrade = true; + }; + + caskArgs = { + no_quarantine = true; + }; + + taps = []; + brews = [ + "raylib" + ]; + casks = [ + "ghostty" + "kicad" + "secretive" + "gcc-arm-embedded" + "librewolf" + ]; + }; + + fonts.packages = with pkgs; [ + (nerdfonts.override { fonts = [ "BigBlueTerminal" ]; }) + ]; + + users.users.uagm.home = "/Users/uagm"; + + nixpkgs.config.allowUnfree = true; + + programs.zsh.enable = true; + + # Necessary for using flakes on this system. + nix.settings.experimental-features = "nix-command flakes"; + + # Set Git commit hash for darwin-version. + system.configurationRevision = self.rev or self.dirtyRev or null; + + # Used for backwards compatibility, please read the changelog before changing. + # $ darwin-rebuild changelog + system.stateVersion = 5; + + # The platform the configuration will be used on. + nixpkgs.hostPlatform = "aarch64-darwin"; +} diff --git a/hosts/macbook/home.nix b/hosts/macbook/home.nix new file mode 100644 index 0000000..1e4238a --- /dev/null +++ b/hosts/macbook/home.nix @@ -0,0 +1,42 @@ +{ config, pkgs, ... } : { + + imports = [ + ../../home-modules/tmux-darwin.nix + ../../home-modules/lazygit.nix + ../../home-modules/zsh.nix + ../../home-modules/starship.nix + ../../home-modules/nvim.nix + ../../home-modules/direnv.nix + # I should update the module with an option for adding winodow decorations. + ../../home-modules/ghostty-mac-config.nix + ]; + + home = { + username = "uagm"; + homeDirectory = "/Users/uagm"; + + packages = with pkgs; [ + fzf + ripgrep + exercism + typescript-language-server + lua-language-server + starship + audacity + ]; + + stateVersion = "24.11"; + }; + + programs.zsh = { + shellAliases = { + # TODO BEGIN Interpolate the name of the host here. + flakeconf = "nvim /Users/uagm/nixos/flake.nix"; + sysconf = "nvim /Users/uagm/nixos/hosts/macbook/configuration.nix"; + homeconf = "nvim /Users/uagm/nixos/hosts/macbook/home.nix"; + nvconf = "nvim /Users/uagm/nixos/home-modules/explicit-configs/nvim/init.lua"; + # TODO: Interpolate the name of the host here. + nrsw = "darwin-rebuild switch --flake /Users/uagm/nixos#macbook"; + }; + }; +} diff --git a/hosts/nixlap/configuration.nix b/hosts/nixlap/configuration.nix new file mode 100644 index 0000000..2911426 --- /dev/null +++ b/hosts/nixlap/configuration.nix @@ -0,0 +1,317 @@ +# Edit this configuration file to define what should be installed on +# your system. Help is available in the configuration.nix(5) man page +# and in the NixOS manual (accessible by running ‘nixos-help’). + +{ lib, config, pkgs, ghostty, ... }: +let + hello-script = import ../../nixos-modules/shell-apps/hello.nix {inherit pkgs; }; + print-colors-script = import ../../nixos-modules/shell-apps/print-colors.nix { inherit pkgs; }; +in +{ + imports = lib.flatten [ + # Include the results of the hardware scan. + ./hardware-configuration.nix + + ../common/core + + # Set up relative to root. + ../common/optional/yubikey.nix + ]; + + # Bootloader. + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + # Firmware updates. + services.fwupd.enable = true; + # Support for the fingerprint reader. + services.fprintd.enable = true; + # bluetooth. + hardware.bluetooth.enable = true; + hardware.bluetooth.powerOnBoot = false; + # power saving on amd laptops. + services.power-profiles-daemon.enable = true; + # Bonjour + services.murmur.bonjour = true; + + # Allow using flakes + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + + virtualisation.docker.enable = true; + + networking.hostName = "nixlap"; # Define your hostname. + # TODO: Move the passwords to secrets before pushing this to source control. + networking.wireless = { # Enables wireless supprto through wpa supplicant. + enable = true; + secretsFile = config.sops.secrets."wireless.env".path; + networks = { + # Home + "UG_LivingRoom_5G" = { + pskRaw = "ext:home_psk"; + }; + # Whidbey coffee. + pioneer = { + psk = "Coffee99"; + }; + "Tim Hortons WiFi" = {}; + # Mayne Island cabin + "SHAW-8D81B0" = { + psk = "2511530A6165"; + }; + # Coffee shops + "Story Coffee" = { + psk = "YOURSTORY"; + }; + "SoulfoodGuest" = { + psk = "javajava123"; + }; + "Cedar And Salt 2.4" = { + psk = "Coffeehouse"; + }; + "Starbucks WiFi" = {}; + }; + }; + + # Set your time zone. + time.timeZone = "America/Los_Angeles"; + + # Select internationalisation properties. + i18n.defaultLocale = "en_US.UTF-8"; + + i18n.extraLocaleSettings = { + LC_ADDRESS = "en_US.UTF-8"; + LC_IDENTIFICATION = "en_US.UTF-8"; + LC_MEASUREMENT = "en_US.UTF-8"; + LC_MONETARY = "en_US.UTF-8"; + LC_NAME = "en_US.UTF-8"; + LC_NUMERIC = "en_US.UTF-8"; + LC_PAPER = "en_US.UTF-8"; + LC_TELEPHONE = "en_US.UTF-8"; + LC_TIME = "en_US.UTF-8"; + }; + + # Display + services.xserver = { + enable = true; + enableTearFree = true; + synaptics = { + scrollDelta = -75; + }; + windowManager.awesome.enable = true; + xkb = { + layout = "us"; + variant = ""; + }; + }; + services.picom = { + enable = true; + vSync = true; + }; + # Allow autorandr hot-plug. + services.autorandr.enable = true; + + programs.zsh.enable = true; + users.users.jmug = { + isNormalUser = true; + description = "Mariano Uvalle"; + extraGroups = [ "wheel" "docker" ]; + shell = pkgs.zsh; + }; + users.users.root = { + shell = pkgs.zsh; + }; + + + # Allow unfree packages + nixpkgs.config.allowUnfree = true; + + # iPhone USB tethering. + services.usbmuxd.enable = true; + + # List packages installed in system profile. To search, run: + # $ nix search wget + environment.systemPackages = with pkgs; [ + rpi-imager + fzf + ripgrep + i3lock-fancy + # Logitech mouse + logiops + # System + power-profiles-daemon + htop + git + neovim + wget + libnotify + autorandr + # iOS tethering and mounting + libimobiledevice + ifuse + # Custom shell apps + hello-script + print-colors-script + # Terminal + ghostty.packages.x86_64-linux.default + # Thin provisioning tools + thin-provisioning-tools + ]; + programs.neovim.enable = true; + programs.neovim.defaultEditor = true; + + fonts = { + fontDir.enable = true; + packages = with pkgs; [ + (nerdfonts.override { fonts = [ "BigBlueTerminal" ]; }) + fira-code-nerdfont + ]; + }; + + # Logiops daemon + systemd.packages = [ pkgs.logiops ]; + systemd.services.logid.wantedBy = [ "multi-user.target" ]; + environment.etc = { + "logid.cfg" = { + # For some reason scroll inversion does not work for the mx vertical... + text = '' + devices: ( + { + name: "Wireless Mouse MX Master 3"; + + hiresscroll: { + invert: true; + }; + + thumbwheel: { + invert: true; + }; + + buttons: ( + { + cid: 0x53; + action = + { + type : "Keypress"; + keys: ["KEY_LEFTMETA", "KEY_LEFT"]; + }; + }, + { + cid: 0x56; + action = + { + type : "Keypress"; + keys: ["KEY_LEFTMETA", "KEY_RIGHT"]; + }; + } + ); + }, + { + name: "MX Vertical Advanced Ergonomic Mouse"; + + hiresscroll: { + hires: true; + invert: false; + target: false; + }; + + buttons: ( + { + cid: 0x53; + action = + { + type : "Keypress"; + keys: ["KEY_LEFTMETA", "KEY_PAGEUP"]; + }; + }, + { + cid: 0x56; + action = + { + type : "Keypress"; + keys: ["KEY_LEFTMETA", "KEY_PAGEDOWN"]; + }; + }, + { + cid: 0xfd; + action = + { + type : "Keypress"; + keys: ["KEY_LEFTMETA"]; + }; + } + ); + } + ); + ''; + }; + }; + + # Install keyd for system level key remapping. + services.keyd = { + enable = true; + keyboards.colemakdhm = { + ids = [ "0001:0001:70533846" ]; + settings = { + main = { + e = "f"; + r = "p"; + t = "b"; + y = "j"; + u = "l"; + i = "u"; + o = "y"; + p = ";"; + s = "r"; + d = "s"; + f = "t"; + h = "m"; + j = "n"; + k = "e"; + l = "i"; + ";" = "o"; + v = "d"; + b = "v"; + n = "k"; + m = "h"; + leftalt = "leftmeta"; + leftmeta = "leftalt"; + capslock = "leftcontrol"; + }; + }; + }; + }; + + services.acpid = { + enable = true; + # Run autorandr when the lid opens/closes. + lidEventCommands = '' + export PATH=/run/wrappers/bin:/run/current-system/sw/bin:$PATH + export DISPLAY=":0.0" + export XAUTHORITY="/home/jmug/.Xauthority" + sudo -u jmug autorandr --change + ''; + }; + + # Bluetooth. + services.blueman.enable = true; + + # USB devices + services.devmon.enable = true; + services.gvfs.enable = true; + services.udisks2.enable = true; + + # Smart Cards (required for yubico authenticator) + services.pcscd.enable = true; + + # Enable the OpenSSH daemon. + # services.openssh.enable = true; + programs.ssh.startAgent = true; + + # This value determines the NixOS release from which the default + # settings for stateful data, like file locations and database versions + # on your system were taken. It‘s perfectly fine and recommended to leave + # this value at the release version of the first install of this system. + # Before changing this value read the documentation for this option + # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + system.stateVersion = "24.11"; # Did you read the comment? +} diff --git a/hosts/nixlap/hardware-configuration.nix b/hosts/nixlap/hardware-configuration.nix new file mode 100644 index 0000000..50c0c2e --- /dev/null +++ b/hosts/nixlap/hardware-configuration.nix @@ -0,0 +1,46 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "thunderbolt" "usbhid" "usb_storage" "sd_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-amd" ]; + boot.extraModulePackages = [ ]; + + # To allow for hibernate. + boot.initrd.systemd.enable = true; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/ac3509f2-1123-461b-89c3-0dcfcb1e2a91"; + fsType = "ext4"; + }; + + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/DEF4-1192"; + fsType = "vfat"; + options = [ "fmask=0077" "dmask=0077" ]; + }; + + swapDevices = + [ { device = "/dev/disk/by-uuid/442b1474-627d-4f0d-bd67-5f91e6396d56"; } + ]; + + # Enables DHCP on each ethernet and wireless interface. In case of scripted networking + # (the default) this is the recommended approach. When using systemd-networkd it's + # still possible to use this option, but it's recommended to use it in conjunction + # with explicit per-interface declarations with `networking.interfaces..useDHCP`. + networking.useDHCP = lib.mkDefault true; + # networking.interfaces.wlp1s0.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; + + services.libinput.touchpad.naturalScrolling = true; + services.libinput.mouse.naturalScrolling = true; +} diff --git a/hosts/nixlap/home-root.nix b/hosts/nixlap/home-root.nix new file mode 100644 index 0000000..8c24109 --- /dev/null +++ b/hosts/nixlap/home-root.nix @@ -0,0 +1,33 @@ + +{ config, pkgs, ... } : + +{ + + imports = [ + ../../home-modules/default.nix + ../../home-modules/nvim.nix + ../../home-modules/git.nix + ../../home-modules/lazygit.nix + ../../home-modules/starship.nix + ../../home-modules/zsh.nix + ]; + + home = { + username = "root"; + homeDirectory = "/root"; + + stateVersion = "24.11"; + }; + + programs.zsh = { + shellAliases = { + lg = "lazygit"; + n = "nvim"; + # TODO: Interpolate the hostname here. + nrsw = "nixos-rebuild switch --flake /home/jmug/nixos#nixlap"; + }; + }; + + # Let Home Manager install and manage itself. + programs.home-manager.enable = true; +} diff --git a/hosts/nixlap/home.nix b/hosts/nixlap/home.nix new file mode 100644 index 0000000..16cf6a6 --- /dev/null +++ b/hosts/nixlap/home.nix @@ -0,0 +1,355 @@ +{ lib, config, pkgs, ... } : +let + pathToKeys = ../common/keys/yubi; + yubiKeys = + lib.lists.forEach (builtins.attrNames (builtins.readDir pathToKeys)) + (key: lib.substring 0 (lib.stringLength key - lib.stringLength ".pub") key); # Remove .pub suffix. + yubikeyPublicKeyEntries = lib.attrsets.mergeAttrsList ( + lib.lists.map + (key: { ".ssh/${key}.pub".source = "${pathToKeys}/${key}.pub"; }) + yubiKeys + ); +in +{ + + imports = [ + ../../home-modules/default.nix + ../../home-modules/nvim.nix + ../../home-modules/tmux.nix + ../../home-modules/git.nix + ../../home-modules/lazygit.nix + ../../home-modules/starship.nix + ../../home-modules/direnv.nix + ../../home-modules/zsh.nix + ../../home-modules/vscode.nix + ../../home-modules/books.nix + ../../home-modules/ghostty-config.nix + ../../home-modules/sops.nix + ]; + + # Let home manager start the X11 session. + xsession = { + enable = true; + windowManager.awesome.enable = true; + initExtra = " + albert & + flameshot & + "; + }; + + home = { + username = "jmug"; + homeDirectory = "/home/jmug"; + + packages = with pkgs; [ + gpick + ticktick + galculator + blueman + albert + xclip + fprintd + keyd + librewolf + zig + xorg.xwininfo + neofetch + # For battery widged. + acpid + # Screen management + brightnessctl + # Audio + spotify + spotify-tray + # Communication + discord + slack + # Wallpapers + nitrogen + # Whatsapp + whatsie + ventoy + # Embedded + usbutils + # Screenshots + flameshot + # xdg-open + xdg-utils + powertop + age + sops + screen + ]; + + file = { + ".config/awesome" = { + recursive = true; + source = ../../home-modules/explicit-configs/awesome; + }; + } // yubikeyPublicKeyEntries; # Move yubikey stuff to a separate module. + + + stateVersion = "24.11"; + }; + + home.pointerCursor = { + gtk.enable = true; + x11.enable = true; + name = "Posy_Cursor_Black"; + package = pkgs."posy-cursors"; + }; + + programs.zsh = { + shellAliases = { + # TODO BEGIN Interpolate the name of the host here. + flakeconf = "nvim /home/jmug/nixos/flake.nix"; + nosconf = "nvim /home/jmug/nixos/hosts/nixlap/configuration.nix"; + homeconf = "nvim /home/jmug/nixos/hosts/nixlap/home.nix"; + nvconf = "nvim /home/jmug/nixos/home-modules/explicit-configs/nvim/init.lua"; + awmconf = "nvim /home/jmug/nixos/home-modules/explicit-configs/awesome/rc.lua"; + # TODO END Interpolate the name of the host here. + rshellconf = "source ~/.zshrc"; + rlogiops = "sudo systemctl restart logid"; # Restart the logid daemon. + # TODO: Interpolate the name of the host here. + nrsw = "sudo nixos-rebuild switch --flake /home/jmug/nixos#nixlap"; # parametrize this as home dir. + }; + }; + + programs.alacritty = { + enable = true; + settings = { + env = { + TERM = "xterm-256color"; + }; + font = { + size = 8.6; + normal.family = "BigBlueTermPlus Nerd Font"; + }; + window.opacity = 0.9; + colors = { + bright = { + black = "#575b70"; + blue = "#caa9fa"; + cyan = "#9aedfe"; + green = "#5af78e"; + magenta = "#ff92d0"; + red = "#ff6e67"; + white = "#e6e6e6"; + yellow = "#f4f99d"; + }; + normal = { + black = "#000000"; + blue = "#caa9fa"; + cyan = "#8be9fd"; + green = "#50fa7b"; + magenta = "#ff79c6"; + red = "#ff5555"; + white = "#bfbfbf"; + yellow = "#f1fa8c"; + }; + primary = { + background = "#282a36"; + foreground = "#f8f8f2"; + }; + }; + }; + }; + + services.autorandr.enable = true; + programs.autorandr = { + enable = true; + profiles = { + laptop = { + fingerprint = { + "eDP-1" = "00ffffffffffff0009e5ca0b000000002f200104a51c137803de50a3544c99260f505400000001010101010101010101010101010101115cd01881e02d50302036001dbe1000001aa749d01881e02d50302036001dbe1000001a000000fe00424f452043510a202020202020000000fe004e4531333546424d2d4e34310a0073"; + }; + config = { + "eDP-1" = { + enable = true; + primary = true; + mode = "2256x1504"; + }; + }; + }; + docked_open_dp8 = { + fingerprint = { + "eDP-1" = "00ffffffffffff0009e5ca0b000000002f200104a51c137803de50a3544c99260f505400000001010101010101010101010101010101115cd01881e02d50302036001dbe1000001aa749d01881e02d50302036001dbe1000001a000000fe00424f452043510a202020202020000000fe004e4531333546424d2d4e34310a0073"; + "DP-8" = "00ffffffffffff001e6d5677061e030003210104b5522278ffa105a9564ba4250f5054210800d1c0614001010101010101010101010148d570b0d0a0465038203a00345a3100001a000000fd003064979737010a202020202020000000fc004c472048445220575148440a20000000ff003330334d58554e36303239340a01840203287423090707830100004410040301e2006ae305c000e606050153535d681a00000101306400e2b370b0d0a03b5038203a00345a3100001a0d7f70b0d0a03c5038203a00345a3100001adc6970b0d0a03c5038203a00345a3100001a0000000000000000000000000000000000000000000000000000000000000000004a"; + }; + config = { + "eDP-1" = { + position = "592x1440"; + enable = true; + mode = "2256x1504"; + }; + "DP-8" = { + enable = true; + primary = true; + mode = "3440x1440"; + rate = "100.00"; + }; + }; + }; + docked_closed_dp8 = { + fingerprint = { + "DP-8" = "00ffffffffffff001e6d5677061e030003210104b5522278ffa105a9564ba4250f5054210800d1c0614001010101010101010101010148d570b0d0a0465038203a00345a3100001a000000fd003064979737010a202020202020000000fc004c472048445220575148440a20000000ff003330334d58554e36303239340a01840203287423090707830100004410040301e2006ae305c000e606050153535d681a00000101306400e2b370b0d0a03b5038203a00345a3100001a0d7f70b0d0a03c5038203a00345a3100001adc6970b0d0a03c5038203a00345a3100001a0000000000000000000000000000000000000000000000000000000000000000004a"; + }; + config = { + "DP-8" = { + enable = true; + primary = true; + mode = "3440x1440"; + rate = "100.00"; + }; + }; + }; + docked_open_dp7 = { + fingerprint = { + "eDP-1" = "00ffffffffffff0009e5ca0b000000002f200104a51c137803de50a3544c99260f505400000001010101010101010101010101010101115cd01881e02d50302036001dbe1000001aa749d01881e02d50302036001dbe1000001a000000fe00424f452043510a202020202020000000fe004e4531333546424d2d4e34310a0073"; + "DP-7" = "00ffffffffffff001e6d5677061e030003210104b5522278ffa105a9564ba4250f5054210800d1c0614001010101010101010101010148d570b0d0a0465038203a00345a3100001a000000fd003064979737010a202020202020000000fc004c472048445220575148440a20000000ff003330334d58554e36303239340a01840203287423090707830100004410040301e2006ae305c000e606050153535d681a00000101306400e2b370b0d0a03b5038203a00345a3100001a0d7f70b0d0a03c5038203a00345a3100001adc6970b0d0a03c5038203a00345a3100001a0000000000000000000000000000000000000000000000000000000000000000004a"; + }; + config = { + "eDP-1" = { + position = "592x1440"; + enable = true; + mode = "2256x1504"; + }; + "DP-7" = { + enable = true; + primary = true; + mode = "3440x1440"; + rate = "100.00"; + }; + }; + }; + docked_closed_dp7 = { + fingerprint = { + "DP-7" = "00ffffffffffff001e6d5677061e030003210104b5522278ffa105a9564ba4250f5054210800d1c0614001010101010101010101010148d570b0d0a0465038203a00345a3100001a000000fd003064979737010a202020202020000000fc004c472048445220575148440a20000000ff003330334d58554e36303239340a01840203287423090707830100004410040301e2006ae305c000e606050153535d681a00000101306400e2b370b0d0a03b5038203a00345a3100001a0d7f70b0d0a03c5038203a00345a3100001adc6970b0d0a03c5038203a00345a3100001a0000000000000000000000000000000000000000000000000000000000000000004a"; + }; + config = { + "DP-7" = { + enable = true; + primary = true; + mode = "3440x1440"; + rate = "100.00"; + }; + }; + }; + docked_open_dp5 = { + fingerprint = { + "eDP-1" = "00ffffffffffff0009e5ca0b000000002f200104a51c137803de50a3544c99260f505400000001010101010101010101010101010101115cd01881e02d50302036001dbe1000001aa749d01881e02d50302036001dbe1000001a000000fe00424f452043510a202020202020000000fe004e4531333546424d2d4e34310a0073"; + "DP-5" = "00ffffffffffff001e6d5677061e030003210104b5522278ffa105a9564ba4250f5054210800d1c0614001010101010101010101010148d570b0d0a0465038203a00345a3100001a000000fd003064979737010a202020202020000000fc004c472048445220575148440a20000000ff003330334d58554e36303239340a01840203287423090707830100004410040301e2006ae305c000e606050153535d681a00000101306400e2b370b0d0a03b5038203a00345a3100001a0d7f70b0d0a03c5038203a00345a3100001adc6970b0d0a03c5038203a00345a3100001a0000000000000000000000000000000000000000000000000000000000000000004a"; + }; + config = { + "eDP-1" = { + position = "592x1440"; + enable = true; + mode = "2256x1504"; + }; + "DP-5" = { + enable = true; + primary = true; + mode = "3440x1440"; + rate = "100.00"; + }; + }; + }; + docked_closed_dp5 = { + fingerprint = { + "DP-5" = "00ffffffffffff001e6d5677061e030003210104b5522278ffa105a9564ba4250f5054210800d1c0614001010101010101010101010148d570b0d0a0465038203a00345a3100001a000000fd003064979737010a202020202020000000fc004c472048445220575148440a20000000ff003330334d58554e36303239340a01840203287423090707830100004410040301e2006ae305c000e606050153535d681a00000101306400e2b370b0d0a03b5038203a00345a3100001a0d7f70b0d0a03c5038203a00345a3100001adc6970b0d0a03c5038203a00345a3100001a0000000000000000000000000000000000000000000000000000000000000000004a"; + }; + config = { + "DP-5" = { + enable = true; + primary = true; + mode = "3440x1440"; + rate = "100.00"; + }; + }; + }; + docked_open_dp3 = { + fingerprint = { + "eDP-1" = "00ffffffffffff0009e5ca0b000000002f200104a51c137803de50a3544c99260f505400000001010101010101010101010101010101115cd01881e02d50302036001dbe1000001aa749d01881e02d50302036001dbe1000001a000000fe00424f452043510a202020202020000000fe004e4531333546424d2d4e34310a0073"; + "DP-3" = "00ffffffffffff001e6d5677061e030003210104b5522278ffa105a9564ba4250f5054210800d1c0614001010101010101010101010148d570b0d0a0465038203a00345a3100001a000000fd003064979737010a202020202020000000fc004c472048445220575148440a20000000ff003330334d58554e36303239340a01840203287423090707830100004410040301e2006ae305c000e606050153535d681a00000101306400e2b370b0d0a03b5038203a00345a3100001a0d7f70b0d0a03c5038203a00345a3100001adc6970b0d0a03c5038203a00345a3100001a0000000000000000000000000000000000000000000000000000000000000000004a"; + }; + config = { + "eDP-1" = { + position = "592x1440"; + enable = true; + mode = "2256x1504"; + }; + "DP-3" = { + enable = true; + primary = true; + mode = "3440x1440"; + rate = "100.00"; + }; + }; + }; + docked_closed_dp3 = { + fingerprint = { + "DP-3" = "00ffffffffffff001e6d5677061e030003210104b5522278ffa105a9564ba4250f5054210800d1c0614001010101010101010101010148d570b0d0a0465038203a00345a3100001a000000fd003064979737010a202020202020000000fc004c472048445220575148440a20000000ff003330334d58554e36303239340a01840203287423090707830100004410040301e2006ae305c000e606050153535d681a00000101306400e2b370b0d0a03b5038203a00345a3100001a0d7f70b0d0a03c5038203a00345a3100001adc6970b0d0a03c5038203a00345a3100001a0000000000000000000000000000000000000000000000000000000000000000004a"; + }; + config = { + "DP-3" = { + enable = true; + primary = true; + mode = "3440x1440"; + rate = "100.00"; + }; + }; + }; + }; + }; + + services.ssh-agent.enable = true; + programs.ssh = { + enable = true; + addKeysToAgent = "yes"; + matchBlocks = { + "git" = { + host = "github.com"; + user = "git"; + identityFile = [ + "/home/jmug/.ssh/id_yubikey" # Auto updated symlik that matches all yubikeys. + "/home/jmug/.ssh/id_jmug" # Fallback key with passphrase. + ]; + }; + "forgejo" = { + host = "code.jmug.me"; + user = "forgejo"; + identityFile = [ + "/home/jmug/.ssh/id_yubikey" # Auto updated symlik that matches all yubikeys. + "/home/jmug/.ssh/id_jmug" # Fallback key with passphrase. + ]; + }; + alarm = { + user = "alarm"; + hostname = "alarm"; + forwardAgent = true; + identityFile = "/home/jmug/.ssh/id_ed25519"; + }; + wsl = { + user = "jmug"; + hostname = "192.168.10.241"; + port = 69; + forwardAgent = true; + identityFile = [ + "/home/jmug/.ssh/id_yubikey" # Auto updated symlik that matches all yubikeys. + ]; + }; + ws = { + user = "jmug"; + hostname = "98.59.213.212"; + port = 69; + forwardAgent = true; + identityFile = [ + "/home/jmug/.ssh/id_yubikey" # Auto updated symlik that matches all yubikeys. + ]; + }; + }; + }; + + # Let Home Manager install and manage itself. + programs.home-manager.enable = true; +} diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix new file mode 100644 index 0000000..69aa1c6 --- /dev/null +++ b/modules/nixos/default.nix @@ -0,0 +1,6 @@ +{ ... }: +{ + imports = [ + ./yubikey + ]; +} diff --git a/modules/nixos/yubikey/default.nix b/modules/nixos/yubikey/default.nix new file mode 100644 index 0000000..c31d204 --- /dev/null +++ b/modules/nixos/yubikey/default.nix @@ -0,0 +1,175 @@ +# This module supports multiple YubiKey 4 and/or 5 devices as well as a single Yubico Security Key device. The limitation to a single Security Key is because they do not have serial numbers and therefore the scripts in this module cannot uniquely identify them. See options.yubikey.identifies.description below for information on how to add a 'mock' serial number for a single Security key. Additional context is available in Issue 14 https://github.com/EmergentMind/nix-config/issues/14 +{ + config, + pkgs, + lib, + ... +}: +let + # homeDirectory = "${config.hostSpec.home}"; + homeDirectory = "/home/jmug"; # Should be parametric. + yubikey-up = + let + yubikeyIds = lib.concatStringsSep " " ( + lib.mapAttrsToList (name: id: "[${name}]=\"${builtins.toString id}\"") config.yubikey.identifiers + ); + in + pkgs.writeShellApplication { + name = "yubikey-up"; + runtimeInputs = builtins.attrValues { inherit (pkgs) gawk yubikey-manager; }; + text = '' + #!/usr/bin/env bash + set -euo pipefail + + serial=$(ykman list | awk '{print $NF}') + # If it got unplugged before we ran, just don't bother + if [ -z "$serial" ]; then + # FIXME(yubikey): Warn probably + exit 0 + fi + + declare -A serials=(${yubikeyIds}) + + key_name="" + for key in "''${!serials[@]}"; do + if [[ $serial == "''${serials[$key]}" ]]; then + key_name="$key" + fi + done + + if [ -z "$key_name" ]; then + echo WARNING: Unidentified yubikey with serial "$serial" . Won\'t link an SSH key. + exit 0 + fi + + echo "Creating links to ${homeDirectory}/id_$key_name" + ln -sf "${homeDirectory}/.ssh/id_$key_name" ${homeDirectory}/.ssh/id_yubikey + ln -sf "${homeDirectory}/.ssh/id_$key_name.pub" ${homeDirectory}/.ssh/id_yubikey.pub + ''; + }; + yubikey-down = pkgs.writeShellApplication { + name = "yubikey-down"; + text = '' + #!/usr/bin/env bash + set -euo pipefail + + rm ${homeDirectory}/.ssh/id_yubikey + rm ${homeDirectory}/.ssh/id_yubikey.pub + ''; + }; +in +{ + options = { + yubikey = { + enable = lib.mkEnableOption "Enable yubikey support"; + identifiers = lib.mkOption { + default = { }; + type = lib.types.attrsOf lib.types.int; + description = "Attrset of Yubikey serial numbers. NOTE: Yubico's 'Security Key' products do not use unique serial number therefore, the scripts in this module are unable to distinguish between multiple 'Security Key' devices and instead will detect a Security Key serial number as the string \"[FIDO]\". This means you can only use a single Security Key but can still mix it with YubiKey 4 and 5 devices."; + example = lib.literalExample '' + { + foo = 12345678; + bar = 87654321; + baz = "[FIDO]"; + } + ''; + }; + }; + }; + config = lib.mkIf config.yubikey.enable { + environment.systemPackages = lib.flatten [ + (builtins.attrValues { + inherit (pkgs) + yubioath-flutter # gui-based authenticator tool. yubioath-desktop on older nixpkg channels + yubikey-manager # cli-based authenticator tool. accessed via `ykman` + + pam_u2f # for yubikey with sudo + ; + }) + yubikey-up + yubikey-down + ]; + + # FIXME(yubikey): Put this behind an option for yubikey ssh + # Create ssh files + + # FIXME(yubikey): Not sure if we need the wheel one. Also my idProduct group is 0407 + # Yubikey 4/5 U2F+CCID + # SUBSYSTEM == "usb", ATTR{idVendor}=="1050", ENV{ID_SECURITY_TOKEN}="1", GROUP="wheel" + # We already have a yubikey rule that sets the ENV variable + + # FIXME(yubikey): This is linux only + services.udev.extraRules = '' + # Link/unlink ssh key on yubikey add/remove + SUBSYSTEM=="usb", ACTION=="add", ATTR{idVendor}=="1050", RUN+="${lib.getBin yubikey-up}/bin/yubikey-up" + # NOTE: Yubikey 4 has a ID_VENDOR_ID on remove, but not Yubikey 5 BIO, whereas both have a HID_NAME. + # Yubikey 5 HID_NAME uses "YubiKey" whereas Yubikey 4 uses "Yubikey", so matching on "Yubi" works for both + SUBSYSTEM=="hid", ACTION=="remove", ENV{HID_NAME}=="Yubico Yubi*", RUN+="${lib.getBin yubikey-down}/bin/yubikey-down" + + ## + # Yubikey 4 + ## + + # Lock the device if you remove the yubikey (use udevadm monitor -p to debug) + # #ENV{ID_MODEL_ID}=="0407", # This doesn't match all the newer keys + # FIXME(yubikey): We only want this to happen if we're undocked, so we need to see how that works. We probably need to run a + # script that does smarter checks + # ACTION=="remove",\ + # ENV{ID_BUS}=="usb",\ + # ENV{ID_VENDOR_ID}=="1050",\ + # ENV{ID_VENDOR}=="Yubico",\ + # RUN+="${pkgs.systemd}/bin/loginctl lock-sessions" + + ## + # Yubikey 5 BIO + # + # NOTE: The remove event for the bio doesn't include the ID_VENDOR_ID for some reason, but we can use the + # hid name instead. Some HID_NAME might be "Yubico YubiKey OTP+FIDO+CCID" or "Yubico YubiKey FIDO", etc so just + # match on "Yubico YubiKey" + ## + + # SUBSYSTEM=="hid",\ + # ACTION=="remove",\ + # ENV{HID_NAME}=="Yubico YubiKey FIDO",\ + # RUN+="${pkgs.systemd}/bin/loginctl lock-sessions" + + # FIXME(yubikey): Change this so it only wakes up the screen to the login screen, xset cmd doesn't work + # SUBSYSTEM=="hid",\ + # ACTION=="add",\ + # ENV{HID_NAME}=="Yubico YubiKey FIDO",\ + # RUN+="${pkgs.systemd}/bin/loginctl activate 1" + # #RUN+="${lib.getBin pkgs.xorg.xset}/bin/xset dpms force on" + ''; + + # Yubikey required services and config. See Dr. Duh NixOS config for + # reference + services.pcscd.enable = true; # smartcard service + services.udev.packages = [ pkgs.yubikey-personalization ]; + + # yubikey login / sudo + security.pam = lib.optionalAttrs pkgs.stdenv.isLinux { + u2f = { + enable = true; + settings = { + cue = true; # Tells user they need to press the button + authFile = "${homeDirectory}/.config/Yubico/u2f_keys"; + }; + }; + services = { + login.u2fAuth = true; + sudo = { + u2fAuth = true; + }; + # Attempt to auto-unlock gnome-keyring using u2f + # NOTE: vscode uses gnome-keyring even if we aren't using gnome, which is why it's still here + # This doesn't work + #gnome-keyring = { + # text = '' + # session include login + # session optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start + # ''; + #}; + }; + }; + }; +} diff --git a/nixos-modules/shell-apps/hello.nix b/nixos-modules/shell-apps/hello.nix new file mode 100644 index 0000000..5b73488 --- /dev/null +++ b/nixos-modules/shell-apps/hello.nix @@ -0,0 +1,8 @@ +{ pkgs }: +pkgs.writeShellApplication { + name = "hello"; + runtimeInputs = with pkgs; [ cowsay lolcat ]; + text = '' + echo "Hello World!" | cowsay | lolcat + ''; +} diff --git a/nixos-modules/shell-apps/print-colors.nix b/nixos-modules/shell-apps/print-colors.nix new file mode 100644 index 0000000..3c7f9b7 --- /dev/null +++ b/nixos-modules/shell-apps/print-colors.nix @@ -0,0 +1,15 @@ +{ pkgs }: +pkgs.writeShellApplication { + name = "print-colors"; + text = '' + printf "|039| \033[39mDefault \033[m |049| \033[49mDefault \033[m |037| \033[37mLight gray \033[m |047| \033[47mLight gray \033[m\n" + printf "|030| \033[30mBlack \033[m |040| \033[40mBlack \033[m |090| \033[90mDark gray \033[m |100| \033[100mDark gray \033[m\n" + printf "|031| \033[31mRed \033[m |041| \033[41mRed \033[m |091| \033[91mLight red \033[m |101| \033[101mLight red \033[m\n" + printf "|032| \033[32mGreen \033[m |042| \033[42mGreen \033[m |092| \033[92mLight green \033[m |102| \033[102mLight green \033[m\n" + printf "|033| \033[33mYellow \033[m |043| \033[43mYellow \033[m |093| \033[93mLight yellow \033[m |103| \033[103mLight yellow \033[m\n" + printf "|034| \033[34mBlue \033[m |044| \033[44mBlue \033[m |094| \033[94mLight blue \033[m |104| \033[104mLight blue \033[m\n" + printf "|035| \033[35mMagenta \033[m |045| \033[45mMagenta \033[m |095| \033[95mLight magenta \033[m |105| \033[105mLight magenta \033[m\n" + printf "|036| \033[36mCyan \033[m |046| \033[46mCyan \033[m |096| \033[96mLight cyan \033[m |106| \033[106mLight cyan \033[m\n" + ''; +} + diff --git a/secrets.yaml b/secrets.yaml new file mode 100644 index 0000000..f038ded --- /dev/null +++ b/secrets.yaml @@ -0,0 +1,36 @@ +private_keys: + jmug: ENC[AES256_GCM,data:OOokznYtow9Ra0FRwBydclS6Z7w7mV9RTtHpjyw8NT+rV2VzTm/K8qYSw9N3gDbao5JOZH+LlVIL0UX2ZqGk8j/4D67LP+FmTUiIqyHH3LI8RcXAixIbR+zwV19Gr7DiFWw8jCyXQFFBJJRIatZLA5T2r23pYxL5drpQUcWsttuHRN7RwfXq2eFQogufzgfUqQgMkqvx64prDbrNq7bqZpPZE/vrCIoSK4rgTZCokD2hoIdEdzxLaVlbaoMMY7Dg83ojFU2Fpe5TWuS+duxUYjCPh86uoa3d0dsKoVkB9zd56EO5z0SB94tht9WJzmqKPh2gZ6DvV+wdaMH7wSqnUB6c2mE3i886Y4LQJjUFzpu1dnZaEEvJ0HI5jelZ3QVwjhp8/Zrkl/MwOG+lCHcIu2T7nQ/O0NxBNgMYbEYHGDcqi+anAzw2eNfep//dC+lT0qiXYAlzdDIIcTE7sL40qq7xXWtZJR4I6B+EOcYVn+zu93tsLiNke26h9ZoiAk40oxNZaiTPKnLkSvPtVZcoA53PamVKTS+tkXd+Pzt07lUgumTRIybkjE7i0hrhBrAv98NqvHobWfJVsQaH,iv:CDOXxoNTTw0rTsQbyYcXk0xL1UZtSbC0EG0audUwriM=,tag:XN2sujg9YHc5FtwCGPweSQ==,type:str] + matcha: ENC[AES256_GCM,data:QbINVe5OuAf1gOK2ubO+oyHoBjcwYqr0bLuZpWMAkZTgd53/+ccF8ktQI81I1BDzrtWYpkTfysCIILZjqfHY/ZSmlA1Q9VDYx+ifsUVT3yLMX7jlvLvGj0T5akiSHGssh+JUnyHq4sQDcxWqe/wMHw/iukcTJ6iZeq4ZzbEp1mndZhpg1PgMSLDVO5zpnso//6Lt+JcmfhZUGchncAnF4EVC6NGcgXbA4DBIKOOprltQuoos40LicwhC73fkCB9iaGQsID51jZWq9qUzZZTjbgOehKfG46//anN5X1AmaFXwN7Z8rYsfUSXnBM05OuSeW9naPA3x/y9jemObvptEGA8aQZO2DOSsajyurv1c6XACsk1MQhXxOMp08egPaT4PKoiqFMASQk5kRysTEUVGlGWAhQA+uJ9/1WGcaQDohFMI+oaafz2vOtRS4lTrI/f45MtanPnqrIxSPDafKVf80cztJP7M3lvSfNHmnCGXr4SemjP9huDV2ThFDmKPPTPMHAHuwkBVmGphZWK1TygliLivC+ExsjO4KG7+WPFtfU+4MWbi1JC+8qRPnJwHdNkTNa8CZypH9BkLm5RlP8dx8fSb6EqoB+vULFU9tqCtILk0Jzk4AWxIe1dwfv9VZsHaNsc39L3zXKJJZDwNE5l7zsMQbtdUcGFIcJSq9M/73CUqt2zLc9cRnaTFnlhTXAORaz7+RrHD6tsqCRFTjWIGAV8gCdh243sUvpvaJxKr0P1jEZc=,iv:hoUyZg6tvqIU+2h7fSUxzkz6zN1ijyHEo/+XPZcsXCA=,tag:jV+ftEKloXS7OS0IniG/Pw==,type:str] +yubico: + u2f_keys: + jmug: ENC[AES256_GCM,data:Z42zNo1DaQutPIfE+0PEAK5F1fmspJp6jmosHSHsUN6dSG4zY93Tdmvisxg0hFUbuMlYg/06z3bsagFY4q+9Eg6qCLqzj1Uzs3VA0vEP+N0UlR5YZvneWzhnw2KYaPSJ/dsxt9tSfJO89P5ffeJgfSds2hLRWngm0agkmZ1P9lRbY0iMTUGl9se4V/anydwH69GQLyul5EtXHr9KZyU2pkT86zQSHGqiiMm85TfyixTWi/PWFl1jtDlyUbvN2HZYFGdQ6O0E,iv:TYel/hCVAMQL1rqok/1YMqcGFuXmsvkwUcA988VULW8=,tag:dnPQiY5i3oHbsC9zdXvY4w==,type:str] +wireless.env: ENC[AES256_GCM,data:HpwPUp7SDUPwWzXzOaWBT605aV8d3fD78dIgl4hdym1O1b2tVOs4zgO77n/DmXNugHjybUWpNZN+R2uiseawyufv5ndTZJrFTK4=,iv:Pp1MyegxAi5AhyG1f9f2+jAa2r0jKDYTeiA27KPlOt0=,tag:Wnt//h2dDxApdKortKPVgg==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1psyctjy329r9v07uqu72vkjl06f26f0epvh6zxejdkwp3m0tnyvq88rnr4 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvTndyWUdmQlJkMVgzbnNH + VWw1MVdHVk1BQWxFcWQ2cm9IMkRsUXJua2hjCkk0aHo2dDhMWjB4ODM2b3NVRmZI + Um52b3llWUxvR3BMVjBRVU5PWVFjcU0KLS0tIFJ1cExBUUc2cmMrdlAzZlRtTHJi + a1ppNndmVk5lUzFQNjZBN2V3THJRUUkK2u/VrhUakNXLWuj8edN3IQzEusPuKfXr + 5DxMUAZAUpkMudfxq7JH9NPVR/swp2QrDxYElWQMqkad2+SRbQy/2Q== + -----END AGE ENCRYPTED FILE----- + - recipient: age1cfcfye2unv89fgyuwpvy9sas40jd87kksw7rlgy4cwmcfjqntv2st2jcnp + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOeElKbEY2dG5iSE5USVIw + Ky9FUzR3aXhEKzZ2V2ZOREcreVUrVnBSaTJjCjNja0tMalpDN2I3b0tIVThXSi9N + L0lkMUx6NGhiWnB4cTIrSTVkTmxUZ28KLS0tIDNwckNGT0I0R09SMmN5MkUwMmlz + TTBza2VncEc3T3l0K2ZZNlFYWEJEOTgKB9btrywDe8vZtJuerk+Fm7jE4H/zAil5 + XvAToUH2HQIMf7bjLVafXG22SKDt4ya6k9yYN63VORp6m7wkimLjPQ== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2025-05-03T21:11:24Z" + mac: ENC[AES256_GCM,data:Jym0832bqt8OPZnBJ3087jpRK/C5adMj5mo9Po3Ms16/q4ahg0ZDlZAIJifNLFcFXmYrnvpgR3cOF1SIpUtIV83k8huErSPgRrQMq8J6yhtXERffybeznnr8grba3aT3uhGUj2fUxLdCgVIajPZvqbQliXA/AxwPTLU5mKMjSGE=,iv:epXMnep+fDsFC/s2SjtXt9y5iqtyp/H1rLW0/fTDQp0=,tag:VkOlEj/uXC5vTQ2u1nDjbw==,type:str] + pgp: [] + unencrypted_suffix: _unencrypted + version: 3.9.4 diff --git a/users/alarm/home.nix b/users/alarm/home.nix new file mode 100644 index 0000000..88b1179 --- /dev/null +++ b/users/alarm/home.nix @@ -0,0 +1,30 @@ +{ pkgs, ... } : + +{ + imports = [ + ../../home-modules/default.nix + ../../home-modules/nvim.nix + ../../home-modules/git.nix + ../../home-modules/lazygit.nix + ../../home-modules/starship.nix + ../../home-modules/direnv.nix + ../../home-modules/zsh.nix + ../../home-modules/tmux.nix + ]; + + home = { + username = "alarm"; + homeDirectory = "/home/alarm"; + + stateVersion = "24.11"; + }; + + programs.zsh = { + shellAliases = { + homesw = "home-manager --flake /home/alarm/nixos#alarm --extra-experimental-features nix-command --extra-experimental-features flakes switch"; + }; + }; + + # Let Home Manager install and manage itself. + programs.home-manager.enable = true; +} diff --git a/users/msftdevbox/home.nix b/users/msftdevbox/home.nix new file mode 100644 index 0000000..ff7adce --- /dev/null +++ b/users/msftdevbox/home.nix @@ -0,0 +1,53 @@ +{ pkgs, pkgs-msft-go, pkgs-unstable, ... } : + +{ + imports = [ + ../../home-modules/default.nix + ../../home-modules/nvim.nix + ../../home-modules/tmux.nix + ../../home-modules/lazygit.nix + ../../home-modules/starship.nix + ../../home-modules/direnv.nix + ../../home-modules/zsh.nix + ]; + + home = { + username = "juva"; + homeDirectory = "/home/juva"; + + packages = with pkgs; [ + pkgs-msft-go.go_1_23 + pkgs-msft-go.gopls + pkgs-msft-go.gotools + pkgs-msft-go.mockgen + pkgs-unstable.kubernetes-controller-tools + jq + yq + kind + kubernetes-helm + fzf + ripgrep + ]; + + stateVersion = "24.11"; + }; + + programs.zsh = { + shellAliases = { + homesw = "home-manager --flake /home/juva/nixos#msftdevbox --extra-experimental-features nix-command --extra-experimental-features flakes switch"; + adev = "/home/juva/dev/aks-rp/bin/aksdev"; + ksc = "KUBECONFIG=/home/juva/Downloads/cxkubeconfig.yaml kubectl"; + kso = "KUBECONFIG=/home/juva/Downloads/overlaykubeconfig.yaml kubectl"; + k = "kubectl"; + }; + initExtra = '' + export GONOPROXY='github.com,golang.org,googlesource.com,opentelemetry.io,uber.org' + export GOPRIVATE='goms.io,*.goms.io' + export GOPROXY='https://goproxyprod.goms.io' + export PATH=$PATH:$HOME/bin + ''; + }; + + # Let Home Manager install and manage itself. + programs.home-manager.enable = true; +} diff --git a/users/nixlapmsft/home.nix b/users/nixlapmsft/home.nix new file mode 100644 index 0000000..b2d2bd1 --- /dev/null +++ b/users/nixlapmsft/home.nix @@ -0,0 +1,66 @@ +{ config, lib, nixgl, pkgs, pkgs-msft-go, pkgs-unstable, ghostty, ... } : + +{ + imports = [ + ../../home-modules/default.nix + ../../home-modules/nvim.nix + ../../home-modules/tmux.nix + ../../home-modules/lazygit.nix + ../../home-modules/starship.nix + ../../home-modules/direnv.nix + ../../home-modules/zsh.nix + ../../home-modules/ghostty-mac-config.nix + ]; + + nixGL.packages = nixgl.packages; + nixGL.defaultWrapper = "mesa"; + nixGL.installScripts = [ "mesa" ]; + + # Effort to make applications show up in Gnome's "show applications" + targets.genericLinux.enable = true; + programs.bash.enable = true; + + home = { + username = "jmug"; + homeDirectory = "/home/jmug"; + + packages = with pkgs; [ + pkgs-msft-go.go_1_23 + pkgs-msft-go.gopls + pkgs-msft-go.gotools + pkgs-msft-go.mockgen + pkgs-unstable.kubernetes-controller-tools + azure-cli + kubectl + jq + yq + kind + kubernetes-helm + fzf + ripgrep + (config.lib.nixGL.wrap ghostty.packages.x86_64-linux.default) + (nerdfonts.override { fonts = [ "BigBlueTerminal" ]; }) + ]; + + stateVersion = "24.11"; + }; + + programs.zsh = { + shellAliases = { + homesw = "home-manager --flake /home/jmug/nixos#nixlapmsft --extra-experimental-features nix-command --extra-experimental-features flakes switch"; + adev = "/home/jmug/dev/aks-rp/bin/aksdev"; + ksc = "KUBECONFIG=/home/jmug/Downloads/cxkubeconfig.yaml kubectl"; + kso = "KUBECONFIG=/home/jmug/Downloads/overlaykubeconfig.yaml kubectl"; + k = "kubectl"; + }; + initExtra = '' + export GONOPROXY='github.com,golang.org,googlesource.com,opentelemetry.io,uber.org' + export GOPRIVATE='goms.io,*.goms.io' + export GOPROXY='https://goproxyprod.goms.io' + export PATH=$PATH:$HOME/bin + ''; + }; + + # Let Home Manager install and manage itself. + programs.home-manager.enable = true; +}