├── .github └── README.md ├── LICENSE ├── evil ├── battery.lua ├── brightness.lua ├── cpu.lua ├── init.lua ├── playerctl.lua ├── ram.lua ├── volume.lua └── weather.lua ├── helpers.lua ├── keys.lua ├── modules ├── json.lua ├── lockscreen │ ├── init.lua │ ├── lib │ │ └── liblua_pam.so │ └── lockscreen.lua ├── notification_center.lua └── window_switcher.lua ├── rc.lua └── themes └── gruva ├── init.lua └── theme.lua /.github/README.md: -------------------------------------------------------------------------------- 1 | # Sugoi (すごい) 2 | 3 | 4 | 5 | > ###### 🇨​​​​​🇱​​​​​🇪​​​​​🇦​​​​​🇳​​​​​ 🇦​​​​​🇳​​​​​🇩​​​​​ 🇨​​​​​🇴​​​​​🇲​​​​​🇫​​​​​🇾​​​​​ 🇦​​​​​🇼​​​​​🇪​​​​​🇸​​​​​🇴​​​​​🇲​​​​​🇪​​​​​🇼​​​​​🇲​​​​​ 🇪​​​​​🇳​​​​​🇻​​​​​🇮​​​​​🇷​​​​​🇴​​​​​🇳​​​​​🇲​​​​​🇪​​​​​🇳​​​​​🇹​​​​​ 6 | > 7 | > --- 8 | > 9 | > This is just my personal configs. I have used many sources for this I do not claim this is my original idea. 10 | 11 |
12 |
13 | 14 | --- 15 | 16 | AwesomeWM is the most powerful and highly configurable next-generation framework window manager for X. This is my favourite WM. Although it takes time and effort to configure it, I am very satisfied with this aesthetic result. 17 | 18 | ## 📢 Information 19 | 20 | > Here are some details about my setup. 21 | > 22 | > Read carefully and go through the dotfiles before using. 23 | 24 | 25 | 26 | | Config Defaults | Info | 27 | | :-------------- | :----------------------------------------------------------------------------------------------------------------- | 28 | | WM | AwesomeWM (git version) | 29 | | OS | NixOS (not relevant) | 30 | | Terminal | Wezterm | 31 | | Colors | Xresources | 32 | | Launcher | Rofi (rofi-emoji + clipmenu) | 33 | | Compositor | Picom (dccsillag's fork) | 34 | | Fonts | JetBrainsMono Nerd Font | 35 | | File manager | Pcmanfm | 36 | | Browser | Firefox | 37 | | Shell | Bash | 38 | | Editor | Helix | 39 | | Wallpaper | [海島千本](https://www.pixiv.net/en/users/18362) / [🗝️𝐆𝐞𝐧𝐞𝐫𝐚𝐥 𝐒𝐭𝐨𝐫𝐞🛋️](https://www.pixiv.net/en/artworks/89143248) | 40 | 41 | _Ofc you can change it._ 42 | 43 | ## 💎 Main features 44 | 45 | - [x] dynamic icons in workspaces 46 | - [x] running apps list in bar 47 | - [x] music name in bar 48 | - [x] weather in bar 49 | - [x] memory usage in bar 50 | - [x] cpu usage in bar 51 | - [x] clock in bar 52 | - [x] layout indicator in bar 53 | - [x] volume & brightness osd 54 | - [x] clipboard 55 | - [x] screenshot 56 | - [x] window switcher 57 | 58 | > Check the user variable in `rc.lua` to change the defaults to your apps first. Most important ones are ` terminal` and `app_launcher`. 59 | 60 | ```lua 61 | user = { 62 | terminal = "", 63 | floating_terminal = "", 64 | scratchpad_terminal = "", 65 | editor = "", 66 | browser = "", 67 | file_manager = "", 68 | visual_editor = "", 69 | openweathermap_key = "", 70 | openweathermap_city_id = { 71 | "", -- lat 72 | "", -- lon 73 | }, 74 | openweathermap_weather_units = "", 75 | lock_screen_custom_password = "", 76 | app_launcher = "", 77 | } 78 | ``` 79 | 80 | > As I am on nixos my .Xresources files are auto-generated and thus I am not able to include it in the repository. You can use your own xresources as well. In this setup however, I am using base16 colorschemes of gruvbox for this setup. You can just add these lines to your ~/.Xresources file. The colors are as follows: 81 | 82 | ``` 83 | ! special 84 | *background: #282828 85 | *foreground: #d5c4a1 86 | 87 | ! black 88 | *color0: #282828 89 | *color8: #665c54 90 | 91 | ! red 92 | *color1: #fb4934 93 | *color9: #fb4934 94 | 95 | ! green 96 | *color2: #b8bb26 97 | *color10: #b8bb26 98 | 99 | ! yellow 100 | *color3: #fabd2f 101 | *color11: #fabd2f 102 | 103 | ! blue 104 | *color4: #83a598 105 | *color12: #83a598 106 | 107 | ! magenta 108 | *color5: #d3869b 109 | *color13: #d3869b 110 | 111 | ! cyan 112 | *color6: #8ec07c 113 | *color14: #8ec07c 114 | 115 | ! white 116 | *color7: #d5c4a1 117 | *color15: #fbf1c7 118 | ``` 119 | 120 | > same goes with wezterm. So here is the wezterm's config 121 | 122 |
~/.config/wezterm 123 | 124 | ```lua 125 | -- ~/.config/wezterm/wezterm.lua 126 | -- See https://wezfurlong.org/wezterm/ 127 | 128 | -- Add config folder to watchlist for config reloads. 129 | local wezterm = require 'wezterm'; 130 | wezterm.add_to_config_reload_watch_list(wezterm.config_dir) 131 | 132 | function font_with_fallback(name, params) 133 | local names = { name, "emoji" } 134 | return wezterm.font_with_fallback(names, params) 135 | end 136 | 137 | local font_name = "monospace" 138 | 139 | return { 140 | -- Font config 141 | font = font_with_fallback(font_name), 142 | font_rules = { 143 | { 144 | italic = true, 145 | font = font_with_fallback(font_name, { italic = true }) 146 | }, 147 | { 148 | italic = true, 149 | intensity = "Bold", 150 | font = font_with_fallback(font_name, { bold = true, italic = true }) 151 | }, 152 | { 153 | intensity = "Bold", 154 | font = font_with_fallback(font_name, { bold = true }) 155 | } 156 | }, 157 | font_size = 10, 158 | line_height = 1.0, 159 | default_cursor_style = "SteadyUnderline", 160 | 161 | -- Keys 162 | disable_default_key_bindings = true, 163 | keys = { 164 | { 165 | mods = "CTRL|SHIFT", 166 | key = [[\]], 167 | action = wezterm.action { 168 | SplitHorizontal = { domain = "CurrentPaneDomain" } 169 | } 170 | }, 171 | { 172 | mods = "CTRL", 173 | key = [[\]], 174 | action = wezterm.action { 175 | SplitVertical = { domain = "CurrentPaneDomain" } 176 | } 177 | }, 178 | { 179 | key = "t", 180 | mods = "CTRL", 181 | action = wezterm.action { SpawnTab = "CurrentPaneDomain" } 182 | }, 183 | { 184 | key = "w", 185 | mods = "CTRL", 186 | action = wezterm.action { CloseCurrentTab = { confirm = false } } 187 | }, 188 | { 189 | mods = "CTRL", 190 | key = "Tab", 191 | action = wezterm.action { ActivateTabRelative = 1 } 192 | }, 193 | { 194 | mods = "CTRL|SHIFT", 195 | key = "Tab", 196 | action = wezterm.action { ActivateTabRelative = -1 } 197 | }, 198 | { key = "x", mods = "CTRL", action = "ActivateCopyMode" }, 199 | { 200 | key = "v", 201 | mods = "CTRL|SHIFT", 202 | action = wezterm.action { PasteFrom = "Clipboard" } 203 | }, 204 | { 205 | key = "c", 206 | mods = "CTRL|SHIFT", 207 | action = wezterm.action { CopyTo = "ClipboardAndPrimarySelection" } 208 | }, 209 | { 210 | key = "L", 211 | mods = "CTRL", 212 | action = wezterm.action.ShowDebugOverlay 213 | } 214 | }, 215 | front_end = "WebGpu", 216 | enable_wayland = true, 217 | check_for_updates = false, 218 | color_scheme = "gruvbox-dark-medium", 219 | window_padding = { left = "30pt", right = "30pt", top = "30pt", bottom = "30pt" }, 220 | inactive_pane_hsb = { saturation = 1.0, brightness = 1.0 }, 221 | enable_tab_bar = true, 222 | use_fancy_tab_bar = false, 223 | hide_tab_bar_if_only_one_tab = true, 224 | show_tab_index_in_tab_bar = false, 225 | } 226 | ``` 227 | 228 | ```toml 229 | # ~/.config/wezterm/colors/gruvbox-dark-medium.toml 230 | [colors] 231 | ansi = ["282828", "fb4934", "b8bb26", "fabd2f", "83a598", "d3869b", "8ec07c", "d5c4a1"] 232 | background = "282828" 233 | brights = ["665c54", "fb4934", "b8bb26", "fabd2f", "83a598", "d3869b", "8ec07c", "fbf1c7"] 234 | cursor_bg = "d5c4a1" 235 | cursor_fg = "282828" 236 | foreground = "d5c4a1" 237 | selection_bg = "d5c4a1" 238 | selection_fg = "282828" 239 | 240 | [colors.tab_bar] 241 | background = "282828" 242 | inactive_tab_edge = "282828" 243 | [colors.tab_bar.active_tab] 244 | bg_color = "83a598" 245 | fg_color = "d5c4a1" 246 | 247 | [colors.tab_bar.inactive_tab] 248 | bg_color = "282828" 249 | fg_color = "d5c4a1" 250 | 251 | [colors.tab_bar.inactive_tab_hover] 252 | bg_color = "282828" 253 | fg_color = "d5c4a1" 254 | 255 | [colors.tab_bar.new_tab] 256 | bg_color = "282828" 257 | fg_color = "83a598" 258 | 259 | [colors.tab_bar.new_tab_hover] 260 | bg_color = "282828" 261 | fg_color = "d5c4a1" 262 | ``` 263 | 264 |

265 | 266 | > For rofi as well 267 | 268 |
~/.config/config.rasi 269 | 270 | ```rasi 271 | * { 272 | active: #b8bb26ff; 273 | active-background: var(active); 274 | active-foreground: var(background); 275 | alternate-active-background: var(active); 276 | alternate-active-foreground: var(background); 277 | alternate-background: var(background-alt); 278 | alternate-normal-background: var(background-alt); 279 | alternate-normal-foreground: var(foreground); 280 | alternate-urgent-background: var(urgent); 281 | alternate-urgent-foreground: var(background); 282 | background: #282828ff; 283 | background-alt: #3c3836ff; 284 | background-colour: var(background); 285 | border-colour: var(selected); 286 | font: "monospace bold 10"; 287 | foreground: #d5c4a1ff; 288 | foreground-colour: var(foreground); 289 | handle-colour: var(selected); 290 | normal-background: var(background-alt); 291 | normal-foreground: var(foreground); 292 | selected: #83a598ff; 293 | selected-active-background: var(urgent); 294 | selected-active-foreground: var(background); 295 | selected-normal-background: var(selected); 296 | selected-normal-foreground: var(background); 297 | selected-urgent-background: var(active); 298 | selected-urgent-foreground: var(background); 299 | urgent: #fb4934ff; 300 | urgent-background: var(urgent); 301 | urgent-foreground: var(background); 302 | } 303 | 304 | configuration { 305 | display-clipboard: ">"; 306 | display-combi: ">"; 307 | display-drun: ">"; 308 | display-emoji: ">"; 309 | drun-display-format: "{name}"; 310 | modi: "drun,combi,emoji"; 311 | show-icons: false; 312 | window-format: "{w} · {c} · {t}"; 313 | } 314 | 315 | element { 316 | background-color: transparent; 317 | border: 0px solid; 318 | border-color: @border-colour; 319 | border-radius: 0px; 320 | cursor: pointer; 321 | enabled: true; 322 | margin: 0px; 323 | padding: 12px; 324 | spacing: 10px; 325 | text-color: @foreground-colour; 326 | } 327 | 328 | element alternate.active { 329 | background-color: var(alternate-active-background); 330 | text-color: var(alternate-active-foreground); 331 | } 332 | 333 | element alternate.normal { 334 | background-color: var(alternate-normal-background); 335 | text-color: var(alternate-normal-foreground); 336 | } 337 | 338 | element alternate.urgent { 339 | background-color: var(alternate-urgent-background); 340 | text-color: var(alternate-urgent-foreground); 341 | } 342 | 343 | element normal.active { 344 | background-color: var(active-background); 345 | text-color: var(active-foreground); 346 | } 347 | 348 | element normal.normal { 349 | background-color: var(normal-background); 350 | text-color: var(normal-foreground); 351 | } 352 | 353 | element normal.urgent { 354 | background-color: var(urgent-background); 355 | text-color: var(urgent-foreground); 356 | } 357 | 358 | element selected.active { 359 | background-color: var(selected-active-background); 360 | text-color: var(selected-active-foreground); 361 | } 362 | 363 | element selected.normal { 364 | background-color: var(selected-normal-background); 365 | text-color: var(selected-normal-foreground); 366 | } 367 | 368 | element selected.urgent { 369 | background-color: var(selected-urgent-background); 370 | text-color: var(selected-urgent-foreground); 371 | } 372 | 373 | element-icon { 374 | background-color: transparent; 375 | cursor: inherit; 376 | size: 24px; 377 | text-color: inherit; 378 | } 379 | 380 | element-text { 381 | background-color: transparent; 382 | cursor: inherit; 383 | highlight: inherit; 384 | horizontal-align: 0.0; 385 | text-color: inherit; 386 | vertical-align: 0.5; 387 | } 388 | 389 | entry { 390 | background-color: inherit; 391 | cursor: text; 392 | enabled: true; 393 | margin: 8px 0px 0px 14px; 394 | placeholder: "Search..."; 395 | placeholder-color: inherit; 396 | text-color: inherit; 397 | } 398 | 399 | inputbar { 400 | background-color: @background-colour; 401 | border: 0px; 402 | border-color: @border-colour; 403 | border-radius: 0px; 404 | children: [ prompt, entry ]; 405 | enabled: true; 406 | margin: 0px; 407 | padding: 0px 0px 6px 0px; 408 | spacing: 10px; 409 | text-color: @foreground-colour; 410 | } 411 | 412 | listview { 413 | background-color: transparent; 414 | border: 0px solid; 415 | border-color: @border-colour; 416 | border-radius: 0px; 417 | columns: 1; 418 | cursor: "default"; 419 | cycle: true; 420 | dynamic: true; 421 | enabled: true; 422 | fixed-columns: true; 423 | fixed-height: true; 424 | layout: vertical; 425 | lines: 7; 426 | margin: 0px; 427 | padding: 0px; 428 | reverse: false; 429 | scrollbar: false; 430 | spacing: 10px; 431 | text-color: @foreground-colour; 432 | } 433 | 434 | mainbox { 435 | background-color: transparent; 436 | border: 0px solid; 437 | border-color: @border-colour; 438 | border-radius: 0px 0px 0px 0px; 439 | children: [ inputbar, listview]; 440 | enabled: true; 441 | margin: 0px; 442 | padding: 10px; 443 | spacing: 10px; 444 | } 445 | 446 | prompt { 447 | background-color: @background-alt; 448 | enabled: true; 449 | padding: 2px 14px 4px 14px; 450 | text-color: inherit; 451 | } 452 | 453 | window { 454 | anchor: center; 455 | background-color: @background-colour; 456 | border: 2px solid; 457 | border-color: @border-colour; 458 | border-radius: 0px; 459 | cursor: "default"; 460 | enabled: true; 461 | fullscreen: false; 462 | location: center; 463 | margin: 0px; 464 | padding: 0px; 465 | transparency: "real"; 466 | width: 420px; 467 | x-offset: 0px; 468 | y-offset: 0px; 469 | } 470 | ``` 471 | 472 |

473 | 474 | > Use https://github.com/tinted-theming/base16-schemes, to add colorschems to more apps Eg. rofi. 475 | 476 | > Some important keybinds. More in `keys.lua` 477 | 478 | | Keybind | Function | 479 | | ----------------------------- | ------------------------ | 480 | | `super + enter` | Open terminal | 481 | | `super + A` | Open rofi drun | 482 | | `super + S` | Open scratchpad terminal | 483 | | `super + shift + q` | Quit awesome | 484 | | `super + shift + r` | Reload awesome | 485 | | `super + q` | Close selected window | 486 | | `alt + tab` | Switch windows | 487 | | `prtscrn` | Screenshot and sketch | 488 | | `shift + prtscrn` | Screenshot and copy | 489 | | `super + space` | Switch layouts | 490 | | `super + f` | Fullscreen window | 491 | | `super + m` | Maximize window | 492 | | `super + n` | Minimize | 493 | | `super + shift + n` | Restore minimized | 494 | | `super + ctrl + [hjkl]` | Resize windows | 495 | | `super + ctrl + [arrow keys]` | ^ | 496 | | `super + ctrl + space` | Toggle floating | 497 | | `super + c` | Center floating client | 498 | | `super + u` | Jump to urgent client | 499 | | `super + alt + l` | Lock screen | 500 | 501 | _And **many many** more...._ 502 | 503 | ## 🔧 Installation 504 | 505 | If you have read all the above then. It is actually very easy to install you don't need to install any other program to run it. If using my defaults then these programs should be enough (programs names may vary depending on the distro). Ofc you can use what you want, be sure to make the respective changes. 506 | 507 | - [x] Software 508 | 509 | - [ ] awesome-git 510 | - [ ] wezterm 511 | - [ ] pipewire pipewire-alsa pipewire-pulse alsa-utils wireplumber 512 | - [ ] rofi 513 | - [ ] clipmenu xclip maim slop swappy 514 | - [ ] inotify-tools light 515 | - [ ] playerctl 516 | - [ ] rofi-emoji 517 | - [ ] yazi 518 | - [ ] pcmanfm 519 | - [ ] bottom 520 | - [ ] ttf-jetbrains-mono-nerd 521 | - [ ] acpid 522 | 523 | - [x] Services 524 | 525 | - [ ] For charger plug/unplug events (if you have a battery) 526 | 527 | ``` 528 | sudo systemctl enable acpid.service 529 | sudo systemctl start acpid.service 530 | ``` 531 | 532 | > finally just clone the repo to your `~/.config/awesome` 533 | 534 | ```bash 535 | git clone https://github.com/MobSenpai/sugoi.git 536 | ``` 537 | 538 | ## Notes 539 | 540 | > I have noticed that wezterm does not work on some installs, so it is advisable to change the default terminal to `kitty` by changing these lines in `rc.lua` 541 | 542 | ``` 543 | terminal = "kitty", 544 | floating_terminal = "kitty --class floating_terminal", 545 | scratchpad_terminal = "kitty --class scratchpad", 546 | ``` 547 | 548 |
Use this theme for kitty 549 | 550 | ``` 551 | # See https://sw.kovidgoyal.net/kitty/conf.html 552 | font_family monospace 553 | font_size 10 554 | 555 | 556 | # Shell integration is sourced and configured manually 557 | shell_integration no-rc enabled 558 | 559 | active_border_color #665c54 560 | active_tab_background #282828 561 | active_tab_foreground #d5c4a1 562 | background #282828 563 | color0 #282828 564 | color1 #fb4934 565 | color10 #b8bb26 566 | color11 #fabd2f 567 | color12 #83a598 568 | color13 #d3869b 569 | color14 #8ec07c 570 | color15 #fbf1c7 571 | color16 #fe8019 572 | color17 #d65d0e 573 | color18 #3c3836 574 | color19 #504945 575 | color2 #b8bb26 576 | color20 #bdae93 577 | color21 #ebdbb2 578 | color3 #fabd2f 579 | color4 #83a598 580 | color5 #d3869b 581 | color6 #8ec07c 582 | color7 #d5c4a1 583 | color8 #665c54 584 | color9 #fb4934 585 | cursor #d5c4a1 586 | foreground #d5c4a1 587 | inactive_border_color #3c3836 588 | inactive_tab_background #3c3836 589 | inactive_tab_foreground #bdae93 590 | scrollback_lines 2000 591 | selection_background #d5c4a1 592 | selection_foreground #282828 593 | tab_bar_background #3c3836 594 | url_color #bdae93 595 | window_padding_width 7 596 | ``` 597 | 598 |

599 | 600 | > Change the bar size to your display resolution by, changing this line in `themes/gruva/theme.lua` 601 | 602 | ``` 603 | theme.wibar_width = dpi(1920) 604 | ``` 605 | 606 | > `Clipmenu` is used as clipboard. It needs some configuration, so research on your own, or replace with something else. 607 | 608 | > Uncomment these lines in `evil/init.lua` to enable specific functions. For battery widget, also uncomment in `themes/gruva/init.lua` in wibar widgets. 609 | 610 | > brightness & battery are WIP 611 | 612 | ``` 613 | require("evil.brightness") 614 | require("evil.battery") 615 | ``` 616 | 617 | ## 💡 Acknowledgements 618 | 619 | - [elenapan](https://github.com/elenapan) 620 | - [rxyhn](https://github.com/rxyhn) 621 | - [JavaCafe01](https://github.com/JavaCafe01) 622 | - [Sinomor](https://github.com/Sinomor) 623 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /evil/battery.lua: -------------------------------------------------------------------------------- 1 | -- Provides: 2 | -- evil::battery 3 | -- percentage (integer) 4 | -- evil::charger 5 | -- plugged (boolean) 6 | 7 | local awful = require("awful") 8 | 9 | local update_interval = 30 10 | 11 | -- Subscribe to power supply status changes with acpi_listen 12 | local charger_script = [[ 13 | sh -c ' 14 | acpi_listen | grep --line-buffered ac_adapter 15 | ' 16 | ]] 17 | 18 | -- First get battery file path 19 | -- If there are multiple, only get the first one 20 | -- TODO support multiple batteries 21 | awful.spawn.easy_async_with_shell( 22 | 'sh -c \'out="$(find /sys/class/power_supply/BAT?/capacity)" && (echo "$out" | head -1) || false\' ', 23 | function(battery_file, _, __, exit_code) 24 | -- No battery file found 25 | if not (exit_code == 0) then 26 | return 27 | end 28 | -- Periodically get battery info 29 | awful.widget.watch("cat " .. battery_file, update_interval, function(_, stdout) 30 | awesome.emit_signal("evil::battery", tonumber(stdout)) 31 | end) 32 | end 33 | ) 34 | 35 | -- First get charger file path 36 | awful.spawn.easy_async_with_shell( 37 | 'sh -c \'out="$(find /sys/class/power_supply/*/online)" && (echo "$out" | head -1) || false\' ', 38 | function(charger_file, _, __, exit_code) 39 | -- No charger file found 40 | if not (exit_code == 0) then 41 | return 42 | end 43 | -- Then initialize function that emits charger info 44 | local emit_charger_info = function() 45 | awful.spawn.easy_async_with_shell("cat " .. charger_file, function(out) 46 | local status = tonumber(out) == 1 47 | awesome.emit_signal("evil::charger", status) 48 | end) 49 | end 50 | 51 | -- Run once to initialize widgets 52 | emit_charger_info() 53 | 54 | -- Kill old acpi_listen process 55 | awful.spawn.easy_async_with_shell( 56 | "ps x | grep \"acpi_listen\" | grep -v grep | awk '{print $1}' | xargs kill", 57 | function() 58 | -- Update charger status with each line printed 59 | awful.spawn.with_line_callback(charger_script, { 60 | stdout = function(_) 61 | emit_charger_info() 62 | end, 63 | }) 64 | end 65 | ) 66 | end 67 | ) 68 | -------------------------------------------------------------------------------- /evil/brightness.lua: -------------------------------------------------------------------------------- 1 | -- Provides: 2 | -- evil::brightness 3 | -- value (integer) 4 | local awful = require("awful") 5 | 6 | local function emit() 7 | awful.spawn.with_line_callback('sh -c "light -G"', { 8 | stdout = function(line) 9 | local value = math.floor(tonumber(line)) 10 | awesome.emit_signal("evil::brightness", value) 11 | end, 12 | }) 13 | end 14 | 15 | -- Run once to initialize widgets 16 | emit() 17 | 18 | -- Subscribe to backlight changes 19 | -- Requires inotify-tools 20 | local subscribe = [[ bash -c "while (inotifywait -e modify /sys/class/backlight/?*/brightness -qq) do echo; done"]] 21 | 22 | -- Kill old inotifywait process 23 | awful.spawn.easy_async_with_shell( 24 | "ps x | grep \"inotifywait -e modify /sys/class/backlight\" | grep -v grep | awk '{print $1}' | xargs kill", 25 | function() 26 | -- Update brightness status with each line printed 27 | awful.spawn.with_line_callback(subscribe, { 28 | stdout = function() 29 | emit() 30 | end, 31 | }) 32 | end 33 | ) 34 | -------------------------------------------------------------------------------- /evil/cpu.lua: -------------------------------------------------------------------------------- 1 | -- Provides: 2 | -- evil::cpu 3 | -- used percentage (integer) 4 | local awful = require("awful") 5 | 6 | local update_interval = 5 7 | local cpu_idle_script = [[ 8 | sh -c " 9 | vmstat 1 2 | tail -1 | awk '{printf \"%d\", $15}' 10 | "]] 11 | 12 | -- Periodically get cpu info 13 | awful.widget.watch(cpu_idle_script, update_interval, function(widget, stdout) 14 | -- local cpu_idle = stdout:match('+(.*)%.%d...(.*)%(') 15 | local cpu_idle = stdout 16 | cpu_idle = string.gsub(cpu_idle, "^%s*(.-)%s*$", "%1") 17 | awesome.emit_signal("evil::cpu", 100 - tonumber(cpu_idle)) 18 | end) 19 | -------------------------------------------------------------------------------- /evil/init.lua: -------------------------------------------------------------------------------- 1 | -- Monitoring 2 | require("evil.cpu") 3 | require("evil.ram") 4 | 5 | -- User controlled 6 | require("evil.volume") 7 | require("evil.playerctl") 8 | -- require("evil.brightness") 9 | -- require("evil.battery") 10 | 11 | -- Internet access required 12 | require("evil.weather") 13 | -------------------------------------------------------------------------------- /evil/playerctl.lua: -------------------------------------------------------------------------------- 1 | -- Provides: 2 | -- evil::playerctl 3 | -- artist (string) 4 | -- song (string) 5 | -- status (string) [playing | paused | stopped] 6 | local awful = require("awful") 7 | 8 | local function emit_info(playerctl_output) 9 | local artist = playerctl_output:match("artist_start(.*)title_start") 10 | local title = playerctl_output:match("title_start(.*)status_start") 11 | -- Use the lower case of status 12 | local status = playerctl_output:match("status_start(.*)"):lower() 13 | status = string.gsub(status, "^%s*(.-)%s*$", "%1") 14 | 15 | awesome.emit_signal("evil::playerctl", artist, title, status) 16 | end 17 | 18 | -- Sleeps until playerctl changes state (pause/play/next/prev) 19 | local playerctl_script = [[ 20 | sh -c ' 21 | playerctl metadata --format 'artist_start{{artist}}title_start{{title}}status_start{{status}}' --follow 22 | ']] 23 | 24 | -- Kill old playerctl process 25 | awful.spawn.easy_async_with_shell( 26 | "ps x | grep \"playerctl metadata\" | grep -v grep | awk '{print $1}' | xargs kill", 27 | function() 28 | -- Emit song info with each line printed 29 | awful.spawn.with_line_callback(playerctl_script, { 30 | stdout = function(line) 31 | emit_info(line) 32 | end, 33 | }) 34 | end 35 | ) 36 | -------------------------------------------------------------------------------- /evil/ram.lua: -------------------------------------------------------------------------------- 1 | -- Provides: 2 | -- evil::ram 3 | -- used (integer - mega bytes) 4 | -- total (integer - mega bytes) 5 | local awful = require("awful") 6 | 7 | local update_interval = 20 8 | -- Returns the used amount of ram in percentage 9 | local ram_script = [[ 10 | sh -c " 11 | free -m | grep 'Mem:' | awk '{printf \"%d@@%d@\", $7, $2}' 12 | "]] 13 | 14 | -- Periodically get ram info 15 | awful.widget.watch(ram_script, update_interval, function(widget, stdout) 16 | local available = stdout:match("(.*)@@") 17 | local total = stdout:match("@@(.*)@") 18 | local used = tonumber(total) - tonumber(available) 19 | awesome.emit_signal("evil::ram", used, tonumber(total)) 20 | end) 21 | -------------------------------------------------------------------------------- /evil/volume.lua: -------------------------------------------------------------------------------- 1 | -- Provides: 2 | -- evil::volume 3 | -- volume 4 | -- muted 5 | local awful = require("awful") 6 | 7 | local volume_old = -1 8 | local muted_old = -1 9 | local function emit() 10 | -- Requires wireplumber 11 | awful.spawn.easy_async_with_shell("wpctl get-volume @DEFAULT_AUDIO_SINK@", function(out) 12 | local volume = tonumber(string.match(out:match("(%d%.%d+)") * 100, "(%d+)")) 13 | local muted = out:match("MUTED") 14 | 15 | local icon = "" 16 | if muted or volume == 0 then 17 | icon = "󰝟" 18 | elseif volume <= 33 then 19 | icon = "󰕿" 20 | elseif volume <= 66 then 21 | icon = "󰖀" 22 | elseif volume <= 100 then 23 | icon = "󰕾" 24 | else 25 | icon = "" 26 | end 27 | 28 | if volume ~= volume_old or muted ~= muted_old then 29 | awesome.emit_signal("evil::volume", volume, muted, icon) 30 | volume_old = volume 31 | muted_old = muted 32 | end 33 | end) 34 | end 35 | 36 | -- Run once to initialize widgets 37 | emit() 38 | 39 | -- Subscribe to pactl changes 40 | local subscribe = [[ bash -c "LANG=C pactl subscribe 2> /dev/null | grep --line-buffered \"Event 'change' on sink\"" ]] 41 | 42 | -- Kill old pactl processes 43 | awful.spawn.easy_async({ "pkill", "--full", "--uid", os.getenv("USER"), "^pactl subscribe" }, function() 44 | awful.spawn.with_line_callback(subscribe, { 45 | stdout = function() 46 | emit() 47 | end, 48 | }) 49 | end) 50 | -------------------------------------------------------------------------------- /evil/weather.lua: -------------------------------------------------------------------------------- 1 | -- Provides: 2 | -- evil::weather 3 | -- result json (string) 4 | local awful = require("awful") 5 | local json = require("modules.json") 6 | 7 | local api_key = user.openweathermap_key 8 | local coordinates = user.openweathermap_city_id 9 | local units = user.openweathermap_weather_units 10 | 11 | local url = ( 12 | "https://api.openweathermap.org/data/2.5/onecall" 13 | .. "?lat=" 14 | .. coordinates[1] 15 | .. "&lon=" 16 | .. coordinates[2] 17 | .. "&appid=" 18 | .. api_key 19 | .. "&units=" 20 | .. units 21 | .. "&exclude=minutely" 22 | ) 23 | 24 | local GET_FORECAST_CMD = [[bash -c "curl -s --show-error -X GET '%s'"]] 25 | 26 | awful.widget.watch(string.format(GET_FORECAST_CMD, url), 600, function(_, stdout, stderr) 27 | if stderr == "" then 28 | local result = json.decode(stdout) 29 | awesome.emit_signal("evil::weather", result) 30 | end 31 | end) 32 | -------------------------------------------------------------------------------- /helpers.lua: -------------------------------------------------------------------------------- 1 | local beautiful = require("beautiful") 2 | local xresources = require("beautiful.xresources") 3 | local dpi = xresources.apply_dpi 4 | local awful = require("awful") 5 | local gears = require("gears") 6 | local helpers = {} 7 | 8 | -- Mouse hover 9 | -- =================================================================== 10 | function helpers.add_hover_cursor(w, hover_cursor) 11 | local original_cursor = "left_ptr" 12 | 13 | w:connect_signal("mouse::enter", function() 14 | local w = _G.mouse.current_wibox 15 | if w then 16 | w.cursor = hover_cursor 17 | end 18 | end) 19 | 20 | w:connect_signal("mouse::leave", function() 21 | local w = _G.mouse.current_wibox 22 | if w then 23 | w.cursor = original_cursor 24 | end 25 | end) 26 | end 27 | 28 | -- Create rounded rectangle shape (in one line) 29 | -- =================================================================== 30 | function helpers.rrect(radius) 31 | return function(cr, width, height) 32 | gears.shape.rounded_rect(cr, width, height, radius) 33 | end 34 | end 35 | 36 | -- Raise or spawn 37 | -- =================================================================== 38 | function helpers.run_or_raise(match, move, spawn_cmd, spawn_args) 39 | local matcher = function(c) 40 | return awful.rules.match(c, match) 41 | end 42 | 43 | -- Find and raise 44 | local found = false 45 | for c in awful.client.iterate(matcher) do 46 | found = true 47 | c.minimized = false 48 | if move then 49 | c:move_to_tag(mouse.screen.selected_tag) 50 | client.focus = c 51 | else 52 | c:jump_to() 53 | end 54 | break 55 | end 56 | 57 | -- Spawn if not found 58 | if not found then 59 | awful.spawn(spawn_cmd, spawn_args) 60 | end 61 | end 62 | 63 | -- Run raise or minimize a client (scratchpad style) 64 | -- =================================================================== 65 | function helpers.scratchpad(match, spawn_cmd, spawn_args) 66 | local cf = client.focus 67 | if cf and awful.rules.match(cf, match) then 68 | cf.minimized = true 69 | else 70 | helpers.run_or_raise(match, true, spawn_cmd, spawn_args) 71 | end 72 | end 73 | 74 | -- Resize DWIM (Do What I Mean) 75 | -- =================================================================== 76 | -- Resize client or factor 77 | -- Constants 78 | local floating_resize_amount = dpi(20) 79 | local tiling_resize_factor = 0.05 80 | --------------- 81 | function helpers.resize_dwim(c, direction) 82 | if c and c.floating then 83 | if direction == "up" then 84 | c:relative_move(0, 0, 0, -floating_resize_amount) 85 | elseif direction == "down" then 86 | c:relative_move(0, 0, 0, floating_resize_amount) 87 | elseif direction == "left" then 88 | c:relative_move(0, 0, -floating_resize_amount, 0) 89 | elseif direction == "right" then 90 | c:relative_move(0, 0, floating_resize_amount, 0) 91 | end 92 | elseif awful.layout.get(mouse.screen) ~= awful.layout.suit.floating then 93 | if direction == "up" then 94 | awful.client.incwfact(-tiling_resize_factor) 95 | elseif direction == "down" then 96 | awful.client.incwfact(tiling_resize_factor) 97 | elseif direction == "left" then 98 | awful.tag.incmwfact(-tiling_resize_factor) 99 | elseif direction == "right" then 100 | awful.tag.incmwfact(tiling_resize_factor) 101 | end 102 | end 103 | end 104 | 105 | -- Change volume 106 | -- =================================================================== 107 | function helpers.volume_control(step) 108 | local cmd 109 | if step == 0 then 110 | cmd = "pactl set-sink-mute @DEFAULT_SINK@ toggle" 111 | else 112 | sign = step > 0 and "+" or "" 113 | cmd = "pactl set-sink-mute @DEFAULT_SINK@ 0 && pactl set-sink-volume @DEFAULT_SINK@ " 114 | .. sign 115 | .. tostring(step) 116 | .. "%" 117 | end 118 | awful.spawn.with_shell(cmd) 119 | end 120 | 121 | -- Quick markup 122 | -- =================================================================== 123 | function helpers.colorize_text(text, color) 124 | return "" .. text .. "" 125 | end 126 | 127 | -- Lighten or darken hex colors 128 | -- =================================================================== 129 | -- Rounds to whole number 130 | local function round(num) 131 | return math.floor(num + 0.5) 132 | end 133 | -- Rounds to hundredths 134 | local function roundH(num) 135 | return math.floor((num * 100) + 0.5) / 100 136 | end 137 | 138 | -- Converts hexadecimal color to HSL 139 | -- Lightness (L) is changed based on amt 140 | -- Converts HSL back to hex 141 | -- amt (0-100) can be negative to darken or positive to lighten 142 | -- The amt specified is added to the color's existing Lightness 143 | -- e.g., (#000000, 25) L = 25 but (#404040, 25) L = 50 144 | 145 | function helpers.lighten(hex_color, amt) 146 | local r, g, b, a 147 | local hex = hex_color:gsub("#", "") 148 | if #hex < 6 then 149 | local t = {} 150 | for i = 1, #hex do 151 | local char = hex:sub(i, i) 152 | t[i] = char .. char 153 | end 154 | hex = table.concat(t) 155 | end 156 | r = tonumber(hex:sub(1, 2), 16) / 255 157 | g = tonumber(hex:sub(3, 4), 16) / 255 158 | b = tonumber(hex:sub(5, 6), 16) / 255 159 | if #hex ~= 6 then 160 | a = roundH(tonumber(hex:sub(7, 8), 16) / 255) 161 | end 162 | 163 | local max = math.max(r, g, b) 164 | local min = math.min(r, g, b) 165 | local c = max - min 166 | 167 | -- Hue 168 | local h 169 | if c == 0 then 170 | h = 0 171 | elseif max == r then 172 | h = ((g - b) / c) % 6 173 | elseif max == g then 174 | h = ((b - r) / c) + 2 175 | elseif max == b then 176 | h = ((r - g) / c) + 4 177 | end 178 | h = h * 60 179 | 180 | -- Luminance 181 | local l = (max + min) * 0.5 182 | 183 | -- Saturation 184 | local s 185 | if l == 0 then 186 | s = 0 187 | elseif l <= 0.5 then 188 | s = c / (l * 2) 189 | elseif l > 0.5 then 190 | s = c / (2 - (l * 2)) 191 | end 192 | 193 | local H, S, L, A 194 | H = round(h) / 360 195 | S = round(s * 100) / 100 196 | L = round(l * 100) / 100 197 | 198 | amt = amt / 100 199 | if L + amt > 1 then 200 | L = 1 201 | elseif L + amt < 0 then 202 | L = 0 203 | else 204 | L = L + amt 205 | end 206 | 207 | local R, G, B 208 | if S == 0 then 209 | R, G, B = round(L * 255), round(L * 255), round(L * 255) 210 | else 211 | local function hue2rgb(p, q, t) 212 | if t < 0 then 213 | t = t + 1 214 | end 215 | if t > 1 then 216 | t = t - 1 217 | end 218 | if t < 1 / 6 then 219 | return p + (q - p) * (6 * t) 220 | end 221 | if t < 1 / 2 then 222 | return q 223 | end 224 | if t < 2 / 3 then 225 | return p + (q - p) * (2 / 3 - t) * 6 226 | end 227 | return p 228 | end 229 | local q 230 | if L < 0.5 then 231 | q = L * (1 + S) 232 | else 233 | q = L + S - (L * S) 234 | end 235 | local p = 2 * L - q 236 | R = round(hue2rgb(p, q, (H + 1 / 3)) * 255) 237 | G = round(hue2rgb(p, q, H) * 255) 238 | B = round(hue2rgb(p, q, (H - 1 / 3)) * 255) 239 | end 240 | 241 | if a ~= nil then 242 | A = round(a * 255) 243 | return string.format("#" .. "%.2x%.2x%.2x%.2x", R, G, B, A) 244 | else 245 | return string.format("#" .. "%.2x%.2x%.2x", R, G, B) 246 | end 247 | end 248 | 249 | return helpers 250 | -- EOF ------------------------------------------------------------------------ 251 | -------------------------------------------------------------------------------- /keys.lua: -------------------------------------------------------------------------------- 1 | -- ░█░█░█▀▀░█░█░█▀▄░▀█▀░█▀█░█▀▄░█▀▀ 2 | -- ░█▀▄░█▀▀░░█░░█▀▄░░█░░█░█░█░█░▀▀█ 3 | -- ░▀░▀░▀▀▀░░▀░░▀▀░░▀▀▀░▀░▀░▀▀░░▀▀▀ 4 | 5 | local gears = require("gears") 6 | local beautiful = require("beautiful") 7 | local awful = require("awful") 8 | local hotkeys_popup = require("awful.hotkeys_popup") 9 | local helpers = require("helpers") 10 | local awesome = awesome 11 | local client = client 12 | local root = root 13 | local keys = {} 14 | 15 | local mod = "Mod4" 16 | local alt = "Mod1" 17 | local ctrl = "Control" 18 | local shift = "Shift" 19 | 20 | -- Menu 21 | -- ============================================= 22 | local menu = {} 23 | menu.awesome = { 24 | { 25 | "Hotkeys", 26 | function() 27 | hotkeys_popup.show_help(nil, awful.screen.focused()) 28 | end, 29 | }, 30 | { 31 | "Manual", 32 | function() 33 | awful.spawn(user.terminal .. " -e man awesome") 34 | end, 35 | }, 36 | { 37 | "Edit config", 38 | function() 39 | awful.spawn(user.editor .. " " .. awesome.conffile) 40 | end, 41 | }, 42 | { 43 | "Restart", 44 | function() 45 | awesome.restart() 46 | end, 47 | }, 48 | { 49 | "Quit", 50 | function() 51 | awesome.quit() 52 | end, 53 | }, 54 | } 55 | 56 | menu.mainmenu = awful.menu({ 57 | items = { 58 | { " Terminal", user.terminal }, 59 | { " Explorer", apps.file_manager }, 60 | { " Browser", apps.browser }, 61 | { " Editor", apps.editor }, 62 | { "󰨞 GUI Editor", apps.visual_editor }, 63 | { " AwesomeWM", menu.awesome }, 64 | { 65 | " Notifications", 66 | function() 67 | awesome.emit_signal("summon::notif_center") 68 | end, 69 | }, 70 | }, 71 | }) 72 | 73 | -- Mouse bindings on desktop 74 | -- ============================================= 75 | keys.desktopbuttons = gears.table.join( 76 | awful.button({}, 1, function() 77 | -- Single tap 78 | awesome.emit_signal("summon::dismiss") 79 | if menu.mainmenu then 80 | menu.mainmenu:hide() 81 | end 82 | -- if sidebar_hide then 83 | -- sidebar_hide() 84 | -- end 85 | end), 86 | 87 | -- Right click - Show app drawer 88 | awful.button({}, 3, function() 89 | menu.mainmenu:toggle() 90 | end), 91 | 92 | -- Middle button - Toggle dashboard 93 | -- awful.button({ }, 2, function() 94 | -- if dashboard_show then 95 | -- dashboard_show() 96 | -- end 97 | -- end), 98 | 99 | -- Scrolling - Switch tags 100 | -- awful.button({ }, 4, awful.tag.viewprev), 101 | -- awful.button({ }, 5, awful.tag.viewnext), 102 | 103 | -- Side buttons - Control volume 104 | awful.button({}, 8, function() 105 | helpers.volume_control(-5) 106 | end), 107 | awful.button({}, 9, function() 108 | helpers.volume_control(5) 109 | end) 110 | 111 | -- Side buttons - Minimize and restore minimized client 112 | -- awful.button({}, 8, function() 113 | -- if client.focus ~= nil then 114 | -- client.focus.minimized = true 115 | -- end 116 | -- end), 117 | -- awful.button({}, 9, function() 118 | -- local c = awful.client.restore() 119 | -- -- Focus restored client 120 | -- if c then 121 | -- client.focus = c 122 | -- end 123 | -- end) 124 | ) 125 | 126 | -- Global bindings 127 | -- ============================================= 128 | keys.globalkeys = gears.table.join( 129 | awful.key({ 130 | modifiers = { mod }, 131 | keygroup = "numrow", 132 | description = "only view tag", 133 | group = "tag", 134 | on_press = function(index) 135 | local screen = awful.screen.focused() 136 | local tag = screen.tags[index] 137 | if tag then 138 | tag:view_only() 139 | end 140 | end, 141 | }), 142 | awful.key({ 143 | modifiers = { mod, ctrl }, 144 | keygroup = "numrow", 145 | description = "toggle tag", 146 | group = "tag", 147 | on_press = function(index) 148 | local screen = awful.screen.focused() 149 | local tag = screen.tags[index] 150 | if tag then 151 | awful.tag.viewtoggle(tag) 152 | end 153 | end, 154 | }), 155 | awful.key({ 156 | modifiers = { mod, shift }, 157 | keygroup = "numrow", 158 | description = "move focused client to tag", 159 | group = "tag", 160 | on_press = function(index) 161 | if client.focus then 162 | local tag = client.focus.screen.tags[index] 163 | if tag then 164 | client.focus:move_to_tag(tag) 165 | end 166 | end 167 | end, 168 | }), 169 | awful.key({ mod }, "space", function() 170 | awful.layout.inc(1) 171 | end, { description = "next layout", group = "tag" }), 172 | awful.key({ mod, shift }, "space", function() 173 | awful.layout.inc(-1) 174 | end, { description = "previous layout", group = "tag" }), 175 | 176 | -- Tag switcher 177 | awful.key({ mod }, "Tab", function() 178 | awful.tag.viewnext() 179 | end, { description = "next tag", group = "client" }), 180 | awful.key({ mod, shift }, "Tab", function() 181 | awful.tag.viewprev() 182 | end, { description = "prev tag", group = "client" }), 183 | 184 | -- Focus client by direction (hjkl keys) 185 | awful.key({ mod }, "j", function() 186 | awful.client.focus.bydirection("down") 187 | end, { description = "focus down", group = "client" }), 188 | awful.key({ mod }, "k", function() 189 | awful.client.focus.bydirection("up") 190 | end, { description = "focus up", group = "client" }), 191 | awful.key({ mod }, "h", function() 192 | awful.client.focus.bydirection("left") 193 | end, { description = "focus left", group = "client" }), 194 | awful.key({ mod }, "l", function() 195 | awful.client.focus.bydirection("right") 196 | end, { description = "focus right", group = "client" }), 197 | 198 | -- Focus client by index (cycle through clients) 199 | awful.key({ mod }, "z", function() 200 | awful.client.focus.byidx(1) 201 | end, { description = "focus next by index", group = "client" }), 202 | awful.key({ mod, shift }, "z", function() 203 | awful.client.focus.byidx(-1) 204 | end, { description = "focus next by index", group = "client" }), 205 | 206 | -- Focus client by direction (arrow keys) 207 | awful.key({ mod }, "Down", function() 208 | awful.client.focus.bydirection("down") 209 | end, { description = "focus down", group = "client" }), 210 | awful.key({ mod }, "Up", function() 211 | awful.client.focus.bydirection("up") 212 | end, { description = "focus up", group = "client" }), 213 | awful.key({ mod }, "Left", function() 214 | awful.client.focus.bydirection("left") 215 | end, { description = "focus left", group = "client" }), 216 | awful.key({ mod }, "Right", function() 217 | awful.client.focus.bydirection("right") 218 | end, { description = "focus right", group = "client" }), 219 | 220 | -- Urgent or Undo: 221 | -- Jump to urgent client or (if there is no such client) go back 222 | -- to the last tag 223 | awful.key({ mod }, "u", function() 224 | uc = awful.client.urgent.get() 225 | -- If there is no urgent client, go back to last tag 226 | if uc == nil then 227 | awful.tag.history.restore() 228 | else 229 | awful.client.urgent.jumpto() 230 | end 231 | end, { description = "jump to urgent client", group = "client" }), 232 | 233 | awful.key({ mod }, "x", function() 234 | awful.tag.history.restore() 235 | end, { description = "go back", group = "tag" }), 236 | 237 | -- Spawn terminal 238 | awful.key({ mod }, "Return", function() 239 | awful.spawn(user.terminal) 240 | end, { description = "open a terminal", group = "launcher" }), 241 | 242 | -- Spawn floating terminal 243 | awful.key({ mod, shift }, "Return", function() 244 | awful.spawn(user.floating_terminal, { floating = true }) 245 | end, { description = "spawn floating terminal", group = "launcher" }), 246 | 247 | -- Reload Awesome 248 | awful.key({ mod, shift }, "r", function() 249 | awesome.restart() 250 | end, { description = "reload awesome", group = "awesome" }), 251 | 252 | -- Quit Awesome 253 | awful.key({ mod, shift }, "q", function() 254 | awesome.quit() 255 | end, { description = "quit awesome", group = "awesome" }), 256 | awful.key({ mod, shift }, "x", function() 257 | awesome.quit() 258 | -- exit_screen_show() 259 | end, { description = "quit awesome", group = "awesome" }), 260 | awful.key({}, "XF86PowerOff", function() 261 | awesome.quit() 262 | -- exit_screen_show() 263 | end, { description = "quit awesome", group = "awesome" }), 264 | 265 | -- Resize focused client or layout factor 266 | awful.key({ mod, ctrl }, "Down", function(c) 267 | helpers.resize_dwim(client.focus, "down") 268 | end), 269 | awful.key({ mod, ctrl }, "Up", function(c) 270 | helpers.resize_dwim(client.focus, "up") 271 | end), 272 | awful.key({ mod, ctrl }, "Left", function(c) 273 | helpers.resize_dwim(client.focus, "left") 274 | end), 275 | awful.key({ mod, ctrl }, "Right", function(c) 276 | helpers.resize_dwim(client.focus, "right") 277 | end), 278 | awful.key({ mod, ctrl }, "j", function(c) 279 | helpers.resize_dwim(client.focus, "down") 280 | end), 281 | awful.key({ mod, ctrl }, "k", function(c) 282 | helpers.resize_dwim(client.focus, "up") 283 | end), 284 | awful.key({ mod, ctrl }, "h", function(c) 285 | helpers.resize_dwim(client.focus, "left") 286 | end), 287 | awful.key({ mod, ctrl }, "l", function(c) 288 | helpers.resize_dwim(client.focus, "right") 289 | end), 290 | 291 | -- Focus restored client 292 | awful.key({ mod, shift }, "n", function() 293 | local c = awful.client.restore() 294 | if c then 295 | client.focus = c 296 | end 297 | end, { description = "restore minimized", group = "client" }), 298 | 299 | -- Dismiss notifications and elements that connect to the dismiss signal 300 | awful.key({ ctrl }, "space", function() 301 | awesome.emit_signal("summon::dismiss") 302 | end, { description = "dismiss notification", group = "notifications" }), 303 | 304 | -- Scratchpad terminal 305 | awful.key({ mod }, "s", function() 306 | helpers.scratchpad({ instance = "scratchpad" }, user.scratchpad_terminal, nil) 307 | end, { description = "scratchpad", group = "launcher" }), 308 | 309 | -- Screen Shots/Vids 310 | awful.key({}, "Print", apps.screenshot_full, { description = "screenshot gui", group = "awesome" }), 311 | awful.key( 312 | { shift }, 313 | "Print", 314 | apps.screenshot_clipboard, 315 | { description = "screenshot to clipboard", group = "awesome" } 316 | ), 317 | 318 | -- Prompt 319 | awful.key({ mod }, "d", function() 320 | awful.screen.focused().mypromptbox:run() 321 | end, { description = "run prompt", group = "launcher" }), 322 | --- App launcher 323 | awful.key({ mod }, "a", function() 324 | awful.spawn(user.app_launcher) 325 | end, { description = "app launcher", group = "app" }), 326 | --- Emoji picker 327 | awful.key({ mod }, "e", apps.emoji_picker, { description = "emoji picker", group = "app" }), 328 | --- Clipboard 329 | awful.key({ mod }, "v", apps.clipboard, { description = "clipboard", group = "app" }), 330 | 331 | -- Hotkeys list 332 | awful.key({ mod }, "F1", function() 333 | hotkeys_popup.show_help() 334 | end, { description = "show help", group = "awesome" }), 335 | 336 | -- Volume control with volume keys 337 | awful.key({}, "XF86AudioMute", function() 338 | helpers.volume_control(0) 339 | end, { description = "(un)mute volume", group = "volume" }), 340 | awful.key({}, "XF86AudioLowerVolume", function() 341 | helpers.volume_control(-5) 342 | awesome.emit_signal("summon::osd") 343 | end, { description = "lower volume", group = "volume" }), 344 | awful.key({}, "XF86AudioRaiseVolume", function() 345 | helpers.volume_control(5) 346 | awesome.emit_signal("summon::osd") 347 | end, { description = "raise volume", group = "volume" }), 348 | 349 | -- Brightness control with brightness keys 350 | awful.key({}, "XF86MonBrightnessUp", function() 351 | awful.spawn("light -A 10") 352 | awesome.emit_signal("summon::osd") 353 | end, { description = "increase brightness", group = "brightness" }), 354 | awful.key({}, "XF86MonBrightnessDown", function() 355 | awful.spawn("light -U 10") 356 | awesome.emit_signal("summon::osd") 357 | end, { description = "decrease brightness", group = "brightness" }), 358 | 359 | -- Music 360 | awful.key({}, "XF86AudioPlay", function() 361 | awful.spawn.with_shell("playerctl play-pause") 362 | end, { description = "play pause music", group = "volume" }), 363 | awful.key({}, "XF86AudioPrev", function() 364 | awful.spawn.with_shell("playerctl previous") 365 | end, { description = "previous music", group = "volume" }), 366 | awful.key({}, "XF86AudioNext", function() 367 | awful.spawn.with_shell("playerctl next") 368 | end, { description = "next music", group = "volume" }), 369 | 370 | -- Lockscreen 371 | awful.key({ mod, alt }, "l", function() 372 | awesome.emit_signal("summon::lock_screen") 373 | end, { description = "lock screen", group = "hotkeys" }), 374 | 375 | -- Apps 376 | -- Spawn browser 377 | awful.key({ mod }, "F2", apps.browser, { description = "web browser", group = "launcher" }), 378 | -- Spawn gui file manager 379 | awful.key({ mod }, "F3", apps.file_manager, { description = "file manager", group = "launcher" }), 380 | -- Terminal file manager 381 | awful.key({ mod }, "F4", apps.term_filemanager, { description = "term filemanager", group = "launcher" }), 382 | -- Primary editor 383 | awful.key({ mod }, "F5", apps.editor, { description = "editor", group = "launcher" }), 384 | -- Gui editor 385 | awful.key({ mod }, "F6", apps.visual_editor, { description = "visual editor", group = "launcher" }), 386 | -- Add more 387 | awful.key({ mod }, "F7", apps.file_manager, { description = "file manager", group = "launcher" }), 388 | awful.key({ mod }, "F8", apps.file_manager, { description = "file manager", group = "launcher" }), 389 | awful.key({ mod }, "F9", apps.file_manager, { description = "file manager", group = "launcher" }), 390 | awful.key({ mod }, "F10", apps.file_manager, { description = "file manager", group = "launcher" }), 391 | -- Volume control 392 | awful.key({ mod }, "F11", apps.volume, { description = "volume control", group = "launcher" }), 393 | -- Process monitor 394 | awful.key({ mod }, "F12", apps.process_monitor, { description = "process monitor", group = "launcher" }) 395 | ) 396 | 397 | -- Client related bindings 398 | -- ============================================= 399 | keys.clientkeys = gears.table.join( 400 | -- Swap by direction 401 | awful.key({ mod, shift }, "j", function() 402 | awful.client.swap.bydirection("down") 403 | end), 404 | awful.key({ mod, shift }, "k", function() 405 | awful.client.swap.bydirection("up") 406 | end), 407 | awful.key({ mod, shift }, "h", function() 408 | awful.client.swap.bydirection("left") 409 | end), 410 | awful.key({ mod, shift }, "l", function() 411 | awful.client.swap.bydirection("right") 412 | end), 413 | 414 | -- Toggle fullscreen 415 | awful.key({ mod }, "f", function(c) 416 | c.fullscreen = not c.fullscreen 417 | c:raise() 418 | end, { description = "toggle fullscreen", group = "client" }), 419 | 420 | -- Close client 421 | awful.key({ mod }, "q", function(c) 422 | c:kill() 423 | end, { description = "close", group = "client" }), 424 | awful.key({ alt }, "F4", function(c) 425 | c:kill() 426 | end, { description = "close", group = "client" }), 427 | 428 | --- Center client 429 | awful.key({ mod }, "c", function() 430 | awful.placement.centered(c, { honor_workarea = true, honor_padding = true }) 431 | end), 432 | 433 | -- Switch clients 434 | awful.key({ alt }, "Tab", function() 435 | awesome.emit_signal("window_switcher::turn_on") 436 | -- awful.client.focus.byidx(1) 437 | end, { description = "focus next by index", group = "client" }), 438 | 439 | -- Toggle floating 440 | awful.key({ mod, ctrl }, "space", function(c) 441 | local layout_is_floating = (awful.layout.get(mouse.screen) == awful.layout.suit.floating) 442 | if not layout_is_floating then 443 | awful.client.floating.toggle() 444 | end 445 | end, { description = "toggle floating", group = "client" }), 446 | 447 | -- Set master 448 | awful.key({ mod, ctrl }, "Return", function(c) 449 | c:swap(awful.client.getmaster()) 450 | end, { description = "move to master", group = "client" }), 451 | 452 | -- P for pin: keep on top OR sticky 453 | -- On top 454 | awful.key({ mod, shift }, "p", function(c) 455 | c.ontop = not c.ontop 456 | end, { description = "toggle keep on top", group = "client" }), 457 | -- Sticky 458 | awful.key({ mod, ctrl }, "p", function(c) 459 | c.sticky = not c.sticky 460 | end, { description = "toggle sticky", group = "client" }), 461 | 462 | -- Minimize 463 | awful.key({ mod }, "n", function(c) 464 | c.minimized = true 465 | end, { description = "minimize", group = "client" }), 466 | 467 | -- Maximize 468 | awful.key({ mod }, "m", function(c) 469 | c.maximized = not c.maximized 470 | end, { description = "(un)maximize", group = "client" }), 471 | awful.key({ mod, ctrl }, "m", function(c) 472 | c.maximized_vertical = not c.maximized_vertical 473 | c:raise() 474 | end, { description = "(un)maximize vertically", group = "client" }), 475 | awful.key({ mod, shift }, "m", function(c) 476 | c.maximized_horizontal = not c.maximized_horizontal 477 | c:raise() 478 | end, { description = "(un)maximize horizontally", group = "client" }) 479 | ) 480 | 481 | -- Mouse buttons on the client (whole window, not just titlebar) 482 | -- ============================================= 483 | keys.clientbuttons = gears.table.join( 484 | awful.button({}, 1, function(c) 485 | client.focus = c 486 | end), 487 | awful.button({ mod }, 1, awful.mouse.client.move), 488 | -- awful.button({ mod }, 2, function(c) c:kill() end), 489 | awful.button({ mod }, 3, function(c) 490 | client.focus = c 491 | awful.mouse.client.resize(c) 492 | -- awful.mouse.resize(c, nil, {jump_to_corner=true}) 493 | end) 494 | 495 | -- Super + scroll = Change client opacity 496 | -- awful.button({ mod }, 4, function(c) 497 | -- c.opacity = c.opacity + 0.1 498 | -- end), 499 | -- awful.button({ mod }, 5, function(c) 500 | -- c.opacity = c.opacity - 0.1 501 | -- end) 502 | ) 503 | 504 | -- Mouse buttons on the tasklist 505 | -- Use 'Any' modifier so that the same buttons can be used in the floating 506 | -- tasklist displayed by the window switcher while the mod is pressed 507 | -- ============================================= 508 | keys.tasklist_buttons = gears.table.join( 509 | awful.button({ "Any" }, 1, function(c) 510 | if c == client.focus then 511 | c.minimized = true 512 | else 513 | -- Without this, the following 514 | -- :isvisible() makes no sense 515 | c.minimized = false 516 | if not c:isvisible() and c.first_tag then 517 | c.first_tag:view_only() 518 | end 519 | -- This will also un-minimize 520 | -- the client, if needed 521 | client.focus = c 522 | end 523 | end), 524 | -- Middle mouse button closes the window (on release) 525 | awful.button({ "Any" }, 2, nil, function(c) 526 | c:kill() 527 | end), 528 | awful.button({ "Any" }, 3, function(c) 529 | c.minimized = true 530 | end), 531 | awful.button({ "Any" }, 4, function() 532 | awful.client.focus.byidx(-1) 533 | end), 534 | awful.button({ "Any" }, 5, function() 535 | awful.client.focus.byidx(1) 536 | end), 537 | 538 | -- Side button up - toggle floating 539 | awful.button({ "Any" }, 9, function(c) 540 | c.floating = not c.floating 541 | end), 542 | -- Side button down - toggle ontop 543 | awful.button({ "Any" }, 8, function(c) 544 | c.ontop = not c.ontop 545 | end) 546 | ) 547 | 548 | -- Mouse buttons on a tag of the taglist widget 549 | -- ============================================= 550 | keys.taglist_buttons = gears.table.join( 551 | awful.button({}, 1, function(t) 552 | t:view_only() 553 | end), 554 | awful.button({ mod }, 1, function(t) 555 | if client.focus then 556 | client.focus:move_to_tag(t) 557 | end 558 | end), 559 | -- awful.button({ }, 3, awful.tag.viewtoggle), 560 | awful.button({}, 3, function(t) 561 | if client.focus then 562 | client.focus:move_to_tag(t) 563 | end 564 | end), 565 | awful.button({ mod }, 3, function(t) 566 | if client.focus then 567 | client.focus:toggle_tag(t) 568 | end 569 | end), 570 | awful.button({}, 4, function(t) 571 | awful.tag.viewprev(t.screen) 572 | end), 573 | awful.button({}, 5, function(t) 574 | awful.tag.viewnext(t.screen) 575 | end) 576 | ) 577 | 578 | root.keys(keys.globalkeys) 579 | root.buttons(keys.desktopbuttons) 580 | 581 | return keys 582 | -- EOF ------------------------------------------------------------------------ 583 | -------------------------------------------------------------------------------- /modules/json.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- json.lua 3 | -- 4 | -- Copyright (c) 2020 rxi 5 | -- 6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | -- this software and associated documentation files (the "Software"), to deal in 8 | -- the Software without restriction, including without limitation the rights to 9 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | -- of the Software, and to permit persons to whom the Software is furnished to do 11 | -- so, subject to the following conditions: 12 | -- 13 | -- The above copyright notice and this permission notice shall be included in all 14 | -- copies or substantial portions of the Software. 15 | -- 16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | -- SOFTWARE. 23 | -- 24 | 25 | local json = { _version = "0.1.2" } 26 | 27 | ------------------------------------------------------------------------------- 28 | -- Encode 29 | ------------------------------------------------------------------------------- 30 | 31 | local encode 32 | 33 | local escape_char_map = { 34 | ["\\"] = "\\", 35 | ['"'] = '"', 36 | ["\b"] = "b", 37 | ["\f"] = "f", 38 | ["\n"] = "n", 39 | ["\r"] = "r", 40 | ["\t"] = "t", 41 | } 42 | 43 | local escape_char_map_inv = { ["/"] = "/" } 44 | for k, v in pairs(escape_char_map) do 45 | escape_char_map_inv[v] = k 46 | end 47 | 48 | local function escape_char(c) 49 | return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) 50 | end 51 | 52 | local function encode_nil(val) 53 | return "null" 54 | end 55 | 56 | local function encode_table(val, stack) 57 | local res = {} 58 | stack = stack or {} 59 | 60 | -- Circular reference? 61 | if stack[val] then 62 | error("circular reference") 63 | end 64 | 65 | stack[val] = true 66 | 67 | if rawget(val, 1) ~= nil or next(val) == nil then 68 | -- Treat as array -- check keys are valid and it is not sparse 69 | local n = 0 70 | for k in pairs(val) do 71 | if type(k) ~= "number" then 72 | error("invalid table: mixed or invalid key types") 73 | end 74 | n = n + 1 75 | end 76 | if n ~= #val then 77 | error("invalid table: sparse array") 78 | end 79 | -- Encode 80 | for i, v in ipairs(val) do 81 | table.insert(res, encode(v, stack)) 82 | end 83 | stack[val] = nil 84 | return "[" .. table.concat(res, ",") .. "]" 85 | else 86 | -- Treat as an object 87 | for k, v in pairs(val) do 88 | if type(k) ~= "string" then 89 | error("invalid table: mixed or invalid key types") 90 | end 91 | table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) 92 | end 93 | stack[val] = nil 94 | return "{" .. table.concat(res, ",") .. "}" 95 | end 96 | end 97 | 98 | local function encode_string(val) 99 | return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' 100 | end 101 | 102 | local function encode_number(val) 103 | -- Check for NaN, -inf and inf 104 | if val ~= val or val <= -math.huge or val >= math.huge then 105 | error("unexpected number value '" .. tostring(val) .. "'") 106 | end 107 | return string.format("%.14g", val) 108 | end 109 | 110 | local type_func_map = { 111 | ["nil"] = encode_nil, 112 | ["table"] = encode_table, 113 | ["string"] = encode_string, 114 | ["number"] = encode_number, 115 | ["boolean"] = tostring, 116 | } 117 | 118 | encode = function(val, stack) 119 | local t = type(val) 120 | local f = type_func_map[t] 121 | if f then 122 | return f(val, stack) 123 | end 124 | error("unexpected type '" .. t .. "'") 125 | end 126 | 127 | function json.encode(val) 128 | return (encode(val)) 129 | end 130 | 131 | ------------------------------------------------------------------------------- 132 | -- Decode 133 | ------------------------------------------------------------------------------- 134 | 135 | local parse 136 | 137 | local function create_set(...) 138 | local res = {} 139 | for i = 1, select("#", ...) do 140 | res[select(i, ...)] = true 141 | end 142 | return res 143 | end 144 | 145 | local space_chars = create_set(" ", "\t", "\r", "\n") 146 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") 147 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") 148 | local literals = create_set("true", "false", "null") 149 | 150 | local literal_map = { 151 | ["true"] = true, 152 | ["false"] = false, 153 | ["null"] = nil, 154 | } 155 | 156 | local function next_char(str, idx, set, negate) 157 | for i = idx, #str do 158 | if set[str:sub(i, i)] ~= negate then 159 | return i 160 | end 161 | end 162 | return #str + 1 163 | end 164 | 165 | local function decode_error(str, idx, msg) 166 | local line_count = 1 167 | local col_count = 1 168 | for i = 1, idx - 1 do 169 | col_count = col_count + 1 170 | if str:sub(i, i) == "\n" then 171 | line_count = line_count + 1 172 | col_count = 1 173 | end 174 | end 175 | error(string.format("%s at line %d col %d", msg, line_count, col_count)) 176 | end 177 | 178 | local function codepoint_to_utf8(n) 179 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa 180 | local f = math.floor 181 | if n <= 0x7f then 182 | return string.char(n) 183 | elseif n <= 0x7ff then 184 | return string.char(f(n / 64) + 192, n % 64 + 128) 185 | elseif n <= 0xffff then 186 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) 187 | elseif n <= 0x10ffff then 188 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, f(n % 4096 / 64) + 128, n % 64 + 128) 189 | end 190 | error(string.format("invalid unicode codepoint '%x'", n)) 191 | end 192 | 193 | local function parse_unicode_escape(s) 194 | local n1 = tonumber(s:sub(1, 4), 16) 195 | local n2 = tonumber(s:sub(7, 10), 16) 196 | -- Surrogate pair? 197 | if n2 then 198 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) 199 | else 200 | return codepoint_to_utf8(n1) 201 | end 202 | end 203 | 204 | local function parse_string(str, i) 205 | local res = "" 206 | local j = i + 1 207 | local k = j 208 | 209 | while j <= #str do 210 | local x = str:byte(j) 211 | 212 | if x < 32 then 213 | decode_error(str, j, "control character in string") 214 | elseif x == 92 then -- `\`: Escape 215 | res = res .. str:sub(k, j - 1) 216 | j = j + 1 217 | local c = str:sub(j, j) 218 | if c == "u" then 219 | local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) 220 | or str:match("^%x%x%x%x", j + 1) 221 | or decode_error(str, j - 1, "invalid unicode escape in string") 222 | res = res .. parse_unicode_escape(hex) 223 | j = j + #hex 224 | else 225 | if not escape_chars[c] then 226 | decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") 227 | end 228 | res = res .. escape_char_map_inv[c] 229 | end 230 | k = j + 1 231 | elseif x == 34 then -- `"`: End of string 232 | res = res .. str:sub(k, j - 1) 233 | return res, j + 1 234 | end 235 | 236 | j = j + 1 237 | end 238 | 239 | decode_error(str, i, "expected closing quote for string") 240 | end 241 | 242 | local function parse_number(str, i) 243 | local x = next_char(str, i, delim_chars) 244 | local s = str:sub(i, x - 1) 245 | local n = tonumber(s) 246 | if not n then 247 | decode_error(str, i, "invalid number '" .. s .. "'") 248 | end 249 | return n, x 250 | end 251 | 252 | local function parse_literal(str, i) 253 | local x = next_char(str, i, delim_chars) 254 | local word = str:sub(i, x - 1) 255 | if not literals[word] then 256 | decode_error(str, i, "invalid literal '" .. word .. "'") 257 | end 258 | return literal_map[word], x 259 | end 260 | 261 | local function parse_array(str, i) 262 | local res = {} 263 | local n = 1 264 | i = i + 1 265 | while 1 do 266 | local x 267 | i = next_char(str, i, space_chars, true) 268 | -- Empty / end of array? 269 | if str:sub(i, i) == "]" then 270 | i = i + 1 271 | break 272 | end 273 | -- Read token 274 | x, i = parse(str, i) 275 | res[n] = x 276 | n = n + 1 277 | -- Next token 278 | i = next_char(str, i, space_chars, true) 279 | local chr = str:sub(i, i) 280 | i = i + 1 281 | if chr == "]" then 282 | break 283 | end 284 | if chr ~= "," then 285 | decode_error(str, i, "expected ']' or ','") 286 | end 287 | end 288 | return res, i 289 | end 290 | 291 | local function parse_object(str, i) 292 | local res = {} 293 | i = i + 1 294 | while 1 do 295 | local key, val 296 | i = next_char(str, i, space_chars, true) 297 | -- Empty / end of object? 298 | if str:sub(i, i) == "}" then 299 | i = i + 1 300 | break 301 | end 302 | -- Read key 303 | if str:sub(i, i) ~= '"' then 304 | decode_error(str, i, "expected string for key") 305 | end 306 | key, i = parse(str, i) 307 | -- Read ':' delimiter 308 | i = next_char(str, i, space_chars, true) 309 | if str:sub(i, i) ~= ":" then 310 | decode_error(str, i, "expected ':' after key") 311 | end 312 | i = next_char(str, i + 1, space_chars, true) 313 | -- Read value 314 | val, i = parse(str, i) 315 | -- Set 316 | res[key] = val 317 | -- Next token 318 | i = next_char(str, i, space_chars, true) 319 | local chr = str:sub(i, i) 320 | i = i + 1 321 | if chr == "}" then 322 | break 323 | end 324 | if chr ~= "," then 325 | decode_error(str, i, "expected '}' or ','") 326 | end 327 | end 328 | return res, i 329 | end 330 | 331 | local char_func_map = { 332 | ['"'] = parse_string, 333 | ["0"] = parse_number, 334 | ["1"] = parse_number, 335 | ["2"] = parse_number, 336 | ["3"] = parse_number, 337 | ["4"] = parse_number, 338 | ["5"] = parse_number, 339 | ["6"] = parse_number, 340 | ["7"] = parse_number, 341 | ["8"] = parse_number, 342 | ["9"] = parse_number, 343 | ["-"] = parse_number, 344 | ["t"] = parse_literal, 345 | ["f"] = parse_literal, 346 | ["n"] = parse_literal, 347 | ["["] = parse_array, 348 | ["{"] = parse_object, 349 | } 350 | 351 | parse = function(str, idx) 352 | local chr = str:sub(idx, idx) 353 | local f = char_func_map[chr] 354 | if f then 355 | return f(str, idx) 356 | end 357 | decode_error(str, idx, "unexpected character '" .. chr .. "'") 358 | end 359 | 360 | function json.decode(str) 361 | if type(str) ~= "string" then 362 | error("expected argument of type string, got " .. type(str)) 363 | end 364 | local res, idx = parse(str, next_char(str, 1, space_chars, true)) 365 | idx = next_char(str, idx, space_chars, true) 366 | if idx <= #str then 367 | decode_error(str, idx, "trailing garbage") 368 | end 369 | return res 370 | end 371 | 372 | return json 373 | -- EOF ------------------------------------------------------------------------ 374 | -------------------------------------------------------------------------------- /modules/lockscreen/init.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gfs = require("gears.filesystem") 3 | 4 | local lock_screen = {} 5 | 6 | local lua_pam_path = gfs.get_configuration_dir() .. "modules/lockscren/lib/liblua_pam.so" 7 | 8 | -- Initialize authentication method based on whether lua-pam has been 9 | -- installed or not 10 | awful.spawn.easy_async_with_shell("stat " .. lua_pam_path .. " >/dev/null 2>&1", function(_, __, ___, exitcode) 11 | if exitcode == 0 then 12 | local pam = require("liblua_pam") 13 | -- lua-pam was installed. 14 | -- Authenticate with PAM 15 | -- TODO: setup lib-pam 16 | lock_screen.authenticate = function(password) 17 | return pam.auth_current_user(password) 18 | end 19 | else 20 | -- lua-pam was NOT installed. 21 | -- Authenticate with user.lock_screen_custom_password 22 | lock_screen.authenticate = function(password) 23 | return password == user.lock_screen_custom_password 24 | end 25 | end 26 | 27 | -- Load the lock_screen element 28 | require("modules.lockscreen.lockscreen") 29 | end) 30 | 31 | return lock_screen 32 | -- EOF ------------------------------------------------------------------------ 33 | -------------------------------------------------------------------------------- /modules/lockscreen/lib/liblua_pam.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobsenpai/sugoi/6514448d5179bbebe0853a7e7c774790a9c39858/modules/lockscreen/lib/liblua_pam.so -------------------------------------------------------------------------------- /modules/lockscreen/lockscreen.lua: -------------------------------------------------------------------------------- 1 | local gears = require("gears") 2 | local awful = require("awful") 3 | local beautiful = require("beautiful") 4 | local xresources = require("beautiful.xresources") 5 | local dpi = xresources.apply_dpi 6 | local wibox = require("wibox") 7 | local lock_screen = require("modules.lockscreen") 8 | 9 | --- Word Clock Lock Screen 10 | --- ~~~~~~~~~~~~~~~~~~~~~~ 11 | 12 | local lock_screen_symbol = "" 13 | local lock_screen_fail_symbol = "" 14 | local lock_animation_icon = wibox.widget({ 15 | --- Set forced size to prevent flickering when the icon rotates 16 | forced_height = dpi(80), 17 | forced_width = dpi(80), 18 | font = beautiful.icon_font_name .. "40", 19 | align = "center", 20 | valign = "center", 21 | widget = wibox.widget.textbox(lock_screen_symbol), 22 | }) 23 | 24 | local some_textbox = wibox.widget.textbox() 25 | 26 | -- Create the lock screen wibox 27 | -- Use widgets::splash::visibility or 28 | -- Set the type to "splash" and set all "splash" windows to be blurred in your 29 | -- compositor configuration file 30 | lock_screen_box = wibox({ visible = false, ontop = true, type = "splash", screen = screen.primary }) 31 | awful.placement.maximize(lock_screen_box) 32 | 33 | lock_screen_box.bg = beautiful.transparent 34 | lock_screen_box.fg = beautiful.fg_normal 35 | 36 | --- Add lockscreen to each screen 37 | awful.screen.connect_for_each_screen(function(s) 38 | if s == screen.primary then 39 | s.mylockscreen = lock_screen_box 40 | else 41 | s.mylockscreen = wibox({ 42 | visible = false, 43 | ontop = true, 44 | type = "splash", 45 | screen = s, 46 | }) 47 | awful.placement.maximize(s.mylockscreen) 48 | s.mylockscreen.bg = beautiful.bg_normal 49 | end 50 | end) 51 | 52 | local function set_visibility(v) 53 | for s in screen do 54 | s.mylockscreen.visible = v 55 | end 56 | end 57 | 58 | --- Word Clock 59 | --- ~~~~~~~~~ 60 | local char = 61 | "I T L I S A S A M P M A C Q U A R T E R D C T W E N T Y F I V E X H A L F S T E N F T O P A S T E R U N I N E O N E S I X T H R E E F O U R F I V E T W O E I G H T E L E V E N S E V E N T W E L V E T E N S E O C L O C K" 62 | 63 | local pos_map = { 64 | ["it"] = { 1, 2 }, 65 | ["is"] = { 4, 5 }, 66 | ["a"] = { 12, 12 }, 67 | ["quarter"] = { 14, 20 }, 68 | ["twenty"] = { 23, 28 }, 69 | ["five"] = { 29, 32 }, 70 | ["half"] = { 34, 37 }, 71 | ["ten"] = { 39, 41 }, 72 | ["past"] = { 45, 48 }, 73 | ["to"] = { 43, 44 }, 74 | ["1"] = { 56, 58 }, 75 | ["2"] = { 75, 77 }, 76 | ["3"] = { 62, 66 }, 77 | ["4"] = { 67, 70 }, 78 | ["5"] = { 71, 74 }, 79 | ["6"] = { 59, 61 }, 80 | ["7"] = { 89, 93 }, 81 | ["8"] = { 78, 82 }, 82 | ["9"] = { 52, 55 }, 83 | ["10"] = { 100, 102 }, 84 | ["11"] = { 83, 88 }, 85 | ["12"] = { 94, 99 }, 86 | ["oclock"] = { 105, 110 }, 87 | } 88 | 89 | local char_map = { 90 | ["it"] = {}, 91 | ["is"] = {}, 92 | ["a"] = {}, 93 | ["quarter"] = {}, 94 | ["twenty"] = {}, 95 | ["five"] = {}, 96 | ["half"] = {}, 97 | ["ten"] = {}, 98 | ["past"] = {}, 99 | ["to"] = {}, 100 | ["1"] = {}, 101 | ["2"] = {}, 102 | ["3"] = {}, 103 | ["4"] = {}, 104 | ["5"] = {}, 105 | ["6"] = {}, 106 | ["7"] = {}, 107 | ["8"] = {}, 108 | ["9"] = {}, 109 | ["10"] = {}, 110 | ["11"] = {}, 111 | ["12"] = {}, 112 | ["oclock"] = {}, 113 | } 114 | 115 | local reset_map = { 4, 12, 14, 23, 29, 34, 39, 43, 45, 52, 56, 59, 62, 67, 71, 75, 78, 83, 89, 94, 100, 105 } 116 | 117 | function split_str(s, delimiter) 118 | result = {} 119 | for match in (s .. delimiter):gmatch("(.-)" .. delimiter) do 120 | table.insert(result, match) 121 | end 122 | 123 | return result 124 | end 125 | 126 | local time_char = split_str(char, " ") 127 | 128 | local time = wibox.widget({ 129 | forced_num_cols = 11, 130 | spacing = dpi(6), 131 | layout = wibox.layout.grid, 132 | }) 133 | 134 | local function create_text_widget(index, w) 135 | local text_widget = wibox.widget({ 136 | id = "t" .. index, 137 | markup = w, 138 | font = beautiful.font_name .. "Bold 24", 139 | align = "center", 140 | valign = "center", 141 | forced_width = dpi(36), 142 | forced_height = dpi(36), 143 | widget = wibox.widget.textbox, 144 | }) 145 | 146 | time:add(text_widget) 147 | 148 | return text_widget 149 | end 150 | 151 | local var_count = 0 152 | for i, m in pairs(time_char) do 153 | local text = "" .. m .. "" 154 | 155 | var_count = var_count + 1 156 | local create_dummy_text = true 157 | 158 | for j, k in pairs(pos_map) do 159 | if i >= pos_map[j][1] and i <= pos_map[j][2] then 160 | char_map[j][var_count] = create_text_widget(i, text) 161 | create_dummy_text = false 162 | end 163 | 164 | for _, n in pairs(reset_map) do 165 | if i == n then 166 | var_count = 1 167 | end 168 | end 169 | end 170 | 171 | if create_dummy_text then 172 | create_text_widget(i, text) 173 | end 174 | end 175 | 176 | local function activate_word(w) 177 | for i, m in pairs(char_map[w]) do 178 | local text = m.text 179 | m.markup = "" .. text .. "" 180 | end 181 | end 182 | 183 | local function deactivate_word(w) 184 | for i, m in pairs(char_map[w]) do 185 | local text = m.text 186 | m.markup = "" .. text .. "" 187 | end 188 | end 189 | 190 | local function reset_time() 191 | for j, k in pairs(char_map) do 192 | deactivate_word(j) 193 | end 194 | 195 | activate_word("it") 196 | activate_word("is") 197 | end 198 | 199 | gears.timer({ 200 | timeout = 1, 201 | call_now = true, 202 | autostart = true, 203 | callback = function() 204 | local time = os.date("%I" .. ":%M") 205 | local h, m = time:match("(%d+):(%d+)") 206 | local min = tonumber(m) 207 | local hour = tonumber(h) 208 | 209 | reset_time() 210 | 211 | if min >= 0 and min <= 2 or min >= 58 and min <= 59 then 212 | activate_word("oclock") 213 | elseif min >= 3 and min <= 7 or min >= 53 and min <= 57 then 214 | activate_word("five") 215 | elseif min >= 8 and min <= 12 or min >= 48 and min <= 52 then 216 | activate_word("ten") 217 | elseif min >= 13 and min <= 17 or min >= 43 and min <= 47 then 218 | activate_word("a") 219 | activate_word("quarter") 220 | elseif min >= 18 and min <= 22 or min >= 38 and min <= 42 then 221 | activate_word("twenty") 222 | elseif min >= 23 and min <= 27 or min >= 33 and min <= 37 then 223 | activate_word("twenty") 224 | activate_word("five") 225 | elseif min >= 28 and min <= 32 then 226 | activate_word("half") 227 | end 228 | 229 | if min >= 3 and min <= 32 then 230 | activate_word("past") 231 | elseif min >= 33 and min <= 57 then 232 | activate_word("to") 233 | end 234 | 235 | local hh 236 | 237 | if min >= 0 and min <= 30 then 238 | hh = hour 239 | else 240 | hh = hour + 1 241 | end 242 | 243 | if hh > 12 then 244 | hh = hh - 12 245 | end 246 | 247 | activate_word(tostring(hh)) 248 | end, 249 | }) 250 | 251 | --- Lock animation 252 | local lock_animation_widget_rotate = wibox.container.rotate() 253 | 254 | local arc = function() 255 | return function(cr, width, height) 256 | gears.shape.arc(cr, width, height, 5, 0, math.pi / 2, true, true) 257 | end 258 | end 259 | 260 | local lock_animation_arc = wibox.widget({ 261 | shape = arc(), 262 | bg = beautiful.transparent, 263 | forced_width = dpi(100), 264 | forced_height = dpi(100), 265 | widget = wibox.container.background, 266 | }) 267 | 268 | local lock_animation = { 269 | { 270 | lock_animation_arc, 271 | widget = lock_animation_widget_rotate, 272 | }, 273 | lock_animation_icon, 274 | layout = wibox.layout.stack, 275 | } 276 | 277 | --- Lock helper functions 278 | local characters_entered = 0 279 | local function reset() 280 | characters_entered = 0 281 | lock_animation_icon.markup = lock_screen_symbol 282 | lock_animation_widget_rotate.direction = "north" 283 | lock_animation_arc.bg = beautiful.transparent 284 | end 285 | 286 | local function fail() 287 | characters_entered = 0 288 | lock_animation_icon.markup = lock_screen_fail_symbol 289 | lock_animation_widget_rotate.direction = "north" 290 | lock_animation_arc.bg = beautiful.transparent 291 | end 292 | 293 | local animation_colors = { 294 | --- Rainbow sequence 295 | beautiful.red, 296 | beautiful.magenta, 297 | beautiful.blue, 298 | beautiful.cyan, 299 | beautiful.green, 300 | beautiful.yellow, 301 | } 302 | 303 | local animation_directions = { "north", "west", "south", "east" } 304 | 305 | --- Function that "animates" every key press 306 | local function key_animation(char_inserted) 307 | local color 308 | local direction = animation_directions[(characters_entered % 4) + 1] 309 | if char_inserted then 310 | color = animation_colors[(characters_entered % 6) + 1] 311 | lock_animation_icon.markup = lock_screen_symbol 312 | else 313 | if characters_entered == 0 then 314 | reset() 315 | else 316 | color = beautiful.white .. "55" 317 | end 318 | end 319 | 320 | lock_animation_arc.bg = color 321 | lock_animation_widget_rotate.direction = direction 322 | end 323 | 324 | --- Get input from user 325 | local function grab_password() 326 | awful.prompt.run({ 327 | hooks = { 328 | --- Custom escape behaviour: Do not cancel input with Escape 329 | --- Instead, this will just clear any input received so far. 330 | { 331 | {}, 332 | "Escape", 333 | function(_) 334 | reset() 335 | grab_password() 336 | end, 337 | }, 338 | --- Fix for Control+Delete crashing the keygrabber 339 | { 340 | { "Control" }, 341 | "Delete", 342 | function() 343 | reset() 344 | grab_password() 345 | end, 346 | }, 347 | }, 348 | keypressed_callback = function(mod, key, cmd) 349 | --- Only count single character keys (thus preventing "Shift", "Escape", etc from triggering the animation) 350 | if #key == 1 then 351 | characters_entered = characters_entered + 1 352 | key_animation(true) 353 | elseif key == "BackSpace" then 354 | if characters_entered > 0 then 355 | characters_entered = characters_entered - 1 356 | end 357 | key_animation(false) 358 | end 359 | end, 360 | exe_callback = function(input) 361 | --- Check input 362 | if lock_screen.authenticate(input) then 363 | --- YAY 364 | reset() 365 | set_visibility(false) 366 | awesome.emit_signal("widgets::splash::visibility", false) 367 | else 368 | --- NAY 369 | fail() 370 | grab_password() 371 | end 372 | end, 373 | textbox = some_textbox, 374 | }) 375 | end 376 | 377 | awesome.connect_signal("summon::lock_screen", function() 378 | set_visibility(true) 379 | grab_password() 380 | awesome.emit_signal("widgets::splash::visibility", true) 381 | end) 382 | 383 | lock_screen_box:setup({ 384 | --- Horizontal centering 385 | nil, 386 | { 387 | --- Vertical centering 388 | nil, 389 | { 390 | wibox.widget({ 391 | forced_height = dpi(20), 392 | layout = wibox.layout.fixed.vertical, 393 | }), 394 | time, 395 | lock_animation, 396 | spacing = dpi(60), 397 | layout = wibox.layout.fixed.vertical, 398 | }, 399 | expand = "none", 400 | layout = wibox.layout.align.vertical, 401 | }, 402 | expand = "none", 403 | layout = wibox.layout.align.horizontal, 404 | }) 405 | -------------------------------------------------------------------------------- /modules/notification_center.lua: -------------------------------------------------------------------------------- 1 | local gears = require("gears") 2 | local awful = require("awful") 3 | local wibox = require("wibox") 4 | local naughty = require("naughty") 5 | local beautiful = require("beautiful") 6 | local helpers = require("helpers") 7 | 8 | -- Notification list 9 | local label = wibox.widget({ 10 | text = "Notifications", 11 | align = "center", 12 | widget = wibox.widget.textbox, 13 | }) 14 | 15 | local notifs_clear = wibox.widget({ 16 | markup = " 󰅙 ", 17 | align = "center", 18 | valign = "center", 19 | widget = wibox.widget.textbox, 20 | }) 21 | 22 | notifs_clear:buttons(gears.table.join(awful.button({}, 1, function() 23 | _G.notif_center_reset_notifs_container() 24 | end))) 25 | 26 | local notifs_empty = wibox.widget({ 27 | forced_height = dpi(300), 28 | widget = wibox.container.background, 29 | { 30 | layout = wibox.layout.flex.vertical, 31 | { 32 | markup = "No notifications", 33 | font = beautiful.font, 34 | align = "center", 35 | valign = "center", 36 | widget = wibox.widget.textbox, 37 | }, 38 | }, 39 | }) 40 | 41 | local notifs_container = wibox.widget({ 42 | forced_width = dpi(240), 43 | forced_height = dpi(715), 44 | layout = wibox.layout.fixed.vertical, 45 | spacing = dpi(10), 46 | spacing_widget = { 47 | top = dpi(2), 48 | bottom = dpi(2), 49 | left = dpi(6), 50 | right = dpi(6), 51 | widget = wibox.container.margin, 52 | { 53 | widget = wibox.container.background, 54 | }, 55 | }, 56 | }) 57 | 58 | local remove_notifs_empty = true 59 | 60 | notif_center_reset_notifs_container = function() 61 | notifs_container:reset(notifs_container) 62 | notifs_container:insert(1, notifs_empty) 63 | remove_notifs_empty = true 64 | end 65 | 66 | notif_center_remove_notif = function(box) 67 | notifs_container:remove_widgets(box) 68 | 69 | if #notifs_container.children == 0 then 70 | notifs_container:insert(1, notifs_empty) 71 | remove_notifs_empty = true 72 | end 73 | end 74 | 75 | local create_notif = function(icon, n, width) 76 | local time = os.date("%H:%M:%S") 77 | 78 | local icon_widget = wibox.widget({ 79 | widget = wibox.container.constraint, 80 | { 81 | widget = wibox.container.margin, 82 | margins = dpi(20), 83 | { 84 | widget = wibox.widget.imagebox, 85 | image = icon, 86 | clip_shape = gears.shape.circle, 87 | halign = "center", 88 | valign = "center", 89 | }, 90 | }, 91 | }) 92 | 93 | local title_widget = wibox.widget({ 94 | widget = wibox.container.scroll.horizontal, 95 | step_function = wibox.container.scroll.step_functions.waiting_nonlinear_back_and_forth, 96 | speed = 50, 97 | forced_width = dpi(200), 98 | { 99 | widget = wibox.widget.textbox, 100 | text = n.title, 101 | align = "left", 102 | forced_width = dpi(200), 103 | }, 104 | }) 105 | 106 | local time_widget = wibox.widget({ 107 | widget = wibox.container.background, 108 | bg = beautiful.fg_normal, 109 | fg = beautiful.bg_normal, 110 | { 111 | widget = wibox.container.margin, 112 | margins = { left = dpi(10), right = dpi(10), top = dpi(4), bottom = dpi(4) }, 113 | { 114 | widget = wibox.widget.textbox, 115 | text = time, 116 | align = "right", 117 | valign = "bottom", 118 | }, 119 | }, 120 | }) 121 | 122 | local text_notif = wibox.widget({ 123 | markup = n.message, 124 | align = "left", 125 | forced_width = dpi(165), 126 | widget = wibox.widget.textbox, 127 | }) 128 | 129 | local box = wibox.widget({ 130 | widget = wibox.container.background, 131 | forced_height = dpi(120), 132 | bg = beautiful.black_alt, 133 | { 134 | layout = wibox.layout.align.horizontal, 135 | icon_widget, 136 | { 137 | widget = wibox.container.margin, 138 | margins = dpi(10), 139 | { 140 | layout = wibox.layout.align.vertical, 141 | expand = "none", 142 | { 143 | layout = wibox.layout.fixed.vertical, 144 | spacing = dpi(10), 145 | { 146 | layout = wibox.layout.align.horizontal, 147 | expand = "none", 148 | title_widget, 149 | nil, 150 | time_widget, 151 | }, 152 | text_notif, 153 | }, 154 | }, 155 | }, 156 | }, 157 | }) 158 | 159 | box:buttons(gears.table.join(awful.button({}, 1, function() 160 | _G.notif_center_remove_notif(box) 161 | end))) 162 | 163 | return box 164 | end 165 | 166 | notifs_container:buttons(gears.table.join( 167 | awful.button({}, 4, nil, function() 168 | if #notifs_container.children == 1 then 169 | return 170 | end 171 | notifs_container:insert(1, notifs_container.children[#notifs_container.children]) 172 | notifs_container:remove(#notifs_container.children) 173 | end), 174 | 175 | awful.button({}, 5, nil, function() 176 | if #notifs_container.children == 1 then 177 | return 178 | end 179 | notifs_container:insert(#notifs_container.children + 1, notifs_container.children[1]) 180 | notifs_container:remove(1) 181 | end) 182 | )) 183 | 184 | notifs_container:insert(1, notifs_empty) 185 | 186 | naughty.connect_signal("request::display", function(n) 187 | if #notifs_container.children == 1 and remove_notifs_empty then 188 | notifs_container:reset(notifs_container) 189 | remove_notifs_empty = false 190 | end 191 | 192 | local appicon = n.icon or n.app_icon 193 | if not appicon then 194 | appicon = beautiful.awesome_icon 195 | end 196 | 197 | notifs_container:insert(1, create_notif(appicon, n, width)) 198 | end) 199 | 200 | local notifs = wibox.widget({ 201 | spacing = dpi(10), 202 | layout = wibox.layout.fixed.vertical, 203 | { 204 | widget = wibox.container.margin, 205 | margins = dpi(10), 206 | { 207 | layout = wibox.layout.align.horizontal, 208 | label, 209 | nil, 210 | notifs_clear, 211 | }, 212 | }, 213 | notifs_container, 214 | }) 215 | 216 | -- Main window 217 | local main = wibox.widget({ 218 | widget = wibox.container.background, 219 | bg = beautiful.bg_normal, 220 | { 221 | widget = wibox.container.margin, 222 | margins = dpi(10), 223 | { 224 | layout = wibox.layout.fixed.vertical, 225 | fill_space = true, 226 | spacing = dpi(10), 227 | notifs, 228 | }, 229 | }, 230 | }) 231 | 232 | local notif_center = awful.popup({ 233 | shape = helpers.rrect(beautiful.border_radius), 234 | visible = false, 235 | ontop = true, 236 | border_width = beautiful.border_width, 237 | border_color = beautiful.widget_border_color, 238 | minimum_height = dpi(585), 239 | maximum_height = dpi(585), 240 | minimum_width = dpi(500), 241 | maximum_width = dpi(500), 242 | placement = function(d) 243 | awful.placement.bottom_right(d, { honor_workarea = true, margins = dpi(5) + beautiful.border_width * 2 }) 244 | end, 245 | widget = main, 246 | }) 247 | 248 | -- Summon functions 249 | awesome.connect_signal("summon::notif_center", function() 250 | notif_center.visible = not notif_center.visible 251 | end) 252 | 253 | -- Hide on click on other windows 254 | client.connect_signal("button::press", function() 255 | notif_center.visible = false 256 | end) 257 | 258 | awesome.connect_signal("summon::dismiss", function() 259 | naughty.destroy_all_notifications() 260 | notif_center.visible = false 261 | end) 262 | -------------------------------------------------------------------------------- /modules/window_switcher.lua: -------------------------------------------------------------------------------- 1 | local awful = require("awful") 2 | local gears = require("gears") 3 | local wibox = require("wibox") 4 | local beautiful = require("beautiful") 5 | local dpi = beautiful.xresources.apply_dpi 6 | 7 | local window_switcher_first_client -- The client that was focused when the window_switcher was activated 8 | local window_switcher_minimized_clients = {} -- The clients that were minimized when the window switcher was activated 9 | local window_switcher_grabber 10 | 11 | local get_num_clients = function() 12 | local minimized_clients_in_tag = 0 13 | local matcher = function(c) 14 | return awful.rules.match(c, { 15 | minimized = true, 16 | skip_taskbar = false, 17 | hidden = false, 18 | first_tag = awful.screen.focused().selected_tag, 19 | }) 20 | end 21 | for c in awful.client.iterate(matcher) do 22 | minimized_clients_in_tag = minimized_clients_in_tag + 1 23 | end 24 | return minimized_clients_in_tag + #awful.screen.focused().clients 25 | end 26 | 27 | local window_switcher_hide = function(window_switcher_box) 28 | -- Add currently focused client to history 29 | if client.focus then 30 | local window_switcher_last_client = client.focus 31 | awful.client.focus.history.add(window_switcher_last_client) 32 | -- Raise client that was focused originally 33 | -- Then raise last focused client 34 | if window_switcher_first_client and window_switcher_first_client.valid then 35 | window_switcher_first_client:raise() 36 | window_switcher_last_client:raise() 37 | end 38 | end 39 | 40 | -- Minimize originally minimized clients 41 | local s = awful.screen.focused() 42 | for _, c in pairs(window_switcher_minimized_clients) do 43 | if c and c.valid and not (client.focus and client.focus == c) then 44 | c.minimized = true 45 | end 46 | end 47 | -- Reset helper table 48 | window_switcher_minimized_clients = {} 49 | 50 | -- Resume recording focus history 51 | awful.client.focus.history.enable_tracking() 52 | -- Stop and hide window_switcher 53 | awful.keygrabber.stop(window_switcher_grabber) 54 | window_switcher_box.visible = false 55 | window_switcher_box.widget = nil 56 | collectgarbage("collect") 57 | end 58 | 59 | local function draw_widget(mouse_keys) 60 | local tasklist_widget = awful.widget.tasklist({ 61 | screen = awful.screen.focused(), 62 | filter = awful.widget.tasklist.filter.currenttags, 63 | buttons = mouse_keys, 64 | style = { 65 | font = beautiful.font, 66 | bg_normal = beautiful.bg_normal, 67 | bg_focus = beautiful.bg_focus, 68 | fg_normal = beautiful.fg_normal, 69 | fg_focus = beautiful.fg_focus, 70 | shape = gears.shape.rounded_rect, 71 | }, 72 | layout = { 73 | layout = wibox.layout.fixed.horizontal, 74 | }, 75 | widget_template = { 76 | { 77 | { 78 | { 79 | awful.widget.clienticon, 80 | forced_height = dpi(80), 81 | forced_width = dpi(80), 82 | halign = "center", 83 | valign = "center", 84 | widget = wibox.container.place, 85 | }, 86 | { 87 | { 88 | widget = wibox.container.scroll.horizontal, 89 | step_function = wibox.container.scroll.step_functions.waiting_nonlinear_back_and_forth, 90 | fps = 60, 91 | speed = 75, 92 | { 93 | id = "text_role", 94 | widget = wibox.widget.textbox, 95 | }, 96 | }, 97 | halign = "center", 98 | valign = "center", 99 | widget = wibox.container.place, 100 | }, 101 | spacing = dpi(15), 102 | layout = wibox.layout.fixed.vertical, 103 | }, 104 | margins = dpi(15), 105 | widget = wibox.container.margin, 106 | }, 107 | forced_width = dpi(150), 108 | forced_height = dpi(150), 109 | id = "background_role", 110 | widget = wibox.container.background, 111 | }, 112 | }) 113 | 114 | return wibox.widget({ 115 | { 116 | tasklist_widget, 117 | margins = dpi(15), 118 | widget = wibox.container.margin, 119 | }, 120 | bg = beautiful.background, 121 | shape = gears.shape.rounded_rect, 122 | widget = wibox.container.background, 123 | }) 124 | end 125 | 126 | local hide_window_switcher_key = "Escape" 127 | 128 | local select_client_key = 1 129 | local minimize_key = "n" 130 | local unminimize_key = "N" 131 | local kill_client_key = "q" 132 | 133 | local cycle_key = "Tab" 134 | 135 | local previous_key = "Left" 136 | local next_key = "Right" 137 | 138 | local vim_previous_key = "h" 139 | local vim_next_key = "l" 140 | 141 | local scroll_previous_key = 4 142 | local scroll_next_key = 5 143 | 144 | local window_switcher_box = awful.popup({ 145 | -- bg = beautiful.transparent, 146 | visible = false, 147 | ontop = true, 148 | placement = awful.placement.centered, 149 | screen = awful.screen.focused(), 150 | widget = wibox.container.background, -- A dummy widget to make awful.popup not scream 151 | widget = draw_widget(), 152 | }) 153 | 154 | local mouse_keys = gears.table.join( 155 | awful.button({ 156 | modifiers = { "Any" }, 157 | button = select_client_key, 158 | on_press = function(c) 159 | client.focus = c 160 | end, 161 | }), 162 | 163 | awful.button({ 164 | modifiers = { "Any" }, 165 | button = scroll_previous_key, 166 | on_press = function() 167 | awful.client.focus.byidx(-1) 168 | end, 169 | }), 170 | 171 | awful.button({ 172 | modifiers = { "Any" }, 173 | button = scroll_next_key, 174 | on_press = function() 175 | awful.client.focus.byidx(1) 176 | end, 177 | }) 178 | ) 179 | 180 | local keyboard_keys = { 181 | [hide_window_switcher_key] = function() 182 | window_switcher_hide(window_switcher_box) 183 | end, 184 | 185 | [minimize_key] = function() 186 | if client.focus then 187 | client.focus.minimized = true 188 | end 189 | end, 190 | [unminimize_key] = function() 191 | if awful.client.restore() then 192 | client.focus = awful.client.restore() 193 | end 194 | end, 195 | [kill_client_key] = function() 196 | if client.focus then 197 | client.focus:kill() 198 | end 199 | end, 200 | 201 | [cycle_key] = function() 202 | awful.client.focus.byidx(1) 203 | end, 204 | 205 | [previous_key] = function() 206 | awful.client.focus.byidx(1) 207 | end, 208 | [next_key] = function() 209 | awful.client.focus.byidx(-1) 210 | end, 211 | 212 | [vim_previous_key] = function() 213 | awful.client.focus.byidx(1) 214 | end, 215 | [vim_next_key] = function() 216 | awful.client.focus.byidx(-1) 217 | end, 218 | } 219 | 220 | window_switcher_box:connect_signal("property::width", function() 221 | if window_switcher_box.visible and get_num_clients() == 0 then 222 | window_switcher_hide(window_switcher_box) 223 | end 224 | end) 225 | 226 | window_switcher_box:connect_signal("property::height", function() 227 | if window_switcher_box.visible and get_num_clients() == 0 then 228 | window_switcher_hide(window_switcher_box) 229 | end 230 | end) 231 | 232 | awesome.connect_signal("window_switcher::turn_on", function() 233 | local number_of_clients = get_num_clients() 234 | if number_of_clients == 0 then 235 | return 236 | end 237 | 238 | -- Store client that is focused in a variable 239 | window_switcher_first_client = client.focus 240 | 241 | -- Stop recording focus history 242 | awful.client.focus.history.disable_tracking() 243 | 244 | -- Go to previously focused client (in the tag) 245 | awful.client.focus.history.previous() 246 | 247 | -- Track minimized clients 248 | -- Unminimize them 249 | -- Lower them so that they are always below other 250 | -- originally unminimized windows 251 | local clients = awful.screen.focused().selected_tag:clients() 252 | for _, c in pairs(clients) do 253 | if c.minimized then 254 | table.insert(window_switcher_minimized_clients, c) 255 | c.minimized = false 256 | c:lower() 257 | end 258 | end 259 | 260 | -- Start the keygrabber 261 | window_switcher_grabber = awful.keygrabber.run(function(_, key, event) 262 | if event == "release" then 263 | -- Hide if the modifier was released 264 | -- We try to match Super or Alt or Control since we do not know which keybind is 265 | -- used to activate the window switcher (the keybind is set by the user in keys.lua) 266 | if key:match("Super") or key:match("Alt") or key:match("Control") then 267 | window_switcher_hide(window_switcher_box) 268 | end 269 | -- Do nothing 270 | return 271 | end 272 | 273 | -- Run function attached to key, if it exists 274 | if keyboard_keys[key] then 275 | keyboard_keys[key]() 276 | end 277 | end) 278 | 279 | window_switcher_box.widget = draw_widget(mouse_keys) 280 | window_switcher_box.visible = true 281 | end) 282 | -------------------------------------------------------------------------------- /rc.lua: -------------------------------------------------------------------------------- 1 | -- ░█▄█░█▀█░█▀▄░█▀▀░█▀▀░█▀█░█▀█░█▀█░▀█▀░▀░█▀▀░░░█▀█░█░█░█▀▀░█▀▀░█▀█░█▄█░█▀▀ 2 | -- ░█░█░█░█░█▀▄░▀▀█░█▀▀░█░█░█▀█░█▀▀░░█░░░░▀▀█░░░█▀█░█▄█░█▀▀░▀▀█░█░█░█░█░█▀▀ 3 | -- ░▀░▀░▀▀▀░▀▀░░▀▀▀░▀▀▀░▀░▀░▀░▀░▀░░░▀▀▀░░░▀▀▀░░░▀░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀▀▀ 4 | 5 | -- >> The file that binds everything together. 6 | local awesome = awesome 7 | local client = client 8 | local screen = screen 9 | local decorations = {} 10 | 11 | -- User variables and preferences 12 | -- =================================================================== 13 | user = { 14 | terminal = "wezterm start --always-new-process", 15 | floating_terminal = "wezterm start --class floating_terminal", 16 | scratchpad_terminal = "wezterm start --class scratchpad", 17 | editor = "wezterm start --class editor -e hx", 18 | browser = "firefox", 19 | file_manager = "pcmanfm", 20 | term_filemanager = "wezterm start --class term_filemanager -e yazi", 21 | visual_editor = "", 22 | openweathermap_key = "d1b3b6a81db867259446b0863d5f9108", 23 | openweathermap_city_id = { 24 | "25.6", -- lat 25 | "85.1167", -- lon 26 | }, 27 | openweathermap_weather_units = "metric", 28 | lock_screen_custom_password = "awesome", 29 | app_launcher = "rofi -matching fuzzy -show drun", 30 | } 31 | 32 | -- Initialization 33 | -- =================================================================== 34 | local beautiful = require("beautiful") 35 | -- Make dpi function global 36 | dpi = beautiful.xresources.apply_dpi 37 | 38 | -- Load AwesomeWM libraries 39 | local gears = require("gears") 40 | local gfs = require("gears.filesystem") 41 | local awful = require("awful") 42 | require("awful.autofocus") 43 | -- Default notification library 44 | local naughty = require("naughty") 45 | 46 | -- Load theme 47 | beautiful.init(gfs.get_configuration_dir() .. "themes/gruva/" .. "theme.lua") 48 | 49 | -- Error handling 50 | -- =================================================================== 51 | naughty.connect_signal("request::display_error", function(message, startup) 52 | naughty.notification({ 53 | urgency = "critical", 54 | title = "Oops, an error happened" .. (startup and " during startup!" or "!"), 55 | message = message, 56 | }) 57 | end) 58 | 59 | -- Features 60 | -- =================================================================== 61 | -- Load helper functions 62 | local helpers = require("helpers") 63 | -- Apps 64 | apps = { 65 | -- TODO: switchtotag not working? 66 | browser = function() 67 | awful.spawn(user.browser, { switchtotag = true }) 68 | end, 69 | file_manager = function() 70 | awful.spawn(user.file_manager, { floating = true }) 71 | end, 72 | term_filemanager = function() 73 | awful.spawn(user.term_filemanager, { floating = true }) 74 | end, 75 | editor = function() 76 | helpers.run_or_raise({ instance = "editor" }, false, user.editor, { switchtotag = true }) 77 | end, 78 | visual_editor = function() 79 | helpers.run_or_raise({ class = "" }, false, user.visual_editor, { switchtotag = true }) 80 | end, 81 | volume = function() 82 | helpers.run_or_raise({ class = "Pavucontrol" }, true, "pavucontrol") 83 | end, 84 | process_monitor = function() 85 | helpers.run_or_raise( 86 | { instance = "bottom" }, 87 | false, 88 | user.terminal .. " --class bottom -e btm", 89 | { switchtotag = true } 90 | ) 91 | end, 92 | screenshot_full = function() 93 | awful.spawn.with_shell("maim -s | swappy -f -") 94 | end, 95 | screenshot_clipboard = function() 96 | awful.spawn.with_shell("maim -s | xclip -selection clipboard -t image/png") 97 | end, 98 | clipboard = function() 99 | awful.spawn.with_shell("clipmenu") 100 | end, 101 | emoji_picker = function() 102 | awful.spawn("rofi -show emoji") 103 | end, 104 | } 105 | 106 | -- Confuguration folder 107 | require("themes/gruva") 108 | 109 | -- Keybinds and mousebinds 110 | local keys = require("keys") 111 | 112 | -- Lock screen 113 | -- Make sure to install lua-pam as described in the README or configure your 114 | -- custom password in the 'user' section above 115 | require("modules.lockscreen") 116 | -- Window switcher 117 | require("modules.window_switcher") 118 | -- Notification center 119 | require("modules.notification_center") 120 | 121 | -- Daemons 122 | -- Most widgets that display system/external info depend on evil. 123 | -- Make sure to initialize it last in order to allow all widgets to connect to 124 | -- their needed evil signals. 125 | require("evil") 126 | 127 | -- Get screen geometry 128 | -- I am using a single screen setup and I assume that screen geometry will not 129 | -- change during the session. 130 | local screen_width = awful.screen.focused().geometry.width 131 | local screen_height = awful.screen.focused().geometry.height 132 | 133 | -- Layouts 134 | -- =================================================================== 135 | -- Table of layouts to cover with awful.layout.inc, order matters. 136 | awful.layout.layouts = { 137 | awful.layout.suit.tile, 138 | awful.layout.suit.floating, 139 | awful.layout.suit.max, 140 | --awful.layout.suit.spiral, 141 | --awful.layout.suit.spiral.dwindle, 142 | --awful.layout.suit.tile.top, 143 | --awful.layout.suit.fair, 144 | --awful.layout.suit.fair.horizontal, 145 | --awful.layout.suit.tile.left, 146 | --awful.layout.suit.tile.bottom, 147 | --awful.layout.suit.max.fullscreen, 148 | --awful.layout.suit.corner.nw, 149 | --awful.layout.suit.magnifier, 150 | --awful.layout.suit.corner.ne, 151 | --awful.layout.suit.corner.sw, 152 | --awful.layout.suit.corner.se, 153 | } 154 | 155 | -- Wallpaper 156 | -- =================================================================== 157 | local function set_wallpaper(s) 158 | if beautiful.wallpaper then 159 | local wallpaper = beautiful.wallpaper 160 | -- If wallpaper is a function, call it with the screen 161 | if type(wallpaper) == "function" then 162 | wallpaper = wallpaper(s) 163 | end 164 | 165 | -- >> Method 1: Built in wallpaper function 166 | gears.wallpaper.maximized(wallpaper, s, true) 167 | 168 | -- >> Method 2: Set theme's wallpaper with feh 169 | -- awful.spawn.with_shell("feh --bg-fill " .. wallpaper) 170 | 171 | -- >> Method 3: Set last wallpaper with feh 172 | -- awful.spawn.with_shell(os.getenv("HOME") .. "/.fehbg") 173 | end 174 | end 175 | 176 | -- Set wallpaper 177 | awful.screen.connect_for_each_screen(function(s) 178 | set_wallpaper(s) 179 | end) 180 | 181 | -- Re-set wallpaper when a screen's geometry changes (e.g. different resolution) 182 | screen.connect_signal("property::geometry", set_wallpaper) 183 | 184 | -- Tags 185 | -- =================================================================== 186 | awful.screen.connect_for_each_screen(function(s) 187 | -- Each screen has its own tag table. 188 | local l = awful.layout.suit -- Alias to save time :) 189 | -- Tag layouts 190 | local layouts = { 191 | l.max, 192 | l.max, 193 | l.max, 194 | l.max, 195 | l.max, 196 | l.max, 197 | l.tile, 198 | l.tile, 199 | l.tile, 200 | l.tile, 201 | } 202 | 203 | -- Tag names 204 | local tagnames = beautiful.tagnames or { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" } 205 | -- Create all tags at once (without seperate configuration for each tag) 206 | awful.tag(tagnames, s, layouts) 207 | 208 | -- Create tags with seperate configuration for each tag 209 | -- awful.tag.add(tagnames[1], { 210 | -- layout = layouts[1], 211 | -- screen = s, 212 | -- master_width_factor = 0.6, 213 | -- selected = true, 214 | -- }) 215 | -- ... 216 | end) 217 | 218 | -- Determines how floating clients should be placed 219 | local floating_client_placement = function(c) 220 | -- If the layout is floating or there are no other visible 221 | -- clients, center client 222 | if awful.layout.get(mouse.screen) ~= awful.layout.suit.floating or #mouse.screen.clients == 1 then 223 | return awful.placement.centered(c, { honor_padding = true, honor_workarea = true }) 224 | end 225 | 226 | -- Else use this placement 227 | local p = awful.placement.no_overlap + awful.placement.no_offscreen 228 | return p(c, { honor_padding = true, honor_workarea = true, margins = beautiful.useless_gap * 2 }) 229 | end 230 | 231 | local centered_client_placement = function(c) 232 | return gears.timer.delayed_call(function() 233 | awful.placement.centered(c, { honor_padding = true, honor_workarea = true }) 234 | end) 235 | end 236 | 237 | -- Rules 238 | -- =================================================================== 239 | -- Rules to apply to new clients (through the "manage" signal). 240 | awful.rules.rules = { 241 | { 242 | -- All clients will match this rule. 243 | rule = {}, 244 | properties = { 245 | border_width = beautiful.border_width, 246 | border_color = beautiful.border_normal, 247 | focus = awful.client.focus.filter, 248 | raise = true, 249 | keys = keys.clientkeys, 250 | buttons = keys.clientbuttons, 251 | screen = awful.screen.focused, 252 | size_hints_honor = false, 253 | honor_workarea = true, 254 | honor_padding = true, 255 | maximized = false, 256 | titlebars_enabled = beautiful.titlebars_enabled, 257 | maximized_horizontal = false, 258 | maximized_vertical = false, 259 | placement = floating_client_placement, 260 | }, 261 | }, 262 | 263 | -- Floating clients 264 | { 265 | rule_any = { 266 | instance = { 267 | "floating_terminal", 268 | "Devtools", -- Firefox devtools 269 | }, 270 | class = { 271 | "Gpick", 272 | "Lxappearance", 273 | "Nm-connection-editor", 274 | "File-roller", 275 | "fst", 276 | "Nvidia-settings", 277 | }, 278 | name = { 279 | "Event Tester", -- xev 280 | "MetaMask Notification", 281 | "Picture in picture", 282 | }, 283 | role = { 284 | "AlarmWindow", 285 | "pop-up", 286 | "GtkFileChooserDialog", 287 | "conversation", 288 | }, 289 | type = { 290 | "dialog", 291 | }, 292 | }, 293 | properties = { floating = true }, 294 | }, 295 | 296 | -- Fullscreen clients 297 | { 298 | rule_any = { 299 | class = { 300 | "dota2", 301 | "dontstarve_steam", 302 | }, 303 | instance = { 304 | "synthetik.exe", 305 | }, 306 | }, 307 | properties = { fullscreen = true }, 308 | }, 309 | 310 | -- Centered clients 311 | { 312 | rule_any = { 313 | type = { 314 | "dialog", 315 | }, 316 | class = { 317 | "Steam", 318 | "discord", 319 | "music", 320 | "scratchpad", 321 | }, 322 | instance = { 323 | "music", 324 | "scratchpad", 325 | }, 326 | role = { 327 | "GtkFileChooserDialog", 328 | "conversation", 329 | }, 330 | }, 331 | properties = { placement = centered_client_placement }, 332 | }, 333 | 334 | -- Titlebars ON (explicitly) 335 | { 336 | rule_any = { 337 | type = { 338 | "dialog", 339 | }, 340 | role = { 341 | "conversation", 342 | }, 343 | }, 344 | callback = function(c) 345 | decorations.show(c) 346 | end, 347 | }, 348 | 349 | -- "Needy": Clients that steal focus when they are urgent 350 | { 351 | rule_any = { 352 | class = { 353 | "TelegramDesktop", 354 | "firefox", 355 | "Nightly", 356 | }, 357 | type = { 358 | "dialog", 359 | }, 360 | }, 361 | callback = function(c) 362 | c:connect_signal("property::urgent", function() 363 | if c.urgent then 364 | c:jump_to() 365 | end 366 | end) 367 | end, 368 | }, 369 | 370 | -- Fixed terminal geometry for floating terminals 371 | { 372 | rule_any = { 373 | class = { 374 | "Alacritty", 375 | "Termite", 376 | "mpvtube", 377 | "kitty", 378 | "st-256color", 379 | "st", 380 | "URxvt", 381 | }, 382 | }, 383 | properties = { width = screen_width * 0.45, height = screen_height * 0.5 }, 384 | }, 385 | 386 | -- File chooser dialog 387 | { 388 | rule_any = { role = { "GtkFileChooserDialog" } }, 389 | properties = { floating = true, width = screen_width * 0.55, height = screen_height * 0.65 }, 390 | }, 391 | 392 | -- Pavucontrol 393 | { 394 | rule_any = { class = { "Pavucontrol" } }, 395 | properties = { floating = true, width = screen_width * 0.45, height = screen_height * 0.8 }, 396 | }, 397 | 398 | -- Galculator 399 | { 400 | rule_any = { class = { "Galculator" } }, 401 | except_any = { type = { "dialog" } }, 402 | properties = { floating = true, width = screen_width * 0.2, height = screen_height * 0.4 }, 403 | }, 404 | 405 | -- File managers 406 | { 407 | rule_any = { 408 | class = { 409 | "Pcmanfm", 410 | "Nemo", 411 | "Thunar", 412 | }, 413 | }, 414 | except_any = { 415 | type = { "dialog" }, 416 | }, 417 | properties = { floating = true, width = screen_width * 0.45, height = screen_height * 0.55 }, 418 | }, 419 | 420 | -- Scratchpad 421 | { 422 | rule_any = { 423 | instance = { 424 | "scratchpad", 425 | "markdown_input", 426 | }, 427 | class = { 428 | "scratchpad", 429 | "markdown_input", 430 | }, 431 | }, 432 | properties = { 433 | skip_taskbar = false, 434 | floating = true, 435 | ontop = false, 436 | minimized = true, 437 | sticky = false, 438 | width = screen_width * 0.7, 439 | height = screen_height * 0.75, 440 | }, 441 | }, 442 | 443 | -- Music clients (usually a terminal running ncmpcpp) 444 | { 445 | rule_any = { 446 | class = { 447 | "music", 448 | }, 449 | instance = { 450 | "music", 451 | }, 452 | }, 453 | properties = { 454 | floating = true, 455 | width = screen_width * 0.45, 456 | height = screen_height * 0.50, 457 | }, 458 | }, 459 | 460 | -- Image viewers 461 | { 462 | rule_any = { 463 | class = { 464 | "feh", 465 | "Sxiv", 466 | }, 467 | }, 468 | properties = { 469 | floating = true, 470 | width = screen_width * 0.7, 471 | height = screen_height * 0.75, 472 | }, 473 | callback = function(c) 474 | awful.placement.centered(c, { honor_padding = true, honor_workarea = true }) 475 | end, 476 | }, 477 | 478 | -- Steam guard 479 | { 480 | rule = { name = "Steam Guard - Computer Authorization Required" }, 481 | properties = { floating = true }, 482 | -- Such a stubborn window, centering it does not work 483 | -- callback = function(c) 484 | -- gears.timer.delayed_call(function() 485 | -- awful.placement.centered(c,{honor_padding = true, honor_workarea=true}) 486 | -- end) 487 | -- end 488 | }, 489 | 490 | -- MPV 491 | { 492 | rule = { class = "mpv" }, 493 | properties = {}, 494 | callback = function(c) 495 | -- Make it floating, ontop and move it out of the way if the current tag is maximized 496 | if awful.layout.get(awful.screen.focused()) == awful.layout.suit.max then 497 | c.floating = true 498 | c.ontop = true 499 | c.width = screen_width * 0.30 500 | c.height = screen_height * 0.35 501 | awful.placement.bottom_right(c, { 502 | honor_padding = true, 503 | honor_workarea = true, 504 | margins = { bottom = beautiful.useless_gap * 2, right = beautiful.useless_gap * 2 }, 505 | }) 506 | end 507 | 508 | -- Restore `ontop` after fullscreen is disabled 509 | -- Sorta tries to fix: https://github.com/awesomeWM/awesome/issues/667 510 | c:connect_signal("property::fullscreen", function() 511 | if not c.fullscreen then 512 | c.ontop = true 513 | end 514 | end) 515 | end, 516 | }, 517 | 518 | -- Start application on specific workspace 519 | -- =================================================================== 520 | -- Browsing 521 | { 522 | rule_any = { 523 | class = { 524 | "firefox", 525 | "Nightly", 526 | "Vivaldi-stable", 527 | }, 528 | }, 529 | except_any = { 530 | role = { "GtkFileChooserDialog" }, 531 | instance = { "Toolkit" }, 532 | type = { "dialog" }, 533 | }, 534 | properties = { screen = 1, tag = awful.screen.focused().tags[1] }, 535 | }, 536 | 537 | -- Editing 538 | { 539 | rule_any = { 540 | class = { 541 | "^editor$", 542 | "VSCodium", 543 | }, 544 | }, 545 | properties = { screen = 1, tag = awful.screen.focused().tags[2] }, 546 | }, 547 | 548 | -- Games 549 | { 550 | rule_any = { 551 | class = { 552 | "Wine", 553 | }, 554 | instance = {}, 555 | }, 556 | properties = { screen = 1, tag = awful.screen.focused().tags[3] }, 557 | }, 558 | 559 | -- Chatting 560 | { 561 | rule_any = { 562 | class = { 563 | "discord", 564 | "Signal", 565 | "Slack", 566 | "zoom", 567 | }, 568 | }, 569 | properties = { screen = 1, tag = awful.screen.focused().tags[4] }, 570 | }, 571 | 572 | -- System monitoring 573 | { 574 | rule_any = { 575 | class = { 576 | "htop", 577 | "bottom", 578 | }, 579 | instance = { 580 | "htop", 581 | "bottom", 582 | }, 583 | }, 584 | properties = { screen = 1, tag = awful.screen.focused().tags[5] }, 585 | }, 586 | 587 | -- Image editing 588 | { 589 | rule_any = { 590 | class = { 591 | "Gimp", 592 | }, 593 | }, 594 | properties = { screen = 1, tag = awful.screen.focused().tags[6] }, 595 | }, 596 | 597 | -- Mail 598 | { 599 | rule_any = { 600 | class = { 601 | "email", 602 | }, 603 | instance = { 604 | "email", 605 | }, 606 | }, 607 | properties = { screen = 1, tag = awful.screen.focused().tags[7] }, 608 | }, 609 | 610 | -- Game clients/launchers 611 | { 612 | rule_any = { 613 | class = { 614 | "Steam", 615 | "battle.net.exe", 616 | "Lutris", 617 | }, 618 | name = { 619 | "Steam", 620 | }, 621 | }, 622 | properties = { screen = 1, tag = awful.screen.focused().tags[8] }, 623 | }, 624 | 625 | -- Miscellaneous 626 | -- All clients that I want out of my way when they are running 627 | { 628 | rule_any = { 629 | class = { 630 | "torrent", 631 | "Transmission", 632 | "Deluge", 633 | "VirtualBox Manager", 634 | "KeePassXC", 635 | }, 636 | instance = { 637 | "torrent", 638 | "qemu", 639 | }, 640 | }, 641 | except_any = { 642 | type = { "dialog" }, 643 | }, 644 | properties = { screen = 1, tag = awful.screen.focused().tags[10] }, 645 | }, 646 | } 647 | 648 | -- Signals 649 | -- =================================================================== 650 | if beautiful.border_width > 0 then 651 | client.connect_signal("focus", function(c) 652 | c.border_color = beautiful.border_focus 653 | end) 654 | client.connect_signal("unfocus", function(c) 655 | c.border_color = beautiful.border_normal 656 | end) 657 | end 658 | 659 | -- Set mouse resize mode (live or after) 660 | awful.mouse.resize.set_mode("live") 661 | 662 | -- Restore geometry for floating clients 663 | -- (for example after swapping from tiling mode to floating mode) 664 | tag.connect_signal("property::layout", function(t) 665 | for k, c in ipairs(t:clients()) do 666 | if awful.layout.get(mouse.screen) == awful.layout.suit.floating then 667 | local cgeo = awful.client.property.get(c, "floating_geometry") 668 | if cgeo then 669 | c:geometry(awful.client.property.get(c, "floating_geometry")) 670 | end 671 | end 672 | end 673 | end) 674 | 675 | client.connect_signal("manage", function(c) 676 | if awful.layout.get(mouse.screen) == awful.layout.suit.floating then 677 | awful.client.property.set(c, "floating_geometry", c:geometry()) 678 | end 679 | end) 680 | 681 | client.connect_signal("property::geometry", function(c) 682 | if awful.layout.get(mouse.screen) == awful.layout.suit.floating then 683 | awful.client.property.set(c, "floating_geometry", c:geometry()) 684 | end 685 | end) 686 | 687 | -- When switching to a tag with urgent clients, raise them. 688 | -- This fixes the issue (visual mismatch) where after switching to 689 | -- a tag which includes an urgent client, the urgent client is 690 | -- unfocused but still covers all other windows (even the currently 691 | -- focused window). 692 | awful.tag.attached_connect_signal(s, "property::selected", function() 693 | local urgent_clients = function(c) 694 | return awful.rules.match(c, { urgent = true }) 695 | end 696 | for c in awful.client.iterate(urgent_clients) do 697 | if c.first_tag == mouse.screen.selected_tag then 698 | client.focus = c 699 | end 700 | end 701 | end) 702 | 703 | -- Enable sloppy focus, so that focus follows mouse. 704 | -- client.connect_signal("mouse::enter", function(c) 705 | -- c:activate { context = "mouse_enter", raise = false } 706 | -- end) 707 | 708 | -- Raise focused clients automatically 709 | client.connect_signal("focus", function(c) 710 | c:raise() 711 | end) 712 | 713 | -- Focus all urgent clients automatically 714 | -- client.connect_signal("property::urgent", function(c) 715 | -- if c.urgent then 716 | -- c.minimized = false 717 | -- c:jump_to() 718 | -- end 719 | -- end) 720 | 721 | -- Disable ontop when the client is not floating, and restore ontop if needed 722 | -- when the client is floating again 723 | -- I never want a non floating client to be ontop. 724 | client.connect_signal("property::floating", function(c) 725 | if c.floating then 726 | if c.restore_ontop then 727 | c.ontop = c.restore_ontop 728 | end 729 | else 730 | c.restore_ontop = c.ontop 731 | c.ontop = false 732 | end 733 | end) 734 | 735 | -- Hide all windows when a splash is shown 736 | awesome.connect_signal("widgets::splash::visibility", function(vis) 737 | local t = screen.primary.selected_tag 738 | if vis then 739 | for idx, c in ipairs(t:clients()) do 740 | c.hidden = true 741 | end 742 | else 743 | for idx, c in ipairs(t:clients()) do 744 | c.hidden = false 745 | end 746 | end 747 | end) 748 | 749 | -- Decorations 750 | -- =================================================================== 751 | local wibox = require("wibox") 752 | -- Apply rounded corners to clients if needed 753 | if beautiful.border_radius and beautiful.border_radius > 0 then 754 | client.connect_signal("manage", function(c, startup) 755 | if not c.fullscreen and not c.maximized then 756 | c.shape = helpers.rrect(beautiful.border_radius) 757 | end 758 | end) 759 | 760 | -- Fullscreen and maximized clients should not have rounded corners 761 | local function no_round_corners(c) 762 | if c.fullscreen or c.maximized then 763 | c.shape = gears.shape.rectangle 764 | else 765 | c.shape = helpers.rrect(beautiful.border_radius) 766 | end 767 | end 768 | 769 | client.connect_signal("property::fullscreen", no_round_corners) 770 | client.connect_signal("property::maximized", no_round_corners) 771 | 772 | beautiful.snap_shape = helpers.rrect(beautiful.border_radius * 2) 773 | else 774 | beautiful.snap_shape = gears.shape.rectangle 775 | end 776 | 777 | function decorations.show(c) 778 | if not c.custom_decoration or not c.custom_decoration[beautiful.titlebar_position] then 779 | awful.titlebar.show(c, beautiful.titlebar_position) 780 | end 781 | end 782 | 783 | -- Dummy titlebar 784 | client.connect_signal("request::titlebars", function(c) 785 | awful.titlebar(c) 786 | end) 787 | 788 | -- Startup apps 789 | -- =================================================================== 790 | -- Spawn once 791 | -- awful.spawn.once({}, false) 792 | -- With shell 793 | -- awful.spawn.with_shell({}, false) 794 | 795 | -- Garbage collection 796 | -- =================================================================== 797 | collectgarbage("setpause", 110) 798 | collectgarbage("setstepmul", 1000) 799 | -------------------------------------------------------------------------------- /themes/gruva/init.lua: -------------------------------------------------------------------------------- 1 | -- ░█▀▀░█▀█░█▀█░█▀▀░▀█▀░█▀▀░█░█░█▀▄░█▀█░▀█▀░▀█▀░█▀█░█▀█ 2 | -- ░█░░░█░█░█░█░█▀▀░░█░░█░█░█░█░█▀▄░█▀█░░█░░░█░░█░█░█░█ 3 | -- ░▀▀▀░▀▀▀░▀░▀░▀░░░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░▀░░▀▀▀░▀▀▀░▀░▀ 4 | 5 | local gears = require("gears") 6 | local awful = require("awful") 7 | local wibox = require("wibox") 8 | local beautiful = require("beautiful") 9 | local naughty = require("naughty") 10 | local menubar = require("menubar") 11 | local keys = require("keys") 12 | local helpers = require("helpers") 13 | local awesome = awesome 14 | local client = client 15 | 16 | -- ░█░█░▀█▀░█▀▄░█▀▀░█▀▀░▀█▀░█▀▀ 17 | -- ░█▄█░░█░░█░█░█░█░█▀▀░░█░░▀▀█ 18 | -- ░▀░▀░▀▀▀░▀▀░░▀▀▀░▀▀▀░░▀░░▀▀▀ 19 | 20 | -- Memory widget 21 | -- =================================================================== 22 | local memory_widget = wibox.widget({ 23 | { 24 | { 25 | { 26 | { 27 | id = "icon", 28 | text = "", 29 | forced_width = dpi(30), 30 | align = "center", 31 | valign = "center", 32 | font = beautiful.wibar_icon_font, 33 | widget = wibox.widget.textbox, 34 | }, 35 | -- fixing font centering 36 | right = dpi(3), 37 | widget = wibox.container.margin, 38 | }, 39 | bg = beautiful.darkblue, 40 | fg = beautiful.light_bg, 41 | widget = wibox.widget.background, 42 | }, 43 | { 44 | { 45 | id = "text", 46 | font = beautiful.wibar_font, 47 | widget = wibox.widget.textbox, 48 | }, 49 | left = dpi(5), 50 | right = dpi(5), 51 | widget = wibox.container.margin, 52 | }, 53 | layout = wibox.layout.fixed.horizontal, 54 | }, 55 | fg = beautiful.light_bg, 56 | bg = beautiful.blue, 57 | widget = wibox.container.background, 58 | }) 59 | 60 | awesome.connect_signal("evil::ram", function(used, total) 61 | memory_widget:get_children_by_id("text")[1].markup = tostring(used) .. " MB" 62 | end) 63 | 64 | -- Clock widget 65 | -- =================================================================== 66 | local clock_widget = wibox.widget({ 67 | { 68 | { 69 | { 70 | { 71 | id = "icon", 72 | text = "󱑆", 73 | forced_width = dpi(30), 74 | align = "center", 75 | valign = "center", 76 | font = beautiful.wibar_icon_font, 77 | widget = wibox.widget.textbox, 78 | }, 79 | -- fixing font centering 80 | right = dpi(3), 81 | widget = wibox.container.margin, 82 | }, 83 | bg = beautiful.darkgreen, 84 | fg = beautiful.light_bg, 85 | widget = wibox.widget.background, 86 | }, 87 | { 88 | { 89 | id = "text", 90 | format = "%a %b %d - %I:%M %p", 91 | font = beautiful.wibar_font, 92 | widget = wibox.widget.textclock, 93 | }, 94 | left = dpi(5), 95 | right = dpi(5), 96 | widget = wibox.container.margin, 97 | }, 98 | layout = wibox.layout.fixed.horizontal, 99 | }, 100 | fg = beautiful.light_bg, 101 | bg = beautiful.green, 102 | widget = wibox.container.background, 103 | }) 104 | 105 | -- CPU widget 106 | -- =================================================================== 107 | local cpu_widget = wibox.widget({ 108 | { 109 | { 110 | { 111 | { 112 | id = "icon", 113 | text = "", 114 | forced_width = dpi(30), 115 | align = "center", 116 | valign = "center", 117 | font = beautiful.wibar_icon_font, 118 | widget = wibox.widget.textbox, 119 | }, 120 | -- fixing font centering 121 | right = dpi(5), 122 | widget = wibox.container.margin, 123 | }, 124 | bg = beautiful.cyan, 125 | fg = beautiful.light_bg, 126 | widget = wibox.widget.background, 127 | }, 128 | { 129 | { 130 | id = "text", 131 | font = beautiful.wibar_font, 132 | widget = wibox.widget.textbox, 133 | }, 134 | left = dpi(5), 135 | right = dpi(5), 136 | widget = wibox.container.margin, 137 | }, 138 | layout = wibox.layout.fixed.horizontal, 139 | }, 140 | fg = beautiful.cyan, 141 | bg = beautiful.light_bg, 142 | widget = wibox.container.background, 143 | }) 144 | 145 | awesome.connect_signal("evil::cpu", function(cpu_idle) 146 | cpu_widget:get_children_by_id("text")[1].markup = tostring(cpu_idle) .. "%" 147 | end) 148 | 149 | -- Weather widget 150 | -- =================================================================== 151 | local weather_widget = wibox.widget({ 152 | { 153 | { 154 | { 155 | id = "icon", 156 | text = "", 157 | forced_width = dpi(30), 158 | align = "center", 159 | valign = "center", 160 | font = beautiful.wibar_icon_font, 161 | widget = wibox.widget.textbox, 162 | }, 163 | bg = beautiful.magenta, 164 | fg = beautiful.light_bg, 165 | widget = wibox.widget.background, 166 | }, 167 | { 168 | { 169 | { 170 | id = "description", 171 | font = beautiful.wibar_font, 172 | widget = wibox.widget.textbox, 173 | }, 174 | nil, 175 | { 176 | id = "temp_current", 177 | align = "right", 178 | font = beautiful.wibar_font, 179 | widget = wibox.widget.textbox, 180 | }, 181 | widget = wibox.layout.align.horizontal, 182 | }, 183 | left = dpi(5), 184 | right = dpi(5), 185 | widget = wibox.container.margin, 186 | }, 187 | layout = wibox.layout.fixed.horizontal, 188 | }, 189 | fg = beautiful.magenta, 190 | bg = beautiful.light_bg, 191 | widget = wibox.container.background, 192 | }) 193 | 194 | awesome.connect_signal("evil::weather", function(result) 195 | weather_widget:get_children_by_id("description")[1].markup = result.current.weather[1].description:gsub( 196 | "^%l", 197 | string.upper 198 | ) .. ", " 199 | weather_widget:get_children_by_id("temp_current")[1].markup = math.floor(result.current.temp) 200 | .. "°C" 201 | end) 202 | 203 | -- Playerctl widget 204 | -- =================================================================== 205 | local playerctl_widget = wibox.widget({ 206 | { 207 | { 208 | { 209 | id = "icon", 210 | text = "󰎈", 211 | forced_width = dpi(30), 212 | align = "center", 213 | valign = "center", 214 | font = beautiful.wibar_icon_font, 215 | widget = wibox.widget.textbox, 216 | }, 217 | bg = beautiful.red, 218 | fg = beautiful.light_bg, 219 | widget = wibox.widget.background, 220 | }, 221 | { 222 | { 223 | id = "text", 224 | text = "Not Playing", 225 | font = beautiful.wibar_font, 226 | align = "center", 227 | forced_width = dpi(100), 228 | widget = wibox.widget.textbox, 229 | }, 230 | left = dpi(10), 231 | right = dpi(10), 232 | widget = wibox.container.margin, 233 | }, 234 | layout = wibox.layout.fixed.horizontal, 235 | }, 236 | fg = beautiful.red, 237 | bg = beautiful.light_bg, 238 | widget = wibox.container.background, 239 | }) 240 | 241 | awesome.connect_signal("evil::playerctl", function(artist, title, status) 242 | playerctl_widget:get_children_by_id("text")[1].markup = title 243 | end) 244 | 245 | playerctl_widget:buttons(gears.table.join( 246 | -- left click = all players 247 | awful.button({}, 1, function() 248 | awful.spawn.with_shell("playerctl play-pause") 249 | end) 250 | -- right click = specific player 251 | -- awful.button({}, 3, function() 252 | -- awful.spawn.with_shell("mpvc toggle") 253 | -- end) 254 | )) 255 | 256 | -- Volume bar widget 257 | -- =================================================================== 258 | local volume_bar = wibox.widget({ 259 | max_value = 100, 260 | value = 70, 261 | forced_width = dpi(100), 262 | shape = helpers.rrect(dpi(6)), 263 | bar_shape = helpers.rrect(dpi(6)), 264 | color = beautiful.bg_focus, 265 | background_color = beautiful.light_bg, 266 | widget = wibox.widget.progressbar, 267 | }) 268 | 269 | awesome.connect_signal("evil::volume", function(volume, muted) 270 | volume_bar.value = volume 271 | 272 | if muted then 273 | volume_bar.color = beautiful.light_bg 274 | else 275 | volume_bar.color = beautiful.bg_focus 276 | end 277 | end) 278 | 279 | -- Battery bar widget 280 | -- =================================================================== 281 | local battery_bar = wibox.widget({ 282 | max_value = 100, 283 | value = 50, 284 | forced_width = dpi(100), 285 | shape = helpers.rrect(dpi(6)), 286 | bar_shape = helpers.rrect(dpi(6)), 287 | color = beautiful.bg_focus, 288 | background_color = beautiful.light_bg, 289 | widget = wibox.widget.progressbar, 290 | }) 291 | 292 | awesome.connect_signal("evil::battery", function(value) 293 | battery_bar.value = value 294 | end) 295 | 296 | -- Osd 297 | -- =================================================================== 298 | local slider = wibox.widget({ 299 | widget = wibox.widget.progressbar, 300 | max_value = 100, 301 | forced_width = dpi(380), 302 | forced_height = dpi(10), 303 | shape = gears.shape.rounded_bar, 304 | bar_shape = gears.shape.rounded_bar, 305 | background_color = beautiful.light_bg, 306 | color = beautiful.bg_focus, 307 | }) 308 | 309 | local icon_widget = wibox.widget({ 310 | widget = wibox.widget.textbox, 311 | font = beautiful.icon_font_name .. "15", 312 | forced_width = dpi(30), 313 | align = "center", 314 | valign = "center", 315 | }) 316 | 317 | local text = wibox.widget({ 318 | widget = wibox.widget.textbox, 319 | halign = "center", 320 | }) 321 | 322 | local info = wibox.widget({ 323 | layout = wibox.layout.fixed.horizontal, 324 | { 325 | widget = wibox.container.margin, 326 | margins = dpi(20), 327 | { 328 | layout = wibox.layout.fixed.horizontal, 329 | fill_space = true, 330 | spacing = dpi(8), 331 | icon_widget, 332 | { 333 | widget = wibox.container.background, 334 | forced_width = dpi(36), 335 | text, 336 | }, 337 | slider, 338 | }, 339 | }, 340 | }) 341 | 342 | local osd = awful.popup({ 343 | shape = helpers.rrect(beautiful.border_radius), 344 | visible = false, 345 | ontop = true, 346 | border_width = beautiful.widget_border_width, 347 | border_color = beautiful.widget_border_color, 348 | minimum_height = dpi(60), 349 | maximum_height = dpi(60), 350 | minimum_width = dpi(290), 351 | maximum_width = dpi(290), 352 | placement = function(c) 353 | awful.placement.bottom(c, { margins = dpi(20) + beautiful.border_width * 2 }) 354 | end, 355 | widget = info, 356 | }) 357 | 358 | -- volume 359 | awesome.connect_signal("evil::volume", function(volume, muted, icon) 360 | slider.value = volume 361 | text.text = volume 362 | icon_widget.text = icon 363 | end) 364 | 365 | -- bright 366 | awesome.connect_signal("evil::brightness", function(value) 367 | slider.value = value 368 | text.text = value 369 | icon_widget.text = "󰃟" 370 | end) 371 | 372 | -- function 373 | local function osd_hide() 374 | osd.visible = false 375 | osd_timer:stop() 376 | end 377 | 378 | local osd_timer = gears.timer({ 379 | timeout = 3, 380 | callback = osd_hide, 381 | }) 382 | 383 | local function osd_toggle() 384 | if not osd.visible then 385 | osd.visible = true 386 | osd_timer:start() 387 | else 388 | osd_timer:again() 389 | end 390 | end 391 | 392 | awesome.connect_signal("summon::osd", function() 393 | osd_toggle() 394 | end) 395 | 396 | -- ░█░█░▀█▀░█▀▄░█▀█░█▀▄ 397 | -- ░█▄█░░█░░█▀▄░█▀█░█▀▄ 398 | -- ░▀░▀░▀▀▀░▀▀░░▀░▀░▀░▀ 399 | -- Create a wibox for each screen and add it 400 | awful.screen.connect_for_each_screen(function(s) 401 | -- Create a promptbox for each screen 402 | s.mypromptbox = awful.widget.prompt({ prompt = " Run: ", fg = beautiful.wibar_fg }) 403 | -- Create an imagebox widget which will contain an icon indicating which layout we're using. 404 | -- We need one layoutbox per screen. 405 | s.mylayoutbox = { 406 | widget = wibox.container.place, 407 | awful.widget.layoutbox({ 408 | screen = s, 409 | resize = true, 410 | forced_width = dpi(15), 411 | forced_height = dpi(15), 412 | widget = wibox.container.place, 413 | -- Add buttons, allowing you to change the layout 414 | buttons = { 415 | awful.button({}, 1, function() 416 | awful.layout.inc(1) 417 | end), 418 | awful.button({}, 3, function() 419 | awful.layout.inc(-1) 420 | end), 421 | awful.button({}, 4, function() 422 | awful.layout.inc(1) 423 | end), 424 | awful.button({}, 5, function() 425 | awful.layout.inc(-1) 426 | end), 427 | }, 428 | }), 429 | } 430 | 431 | -- Create a taglist widget 432 | -- Helper function that updates a taglist item 433 | local update_taglist = function(self, tag, index) 434 | local tagBox = self:get_children_by_id("underline")[1] 435 | local tagName = self:get_children_by_id("index_role")[1] 436 | if tag.selected then 437 | tagName.markup = helpers.colorize_text( 438 | beautiful.taglist_text_focused[index], 439 | beautiful.taglist_text_color_focused[index] 440 | ) 441 | tagBox.bg = beautiful.taglist_text_color_occupied[index] 442 | elseif tag.urgent then 443 | tagName.markup = 444 | helpers.colorize_text(beautiful.taglist_text_urgent[index], beautiful.taglist_text_color_urgent[index]) 445 | tagBox.bg = beautiful.wibar_bg 446 | elseif #tag:clients() > 0 then 447 | tagName.markup = helpers.colorize_text( 448 | beautiful.taglist_text_occupied[index], 449 | beautiful.taglist_text_color_occupied[index] 450 | ) 451 | tagBox.bg = beautiful.wibar_bg 452 | else 453 | tagName.markup = 454 | helpers.colorize_text(beautiful.taglist_text_empty[index], beautiful.taglist_text_color_empty[index]) 455 | tagBox.bg = beautiful.wibar_bg 456 | end 457 | end 458 | 459 | s.mytaglist = awful.widget.taglist({ 460 | screen = s, 461 | filter = awful.widget.taglist.filter.all, 462 | layout = wibox.layout.fixed.horizontal, 463 | buttons = keys.taglist_buttons, 464 | widget_template = { 465 | { 466 | { 467 | layout = wibox.layout.fixed.vertical, 468 | { 469 | { 470 | id = "index_role", 471 | font = beautiful.taglist_text_font, 472 | align = "center", 473 | valign = "center", 474 | forced_width = dpi(24), 475 | widget = wibox.widget.textbox, 476 | }, 477 | top = dpi(0), 478 | right = dpi(4), 479 | bottom = dpi(-1), 480 | widget = wibox.container.margin, 481 | }, 482 | { 483 | { 484 | top = dpi(0), 485 | bottom = dpi(3), 486 | widget = wibox.container.margin, 487 | }, 488 | id = "underline", 489 | bg = beautiful.wibar_bg, 490 | shape = gears.shape.rectangle, 491 | widget = wibox.container.background, 492 | }, 493 | }, 494 | widget = wibox.container.margin, 495 | right = dpi(4), 496 | }, 497 | id = "background_role", 498 | widget = wibox.container.background, 499 | shape = gears.shape.rectangle, 500 | create_callback = update_taglist, 501 | update_callback = update_taglist, 502 | }, 503 | }) 504 | 505 | -- Create a tasklist widget 506 | s.mytasklist = awful.widget.tasklist({ 507 | screen = s, 508 | filter = awful.widget.tasklist.filter.currenttags, 509 | buttons = keys.tasklist_buttons, 510 | style = { 511 | font = beautiful.tasklist_font, 512 | bg = beautiful.tasklist_bg_normal, 513 | }, 514 | layout = { 515 | spacing = dpi(4), 516 | -- layout = wibox.layout.fixed.horizontal 517 | layout = wibox.layout.flex.horizontal, 518 | }, 519 | widget_template = { 520 | { 521 | { 522 | { 523 | { 524 | id = "icon_role", 525 | widget = wibox.widget.imagebox, 526 | }, 527 | margins = dpi(2), 528 | widget = wibox.container.margin, 529 | }, 530 | { 531 | id = "text_role", 532 | widget = wibox.widget.textbox, 533 | }, 534 | layout = wibox.layout.fixed.horizontal, 535 | }, 536 | forced_width = dpi(220), 537 | left = dpi(10), 538 | right = dpi(10), 539 | widget = wibox.container.margin, 540 | }, 541 | -- border_width = dpi(2), 542 | -- shape = gears.shape.rounded_bar, 543 | -- id = "bg_role", 544 | id = "background_role", 545 | widget = wibox.container.background, 546 | }, 547 | }) 548 | 549 | -- Create a system tray widget 550 | s.systray = wibox.widget.systray() 551 | s.systray.visible = true -- can be toggled by a keybind 552 | 553 | -- Create the wibox 554 | s.mywibox = awful.wibar({ 555 | position = beautiful.wibar_position, 556 | screen = s, 557 | type = "dock", 558 | width = beautiful.wibar_width, 559 | height = beautiful.wibar_height, 560 | shape = helpers.rrect(beautiful.wibar_border_radius), 561 | }) 562 | 563 | s.mywibox:setup({ 564 | { 565 | { 566 | s.mytaglist, 567 | s.mytasklist, 568 | align = "left", 569 | spacing = dpi(10), 570 | layout = wibox.layout.fixed.horizontal, 571 | }, 572 | { 573 | s.mypromptbox, 574 | playerctl_widget, 575 | -- volume_bar, 576 | -- battery_bar, 577 | align = "center", 578 | spacing = dpi(10), 579 | layout = wibox.layout.fixed.horizontal, 580 | }, 581 | { 582 | weather_widget, 583 | memory_widget, 584 | cpu_widget, 585 | clock_widget, 586 | s.systray, 587 | s.mylayoutbox, 588 | align = "right", 589 | spacing = dpi(10), 590 | layout = wibox.layout.fixed.horizontal, 591 | }, 592 | expand = "none", 593 | layout = wibox.layout.align.horizontal, 594 | }, 595 | top = dpi(3), 596 | bottom = dpi(3), 597 | left = dpi(3), 598 | right = dpi(3), 599 | widget = wibox.container.margin, 600 | }) 601 | 602 | -- Place bar at the top and add margins 603 | awful.placement.top(s.mywibox, { margins = beautiful.screen_margin * 0 }) -- No margin 604 | end) 605 | 606 | -- ░█▀█░█▀█░▀█▀░▀█▀░█▀█░█▀█░█▀▀ 607 | -- ░█░█░█▀▀░░█░░░█░░█░█░█░█░▀▀█ 608 | -- ░▀▀▀░▀░░░░▀░░▀▀▀░▀▀▀░▀░▀░▀▀▀ 609 | 610 | -- Notification settings 611 | -- =================================================================== 612 | -- Handle notification icon 613 | naughty.connect_signal("request::icon", function(n, context, hints) 614 | -- Handle other contexts here 615 | if context ~= "app_icon" then 616 | n.icon = beautiful.awesome_icon 617 | return 618 | end 619 | 620 | -- Use XDG icon 621 | local path = menubar.utils.lookup_icon(hints.app_icon) or menubar.utils.lookup_icon(hints.app_icon:lower()) 622 | 623 | if path then 624 | n.icon = path 625 | end 626 | end) 627 | 628 | naughty.connect_signal("request::display", function(n) 629 | naughty.layout.box({ 630 | notification = n, 631 | type = "notification", 632 | cursor = "hand1", 633 | shape = beautiful.notification_shape, 634 | maximum_width = beautiful.notification_max_width, 635 | maximum_height = beautiful.notification_max_height, 636 | widget_template = { 637 | widget = wibox.container.constraint, 638 | strategy = "max", 639 | { 640 | widget = naughty.container.background, 641 | id = "background_role", 642 | { 643 | widget = wibox.container.margin, 644 | margins = dpi(10), 645 | { 646 | layout = wibox.layout.fixed.horizontal, 647 | spacing = dpi(20), 648 | fill_space = true, 649 | { 650 | widget = wibox.container.place, 651 | { 652 | widget = wibox.container.background, 653 | shape = gears.shape.circle, 654 | naughty.widget.icon, 655 | }, 656 | }, 657 | { 658 | layout = wibox.layout.fixed.vertical, 659 | spacing = dpi(10), 660 | naughty.widget.title, 661 | naughty.widget.message, 662 | }, 663 | }, 664 | }, 665 | }, 666 | }, 667 | }) 668 | end) 669 | 670 | -- Titlebar 671 | -- =================================================================== 672 | client.connect_signal("request::titlebars", function(c) 673 | local titlebar = awful.titlebar(c, { 674 | position = beautiful.titlebar_position, 675 | size = beautiful.titlebar_size, 676 | }) 677 | 678 | local buttons = gears.table.join( 679 | awful.button({}, 1, function() 680 | client.focus = c 681 | c:raise() 682 | awful.mouse.client.move(c) 683 | end), 684 | awful.button({}, 3, function() 685 | client.focus = c 686 | c:raise() 687 | awful.mouse.client.resize(c) 688 | end) 689 | ) 690 | 691 | titlebar.widget = { 692 | layout = wibox.layout.flex.horizontal, 693 | -- { 694 | -- widget = wibox.container.place, 695 | -- align = "left", 696 | -- { 697 | -- widget = wibox.container.margin, 698 | -- margins = { left = dpi(10), right = dpi(10) top = dpi(8), bottom = dpi(8) }, 699 | -- { 700 | -- layout = wibox.layout.fixed.horizontal, 701 | -- spacing = 8, 702 | -- awful.titlebar.widget.maximizedbutton(c), 703 | -- awful.titlebar.widget.minimizebutton(c), 704 | -- awful.titlebar.widget.closebutton(c), 705 | -- }, 706 | -- }, 707 | -- }, 708 | { 709 | widget = wibox.container.background, 710 | buttons = buttons, 711 | { 712 | widget = wibox.container.margin, 713 | left = dpi(10), 714 | right = dpi(10), 715 | { 716 | widget = wibox.container.constraint, 717 | width = dpi(100), 718 | { 719 | align = beautiful.titlebar_title_align, 720 | widget = awful.titlebar.widget.titlewidget(c), 721 | }, 722 | }, 723 | }, 724 | }, 725 | } 726 | end) 727 | 728 | -- Startup apps 729 | -- =================================================================== 730 | -- awful.spawn.once({},false) 731 | -- With shell 732 | -- awful.spawn.with_shell({}, false) 733 | -- EOF ------------------------------------------------------------------------ 734 | -------------------------------------------------------------------------------- /themes/gruva/theme.lua: -------------------------------------------------------------------------------- 1 | -- ░▀█▀░█░█░█▀▀░█▄█░█▀▀ 2 | -- ░░█░░█▀█░█▀▀░█░█░█▀▀ 3 | -- ░░▀░░▀░▀░▀▀▀░▀░▀░▀▀▀ 4 | 5 | local theme_assets = require("beautiful.theme_assets") 6 | local xresources = require("beautiful.xresources") 7 | local dpi = xresources.apply_dpi 8 | local xrdb = xresources.get_current_theme() 9 | local gears = require("gears") 10 | local gfs = require("gears.filesystem") 11 | local helpers = require("helpers") 12 | local rnotification = require("ruled.notification") 13 | local themes_path = gfs.get_themes_dir() 14 | local theme = {} 15 | 16 | -- Default wallpaper 17 | theme.wallpaper = os.getenv("HOME") .. "/Pictures/wall.png" 18 | 19 | -- ░█▀▀░█▀█░█▀█░▀█▀░█▀▀ 20 | -- ░█▀▀░█░█░█░█░░█░░▀▀█ 21 | -- ░▀░░░▀▀▀░▀░▀░░▀░░▀▀▀ 22 | 23 | theme.font_name = "monospace " 24 | theme.font = theme.font_name .. "10" 25 | theme.wibar_font = theme.font_name .. "Bold 10" 26 | theme.icon_font_name = "monospace " 27 | theme.wibar_icon_font = theme.font_name .. "12" 28 | 29 | -- ░█▀▀░█▀█░█░░░█▀█░█▀▄░█▀▀ 30 | -- ░█░░░█░█░█░░░█░█░█▀▄░▀▀█ 31 | -- ░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀▀▀ 32 | 33 | -- Colors 34 | theme.black = xrdb.color0 35 | theme.black_alt = xrdb.color8 36 | theme.red = xrdb.color1 37 | theme.darkred = helpers.lighten(theme.red, -10) 38 | theme.green = xrdb.color2 39 | theme.darkgreen = helpers.lighten(theme.green, -10) 40 | theme.yellow = xrdb.color3 41 | theme.darkyellow = helpers.lighten(theme.yellow, -10) 42 | theme.blue = xrdb.color4 43 | theme.darkblue = helpers.lighten(theme.blue, -10) 44 | theme.magenta = xrdb.color5 45 | theme.darkmagenta = helpers.lighten(theme.magenta, -10) 46 | theme.cyan = xrdb.color6 47 | theme.darkcyan = helpers.lighten(theme.cyan, -10) 48 | theme.white = xrdb.color7 49 | theme.white_alt = xrdb.color15 50 | theme.light_bg = helpers.lighten(theme.black, 5) 51 | theme.transparent = "#00000000" 52 | 53 | -- Background Colors 54 | theme.bg_normal = theme.black 55 | theme.bg_focus = theme.blue 56 | theme.bg_urgent = theme.red 57 | theme.bg_minimize = theme.black 58 | 59 | -- Foreground Colors 60 | theme.fg_normal = theme.white 61 | theme.fg_focus = theme.black 62 | theme.fg_urgent = theme.white 63 | theme.fg_minimize = theme.black_alt 64 | 65 | --- ░█░█░▀█▀░░░█▀▀░█░░░█▀▀░█▄█░█▀▀░█▀█░▀█▀░█▀▀ 66 | --- ░█░█░░█░░░░█▀▀░█░░░█▀▀░█░█░█▀▀░█░█░░█░░▀▀█ 67 | --- ░▀▀▀░▀▀▀░░░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀▀▀░▀░▀░░▀░░▀▀▀ 68 | 69 | -- Borders 70 | -- =================================================================== 71 | theme.border_width = dpi(3) 72 | theme.border_normal = theme.light_bg 73 | theme.border_focus = theme.blue 74 | theme.border_radius = dpi(15) 75 | theme.widget_border_width = dpi(2) 76 | theme.widget_border_color = theme.light_bg 77 | 78 | -- Wibar 79 | -- =================================================================== 80 | theme.wibar_position = "top" 81 | theme.wibar_height = dpi(27) 82 | theme.wibar_width = dpi(1366) 83 | theme.wibar_bg = theme.black 84 | theme.wibar_fg = theme.white 85 | theme.wibar_border_color = theme.black 86 | theme.wibar_border_width = dpi(0) 87 | theme.wibar_border_radius = dpi(0) 88 | 89 | -- Taglist 90 | -- =================================================================== 91 | theme.taglist_text_font = theme.wibar_ico_font 92 | theme.taglist_text_empty = { "", "", "", "", "", "", "[]=", "", "", "" } 93 | theme.taglist_text_occupied = { "", "", "", "", "", "", "[]=", "", "", "" } 94 | theme.taglist_text_focused = { "", "", "", "", "", "", "[]=", "", "", "" } 95 | theme.taglist_text_urgent = { "", "", "", "", "", "", "", "", "", "" } 96 | 97 | theme.taglist_text_color_empty = { 98 | theme.black_alt, 99 | theme.black_alt, 100 | theme.black_alt, 101 | theme.black_alt, 102 | theme.black_alt, 103 | theme.black_alt, 104 | theme.black_alt, 105 | theme.black_alt, 106 | theme.black_alt, 107 | theme.black_alt, 108 | } 109 | theme.taglist_text_color_occupied = { 110 | theme.blue, 111 | theme.green, 112 | theme.yellow, 113 | theme.red, 114 | theme.cyan, 115 | theme.magenta, 116 | theme.cyan, 117 | theme.cyan, 118 | theme.cyan, 119 | theme.cyan, 120 | } 121 | theme.taglist_text_color_focused = { 122 | theme.blue, 123 | theme.green, 124 | theme.yellow, 125 | theme.red, 126 | theme.cyan, 127 | theme.magenta, 128 | theme.cyan, 129 | theme.cyan, 130 | theme.cyan, 131 | theme.cyan, 132 | } 133 | theme.taglist_text_color_urgent = { 134 | theme.blue, 135 | theme.green, 136 | theme.yellow, 137 | theme.red, 138 | theme.cyan, 139 | theme.magenta, 140 | theme.cyan, 141 | theme.cyan, 142 | theme.cyan, 143 | theme.cyan, 144 | } 145 | 146 | -- Text Taglist (default) 147 | theme.taglist_font = theme.wibar_font 148 | theme.taglist_bg_focus = theme.wibar_bg 149 | theme.taglist_fg_focus = theme.fg_focus 150 | theme.taglist_bg_occupied = theme.wibar_bg 151 | theme.taglist_fg_occupied = theme.fg_normal 152 | theme.taglist_bg_empty = theme.wibar_bg 153 | theme.taglist_fg_empty = theme.black_alt 154 | theme.taglist_bg_urgent = theme.wibar_bg 155 | theme.taglist_fg_urgent = theme.fg_urgent 156 | theme.taglist_disable_icon = true 157 | theme.taglist_spacing = dpi(0) 158 | local taglist_square_size = dpi(0) 159 | theme.taglist_squares_sel = theme_assets.taglist_squares_sel(taglist_square_size, theme.fg_focus) 160 | theme.taglist_squares_unsel = theme_assets.taglist_squares_unsel(taglist_square_size, theme.fg_normal) 161 | 162 | -- Tasklist 163 | -- =================================================================== 164 | theme.tasklist_font = theme.font 165 | theme.tasklist_disable_icon = false 166 | theme.tasklist_plain_task_name = true 167 | theme.tasklist_bg_focus = theme.wibar_bg 168 | theme.tasklist_fg_focus = theme.fg_normal 169 | theme.tasklist_bg_normal = theme.wibar_bg 170 | theme.tasklist_fg_normal = theme.black_alt 171 | theme.tasklist_bg_minimize = theme.bg_minimize 172 | theme.tasklist_fg_minimize = theme.fg_minimize 173 | theme.tasklist_bg_urgent = theme.bg_urgent 174 | theme.tasklist_fg_urgent = theme.fg_urgent 175 | theme.tasklist_align = "left" 176 | 177 | --- Titlebar 178 | -- =================================================================== 179 | theme.titlebars_enabled = false 180 | theme.titlebar_size = dpi(32) 181 | theme.titlebar_title_enabled = true 182 | theme.titlebar_font = theme.font 183 | theme.titlebar_title_align = "right" 184 | theme.titlebar_position = "top" 185 | theme.titlebar_bg = theme.bg_normal 186 | -- theme.titlebar_bg_focus = theme.bg_focus 187 | -- theme.titlebar_bg_normal = theme.bg_normal 188 | -- theme.titlebar_fg = theme.fg_normal 189 | -- theme.titlebar_fg_focus = theme.fg_focus 190 | -- theme.titlebar_fg_normal = theme.fg_normal 191 | 192 | -- Menu 193 | -- Variables set for theming the menu: 194 | -- =================================================================== 195 | theme.menu_height = dpi(30) 196 | theme.menu_width = dpi(150) 197 | theme.menu_bg_normal = theme.bg_normal 198 | theme.menu_fg_normal = theme.fg_normal 199 | theme.menu_bg_focus = theme.bg_focus 200 | theme.menu_fg_focus = theme.fg_focus 201 | theme.menu_border_width = theme.widget_border_width 202 | theme.menu_border_color = theme.widget_border_color 203 | 204 | -- Gaps 205 | -- =================================================================== 206 | theme.useless_gap = dpi(8) 207 | theme.screen_margin = dpi(3) 208 | 209 | -- Systray 210 | -- =================================================================== 211 | theme.bg_systray = theme.wibar_bg 212 | 213 | -- Notifications 214 | -- =================================================================== 215 | theme.notification_font = theme.font 216 | theme.notification_bg = theme.bg_normal 217 | theme.notification_fg = theme.fg_normal 218 | theme.notification_border_width = theme.widget_border_width 219 | theme.notification_border_color = theme.widget_border_color 220 | theme.notification_max_width = dpi(300) 221 | theme.notification_max_height = dpi(190) 222 | theme.notification_position = "top_right" 223 | theme.notification_border_radius = theme.border_radius 224 | theme.notification_spacing = theme.screen_margin * 2 225 | theme.notification_shape = helpers.rrect(theme.notification_border_radius) 226 | 227 | -- Set different colors for notifications. 228 | rnotification.connect_signal("request::rules", function() 229 | rnotification.append_rule({ 230 | rule = { urgency = "critical" }, 231 | properties = { 232 | implicit_timeout = 4, 233 | bg = theme.bg_urgent, 234 | fg = theme.fg_urgent, 235 | font = theme.notification_font, 236 | position = theme.notification_position, 237 | border_color = theme.notification_border_color, 238 | margin = theme.notification_margin, 239 | }, 240 | }) 241 | rnotification.append_rule({ 242 | rule = { urgency = "normal" }, 243 | properties = { 244 | implicit_timeout = 4, 245 | bg = theme.bg_normal, 246 | fg = theme.fg_normal, 247 | font = theme.notification_font, 248 | position = theme.notification_position, 249 | border_color = theme.notification_border_color, 250 | margin = theme.notification_margin, 251 | }, 252 | }) 253 | rnotification.append_rule({ 254 | rule = { urgency = "low" }, 255 | properties = { 256 | implicit_timeout = 4, 257 | bg = theme.bg_normal, 258 | fg = theme.fg_normal, 259 | font = theme.notification_font, 260 | position = theme.notification_position, 261 | border_color = theme.notification_border_color, 262 | margin = theme.notification_margin, 263 | }, 264 | }) 265 | end) 266 | 267 | -- Layout icons 268 | -- =================================================================== 269 | theme.layout_max = themes_path .. "default/layouts/maxw.png" 270 | theme.layout_tile = themes_path .. "default/layouts/tilew.png" 271 | theme.layout_floating = themes_path .. "default/layouts/floatingw.png" 272 | -- not in use layouts 273 | theme.layout_cornernw = themes_path .. "default/layouts/cornernww.png" 274 | theme.layout_cornerne = themes_path .. "default/layouts/cornernew.png" 275 | theme.layout_cornersw = themes_path .. "default/layouts/cornersww.png" 276 | theme.layout_cornerse = themes_path .. "default/layouts/cornersew.png" 277 | theme.layout_fairh = themes_path .. "default/layouts/fairhw.png" 278 | theme.layout_fairv = themes_path .. "default/layouts/fairvw.png" 279 | theme.layout_magnifier = themes_path .. "default/layouts/magnifierw.png" 280 | theme.layout_fullscreen = themes_path .. "default/layouts/fullscreenw.png" 281 | theme.layout_spiral = themes_path .. "default/layouts/spiralw.png" 282 | theme.layout_dwindle = themes_path .. "default/layouts/dwindlew.png" 283 | theme.layout_tiletop = themes_path .. "default/layouts/tiletopw.png" 284 | theme.layout_tilebottom = themes_path .. "default/layouts/tilebottomw.png" 285 | theme.layout_tileleft = themes_path .. "default/layouts/tileleftw.png" 286 | -- Recolor layout icons 287 | theme = theme_assets.recolor_layout(theme, theme.red) 288 | -- Layoutlist 289 | theme.layoutlist_shape_selected = gears.shape.rounded_rect 290 | theme.layoutlist_bg_selected = theme.bg_focus 291 | 292 | -- Misc 293 | -- =================================================================== 294 | -- Edge snap 295 | theme.snap_bg = theme.light_bg 296 | theme.snap_shape = helpers.rrect(theme.border_radius) 297 | theme.snap_border_width = theme.widget_border_width 298 | 299 | -- Hotkeys popup 300 | theme.hotkeys_modifiers_fg = theme.blue 301 | 302 | -- Generate Awesome icon: 303 | theme.awesome_icon = theme_assets.awesome_icon(theme.menu_height, theme.bg_focus, theme.fg_focus) 304 | -- Define the icon theme for application icons. If not set then the icons 305 | -- from /usr/share/icons and /usr/share/icons/hicolor will be used. 306 | theme.icon_theme = nil 307 | 308 | return theme 309 | -- EOF ------------------------------------------------------------------------ 310 | --------------------------------------------------------------------------------