[
  {
    "path": ".gitignore",
    "content": "pkg/\n*.tar.*\n*.deb\npackage/"
  },
  {
    "path": "DEBIAN/control",
    "content": "Package: calla\nVersion: 0.3.0-5\nMaintainer: Stella <stardust-kyun@proton.me>\nDepends: awesome (>=4.3.0.0~git), xorg, pipewire, pipewire-pulse, pulseaudio-utils, wireplumber, brightnessctl, inotify-tools, picom, maim, papirus-icon-theme, xsettingsd, network-manager-gnome, policykit-1-gnome, playerctl, liblua-pam, gir1.2-upowerglib-1.0, xclip, breeze-cursor-theme\nSuggests: vim-gtk3, nemo, lollypop, firefox-esr\nArchitecture: amd64\nHomepage: https://github.com/stardust-kyun/calla\nDescription: Calla Desktop Environment\n"
  },
  {
    "path": "PKGBUILD",
    "content": "# From @levraiardox \n# To https://github.com/Stardust-kyun/calla\n\npkgname=\"calla\"\npkgver=\"0.3\"\npkgrel=\"1\"\npkgdesc=\"Calla desktop environement\"\narch=(\"x86_64\")\nurl=\"https://github.com/Stardust-kyun/calla\"\n\ndepends=(\n        \"xorg-server\" \n        \"pipewire-pulse\"\n        \"brightnessctl\"\n        \"inotify-tools\"\n        \"awesome-git\"\n        \"picom\"\n        \"maim\"\n        \"papirus-icon-theme\"\n        \"noto-fonts\"\n        \"noto-fonts-cjk\"\n        \"noto-color-emoji-fontconfig\"\n        \"noto-fonts-extra\"\n        \"lua-pam-git\"\n        )\n\noptdepends=(\n        \"st: terminal\",\n        \"vim-gtk3: vim with clipboard\",\n        \"nemo: file manager\",\n        \"network-manager-gnome: network applet\",\n        \"polkit-gnome: polkit\",\n        \"cbatticon: battery applet\",\n        \"blueman: bluetooth applet\",\n        \"xdg-user-dirs: generate home directories\",\n        \"lollypop: music player\",\n        )\n\npackage() {\n    cp -r \"${srcdir}/usr\" \"${pkgdir}/\"\n}\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=center>Calla</h1>\n\n<div align=\"center\">\n<a href=\"#install\">Install</a> - <a href=\"#usage\">Usage</a> - <a href=\"#gallery\">Gallery</a> - <a href=\"#credit\">Credit</a> - <a href=\"#contact\">Contact</a>\n</div>\n\n---\n\n![latest](.github/latest.png)\n\n<!-- [showcase](https://github.com/Stardust-kyun/dotfiles/assets/56178655/d52b1338-b3f6-444e-b97c-59bdc2544414) -->\n\n---\n\n## Install\n\n### Read Before Installing\n\nIn my previous work, I have provided scripts to make it much easier for the average user to install. However, I have found that these scripts are a massive pain to maintain for a variety of distributions, so this time I have provided universal instructions instead. These instructions have been written based on my Debian installation, so you may find that you have additional steps. If you have any questions, see <a href=\"#contact\">contact</a>.\n\n<details>\n<summary><b>Installation</b></summary>\n\n---\n\n# Under Construction\n\nInstallation instructions are currently being revised as Calla is packaged for major distros. As of version 0.3.0-1, only Debian is supported. If you are attempting to install Calla on Debian, find the newest release in the releases tab. If you are attempting to install on other distros, make sure you have the following installed:\n\n- AwesomeWM git\n- Your distribution's package equivalents for line 4 of `DEBIAN/control` (the line that starts with `Depends:`)\n\n---\n\n</details>\n\n## Usage\n\n<details>\n<summary><b>Settings App</b></summary>\n\n---\n\nCalla contains a settings app to configure itself without editing any files. It can be opened through the settings icon in the control center, or by pressing `Mod+Shift+C`.\n\n### General\n\n- Terminal - The terminal to run on `Mod+Enter`\n- Shutdown/Reboot - The commands to use to shutdown and reboot the system\n- Fallback Password - The password Calla will use if it cannot use your user's password\n- Font(s) - Fonts used throughout the desktop environment\n- Battery - The name of your system's battery (found in `/sys/class/power_supply/`\n- Wallpaper - If you would like to overwrite the theme's default wallpaper\n- Screenshot Directory - The directory that screenshots are saved to\n\n### Theme\n\n- Color Scheme - The theme Calla will use\n- Colors - The colors the theme will use, in base8\n- Compositor Settings - Settings for the shadow picom sets\n- Gui/Icon Theme - The names of the gui and icon themes the theme should use\n\n---\n\n</details>\n\n## Gallery\n\n<details>\n<summary><b>Screenshots</b></summary>\n\n---\n\n### Apps\n![apps](.github/apps.png)\n\n### Launcher\n![launcher](.github/launcher.png)\n\n### Tag Preview\n![preview](.github/preview.png)\n\n### Volume/Brightness Popup\n![volume](.github/volume.png)\n\n### Lock Screen\n![lockscreen](.github/lockscreen.png)\n\n---\n\n</details>\n\n## Credit\n\n### Thanks\n\n- [Ardox](https://github.com/LeVraiArdox) for refactoring and adding proper packaging for Arch.\n- [Sammy](https://github.com/TorchedSammy) for help understanding and adding live reloading.\n- [Crylia](https://github.com/Crylia) for massive amounts of help learning awesomewm.\n- [Jimmy](https://github.com/Jimmysit0) and [Petrolblue](https://github.com/petrolblue) for help with color schemes and lots of support.\n- And the support of many more!\n\n### Historical Contributions\n\n- [AloneERO](https://gitlab.com/AloneER0) for help adding support for Void, Fedora, OpenSUSE, and Alpine!\n- [Frankfut](https://github.com/frankfutlg) for help adding support for Void and lots of help with debugging.\n- [Qwickdom](https://github.com/Qwickdom) for help adding support for Arch.\n- [Reverse](https://github.com/Reversedc) for help adding support for Debian.\n- [Alyssa](https://github.com/alyssa-sudo) for help adding support for Gentoo.\n\n### References\n\n- [Sammy's Dotfiles](https://github.com/TorchedSammy/dotfiles)\n- [Saimoom's Dotfiles](https://github.com/saimoomedits/dotfiles)\n- [Smeueg's Dotfiles](https://github.com/Smeueg/Dotfiles)\n- [Bling](https://github.com/BlingCorp/bling)\n\n### Projects\n\n- [Phocus](https://github.com/phocus/gtk)\n\n## Contact\n\nYou can find my contact information on my [website](https://star.is-a.dev/). I also have a [discord server](https://discord.gg/38hQb6V8AW) with help available and updates for when new features are added.\n\n## Stars\n\n![Star History Chart](https://api.star-history.com/svg?repos=Stardust-kyun/calla&type=Date)\n"
  },
  {
    "path": "build.sh",
    "content": "#!/usr/bin/env bash\nurl=`cat ./.git/config | grep \"url = \" | sed \"s/^[^=]*= //\"`\nif [[ $url == \"https://github.com/stardust-kyun/calla\" ]]; then # make this case insensitive\n\tread -p \"\nWhich distro would you like to build for?\n\n(1) Debian\n(2) Arch\n\n(?) Select option: \" dist\n\tcase $dist in\n\t\t\"1\")\n\t\t\tmkdir -p package\n\t\t\tread -p \"Version (0.1.0-1): \" ver\n\t\t\tcp -r src/usr package/\n\t\t\tcp -r DEBIAN package/\n\t\t\t\n\t\t\tdpkg-deb --build package calla_$ver\\_amd64.deb\n\t\t\trm -rf package\n\t\t\t;;\n\t\t\"2\")\n\t\t\tmakepkg -f --noconfirm\n\t\t\t;;\n\t\t*)\n\t\t\techo \"Something went wrong. Did you choose an option correctly?\"\n\t\t\texit\n\tesac\nelse\n\techo \"Something went wrong. Are you inside the project directory?\"\nfi\n"
  },
  {
    "path": "src/usr/bin/calla",
    "content": "#!/bin/sh\nxrdb /usr/share/calla/Xresources\nawesome --config /usr/share/calla/desktop/rc.lua\n"
  },
  {
    "path": "src/usr/share/calla/Xresources",
    "content": "#define BG #f5f5f5\n#define FG #303030\n#define BL #505050\n#define WH #d0d0d0\n#define R #ac4142\n#define G #90a959\n#define Y #f4bf75\n#define B #6a9fb5\n#define M #aa759f\n#define C #75b5aa\n\nst.font: RobotoMono:size=11\nst.borderpx: 25\n\n! special\n*.foreground:   FG\n*.background:   BG\n*.cursorColor:  FG\n\n! black\n*.color0:       BL\n*.color8:       BL\n\n! red\n*.color1:       R\n*.color9:       R\n\n! green\n*.color2:       G\n*.color10:      G\n\n! yellow\n*.color3:       Y\n*.color11:      Y\n\n! blue\n*.color4:       B\n*.color12:      B\n\n! magenta\n*.color5:       M\n*.color13:      M\n\n! cyan\n*.color6:       C\n*.color14:      C\n\n! white\n*.color7:       WH\n*.color15:      WH\n"
  },
  {
    "path": "src/usr/share/calla/compositor.conf",
    "content": "#################################\n#\n# Shadows\n#\n#################################\n\n# Enabled client-side shadows on windows.\nshadow = true;\n# The blur radius for shadows. (default 30)\nshadow-radius = 30;\n# The left offset for shadows. (default -30)\nshadow-offset-x = -30;\n# The top offset for shadows. (default -30)\nshadow-offset-y = -30;\n# The translucency for shadows. (default .25)\nshadow-opacity = .25;\n# The radius of corner rounding. (default 15)\ncorner-radius = 15;\n# Window types to exclude from rounding.\nrounded-corners-exclude = [\n\t\"window_type = 'splash'\"\n];\n# Special rules.\ncorner-radius-rules = [\n\t\"20:window_type = 'desktop'\"\n];\n\n#################################\n#\n# Opacity\n#\n#################################\n\n# Don't make anything transparent!\ninactive-opacity = 1;\nactive-opacity = 1;\nframe-opacity = 1;\ninactive-opacity-override = false;\n\n#################################\n#\n# Fading\n#\n#################################\n\n# Fade windows during opacity changes.\nfading = true;\n# The time between steps in a fade in milliseconds. (default 3).\nfade-delta = 3;\n# Opacity change between steps while fading in. (default 0.03).\nfade-in-step = 0.03;\n# Opacity change between steps while fading out. (default 0.03).\nfade-out-step = 0.03;\n"
  },
  {
    "path": "src/usr/share/calla/desktop/color/.backup/bloom/bloom.json",
    "content": "{\n\t\"bg\": \"#fffaf5\",\n\t\"bgalt\": \"#ebe6e1\",\n\t\"bgmid\": \"#ebe6e1\",\n\t\"black\": \"#4b4646\",\n\t\"blue\": \"#9bb9f0\",\n\t\"compoffset\": \"-25\",\n\t\"compopacity\": \".1\",\n\t\"compradius\": \"25\",\n\t\"cyan\": \"#a0e1d2\",\n\t\"fg\": \"#4b4646\",\n\t\"green\": \"#96e6a5\",\n\t\"gtk\": \"bloom\",\n\t\"icons\": \"bloom\",\n\t\"magenta\": \"#d7a0e6\",\n\t\"red\": \"#eb8c8c\",\n\t\"wall\": \"color/bloom/bloom.png\",\n\t\"white\": \"#ebe6e1\",\n\t\"yellow\": \"#f0cd96\"\n}\n"
  },
  {
    "path": "src/usr/share/calla/desktop/color/.backup/dark/dark.json",
    "content": "{\n\t\"bg\": \"#151515\",\n\t\"bgalt\": \"#1a1a1a\",\n\t\"bgmid\": \"#202020\",\n\t\"black\": \"#505050\",\n\t\"blue\": \"#6a9fb5\",\n\t\"compoffset\": \"-25\",\n\t\"compopacity\": \".5\",\n\t\"compradius\": \"25\",\n\t\"cyan\": \"#75b5aa\",\n\t\"fg\": \"#f5f5f5\",\n\t\"green\": \"#90a959\",\n\t\"gtk\": \"dark\",\n\t\"icons\": \"Papirus-Dark\",\n\t\"magenta\": \"#aa759f\",\n\t\"red\": \"#ac4142\",\n\t\"wall\": \"color/dark/dark.png\",\n\t\"white\": \"#d0d0d0\",\n\t\"yellow\": \"#f4bf75\"\n}"
  },
  {
    "path": "src/usr/share/calla/desktop/color/.backup/dark/dark.json.bak",
    "content": "{\n\t\"bg\": \"#151515\",\n\t\"bgalt\": \"#252525\",\n\t\"bgmid\": \"#1d1d1d\",\n\t\"black\": \"#505050\",\n\t\"blue\": \"#6a9fb5\",\n\t\"compoffset\": \"-25\",\n\t\"compopacity\": \".5\",\n\t\"compradius\": \"25\",\n\t\"cyan\": \"#75b5aa\",\n\t\"fg\": \"#f5f5f5\",\n\t\"green\": \"#90a959\",\n\t\"gtk\": \"dark\",\n\t\"icons\": \"Papirus\",\n\t\"magenta\": \"#aa759f\",\n\t\"red\": \"#ac4142\",\n\t\"wall\": \"color/dark/dark.png\",\n\t\"white\": \"#d0d0d0\",\n\t\"yellow\": \"#f4bf75\"\n}\n"
  },
  {
    "path": "src/usr/share/calla/desktop/color/.backup/light/light.json",
    "content": "{\n    \"bg\": \"#f5f5f5\",\n    \"bgalt\": \"#d0d0d0\",\n    \"bgmid\": \"#e3e3e3\",\n    \"black\": \"#505050\",\n    \"blue\": \"#6a9fb5\",\n    \"compoffset\": \"-25\",\n    \"compopacity\": \".5\",\n    \"compradius\": \"25\",\n    \"cyan\": \"#75b5aa\",\n    \"fg\": \"#303030\",\n    \"green\": \"#90a959\",\n    \"gtk\": \"light\",\n    \"icons\": \"Papirus-Light\",\n    \"magenta\": \"#aa759f\",\n    \"red\": \"#ac4142\",\n    \"wall\": \"color/light/light.png\",\n    \"white\": \"#d0d0d0\",\n    \"yellow\": \"#f4bf75\"\n}\n"
  },
  {
    "path": "src/usr/share/calla/desktop/color/.backup/sakura/sakura.json",
    "content": "{\n\t\"bg\": \"#000f14\",\n\t\"bgalt\": \"#0a191e\",\n\t\"bgmid\": \"#0a191e\",\n\t\"black\": \"#0a191e\",\n\t\"blue\": \"#326482\",\n\t\"compoffset\": \"-25\",\n\t\"compopacity\": \".5\",\n\t\"compradius\": \"25\",\n\t\"cyan\": \"#327d7d\",\n\t\"fg\": \"#a0a0b4\",\n\t\"green\": \"#468264\",\n\t\"gtk\": \"sakura\",\n\t\"icons\": \"sakura\",\n\t\"magenta\": \"#645078\",\n\t\"red\": \"#824655\",\n\t\"wall\": \"color/sakura/sakura.png\",\n\t\"white\": \"#a0a0b4\",\n\t\"yellow\": \"#827d50\"\n}\n"
  },
  {
    "path": "src/usr/share/calla/desktop/color/.backup/shore/shore.json",
    "content": "{\n\t\"bg\": \"#19191e\",\n\t\"bgalt\": \"#2b2b33\",\n\t\"bgmid\": \"#2b2b33\",\n\t\"black\": \"#2b2b33\",\n\t\"blue\": \"#505a82\",\n\t\"compoffset\": \"-25\",\n\t\"compopacity\": \".5\",\n\t\"compradius\": \"25\",\n\t\"cyan\": \"#5a7387\",\n\t\"fg\": \"#9999a8\",\n\t\"green\": \"#5a825a\",\n\t\"gtk\": \"shore\",\n\t\"icons\": \"shore\",\n\t\"magenta\": \"#735a87\",\n\t\"red\": \"#825a5a\",\n\t\"wall\": \"color/shore/shore.png\",\n\t\"white\": \"#9999a8\",\n\t\"yellow\": \"#968264\"\n}\n"
  },
  {
    "path": "src/usr/share/calla/desktop/color/.backup/wave/wave.json",
    "content": "{\n\t\"bg\": \"#f0fafa\",\n\t\"bgalt\": \"#dce6e6\",\n\t\"bgmid\": \"#dce6e6\",\n\t\"black\": \"#404040\",\n\t\"blue\": \"#83b4e6\",\n\t\"compoffset\": \"-25\",\n\t\"compopacity\": \".1\",\n\t\"compradius\": \"25\",\n\t\"cyan\": \"#8cd7d2\",\n\t\"fg\": \"#262626\",\n\t\"green\": \"#a0e6af\",\n\t\"gtk\": \"wave\",\n\t\"icons\": \"wave\",\n\t\"magenta\": \"#e1aae1\",\n\t\"red\": \"#e68383\",\n\t\"wall\": \"color/wave/wave.png\",\n\t\"white\": \"#dce6e6\",\n\t\"yellow\": \"#ffcd96\"\n}\n"
  },
  {
    "path": "src/usr/share/calla/desktop/color/dark/dark.json",
    "content": "{\n\t\"bg\": \"#151515\",\n\t\"bgalt\": \"#2B2B2B\",\n\t\"bgmid\": \"#202020\",\n\t\"black\": \"#505050\",\n\t\"blue\": \"#6a9fb5\",\n\t\"compoffset\": \"-25\",\n\t\"compopacity\": \".5\",\n\t\"compradius\": \"25\",\n\t\"cyan\": \"#75b5aa\",\n\t\"fg\": \"#f5f5f5\",\n\t\"green\": \"#90a959\",\n\t\"gtk\": \"dark\",\n\t\"icons\": \"Papirus-Dark\",\n\t\"magenta\": \"#aa759f\",\n\t\"red\": \"#ac4142\",\n\t\"wall\": \"color/dark/dark.png\",\n\t\"white\": \"#d0d0d0\",\n\t\"yellow\": \"#f4bf75\"\n}"
  },
  {
    "path": "src/usr/share/calla/desktop/color/desktop.lua",
    "content": "local calla=\"/usr/share/calla/\"\nlocal picom=calla..\"compositor.conf\"\nlocal xresources=calla..\"Xresources\"\nlocal xsettingsd=calla..\"xsettingsd\"\n\nlocal function compositor(radius, x, y, opacity)\n\tlocal r = assert(io.open(picom, \"r\"))\n\tlocal file = r:read(\"*all\")\n\tr:close()\n\t\n\tfile = file:gsub(\"shadow%-radius = .-\\n\", \"shadow-radius = \"..radius..\";\\n\")\n\tfile = file:gsub(\"shadow%-offset%-x = .-\\n\", \"shadow-offset-x = \"..x..\";\\n\")\n\tfile = file:gsub(\"shadow%-offset%-y = .-\\n\", \"shadow-offset-y = \"..y..\";\\n\")\n\tfile = file:gsub(\"shadow%-opacity = .-\\n\", \"shadow-opacity = \"..opacity..\";\\n\")\n\n\tlocal w = assert(io.open(picom, \"w\"))\n\tw:write(file)\n\tw:close()\nend\n\nlocal function terminal(bg, fg, bl, wh, r, g, y, b, m, c)\n\tlocal read = assert(io.open(xresources, \"r\"))\n\tlocal file = read:read(\"*all\")\n\tread:close()\n\n\tfile = file:gsub(\"%#define BG .-\\n\", \"#define BG \"..bg..\"\\n\")\n\tfile = file:gsub(\"%#define FG .-\\n\", \"#define FG \"..fg..\"\\n\")\n\tfile = file:gsub(\"%#define BL .-\\n\", \"#define BL \"..bl..\"\\n\")\n\tfile = file:gsub(\"%#define WH .-\\n\", \"#define WH \"..wh..\"\\n\")\n\tfile = file:gsub(\"%#define R .-\\n\", \"#define R \"..r..\"\\n\")\n\tfile = file:gsub(\"%#define G .-\\n\", \"#define G \"..g..\"\\n\")\n\tfile = file:gsub(\"%#define Y .-\\n\", \"#define Y \"..y..\"\\n\")\n\tfile = file:gsub(\"%#define B .-\\n\", \"#define B \"..b..\"\\n\")\n\tfile = file:gsub(\"%#define M .-\\n\", \"#define M \"..m..\"\\n\")\n\tfile = file:gsub(\"%#define C .-\\n\", \"#define C \"..c..\"\\n\")\n\n\tlocal w = assert(io.open(xresources, \"w\"))\n\tw:write(file)\n\tw:close()\n\n\tos.execute(\"xrdb \" .. xresources)\n\trequire(\"awful\").spawn.easy_async_with_shell(require(\"gears\").filesystem.get_configuration_dir() .. \"color/terminal.sh\")\nend\n\nlocal function theme(theme, icon)\n\tlocal r = assert(io.open(xsettingsd, \"r\"))\n\tlocal file = r:read(\"*all\")\n\tr:close()\n\n\tfile = file:gsub(\"Net/ThemeName .-\\n\", \"Net/ThemeName \\\"\"..theme..\"\\\"\\n\")\n\tfile = file:gsub(\"Net/IconThemeName .-\\n\", \"Net/IconThemeName \\\"\"..icon..\"\\\"\\n\")\n\n\tlocal w = assert(io.open(xsettingsd, \"w\"))\n\tw:write(file)\n\tw:close()\n\n\tos.execute(\"xsettingsd --config '/usr/share/calla/xsettingsd' &\")\nend\n\nawesome.connect_signal(\"color::change\", function(color)\n\tcompositor(color.compradius, color.compoffset, color.compoffset, color.compopacity)\n\n\tterminal(color.bg, color.fg, color.black, color.white, color.red, color.green, color.yellow, color.blue, color.magenta, color.cyan)\n\n\ttheme(color.gtk, color.icons)\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/color/light/light.json",
    "content": "{\n\t\"bg\": \"#f5f5f5\",\n\t\"bgalt\": \"#d0d0d0\",\n\t\"bgmid\": \"#e3e3e3\",\n\t\"black\": \"#505050\",\n\t\"blue\": \"#6a9fb5\",\n\t\"compoffset\": \"-30\",\n\t\"compopacity\": \".25\",\n\t\"compradius\": \"30\",\n\t\"cyan\": \"#75b5aa\",\n\t\"fg\": \"#303030\",\n\t\"green\": \"#90a959\",\n\t\"gtk\": \"light\",\n\t\"icons\": \"Papirus-Light\",\n\t\"magenta\": \"#aa759f\",\n\t\"red\": \"#ac4142\",\n\t\"wall\": \"color/light/light.png\",\n\t\"white\": \"#d0d0d0\",\n\t\"yellow\": \"#f4bf75\"\n}"
  },
  {
    "path": "src/usr/share/calla/desktop/color/solarized/solarized.json",
    "content": "{\n\t\"bg\": \"#002B36\",\n\t\"bgalt\": \"#063C4A\",\n\t\"bgmid\": \"#043643\",\n\t\"black\": \"#657B83\",\n\t\"blue\": \"#268BD2\",\n\t\"compoffset\": \"-30\",\n\t\"compopacity\": \".25\",\n\t\"compradius\": \"30\",\n\t\"cyan\": \"#2AA198\",\n\t\"fg\": \"#93A1A1\",\n\t\"green\": \"#859900\",\n\t\"gtk\": \"solarized\",\n\t\"icons\": \"Papirus-Dark\",\n\t\"magenta\": \"#6C71C4\",\n\t\"red\": \"#DC322F\",\n\t\"wall\": \"color/solarized/solarized.png\",\n\t\"white\": \"#FDF6E3\",\n\t\"yellow\": \"#B58900\"\n}"
  },
  {
    "path": "src/usr/share/calla/desktop/color/terminal.sh",
    "content": "#!/usr/bin/env bash\n\nxrdb_query() {\n\tvalue=$(xrdb -query | grep -i \"^*\\.$1:\" | cut -f 2)\n\tif [ -n \"${value}\" ]; then\n\t\techo \"${value}\"\n\t\treturn 0\n\tfi\n\treturn 1\n}\n\nsequences=\"\"\nfor i in $(seq 0 15); do\n\tsequences+=\"\\033]4;${i};$(xrdb_query \"color${i}\")\\007\"\ndone\nsequences+=\"\\\\033]10;$(xrdb_query foreground)\\\\007\"\nsequences+=\"\\\\033]11;$(xrdb_query background)\\\\007\"\nsequences+=\"\\\\033]12;$(xrdb_query cursorColor)\\\\007\"\nsequences+=\"\\\\033]708;$(xrdb_query background)\\\\007\"\n\nfor term in /dev/pts/[0-9]*; do\n\tprintf \"%b\" \"${sequences}\" > \"${term}\" &\ndone\n"
  },
  {
    "path": "src/usr/share/calla/desktop/config/bind.lua",
    "content": "local awful = require(\"awful\")\nlocal mod = user.mod\n\n-- Mouse \n\nclient.connect_signal(\"request::default_mousebindings\", function()\n    awful.mouse.append_client_mousebindings({\n\n\t\t-- Move\n\n        awful.button({ mod }, 1, function(c)\n            c:activate { context = \"mouse_click\", action = \"mouse_move\"  }\n        end),\n\n\t\t-- Resize\n\n        awful.button({ mod }, 3, function(c)\n            c:activate { context = \"mouse_click\", action = \"mouse_resize\"}\n        end),\n\n    })\nend)\n\n-- Keys\n\nawful.keyboard.append_global_keybindings({\n\n\t-- Awesome\n\n\tawful.key(\n\t\t{ mod, \"Shift\" }, \"r\", \n\t\t\tawesome.restart,\n\t\t{ description = \"reload awesome\", group = \"awesome\" }\n\t),\n\tawful.key(\n\t\t{ mod }, \"z\", function() \n\t\t\tawful.layout.inc(1) \n\t\tend,\n \t\t{ description = \"next layout\", group = \"awesome\" }\n\t),\n\tawful.key(\n\t\t{ mod, \"Shift\" }, \"z\", function() \n\t\t\tawful.layout.inc(-1) \n\t\tend,\n \t\t{ description = \"previous layout\", group = \"awesome\" }\n\t),\n    awful.key(\n\t\t{ mod }, \"Tab\", function() \n\t\t\tawful.client.focus.byidx(1) \n\t\tend,\n        { description = \"next window\", group = \"awesome\" }\n    ),\n    awful.key(\n\t\t{ mod, \"Shift\" }, \"Tab\", function() \n\t\t\tawful.client.focus.byidx(-1) \n\t\tend,\n\t\t{ description = \"previous window\", group = \"awesome\" }\n    ),\n\tawful.key(\n\t\t{ mod }, \"space\", function() \n\t\t\tawesome.emit_signal(\"widget::menu\") \n\t\tend,\n\t\t{ description = \"show menu\", group = \"awesome\" }\n\t),\n    awful.key(\n\t\t{ mod }, \"d\", function() \n\t\t\tawesome.emit_signal(\"widget::launcher\")\n\t\tend,\n        { description = \"show launcher\", group = \"awesome\" }\n\t),\n    awful.key(\n\t\t{ mod, \"Shift\" }, \"c\", function() \n\t\t\tawesome.emit_signal(\"widget::config\")\n\t\tend,\n        { description = \"show config\", group = \"awesome\" }\n\t),\n    awful.key(\n\t\t{ mod }, \"BackSpace\", function() \n\t\t\tawesome.emit_signal(\"widget::lockscreen\")\n\t\tend,\n        { description = \"lock screen\", group = \"awesome\" }\n\t),\n    awful.key(\n\t\t{ mod }, \"space\", function() \n\t\t\tawesome.emit_signal(\"widget::control\")\n\t\tend,\n        { description = \"control center\", group = \"awesome\" }\n\t),\n\n\t-- Programs\n\n\tawful.key(\n\t\t{ mod }, \"Return\", function() \n\t\t\tawful.spawn.with_shell(user.terminal) \n\t\tend,\n        { description = \"open a terminal\", group = \"programs\" }\n\t),\n\n\t-- Screenshot\n\n\tawful.key(\n\t\t{ mod }, \"Print\", function() \n\t\t\tawesome.emit_signal(\"util::screenshot\", { args = \"-u\", time = \"0\" }) \n\t\tend,\n        { description = \"full screen\", group = \"screenshot\" }\n\t),\n\tawful.key(\n\t\t{ mod, \"Control\" }, \"Print\", function()\n\t\t\tawesome.emit_signal(\"util::screenshot\", { args = \"-u\", time = \"5\" }) \n\t\tend,\n        { description = \"full screen delay\", group = \"screenshot\" }\n\t),\n\tawful.key(\n\t\t{ mod, \"Shift\" }, \"Print\", function() \n\t\t\tawesome.emit_signal(\"util::screenshot\", { args = \"-s -u\", time = \"0\" }) \n\t\tend,\n        { description = \"part screen\", group = \"screenshot\" }\n\t),\n\n\t-- Volume\n\n    awful.key(\n\t\t{ }, \"XF86AudioRaiseVolume\", function() \n\t\t\tawful.spawn.with_shell(\"wpctl set-mute @DEFAULT_AUDIO_SINK@ 0\")\n\t\t\tawful.spawn.with_shell(\"wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+\")\n\t\t\tawesome.emit_signal(\"widget::volume\")\n\t\tend,\n        { description = \"raise volume\", group = \"volume\" }\n\t),\n    awful.key(\n\t\t{ }, \"XF86AudioLowerVolume\", function() \n\t\t\tawful.spawn.with_shell(\"wpctl set-mute @DEFAULT_AUDIO_SINK@ 0\")\n\t\t\tawful.spawn.with_shell(\"wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-\")\n\t\t\tawesome.emit_signal(\"widget::volume\")\n\t\tend,\n        { description = \"lower volume\", group = \"volume\" }\n\t),\n    awful.key(\n\t\t{ }, \"XF86AudioMute\", function() \n\t\t\tawful.spawn.with_shell(\"wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle\")\n\t\t\tawesome.emit_signal(\"widget::volume\")\n\t\tend,\n        { description = \"mute volume\", group = \"volume\" }\n\t),\n\n\t-- Brightness\n\n\tawful.key(\n\t\t{ }, \"XF86MonBrightnessUp\", function() \n\t\t\tawful.spawn.with_shell(\"brightnessctl s 5%+\")\n\t\t\tawesome.emit_signal(\"widget::brightness\")\n\t\tend,\n        { description = \"raise brightness\", group = \"brightness\" }\n\t),\n\tawful.key(\n\t\t{ }, \"XF86MonBrightnessDown\", function() \n\t\t\tawful.spawn.with_shell(\"brightnessctl s 5%-\")\n\t\t\tawesome.emit_signal(\"widget::brightness\")\n\t\tend,\n        { description = \"lower brightness\", group = \"brightness\" }\n\t),\n\n\t-- Tag\n\n    awful.key {\n        modifiers   = { mod },\n        keygroup    = \"numrow\",\n        description = \"only view tag\",\n        group       = \"tag\",\n        on_press    = function(index)\n            local screen = awful.screen.focused()\n            local tag = screen.tags[index]\n            if tag then\n                tag:view_only()\n            end\n        end,\n    },\n    awful.key {\n        modifiers = { mod, \"Control\" },\n        keygroup    = \"numrow\",\n        description = \"move focused client to tag\",\n        group       = \"tag\",\n        on_press    = function(index)\n            if client.focus then\n                local tag = client.focus.screen.tags[index]\n                if tag then\n                    client.focus:move_to_tag(tag)\n                end\n            end\n        end,\n    },\n    awful.key {\n        modifiers = { mod, \"Shift\" },\n        keygroup    = \"numrow\",\n        description = \"move focused client to tag and follow\",\n        group       = \"tag\",\n        on_press    = function(index)\n            if client.focus then\n                local tag = client.focus.screen.tags[index]\n                if tag then\n                    client.focus:move_to_tag(tag)\n\t\t\t\t\ttag:view_only()\n                end\n            end\n        end,\n    }\n\n})\n\nclient.connect_signal(\"request::default_keybindings\", function()\n    awful.keyboard.append_client_keybindings({\n\n\t\t-- Client\n\t\n\t\tawful.key(\n\t\t\t{ mod }, \"c\", \n\t\t\tfunction(c) \n\t\t\t\tawful.placement.centered(c, { honor_workarea = true }) \n\t\t\tend,\n\t\t\t{ description = \"center window\", group = \"client\" }\n\t\t),\n        awful.key(\n\t\t\t{ mod }, \"f\",\n            function(c)\n                c.fullscreen = not c.fullscreen\n                c:raise()\n            end,\n            { description = \"toggle fullscreen\", group = \"client\" }\n\t\t),\n\t    awful.key(\n\t\t\t{ mod }, \"s\", \n\t\t\tfunction(c)\n\t\t\t\tc.floating = not c.floating\n\t\t\t\tc:raise()\n\t\t\tend,\n        \t{ description = \"toggle floating\", group = \"client\" }\n\t\t),\n\t    awful.key(\n\t\t\t{ mod }, \"n\", \n\t\t\tfunction(c)\n\t\t\t\tclient.focus.minimized = true\n\t\t\tend,\n        \t{ description = \"minimize\", group = \"client\" }\n\t\t),\n\t    awful.key(\n\t\t\t{ mod }, \"m\", \n\t\t\tfunction(c)\n\t\t\t\tc.maximized = not c.maximized\n\t\t\t\tc:raise()\n\t\t\tend,\n        \t{ description = \"toggle maximize\", group = \"client\" }\n\t\t),\n\t    awful.key(\n\t\t\t{ mod }, \"b\", \n\t\t\tfunction(c)\n\t\t\t\tc.sticky = not c.sticky\n\t\t\t\tc.above = not c.above\n\t\t\t\tc:raise()\n\t\t\tend,\n        \t{ description = \"toggle sticky\", group = \"client\" }\n\t\t),\n\t\tawful.key(\n\t\t\t{ mod, \"Shift\" }, \"q\", function(c) \n\t\t\t\tc:kill() \n\t\t\tend,\n \t\t\t{ description = \"close\", group = \"client\" }\n\t\t),\n\n    })\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/config/init.lua",
    "content": "require(\"config.main\")\nrequire(\"config.bind\")\nrequire(\"config.rule\")\nrequire(\"config.shot\")\n"
  },
  {
    "path": "src/usr/share/calla/desktop/config/main.lua",
    "content": "local awful = require(\"awful\")\nlocal gears = require(\"gears\")\nlocal beautiful = require(\"beautiful\")\n\n-- Manage new windows\n\nclient.connect_signal('manage', function(c)\n\n\t-- Put new windows in stack\n\n\tif not awesome.startup then awful.client.setslave(c) end\n\tif awesome.startup and not c.size_hints.user_position and not c.size_hints.program_position then\n\t\tawful.placement.no_offscreen(c)\n\tend\n\n\tif c.x == 0 and c.y == 0 then\n\t\tawful.placement.centered(c)\n\tend\n\n\t-- Default icon\n\n\tlocal cairo = require(\"lgi\").cairo\n\tlocal defaulticon = \"/usr/share/icons/\" .. beautiful.icons .. \"/64x64/apps/application-default-icon.svg\"\n\tif c and c.valid and not c.icon then\n\t\tlocal s = gears.surface(defaulticon)\n\t\tlocal img = cairo.ImageSurface.create(cairo.Format.ARGB32, s:get_width(), s:get_height())\n\t\tlocal cr = cairo.Context(img)\n\t\tcr:set_source_surface(s, 0, 0)\n\t\tcr:paint()\n\t\tc.icon = img._native\n\tend\n\nend)\n\n-- Sloppy focus\n\nclient.connect_signal(\"mouse::enter\", function(c)\n    c:activate { context = \"mouse_enter\", raise = false }\nend)\n\n-- Layouts and tag table\n\nscreen.connect_signal(\"request::desktop_decoration\", function(s)\n\ttag.connect_signal(\"request::default_layouts\", function()\n    \tawful.layout.append_default_layouts({\n\t\t\tawful.layout.suit.tile,\n\t\t\tawful.layout.suit.floating,\n    \t})\n\tend)\n\n    awful.tag({ \"1\", \"2\", \"3\" }, s, awful.layout.layouts[1])\nend)\n\n-- Touchpad gestures\n\nawesome.connect_signal(\"touchpad::gesture\", function(direction)\n\trequire(\"naughty\").notification{text=\"test\"}\n\tif direction == \"left\" then\n\t\tawful.tag.viewprev()\n\telseif direction == \"right\" then\n\t\tawful.tag.viewnext()\n\telseif direction == \"up\" then\n\t\tawesome.emit_signal(\"widget::preview\")\n\telseif direction == \"down\" then\n\t\tawesome.emit_signal(\"widget::preview:hide\")\n\tend\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/config/rule.lua",
    "content": "local awful = require(\"awful\")\nlocal gears = require(\"gears\")\nlocal ruled = require(\"ruled\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\nruled.client.connect_signal(\"request::rules\", function()\n\n\t-- New clients\n\n    ruled.client.append_rule {\n        id         = \"global\",\n        rule       = { },\n        properties = {\n            focus     = awful.client.focus.filter,\n            raise     = true,\n            screen    = awful.screen.preferred,\n\t\t\tplacement = awful.placement.no_offscreen\n\t\t}\n    }\n\n    -- Floating clients\n\n    ruled.client.append_rule {\n        id       = \"floating\",\n        rule_any = {\n            instance = { \"copyq\", \"pinentry\" },\n            class    = {\n                \"Arandr\", \"Gcolor3\", \"Blueberry.py\", \"SimpleScreenRecorder\", \"Usbimager\", \"Yad\", \"Settings\"\n            },\n            name    = {\n                \"Event Tester\",  -- xev\n\t\t\t\t\"ncmpcpp\"\n            },\n        },\n        properties = { floating = true }\n    }\n\n    -- Titlebars\n\n    ruled.client.append_rule {\n        id         = \"titlebars\",\n        rule_any   = { type = { \"normal\", \"dialog\" } },\n        properties = { titlebars_enabled = true }\n    }\n\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/config/shot.lua",
    "content": "local awful = require(\"awful\")\nlocal naughty = require(\"naughty\")\n\nlocal function screenshot(args)\n\n\tlocal tmp = \"/tmp/\" .. os.date(\"%F-%H%M%S\") .. \".png\"\n\t\n\tawful.spawn.easy_async_with_shell(\"sleep \" .. args.time .. \" && maim \" .. args.args .. \" \" .. tmp, function()\n\t\tawful.spawn.easy_async_with_shell(\"[ -e '\" .. tmp .. \"' ] && echo exists\", function(output)\n\t\t\tif output:match('%w+') then\n\t\t\t\t--[[awful.spawn.easy_async_with_shell(\"convert -trim \" .. tmp .. \" \" .. tmp, function()\n\t\t\t\t\tawful.spawn.with_shell(\"cat \" .. tmp .. \" | xclip -se c -t image/png -i\")\n\t\t\t\t\tawful.spawn.with_shell(\"cp \" .. tmp .. \" \" .. user.shotdir)\n\t\t\t\t\tawful.spawn.with_shell(\"rm \" .. tmp)\n\t\t\t\tend)--]]\n\t\t\t\tawful.spawn.with_shell(\"cat \" .. tmp .. \" | xclip -se c -t image/png -i\")\n\t\t\t\tawful.spawn.with_shell(\"cp \" .. tmp .. \" \" .. user.shotdir)\n\t\t\t\tawful.spawn.with_shell(\"rm \" .. tmp)\n\t\t\t\tnaughty.notification {\n\t\t\t\t\ttitle = \"Screenshot\",\n\t\t\t\t\ttext = \"Saved to \" .. user.shotdir\n\t\t\t\t}\n\t\t\telse\n\t\t\t\tnaughty.notification {\n\t\t\t\t\ttitle = \"Screenshot\",\n\t\t\t\t\ttext = \"Cancelled\"\n\t\t\t\t}\n\t\t\tend\n\t\tend)\n\tend)\n\nend\n\nawesome.connect_signal(\"util::screenshot\", function(args)\n\tscreenshot(args)\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/json.lua",
    "content": "-- -*- coding: utf-8 -*-\n--\n-- Simple JSON encoding and decoding in pure Lua.\n--\n-- Copyright 2010-2017 Jeffrey Friedl\n-- http://regex.info/blog/\n-- Latest version: http://regex.info/blog/lua/json\n--\n-- This code is released under a Creative Commons CC-BY \"Attribution\" License:\n-- http://creativecommons.org/licenses/by/3.0/deed.en_US\n--\n-- It can be used for any purpose so long as:\n--    1) the copyright notice above is maintained\n--    2) the web-page links above are maintained\n--    3) the 'AUTHOR_NOTE' string below is maintained\n--\nlocal VERSION = '20211016.28' -- version history at end of file\nlocal AUTHOR_NOTE = \"-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20211016.28 ]-\"\n\n--\n-- The 'AUTHOR_NOTE' variable exists so that information about the source\n-- of the package is maintained even in compiled versions. It's also\n-- included in OBJDEF below mostly to quiet warnings about unused variables.\n--\nlocal OBJDEF = {\n   VERSION      = VERSION,\n   AUTHOR_NOTE  = AUTHOR_NOTE,\n}\n\n\n--\n-- Simple JSON encoding and decoding in pure Lua.\n-- JSON definition: http://www.json.org/\n--\n--\n--   JSON = assert(loadfile \"JSON.lua\")() -- one-time load of the routines\n--\n--   local lua_value = JSON:decode(raw_json_text)\n--\n--   local raw_json_text    = JSON:encode(lua_table_or_value)\n--   local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- \"pretty printed\" version for human readability\n--\n--\n--\n-- DECODING (from a JSON string to a Lua table)\n--\n--\n--   JSON = assert(loadfile \"JSON.lua\")() -- one-time load of the routines\n--\n--   local lua_value = JSON:decode(raw_json_text)\n--\n--   If the JSON text is for an object or an array, e.g.\n--     { \"what\": \"books\", \"count\": 3 }\n--   or\n--     [ \"Larry\", \"Curly\", \"Moe\" ]\n--\n--   the result is a Lua table, e.g.\n--     { what = \"books\", count = 3 }\n--   or\n--     { \"Larry\", \"Curly\", \"Moe\" }\n--\n--\n--   The encode and decode routines accept an optional second argument,\n--   \"etc\", which is not used during encoding or decoding, but upon error\n--   is passed along to error handlers. It can be of any type (including nil).\n--\n--\n--\n-- ERROR HANDLING DURING DECODE\n--\n--   With most errors during decoding, this code calls\n--\n--      JSON:onDecodeError(message, text, location, etc)\n--\n--   with a message about the error, and if known, the JSON text being\n--   parsed and the byte count where the problem was discovered. You can\n--   replace the default JSON:onDecodeError() with your own function.\n--\n--   The default onDecodeError() merely augments the message with data\n--   about the text and the location (and, an 'etc' argument had been\n--   provided to decode(), its value is tacked onto the message as well),\n--   and then calls JSON.assert(), which itself defaults to Lua's built-in\n--   assert(), and can also be overridden.\n--\n--   For example, in an Adobe Lightroom plugin, you might use something like\n--\n--          function JSON:onDecodeError(message, text, location, etc)\n--             LrErrors.throwUserError(\"Internal Error: invalid JSON data\")\n--          end\n--\n--   or even just\n--\n--          function JSON.assert(message)\n--             LrErrors.throwUserError(\"Internal Error: \" .. message)\n--          end\n--\n--   If JSON:decode() is passed a nil, this is called instead:\n--\n--      JSON:onDecodeOfNilError(message, nil, nil, etc)\n--\n--   and if JSON:decode() is passed HTML instead of JSON, this is called:\n--\n--      JSON:onDecodeOfHTMLError(message, text, nil, etc)\n--\n--   The use of the 'etc' argument allows stronger coordination between\n--   decoding and error reporting, especially when you provide your own\n--   error-handling routines. Continuing with the the Adobe Lightroom\n--   plugin example:\n--\n--          function JSON:onDecodeError(message, text, location, etc)\n--             local note = \"Internal Error: invalid JSON data\"\n--             if type(etc) = 'table' and etc.photo then\n--                note = note .. \" while processing for \" .. etc.photo:getFormattedMetadata('fileName')\n--             end\n--             LrErrors.throwUserError(note)\n--          end\n--\n--            :\n--            :\n--\n--          for i, photo in ipairs(photosToProcess) do\n--               :             \n--               :             \n--               local data = JSON:decode(someJsonText, { photo = photo })\n--               :             \n--               :             \n--          end\n--\n--\n--\n--   If the JSON text passed to decode() has trailing garbage (e.g. as with the JSON \"[123]xyzzy\"),\n--   the method\n--\n--       JSON:onTrailingGarbage(json_text, location, parsed_value, etc)\n--\n--   is invoked, where:\n--\n--       'json_text' is the original JSON text being parsed,\n--       'location' is the count of bytes into 'json_text' where the garbage starts (6 in the example),\n--       'parsed_value' is the Lua result of what was successfully parsed ({123} in the example),\n--       'etc' is as above.\n--\n--   If JSON:onTrailingGarbage() does not abort, it should return the value decode() should return,\n--   or nil + an error message.\n--\n--     local new_value, error_message = JSON:onTrailingGarbage()\n--\n--   The default JSON:onTrailingGarbage() simply invokes JSON:onDecodeError(\"trailing garbage\"...),\n--   but you can have this package ignore trailing garbage via\n--\n--      function JSON:onTrailingGarbage(json_text, location, parsed_value, etc)\n--         return parsed_value\n--      end\n--\n--\n-- DECODING AND STRICT TYPES\n--\n--   Because both JSON objects and JSON arrays are converted to Lua tables,\n--   it's not normally possible to tell which original JSON type a\n--   particular Lua table was derived from, or guarantee decode-encode\n--   round-trip equivalency.\n--\n--   However, if you enable strictTypes, e.g.\n--\n--      JSON = assert(loadfile \"JSON.lua\")() --load the routines\n--      JSON.strictTypes = true\n--\n--   then the Lua table resulting from the decoding of a JSON object or\n--   JSON array is marked via Lua metatable, so that when re-encoded with\n--   JSON:encode() it ends up as the appropriate JSON type.\n--\n--   (This is not the default because other routines may not work well with\n--   tables that have a metatable set, for example, Lightroom API calls.)\n--\n--\n-- DECODING AND STRICT PARSING\n--\n--   If strictParsing is true in your JSON object, or if you set strictParsing as a decode option,\n--   some kinds of technically-invalid JSON that would normally be accepted are rejected with an error.\n--\n--   For example, passing in an empty string\n--\n--      JSON:decode(\"\")\n--\n--   normally succeeds with a return value of nil, but\n--\n--      JSON:decode(\"\", nil, { strictParsing = true })\n--\n--   results in an error being raised (onDecodeError is called).\n--\n--      JSON.strictParsing = true\n--      JSON:decode(\"\")\n--\n--   achieves the same thing.\n--\n--\n--\n-- ENCODING (from a lua table to a JSON string)\n--\n--   JSON = assert(loadfile \"JSON.lua\")() -- one-time load of the routines\n--\n--   local raw_json_text    = JSON:encode(lua_table_or_value)\n--   local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- \"pretty printed\" version for human readability\n--   local custom_pretty    = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = \"|  \", align_keys = false })\n--\n--   On error during encoding, this code calls:\n--\n--     JSON:onEncodeError(message, etc)\n--\n--   which you can override in your local JSON object. Also see \"HANDLING UNSUPPORTED VALUE TYPES\" below.\n--\n--   The 'etc' in the error call is the second argument to encode() and encode_pretty(), or nil if it wasn't provided.\n--\n--\n--\n--\n-- ENCODING OPTIONS\n--\n--   An optional third argument, a table of options, can be provided to encode().\n--\n--       encode_options =  {\n--           -- options for making \"pretty\" human-readable JSON (see \"PRETTY-PRINTING\" below)\n--           pretty         = true,   -- turn pretty formatting on\n--           indent         = \"   \",  -- use this indent for each level of an array/object\n--           align_keys     = false,  -- if true, align the keys in a way that sounds like it should be nice, but is actually ugly\n--           array_newline  = false,  -- if true, array elements become one to a line rather than inline\n--           \n--           -- other output-related options\n--           null           = \"\\0\",   -- see \"ENCODING JSON NULL VALUES\" below\n--           stringsAreUtf8 = false,  -- see \"HANDLING UNICODE LINE AND PARAGRAPH SEPARATORS FOR JAVA\" below\n--       }\n--  \n--       json_string = JSON:encode(mytable, etc, encode_options)\n--\n--\n--\n-- For reference, the defaults are:\n--\n--           pretty         = false\n--           null           = nil,\n--           stringsAreUtf8 = false,\n--\n--\n--\n-- PRETTY-PRINTING\n--\n--   Enabling the 'pretty' encode option helps generate human-readable JSON.\n--\n--     pretty = JSON:encode(val, etc, {\n--                                       pretty = true,\n--                                       indent = \"   \",\n--                                       align_keys = false,\n--                                     })\n--\n--   encode_pretty() is also provided: it's identical to encode() except\n--   that encode_pretty() provides a default options table if none given in the call:\n--\n--       { pretty = true, indent = \"  \", align_keys = false, array_newline = false }\n--\n--   For example, if\n--\n--      JSON:encode(data)\n--\n--   produces:\n--\n--      {\"city\":\"Kyoto\",\"climate\":{\"avg_temp\":16,\"humidity\":\"high\",\"snowfall\":\"minimal\"},\"country\":\"Japan\",\"wards\":11}\n--\n--   then\n--\n--      JSON:encode_pretty(data)\n--\n--   produces:\n--\n--      {\n--        \"city\": \"Kyoto\",\n--        \"climate\": {\n--          \"avg_temp\": 16,\n--          \"humidity\": \"high\",\n--          \"snowfall\": \"minimal\"\n--        },\n--        \"country\": \"Japan\",\n--        \"wards\": 11\n--      }\n--\n--   The following lines all return identical strings:\n--       JSON:encode_pretty(data)\n--       JSON:encode_pretty(data, nil, { pretty = true, indent = \"  \", align_keys = false, array_newline = false})\n--       JSON:encode_pretty(data, nil, { pretty = true, indent = \"  \" })\n--       JSON:encode       (data, nil, { pretty = true, indent = \"  \" })\n--\n--   An example of setting your own indent string:\n--\n--     JSON:encode_pretty(data, nil, { pretty = true, indent = \"|    \" })\n--\n--   produces:\n--\n--      {\n--      |    \"city\": \"Kyoto\",\n--      |    \"climate\": {\n--      |    |    \"avg_temp\": 16,\n--      |    |    \"humidity\": \"high\",\n--      |    |    \"snowfall\": \"minimal\"\n--      |    },\n--      |    \"country\": \"Japan\",\n--      |    \"wards\": 11\n--      }\n--\n--   An example of setting align_keys to true:\n--\n--     JSON:encode_pretty(data, nil, { pretty = true, indent = \"  \", align_keys = true })\n--  \n--   produces:\n--   \n--      {\n--           \"city\": \"Kyoto\",\n--        \"climate\": {\n--                     \"avg_temp\": 16,\n--                     \"humidity\": \"high\",\n--                     \"snowfall\": \"minimal\"\n--                   },\n--        \"country\": \"Japan\",\n--          \"wards\": 11\n--      }\n--\n--   which I must admit is kinda ugly, sorry. This was the default for\n--   encode_pretty() prior to version 20141223.14.\n--\n--\n--  HANDLING UNICODE LINE AND PARAGRAPH SEPARATORS FOR JAVA\n--\n--    If the 'stringsAreUtf8' encode option is set to true, consider Lua strings not as a sequence of bytes,\n--    but as a sequence of UTF-8 characters.\n--\n--    Currently, the only practical effect of setting this option is that Unicode LINE and PARAGRAPH\n--    separators, if found in a string, are encoded with a JSON escape instead of being dumped as is.\n--    The JSON is valid either way, but encoding this way, apparently, allows the resulting JSON\n--    to also be valid Java.\n--\n--  AMBIGUOUS SITUATIONS DURING THE ENCODING\n--\n--   During the encode, if a Lua table being encoded contains both string\n--   and numeric keys, it fits neither JSON's idea of an object, nor its\n--   idea of an array. To get around this, when any string key exists (or\n--   when non-positive numeric keys exist), numeric keys are converted to\n--   strings.\n--\n--   For example, \n--     JSON:encode({ \"one\", \"two\", \"three\", SOMESTRING = \"some string\" }))\n--   produces the JSON object\n--     {\"1\":\"one\",\"2\":\"two\",\"3\":\"three\",\"SOMESTRING\":\"some string\"}\n--\n--   To prohibit this conversion and instead make it an error condition, set\n--      JSON.noKeyConversion = true\n--\n--\n-- ENCODING JSON NULL VALUES\n--\n--   Lua tables completely omit keys whose value is nil, so without special handling there's\n--   no way to represent JSON object's null value in a Lua table.  For example\n--      JSON:encode({ username = \"admin\", password = nil })\n--\n--   produces:\n--\n--      {\"username\":\"admin\"}\n--\n--   In order to actually produce\n--\n--      {\"username\":\"admin\", \"password\":null}\n--\n\n--   one can include a string value for a \"null\" field in the options table passed to encode().... \n--   any Lua table entry with that value becomes null in the JSON output:\n--\n--      JSON:encode({ username = \"admin\", password = \"xyzzy\" }, -- First arg is the Lua table to encode as JSON.\n--                  nil,                                        -- Second arg is the 'etc' value, ignored here\n--                  { null = \"xyzzy\" })                         -- Third arg is th options table\n--\n--   produces:\n--\n--      {\"username\":\"admin\", \"password\":null}\n--\n--   Just be sure to use a string that is otherwise unlikely to appear in your data.\n--   The string \"\\0\" (a string with one null byte) may well be appropriate for many applications.\n--\n--   The \"null\" options also applies to Lua tables that become JSON arrays.\n--      JSON:encode({ \"one\", \"two\", nil, nil })\n--\n--   produces\n--\n--      [\"one\",\"two\"]\n--\n--   while\n--\n--      NullPlaceholder = \"\\0\"\n--      encode_options = { null = NullPlaceholder }\n--      JSON:encode({ \"one\", \"two\", NullPlaceholder, NullPlaceholder}, nil, encode_options)\n--   produces\n--\n--      [\"one\",\"two\",null,null]\n--\n--\n--\n-- HANDLING LARGE AND/OR PRECISE NUMBERS\n--\n--\n--   Without special handling, numbers in JSON can lose precision in Lua.\n--   For example:\n--   \n--      T = JSON:decode('{  \"small\":12345, \"big\":12345678901234567890123456789, \"precise\":9876.67890123456789012345  }')\n--\n--      print(\"small:   \",  type(T.small),    T.small)\n--      print(\"big:     \",  type(T.big),      T.big)\n--      print(\"precise: \",  type(T.precise),  T.precise)\n--   \n--   produces\n--   \n--      small:          number  12345\n--      big:            number  1.2345678901235e+28\n--      precise:        number  9876.6789012346\n--\n--   Precision is lost with both 'big' and 'precise'.\n--\n--   This package offers ways to try to handle this better (for some definitions of \"better\")...\n--\n--   The most precise method is by setting the global:\n--   \n--      JSON.decodeNumbersAsObjects = true\n--   \n--   When this is set, numeric JSON data is encoded into Lua in a form that preserves the exact\n--   JSON numeric presentation when re-encoded back out to JSON, or accessed in Lua as a string.\n--\n--   This is done by encoding the numeric data with a Lua table/metatable that returns\n--   the possibly-imprecise numeric form when accessed numerically, but the original precise\n--   representation when accessed as a string.\n--\n--   Consider the example above, with this option turned on:\n--\n--      JSON.decodeNumbersAsObjects = true\n--      \n--      T = JSON:decode('{  \"small\":12345, \"big\":12345678901234567890123456789, \"precise\":9876.67890123456789012345  }')\n--\n--      print(\"small:   \",  type(T.small),    T.small)\n--      print(\"big:     \",  type(T.big),      T.big)\n--      print(\"precise: \",  type(T.precise),  T.precise)\n--   \n--   This now produces:\n--   \n--      small:          table   12345\n--      big:            table   12345678901234567890123456789\n--      precise:        table   9876.67890123456789012345\n--   \n--   However, within Lua you can still use the values (e.g. T.precise in the example above) in numeric\n--   contexts. In such cases you'll get the possibly-imprecise numeric version, but in string contexts\n--   and when the data finds its way to this package's encode() function, the original full-precision\n--   representation is used.\n--\n--   You can force access to the string or numeric version via\n--        JSON:forceString()\n--        JSON:forceNumber()\n--   For example,\n--        local probably_okay = JSON:forceNumber(T.small) -- 'probably_okay' is a number\n--\n--   Code the inspects the JSON-turned-Lua data using type() can run into troubles because what used to\n--   be a number can now be a table (e.g. as the small/big/precise example above shows). Update these\n--   situations to use JSON:isNumber(item), which returns nil if the item is neither a number nor one\n--   of these number objects. If it is either, it returns the number itself. For completeness there's\n--   also JSON:isString(item).\n--\n--   If you want to try to avoid the hassles of this \"number as an object\" kludge for all but really\n--   big numbers, you can set JSON.decodeNumbersAsObjects and then also set one or both of\n--            JSON:decodeIntegerObjectificationLength\n--            JSON:decodeDecimalObjectificationLength\n--   They refer to the length of the part of the number before and after a decimal point. If they are\n--   set and their part is at least that number of digits, objectification occurs. If both are set,\n--   objectification occurs when either length is met.\n--\n--   -----------------------\n--\n--   Even without using the JSON.decodeNumbersAsObjects option, you can encode numbers in your Lua\n--   table that retain high precision upon encoding to JSON, by using the JSON:asNumber() function:\n--\n--      T = {\n--         imprecise =                123456789123456789.123456789123456789,\n--         precise   = JSON:asNumber(\"123456789123456789.123456789123456789\")\n--      }\n--\n--      print(JSON:encode_pretty(T))\n--\n--   This produces:\n--\n--      { \n--         \"precise\": 123456789123456789.123456789123456789,\n--         \"imprecise\": 1.2345678912346e+17\n--      }\n--\n--\n--   -----------------------\n--\n--   A different way to handle big/precise JSON numbers is to have decode() merely return the exact\n--   string representation of the number instead of the number itself. This approach might be useful\n--   when the numbers are merely some kind of opaque object identifier and you want to work with them\n--   in Lua as strings anyway.\n--   \n--   This approach is enabled by setting\n--\n--      JSON.decodeIntegerStringificationLength = 10\n--\n--   The value is the number of digits (of the integer part of the number) at which to stringify numbers.\n--   NOTE: this setting is ignored if JSON.decodeNumbersAsObjects is true, as that takes precedence.\n--\n--   Consider our previous example with this option set to 10:\n--\n--      JSON.decodeIntegerStringificationLength = 10\n--      \n--      T = JSON:decode('{  \"small\":12345, \"big\":12345678901234567890123456789, \"precise\":9876.67890123456789012345  }')\n--\n--      print(\"small:   \",  type(T.small),    T.small)\n--      print(\"big:     \",  type(T.big),      T.big)\n--      print(\"precise: \",  type(T.precise),  T.precise)\n--\n--   This produces:\n--\n--      small:          number  12345\n--      big:            string  12345678901234567890123456789\n--      precise:        number  9876.6789012346\n--\n--   The long integer of the 'big' field is at least JSON.decodeIntegerStringificationLength digits\n--   in length, so it's converted not to a Lua integer but to a Lua string. Using a value of 0 or 1 ensures\n--   that all JSON numeric data becomes strings in Lua.\n--\n--   Note that unlike\n--      JSON.decodeNumbersAsObjects = true\n--   this stringification is simple and unintelligent: the JSON number simply becomes a Lua string, and that's the end of it.\n--   If the string is then converted back to JSON, it's still a string. After running the code above, adding\n--      print(JSON:encode(T))\n--   produces\n--      {\"big\":\"12345678901234567890123456789\",\"precise\":9876.6789012346,\"small\":12345}\n--   which is unlikely to be desired.\n--\n--   There's a comparable option for the length of the decimal part of a number:\n--\n--      JSON.decodeDecimalStringificationLength\n--\n--   This can be used alone or in conjunction with\n--\n--      JSON.decodeIntegerStringificationLength\n--\n--   to trip stringification on precise numbers with at least JSON.decodeIntegerStringificationLength digits after\n--   the decimal point. (Both are ignored if JSON.decodeNumbersAsObjects is true.)\n--\n--   This example:\n--\n--      JSON.decodeIntegerStringificationLength = 10\n--      JSON.decodeDecimalStringificationLength =  5\n--\n--      T = JSON:decode('{  \"small\":12345, \"big\":12345678901234567890123456789, \"precise\":9876.67890123456789012345  }')\n--      \n--      print(\"small:   \",  type(T.small),    T.small)\n--      print(\"big:     \",  type(T.big),      T.big)\n--      print(\"precise: \",  type(T.precise),  T.precise)\n--\n--  produces:\n--\n--      small:          number  12345\n--      big:            string  12345678901234567890123456789\n--      precise:        string  9876.67890123456789012345\n--\n--\n--  HANDLING UNSUPPORTED VALUE TYPES\n--\n--   Among the encoding errors that might be raised is an attempt to convert a table value that has a type\n--   that this package hasn't accounted for: a function, userdata, or a thread. You can handle these types as table\n--   values (but not as table keys) if you supply a JSON:unsupportedTypeEncoder() method along the lines of the\n--   following example:\n--        \n--        function JSON:unsupportedTypeEncoder(value_of_unsupported_type)\n--           if type(value_of_unsupported_type) == 'function' then\n--              return \"a function value\"\n--           else\n--              return nil\n--           end\n--        end\n--        \n--   Your unsupportedTypeEncoder() method is actually called with a bunch of arguments:\n--\n--      self:unsupportedTypeEncoder(value, parents, etc, options, indent, for_key)\n--\n--   The 'value' is the function, thread, or userdata to be converted to JSON.\n--\n--   The 'etc' and 'options' arguments are those passed to the original encode(). The other arguments are\n--   probably of little interest; see the source code. (Note that 'for_key' is never true, as this function\n--   is invoked only on table values; table keys of these types still trigger the onEncodeError method.)\n--\n--   If your unsupportedTypeEncoder() method returns a string, it's inserted into the JSON as is.\n--   If it returns nil plus an error message, that error message is passed through to an onEncodeError invocation.\n--   If it returns only nil, processing falls through to a default onEncodeError invocation.\n--\n--   If you want to handle everything in a simple way:\n--\n--        function JSON:unsupportedTypeEncoder(value)\n--           return tostring(value)\n--        end\n--\n--\n-- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT\n--\n--    assert\n--    onDecodeError\n--    onDecodeOfNilError\n--    onDecodeOfHTMLError\n--    onTrailingGarbage\n--    onEncodeError\n--    unsupportedTypeEncoder\n--\n--  If you want to create a separate Lua JSON object with its own error handlers,\n--  you can reload JSON.lua or use the :new() method.\n--\n---------------------------------------------------------------------------\n\nlocal default_pretty_indent  = \"  \"\nlocal default_pretty_options = { pretty = true, indent = default_pretty_indent, align_keys = false, array_newline = false }\n\nlocal isArray  = { __tostring = function() return \"JSON array\"         end }  isArray.__index  = isArray\nlocal isObject = { __tostring = function() return \"JSON object\"        end }  isObject.__index = isObject\n\nfunction OBJDEF:newArray(tbl)\n   return setmetatable(tbl or {}, isArray)\nend\n\nfunction OBJDEF:newObject(tbl)\n   return setmetatable(tbl or {}, isObject)\nend\n\n\n\n\nlocal function getnum(op)\n   return type(op) == 'number' and op or op.N\nend\n\nlocal isNumber = {\n   __tostring = function(T)  return T.S        end,\n   __unm      = function(op) return getnum(op) end,\n\n   __concat   = function(op1, op2) return tostring(op1) .. tostring(op2) end,\n   __add      = function(op1, op2) return getnum(op1)   +   getnum(op2)  end,\n   __sub      = function(op1, op2) return getnum(op1)   -   getnum(op2)  end,\n   __mul      = function(op1, op2) return getnum(op1)   *   getnum(op2)  end,\n   __div      = function(op1, op2) return getnum(op1)   /   getnum(op2)  end,\n   __mod      = function(op1, op2) return getnum(op1)   %   getnum(op2)  end,\n   __pow      = function(op1, op2) return getnum(op1)   ^   getnum(op2)  end,\n   __lt       = function(op1, op2) return getnum(op1)   <   getnum(op2)  end,\n   __eq       = function(op1, op2) return getnum(op1)   ==  getnum(op2)  end,\n   __le       = function(op1, op2) return getnum(op1)   <=  getnum(op2)  end,\n}\nisNumber.__index = isNumber\n\nfunction OBJDEF:asNumber(item)\n\n   if getmetatable(item) == isNumber then\n      -- it's already a JSON number object.\n      return item\n   elseif type(item) == 'table' and type(item.S) == 'string' and type(item.N) == 'number' then\n      -- it's a number-object table that lost its metatable, so give it one\n      return setmetatable(item, isNumber)\n   else\n      -- the normal situation... given a number or a string representation of a number....\n      local holder = {\n         S = tostring(item), -- S is the representation of the number as a string, which remains precise\n         N = tonumber(item), -- N is the number as a Lua number.\n      }\n      return setmetatable(holder, isNumber)\n   end\nend\n\n--\n-- Given an item that might be a normal string or number, or might be an 'isNumber' object defined above,\n-- return the string version. This shouldn't be needed often because the 'isNumber' object should autoconvert\n-- to a string in most cases, but it's here to allow it to be forced when needed.\n--\nfunction OBJDEF:forceString(item)\n   if type(item) == 'table' and type(item.S) == 'string' then\n      return item.S\n   else\n      return tostring(item)\n   end\nend\n\n--\n-- Given an item that might be a normal string or number, or might be an 'isNumber' object defined above,\n-- return the numeric version.\n--\nfunction OBJDEF:forceNumber(item)\n   if type(item) == 'table' and type(item.N) == 'number' then\n      return item.N\n   else\n      return tonumber(item)\n   end\nend\n\n--\n-- If the given item is a number, return it. Otherwise, return nil.\n-- This, this can be used both in a conditional and to access the number when you're not sure its form.\n--\nfunction OBJDEF:isNumber(item)\n   if type(item) == 'number' then\n      return item\n   elseif type(item) == 'table' and type(item.N) == 'number' then\n      return item.N\n   else\n      return nil\n   end\nend\n\nfunction OBJDEF:isString(item)\n   if type(item) == 'string' then\n      return item\n   elseif type(item) == 'table' and type(item.S) == 'string' then\n      return item.S\n   else\n      return nil\n   end\nend\n\n\n\n\n--\n-- Some utf8 routines to deal with the fact that Lua handles only bytes\n--\nlocal function top_three_bits(val)\n   return math.floor(val / 0x20)\nend\n\nlocal function top_four_bits(val)\n   return math.floor(val / 0x10)\nend\n\nlocal function unicode_character_bytecount_based_on_first_byte(first_byte)\n   local W = string.byte(first_byte)\n   if W < 0x80 then\n      return 1\n   elseif (W == 0xC0) or (W == 0xC1) or (W >= 0x80 and W <= 0xBF) or (W >= 0xF5) then\n      -- this is an error -- W can't be the start of a utf8 character\n      return 0\n   elseif top_three_bits(W) == 0x06 then\n      return 2\n   elseif top_four_bits(W) == 0x0E then\n      return 3\n   else\n      return 4\n   end\nend\n\n\n\nlocal function unicode_codepoint_as_utf8(codepoint)\n   --\n   -- codepoint is a number\n   --\n   if codepoint <= 127 then\n      return string.char(codepoint)\n\n   elseif codepoint <= 2047 then\n      --\n      -- 110yyyxx 10xxxxxx         <-- useful notation from http://en.wikipedia.org/wiki/Utf8\n      --\n      local highpart = math.floor(codepoint / 0x40)\n      local lowpart  = codepoint - (0x40 * highpart)\n      return string.char(0xC0 + highpart,\n                         0x80 + lowpart)\n\n   elseif codepoint <= 65535 then\n      --\n      -- 1110yyyy 10yyyyxx 10xxxxxx\n      --\n      local highpart  = math.floor(codepoint / 0x1000)\n      local remainder = codepoint - 0x1000 * highpart\n      local midpart   = math.floor(remainder / 0x40)\n      local lowpart   = remainder - 0x40 * midpart\n\n      highpart = 0xE0 + highpart\n      midpart  = 0x80 + midpart\n      lowpart  = 0x80 + lowpart\n\n      --\n      -- Check for an invalid character (thanks Andy R. at Adobe).\n      -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070\n      --\n      if ( highpart == 0xE0 and midpart < 0xA0 ) or\n         ( highpart == 0xED and midpart > 0x9F ) or\n         ( highpart == 0xF0 and midpart < 0x90 ) or\n         ( highpart == 0xF4 and midpart > 0x8F )\n      then\n         return \"?\"\n      else\n         return string.char(highpart,\n                            midpart,\n                            lowpart)\n      end\n\n   else\n      --\n      -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx\n      --\n      local highpart  = math.floor(codepoint / 0x40000)\n      local remainder = codepoint - 0x40000 * highpart\n      local midA      = math.floor(remainder / 0x1000)\n      remainder       = remainder - 0x1000 * midA\n      local midB      = math.floor(remainder / 0x40)\n      local lowpart   = remainder - 0x40 * midB\n\n      return string.char(0xF0 + highpart,\n                         0x80 + midA,\n                         0x80 + midB,\n                         0x80 + lowpart)\n   end\nend\n\nfunction OBJDEF:onDecodeError(message, text, location, etc)\n   if text then\n      if location then\n         message = string.format(\"%s at byte %d of: %s\", message, location, text)\n      else\n         message = string.format(\"%s: %s\", message, text)\n      end\n   end\n\n   if etc ~= nil then\n      message = message .. \" (\" .. OBJDEF:encode(etc) .. \")\"\n   end\n\n   if self.assert then\n      self.assert(false, message)\n   else\n      assert(false, message)\n   end\nend\n\nfunction OBJDEF:onTrailingGarbage(json_text, location, parsed_value, etc)\n   return self:onDecodeError(\"trailing garbage\", json_text, location, etc)\nend\n\nOBJDEF.onDecodeOfNilError  = OBJDEF.onDecodeError\nOBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError\n\nfunction OBJDEF:onEncodeError(message, etc)\n   if etc ~= nil then\n      message = message .. \" (\" .. OBJDEF:encode(etc) .. \")\"\n   end\n\n   if self.assert then\n      self.assert(false, message)\n   else\n      assert(false, message)\n   end\nend\n\nlocal function grok_number(self, text, start, options)\n   --\n   -- Grab the integer part\n   --\n   local integer_part = text:match('^-?[1-9]%d*', start)\n                     or text:match(\"^-?0\",        start)\n\n   if not integer_part then\n      self:onDecodeError(\"expected number\", text, start, options.etc)\n      return nil, start -- in case the error method doesn't abort, return something sensible\n   end\n\n   local i = start + integer_part:len()\n\n   --\n   -- Grab an optional decimal part\n   --\n   local decimal_part = text:match('^%.%d+', i) or \"\"\n\n   i = i + decimal_part:len()\n\n   --\n   -- Grab an optional exponential part\n   --\n   local exponent_part = text:match('^[eE][-+]?%d+', i) or \"\"\n\n   i = i + exponent_part:len()\n\n   local full_number_text = integer_part .. decimal_part .. exponent_part\n\n   if options.decodeNumbersAsObjects then\n\n      local objectify = false\n\n      if not options.decodeIntegerObjectificationLength and not options.decodeDecimalObjectificationLength then\n         -- no options, so objectify\n         objectify = true\n\n      elseif (options.decodeIntegerObjectificationLength\n          and\n         (integer_part:len() >= options.decodeIntegerObjectificationLength or exponent_part:len() > 0))\n\n          or\n         (options.decodeDecimalObjectificationLength \n          and\n          (decimal_part:len() >= options.decodeDecimalObjectificationLength  or exponent_part:len() > 0))\n      then\n         -- have options and they are triggered, so objectify\n         objectify = true\n      end\n\n      if objectify then\n         return OBJDEF:asNumber(full_number_text), i\n      end\n      -- else, fall through to try to return as a straight-up number\n\n   else\n\n      -- Not always decoding numbers as objects, so perhaps encode as strings?\n\n      --\n      -- If we're told to stringify only under certain conditions, so do.\n      -- We punt a bit when there's an exponent by just stringifying no matter what.\n      -- I suppose we should really look to see whether the exponent is actually big enough one\n      -- way or the other to trip stringification, but I'll be lazy about it until someone asks.\n      --\n      if (options.decodeIntegerStringificationLength\n          and\n         (integer_part:len() >= options.decodeIntegerStringificationLength or exponent_part:len() > 0))\n\n          or\n\n         (options.decodeDecimalStringificationLength \n          and\n          (decimal_part:len() >= options.decodeDecimalStringificationLength or exponent_part:len() > 0))\n      then\n         return full_number_text, i -- this returns the exact string representation seen in the original JSON\n      end\n\n   end\n\n\n   local as_number = tonumber(full_number_text)\n\n   if not as_number then\n      self:onDecodeError(\"bad number\", text, start, options.etc)\n      return nil, start -- in case the error method doesn't abort, return something sensible\n   end\n\n   return as_number, i\nend\n\n\nlocal backslash_escape_conversion = {\n   ['\"'] = '\"',\n   ['/'] = \"/\",\n   ['\\\\'] = \"\\\\\",\n   ['b'] = \"\\b\",\n   ['f'] = \"\\f\",\n   ['n'] = \"\\n\",\n   ['r'] = \"\\r\",\n   ['t'] = \"\\t\",\n}\n\nlocal function grok_string(self, text, start, options)\n\n   if text:sub(start,start) ~= '\"' then\n      self:onDecodeError(\"expected string's opening quote\", text, start, options.etc)\n      return nil, start -- in case the error method doesn't abort, return something sensible\n   end\n\n   local i = start + 1 -- +1 to bypass the initial quote\n   local text_len = text:len()\n   local VALUE = \"\"\n   while i <= text_len do\n      local c = text:sub(i,i)\n      if c == '\"' then\n         return VALUE, i + 1\n      end\n      if c ~= '\\\\' then\n         \n         -- should grab the next bytes as per the number of bytes for this utf8 character\n         local byte_count = unicode_character_bytecount_based_on_first_byte(c)\n\n         local next_character\n         if byte_count == 0 then\n            self:onDecodeError(\"non-utf8 sequence\", text, i, options.etc)\n         elseif byte_count == 1 then\n            if options.strictParsing and string.byte(c) < 0x20 then\n               self:onDecodeError(\"Unescaped control character\", text, i+1, options.etc)\n               return nil, start -- in case the error method doesn't abort, return something sensible\n            end\n            next_character = c\n         elseif byte_count == 2 then\n            next_character = text:match('^(.[\\128-\\191])', i)\n         elseif byte_count == 3 then\n            next_character = text:match('^(.[\\128-\\191][\\128-\\191])', i)\n         elseif byte_count == 4 then\n            next_character = text:match('^(.[\\128-\\191][\\128-\\191][\\128-\\191])', i)\n         end\n\n         if not next_character then\n            self:onDecodeError(\"incomplete utf8 sequence\", text, i, options.etc) \n            return nil, i -- in case the error method doesn't abort, return something sensible           \n         end\n\n\n         VALUE = VALUE .. next_character\n         i = i + byte_count\n\n      else\n         --\n         -- We have a backslash escape\n         --\n         i = i + 1\n\n         local next_byte = text:match('^(.)', i)\n\n         if next_byte == nil then\n            -- string ended after the \\ \n            self:onDecodeError(\"unfinished \\\\ escape\", text, i, options.etc)\n            return nil, start -- in case the error method doesn't abort, return something sensible\n         end\n\n         if backslash_escape_conversion[next_byte] then\n            VALUE = VALUE .. backslash_escape_conversion[next_byte]\n            i = i + 1\n         else\n            --\n            -- The only other valid use of \\ that remains is in the form of \\u####\n            --\n\n            local hex = text:match('^u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)\n            if hex then\n               i = i + 5 -- bypass what we just read\n\n               -- We have a Unicode codepoint. It could be standalone, or if in the proper range and\n               -- followed by another in a specific range, it'll be a two-code surrogate pair.\n               local codepoint = tonumber(hex, 16)\n               if codepoint >= 0xD800 and codepoint <= 0xDBFF then\n                  -- it's a hi surrogate... see whether we have a following low\n                  local lo_surrogate = text:match('^\\\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)\n                  if lo_surrogate then\n                     i = i + 6 -- bypass the low surrogate we just read\n                     codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16)\n                  else\n                     -- not a proper low, so we'll just leave the first codepoint as is and spit it out.\n                  end\n               end\n               VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint)\n\n            elseif options.strictParsing then\n               --local next_byte = text:match('^\\\\(.)', i) printf(\"NEXT[%s]\", next_byte);\n               self:onDecodeError(\"illegal use of backslash escape\", text, i, options.etc)\n               return nil, start -- in case the error method doesn't abort, return something sensible\n            else\n               local byte_count = unicode_character_bytecount_based_on_first_byte(next_byte)\n               if byte_count == 0 then\n                  self:onDecodeError(\"non-utf8 sequence after backslash escape\", text, i, options.etc)\n                  return nil, start -- in case the error method doesn't abort, return something sensible\n               end\n\n               local next_character\n               if byte_count == 1 then\n                  next_character = next_byte\n               elseif byte_count == 2 then\n                  next_character = text:match('^(.[\\128-\\191])', i)\n               elseif byte_count == 3 then\n                  next_character = text:match('^(.[\\128-\\191][\\128-\\191])', i)\n               elseif byte_count == 3 then\n                  next_character = text:match('^(.[\\128-\\191][\\128-\\191][\\128-\\191])', i)\n               end\n\n               if next_character == nil then\n                  -- incomplete utf8 character after escape\n                  self:onDecodeError(\"incomplete utf8 sequence after backslash escape\", text, i, options.etc)\n                  return nil, start -- in case the error method doesn't abort, return something sensible\n               end\n\n               VALUE = VALUE .. next_character\n               i = i + byte_count\n            end\n         end\n      end\n   end\n\n   self:onDecodeError(\"unclosed string\", text, start, options.etc)\n   return nil, start -- in case the error method doesn't abort, return something sensible\nend\n\nlocal function skip_whitespace(text, start)\n\n   local _, match_end = text:find(\"^[ \\n\\r\\t]+\", start) -- [ https://datatracker.ietf.org/doc/html/rfc7158#section-2 ]\n   if match_end then\n      return match_end + 1\n   else\n      return start\n   end\nend\n\nlocal grok_one -- assigned later\n\nlocal function grok_object(self, text, start, options)\n\n   if text:sub(start,start) ~= '{' then\n      self:onDecodeError(\"expected '{'\", text, start, options.etc)\n      return nil, start -- in case the error method doesn't abort, return something sensible\n   end\n\n   local i = skip_whitespace(text, start + 1) -- +1 to skip the '{'\n\n   local VALUE = self.strictTypes and self:newObject { } or { }\n\n   if text:sub(i,i) == '}' then\n      return VALUE, i + 1\n   end\n   local text_len = text:len()\n   while i <= text_len do\n      local key, new_i = grok_string(self, text, i, options)\n\n      i = skip_whitespace(text, new_i)\n\n      if text:sub(i, i) ~= ':' then\n         self:onDecodeError(\"expected colon\", text, i, options.etc)\n         return nil, i -- in case the error method doesn't abort, return something sensible\n      end\n\n      i = skip_whitespace(text, i + 1)\n\n      local new_val, new_i = grok_one(self, text, i, options)\n\n      VALUE[key] = new_val\n\n      --\n      -- Expect now either '}' to end things, or a ',' to allow us to continue.\n      --\n      i = skip_whitespace(text, new_i)\n\n      local c = text:sub(i,i)\n\n      if c == '}' then\n         return VALUE, i + 1\n      end\n\n      if text:sub(i, i) ~= ',' then\n         self:onDecodeError(\"expected comma or '}'\", text, i, options.etc)\n         return nil, i -- in case the error method doesn't abort, return something sensible\n      end\n\n      i = skip_whitespace(text, i + 1)\n   end\n\n   self:onDecodeError(\"unclosed '{'\", text, start, options.etc)\n   return nil, start -- in case the error method doesn't abort, return something sensible\nend\n\nlocal function grok_array(self, text, start, options)\n   if text:sub(start,start) ~= '[' then\n      self:onDecodeError(\"expected '['\", text, start, options.etc)\n      return nil, start -- in case the error method doesn't abort, return something sensible\n   end\n\n   local i = skip_whitespace(text, start + 1) -- +1 to skip the '['\n   local VALUE = self.strictTypes and self:newArray { } or { }\n   if text:sub(i,i) == ']' then\n      return VALUE, i + 1\n   end\n\n   local VALUE_INDEX = 1\n\n   local text_len = text:len()\n   while i <= text_len do\n      local val, new_i = grok_one(self, text, i, options)\n\n      -- can't table.insert(VALUE, val) here because it's a no-op if val is nil\n      VALUE[VALUE_INDEX] = val\n      VALUE_INDEX = VALUE_INDEX + 1\n\n      i = skip_whitespace(text, new_i)\n\n      --\n      -- Expect now either ']' to end things, or a ',' to allow us to continue.\n      --\n      local c = text:sub(i,i)\n      if c == ']' then\n         return VALUE, i + 1\n      end\n      if text:sub(i, i) ~= ',' then\n         self:onDecodeError(\"expected comma or ']'\", text, i, options.etc)\n         return nil, i -- in case the error method doesn't abort, return something sensible\n      end\n      i = skip_whitespace(text, i + 1)\n   end\n   self:onDecodeError(\"unclosed '['\", text, start, options.etc)\n   return nil, i -- in case the error method doesn't abort, return something sensible\nend\n\n\ngrok_one = function(self, text, start, options)\n   -- Skip any whitespace\n   start = skip_whitespace(text, start)\n\n   if start > text:len() then\n      self:onDecodeError(\"unexpected end of string\", text, nil, options.etc)\n      return nil, start -- in case the error method doesn't abort, return something sensible\n   end\n\n   if text:find('^\"', start) then\n      return grok_string(self, text, start, options)\n\n   elseif text:find('^[-0123456789 ]', start) then\n      return grok_number(self, text, start, options)\n\n   elseif text:find('^%{', start) then\n      return grok_object(self, text, start, options)\n\n   elseif text:find('^%[', start) then\n      return grok_array(self, text, start, options)\n\n   elseif text:find('^true', start) then\n      return true, start + 4\n\n   elseif text:find('^false', start) then\n      return false, start + 5\n\n   elseif text:find('^null', start) then\n      return options.null, start + 4\n\n   else\n      self:onDecodeError(\"can't parse JSON\", text, start, options.etc)\n      return nil, 1 -- in case the error method doesn't abort, return something sensible\n   end\nend\n\nfunction OBJDEF:decode(text, etc, options)\n   --\n   -- If the user didn't pass in a table of decode options, make an empty one.\n   --\n   if type(options) ~= 'table' then\n      options = {}\n   end\n\n   --\n   -- If they passed in an 'etc' argument, stuff it into the options.\n   -- (If not, any 'etc' field in the options they passed in remains to be used)\n   --\n   if etc ~= nil then\n      options.etc = etc\n   end\n\n\n   --\n   -- apply global options\n   --\n   if options.decodeNumbersAsObjects == nil then\n      options.decodeNumbersAsObjects = self.decodeNumbersAsObjects\n   end\n   if options.decodeIntegerObjectificationLength == nil then\n      options.decodeIntegerObjectificationLength = self.decodeIntegerObjectificationLength\n   end\n   if options.decodeDecimalObjectificationLength == nil then\n      options.decodeDecimalObjectificationLength = self.decodeDecimalObjectificationLength\n   end\n   if options.decodeIntegerStringificationLength == nil then\n      options.decodeIntegerStringificationLength = self.decodeIntegerStringificationLength\n   end\n   if options.decodeDecimalStringificationLength == nil then\n      options.decodeDecimalStringificationLength = self.decodeDecimalStringificationLength\n   end\n   if options.strictParsing == nil then\n      options.strictParsing = self.strictParsing\n   end\n\n\n   if type(self) ~= 'table' or self.__index ~= OBJDEF then\n      local error_message = \"JSON:decode must be called in method format\"\n      OBJDEF:onDecodeError(error_message, nil, nil, options.etc)\n      return nil, error_message -- in case the error method doesn't abort, return something sensible\n   end\n\n   if text == nil then\n      local error_message = \"nil passed to JSON:decode()\"\n      self:onDecodeOfNilError(error_message, nil, nil, options.etc)\n      return nil, error_message -- in case the error method doesn't abort, return something sensible\n\n   elseif type(text) ~= 'string' then\n      local error_message = \"expected string argument to JSON:decode()\"\n      self:onDecodeError(string.format(\"%s, got %s\", error_message, type(text)), nil, nil, options.etc)\n      return nil, error_message -- in case the error method doesn't abort, return something sensible\n   end\n\n   -- If passed an empty string....\n   if text:match('^%s*$') then\n      if options.strictParsing then\n         local error_message = \"empty string passed to JSON:decode()\"\n         self:onDecodeOfNilError(error_message, nil, nil, options.etc)\n         return nil, error_message -- in case the error method doesn't abort, return something sensible\n      else\n         -- we'll consider it nothing, but not an error\n         return nil\n      end\n   end\n\n   if text:match('^%s*<') then\n      -- Can't be JSON... we'll assume it's HTML\n      local error_message = \"HTML passed to JSON:decode()\"\n      self:onDecodeOfHTMLError(error_message, text, nil, options.etc)\n      return nil, error_message -- in case the error method doesn't abort, return something sensible\n   end\n\n   --\n   -- Ensure that it's not UTF-32 or UTF-16.\n   -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3),\n   -- but this package can't handle them.\n   --\n   if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then\n      local error_message = \"JSON package groks only UTF-8, sorry\"\n      self:onDecodeError(error_message, text, nil, options.etc)\n      return nil, error_message -- in case the error method doesn't abort, return something sensible\n   end\n\n\n   --\n   -- Finally, go parse it\n   --\n   local success, value, next_i = pcall(grok_one, self, text, 1, options)\n\n   if success then\n\n      local error_message = nil\n      if next_i ~= #text + 1 then\n         -- something's left over after we parsed the first thing.... whitespace is allowed.\n         next_i = skip_whitespace(text, next_i)\n\n         -- if we have something left over now, it's trailing garbage\n         if next_i ~= #text + 1 then\n            value, error_message = self:onTrailingGarbage(text, next_i, value, options.etc)\n         end\n      end\n      return value, error_message\n\n   else\n\n      -- If JSON:onDecodeError() didn't abort out of the pcall, we'll have received\n      -- the error message here as \"value\", so pass it along as an assert.\n      local error_message = value\n      if self.assert then\n         self.assert(false, error_message)\n      else\n         assert(false, error_message)\n      end\n      -- ...and if we're still here (because the assert didn't throw an error),\n      -- return a nil and throw the error message on as a second arg\n      return nil, error_message\n\n   end\nend\n\nlocal function backslash_replacement_function(c)\n   if     c == \"\\n\" then     return \"\\\\n\"\n   elseif c == \"\\r\" then     return \"\\\\r\"\n   elseif c == \"\\t\" then     return \"\\\\t\"\n   elseif c == \"\\b\" then     return \"\\\\b\"\n   elseif c == \"\\f\" then     return \"\\\\f\"\n   elseif c == '\"' then      return '\\\\\"'\n   elseif c == '\\\\' then     return '\\\\\\\\'\n   elseif c == '/' then      return '/'\n   else\n      return string.format(\"\\\\u%04x\", c:byte())\n   end\nend\n\nlocal chars_to_be_escaped_in_JSON_string\n   = '['\n   ..    '\"'    -- class sub-pattern to match a double quote\n   ..    '%\\\\'  -- class sub-pattern to match a backslash\n   ..    '/'    -- class sub-pattern to match a forwardslash\n   ..    '%z'   -- class sub-pattern to match a null\n   ..    '\\001' .. '-' .. '\\031' -- class sub-pattern to match control characters\n   .. ']'\n\n\nlocal LINE_SEPARATOR_as_utf8      = unicode_codepoint_as_utf8(0x2028)\nlocal PARAGRAPH_SEPARATOR_as_utf8 = unicode_codepoint_as_utf8(0x2029)\nlocal function json_string_literal(value, options)\n   local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function)\n   if options.stringsAreUtf8 then\n      --\n      -- This feels really ugly to just look into a string for the sequence of bytes that we know to be a particular utf8 character,\n      -- but utf8 was designed purposefully to make this kind of thing possible. Still, feels dirty.\n      -- I'd rather decode the byte stream into a character stream, but it's not technically needed so\n      -- not technically worth it.\n      --\n      newval = newval:gsub(LINE_SEPARATOR_as_utf8, '\\\\u2028'):gsub(PARAGRAPH_SEPARATOR_as_utf8,'\\\\u2029')\n   end\n   return '\"' .. newval .. '\"'\nend\n\nlocal function object_or_array(self, T, etc)\n   --\n   -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON\n   -- object. If there are only numbers, it's a JSON array.\n   --\n   -- If we'll be converting to a JSON object, we'll want to sort the keys so that the\n   -- end result is deterministic.\n   --\n   local string_keys = { }\n   local number_keys = { }\n   local number_keys_must_be_strings = false\n   local maximum_number_key\n\n   for key in pairs(T) do\n      if type(key) == 'string' then\n         table.insert(string_keys, key)\n      elseif type(key) == 'number' then\n         table.insert(number_keys, key)\n         if key <= 0 or key >= math.huge then\n            number_keys_must_be_strings = true\n         elseif not maximum_number_key or key > maximum_number_key then\n            maximum_number_key = key\n         end\n      elseif type(key) == 'boolean' then\n         table.insert(string_keys, tostring(key))\n      else\n         self:onEncodeError(\"can't encode table with a key of type \" .. type(key), etc)\n      end\n   end\n\n   if #string_keys == 0 and not number_keys_must_be_strings then\n      --\n      -- An empty table, or a numeric-only array\n      --\n      if #number_keys > 0 then\n         return nil, maximum_number_key -- an array\n      elseif tostring(T) == \"JSON array\" then\n         return nil\n      elseif tostring(T) == \"JSON object\" then\n         return { }\n      else\n         -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects\n         return nil\n      end\n   end\n\n   table.sort(string_keys)\n\n   local map\n   if #number_keys > 0 then\n      --\n      -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array\n      -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object.\n      --\n\n      if self.noKeyConversion then\n         self:onEncodeError(\"a table with both numeric and string keys could be an object or array; aborting\", etc)\n      end\n\n      --\n      -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings\n      --\n      map = { }\n      for key, val in pairs(T) do\n         map[key] = val\n      end\n\n      table.sort(number_keys)\n\n      --\n      -- Throw numeric keys in there as strings\n      --\n      for _, number_key in ipairs(number_keys) do\n         local string_key = tostring(number_key)\n         if map[string_key] == nil then\n            table.insert(string_keys , string_key)\n            map[string_key] = T[number_key]\n         else\n            self:onEncodeError(\"conflict converting table with mixed-type keys into a JSON object: key \" .. number_key .. \" exists both as a string and a number.\", etc)\n         end\n      end\n   end\n\n   return string_keys, nil, map\nend\n\n--\n-- Encode\n--\n-- 'options' is nil, or a table with possible keys:\n--\n--    pretty         -- If true, return a pretty-printed version.\n--\n--    indent         -- A string (usually of spaces) used to indent each nested level.\n--\n--    align_keys     -- If true, align all the keys when formatting a table. The result is uglier than one might at first imagine.\n--                      Results are undefined if 'align_keys' is true but 'pretty' is not.\n--\n--    array_newline  -- If true, array elements are formatted each to their own line. The default is to all fall inline.\n--                      Results are undefined if 'array_newline' is true but 'pretty' is not.\n--\n--    null           -- If this exists with a string value, table elements with this value are output as JSON null.\n--\n--    stringsAreUtf8 -- If true, consider Lua strings not as a sequence of bytes, but as a sequence of UTF-8 characters.\n--                      (Currently, the only practical effect of setting this option is that Unicode LINE and PARAGRAPH\n--                       separators, if found in a string, are encoded with a JSON escape instead of as raw UTF-8.\n--                       The JSON is valid either way, but encoding this way, apparently, allows the resulting JSON\n--                       to also be valid Java.)\n--\n--\nlocal function encode_value(self, value, parents, etc, options, indent, for_key)\n\n   --\n   -- keys in a JSON object can never be null, so we don't even consider options.null when converting a key value\n   --\n   if value == nil or (not for_key and options and options.null and value == options.null) then\n      return 'null'\n\n   elseif type(value) == 'string' then\n      return json_string_literal(value, options)\n\n   elseif type(value) == 'number' then\n      if value ~= value then\n         --\n         -- NaN (Not a Number).\n         -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option.\n         --\n         return \"null\"\n      elseif value >= math.huge then\n         --\n         -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should\n         -- really be a package option. Note: at least with some implementations, positive infinity\n         -- is both \">= math.huge\" and \"<= -math.huge\", which makes no sense but that's how it is.\n         -- Negative infinity is properly \"<= -math.huge\". So, we must be sure to check the \">=\"\n         -- case first.\n         --\n         return \"1e+9999\"\n      elseif value <= -math.huge then\n         --\n         -- Negative infinity.\n         -- JSON has no INF, so we have to fudge the best we can. This should really be a package option.\n         --\n         return \"-1e+9999\"\n      else\n         return tostring(value)\n      end\n\n   elseif type(value) == 'boolean' then\n      return tostring(value)\n\n   elseif type(value) ~= 'table' then\n\n      if self.unsupportedTypeEncoder then\n         local user_value, user_error = self:unsupportedTypeEncoder(value, parents, etc, options, indent, for_key)\n         -- If the user's handler returns a string, use that. If it returns nil plus an error message, bail with that.\n         -- If only nil returned, fall through to the default error handler.\n         if type(user_value) == 'string' then\n            return user_value\n         elseif user_value ~= nil then\n            self:onEncodeError(\"unsupportedTypeEncoder method returned a \" .. type(user_value), etc)\n         elseif user_error then\n            self:onEncodeError(tostring(user_error), etc)\n         end\n      end\n\n      self:onEncodeError(\"can't convert \" .. type(value) .. \" to JSON\", etc)\n\n   elseif getmetatable(value) == isNumber then\n      return tostring(value)\n   else\n      --\n      -- A table to be converted to either a JSON object or array.\n      --\n      local T = value\n\n      if type(options) ~= 'table' then\n         options = {}\n      end\n      if type(indent) ~= 'string' then\n         indent = \"\"\n      end\n\n      if parents[T] then\n         self:onEncodeError(\"table \" .. tostring(T) .. \" is a child of itself\", etc)\n      else\n         parents[T] = true\n      end\n\n      local result_value\n\n      local object_keys, maximum_number_key, map = object_or_array(self, T, etc)\n      if maximum_number_key then\n         --\n         -- An array...\n         --\n         local key_indent\n         if options.array_newline then\n            key_indent = indent .. tostring(options.indent or \"\")\n         else\n            key_indent = indent\n         end\n\n         local ITEMS = { }\n         for i = 1, maximum_number_key do\n            table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, key_indent))\n         end\n\n         if options.array_newline then\n            result_value = \"[\\n\" .. key_indent .. table.concat(ITEMS, \",\\n\" .. key_indent) .. \"\\n\" .. indent .. \"]\"\n         elseif options.pretty then\n            result_value = \"[ \" .. table.concat(ITEMS, \", \") .. \" ]\"\n         else\n            result_value = \"[\"  .. table.concat(ITEMS, \",\")  .. \"]\"\n         end\n\n      elseif object_keys then\n         --\n         -- An object\n         --\n         local TT = map or T\n\n         if options.pretty then\n\n            local KEYS = { }\n            local max_key_length = 0\n            for _, key in ipairs(object_keys) do\n               local encoded = encode_value(self, tostring(key), parents, etc, options, indent, true)\n               if options.align_keys then\n                  max_key_length = math.max(max_key_length, #encoded)\n               end\n               table.insert(KEYS, encoded)\n            end\n            local key_indent = indent .. tostring(options.indent or \"\")\n            local subtable_indent = key_indent .. string.rep(\" \", max_key_length) .. (options.align_keys and \"  \" or \"\")\n            local FORMAT = \"%s%s: %s\"\n\n            local COMBINED_PARTS = { }\n            for i, key in ipairs(object_keys) do\n               local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent)\n               table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val))\n            end\n            result_value = \"{\\n\" .. table.concat(COMBINED_PARTS, \",\\n\") .. \"\\n\" .. indent .. \"}\"\n\n         else\n\n            local PARTS = { }\n            for _, key in ipairs(object_keys) do\n               local encoded_val = encode_value(self, TT[key],       parents, etc, options, indent)\n               local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent, true)\n               table.insert(PARTS, string.format(\"%s:%s\", encoded_key, encoded_val))\n            end\n            result_value = \"{\" .. table.concat(PARTS, \",\") .. \"}\"\n\n         end\n      else\n         --\n         -- An empty array/object... we'll treat it as an array, though it should really be an option\n         --\n         result_value = \"[]\"\n      end\n\n      parents[T] = false\n      return result_value\n   end\nend\n\nlocal function top_level_encode(self, value, etc, options)\n   local val = encode_value(self, value, {}, etc, options)\n   if val == nil then\n      --PRIVATE(\"may need to revert to the previous public verison if I can't figure out what the guy wanted\")\n      return val\n   else\n      return val\n   end\nend\n\nfunction OBJDEF:encode(value, etc, options)\n   if type(self) ~= 'table' or self.__index ~= OBJDEF then\n      OBJDEF:onEncodeError(\"JSON:encode must be called in method format\", etc)\n   end\n\n   --\n   -- If the user didn't pass in a table of decode options, make an empty one.\n   --\n   if type(options) ~= 'table' then\n      options = {}\n   end\n\n   return top_level_encode(self, value, etc, options)\nend\n\nfunction OBJDEF:encode_pretty(value, etc, options)\n   if type(self) ~= 'table' or self.__index ~= OBJDEF then\n      OBJDEF:onEncodeError(\"JSON:encode_pretty must be called in method format\", etc)\n   end\n\n   --\n   -- If the user didn't pass in a table of decode options, use the default pretty ones\n   --\n   if type(options) ~= 'table' then\n      options = default_pretty_options\n   end\n\n   return top_level_encode(self, value, etc, options)\nend\n\nfunction OBJDEF.__tostring()\n   return \"JSON encode/decode package\"\nend\n\nOBJDEF.__index = OBJDEF\n\nfunction OBJDEF:new(args)\n   local new = { }\n\n   if args then\n      for key, val in pairs(args) do\n         new[key] = val\n      end\n   end\n\n   return setmetatable(new, OBJDEF)\nend\n\nreturn OBJDEF:new()\n\n--\n-- Version history:\n--\n--   20211016.28   Had forgotten to document the strictParsing option.\n--\n--   20211015.27   Better handle some edge-case errors [ thank you http://seriot.ch/projects/parsing_json.html ; all tests are now successful ]\n--\n--                 Added some semblance of proper UTF8 parsing, and now aborts with an error on ilformatted UTF8.\n--\n--                 Added the strictParsing option:\n--                    Aborts with an error on unknown backslash-escape in strings\n--                    Aborts on naked control characters in strings\n--                    Aborts when decode is passed a whitespace-only string\n--\n--                 For completeness, when encoding a Lua string into a JSON string, escape a forward slash.\n--\n--                 String decoding should be a bit more efficient now.\n--\n--   20170927.26   Use option.null in decoding as well. Thanks to Max Sindwani for the bump, and sorry to Oliver Hitz\n--                 whose first mention of it four years ago was completely missed by me.\n--\n--   20170823.25   Added support for JSON:unsupportedTypeEncoder().\n--                 Thanks to Chronos Phaenon Eosphoros (https://github.com/cpeosphoros) for the idea.\n--\n--   20170819.24   Added support for boolean keys in tables.\n--\n--   20170416.23   Added the \"array_newline\" formatting option suggested by yurenchen (http://www.yurenchen.com/)\n--\n--   20161128.22   Added:\n--                   JSON:isString()\n--                   JSON:isNumber()\n--                   JSON:decodeIntegerObjectificationLength\n--                   JSON:decodeDecimalObjectificationLength\n--\n--   20161109.21   Oops, had a small boo-boo in the previous update.\n--\n--   20161103.20   Used to silently ignore trailing garbage when decoding. Now fails via JSON:onTrailingGarbage()\n--                 http://seriot.ch/parsing_json.php\n--\n--                 Built-in error message about \"expected comma or ']'\" had mistakenly referred to '['\n--\n--                 Updated the built-in error reporting to refer to bytes rather than characters.\n--\n--                 The decode() method no longer assumes that error handlers abort.\n--\n--                 Made the VERSION string a string instead of a number\n--\n\n--   20160916.19   Fixed the isNumber.__index assignment (thanks to Jack Taylor)\n--   \n--   20160730.18   Added JSON:forceString() and JSON:forceNumber()\n--\n--   20160728.17   Added concatenation to the metatable for JSON:asNumber()\n--\n--   20160709.16   Could crash if not passed an options table (thanks jarno heikkinen <jarnoh@capturemonkey.com>).\n--\n--                 Made JSON:asNumber() a bit more resilient to being passed the results of itself.\n--\n--   20160526.15   Added the ability to easily encode null values in JSON, via the new \"null\" encoding option.\n--                 (Thanks to Adam B for bringing up the issue.)\n--\n--                 Added some support for very large numbers and precise floats via\n--                    JSON.decodeNumbersAsObjects\n--                    JSON.decodeIntegerStringificationLength\n--                    JSON.decodeDecimalStringificationLength\n--\n--                 Added the \"stringsAreUtf8\" encoding option. (Hat tip to http://lua-users.org/wiki/JsonModules )\n--\n--   20141223.14   The encode_pretty() routine produced fine results for small datasets, but isn't really\n--                 appropriate for anything large, so with help from Alex Aulbach I've made the encode routines\n--                 more flexible, and changed the default encode_pretty() to be more generally useful.\n--\n--                 Added a third 'options' argument to the encode() and encode_pretty() routines, to control\n--                 how the encoding takes place.\n--\n--                 Updated docs to add assert() call to the loadfile() line, just as good practice so that\n--                 if there is a problem loading JSON.lua, the appropriate error message will percolate up.\n--\n--   20140920.13   Put back (in a way that doesn't cause warnings about unused variables) the author string,\n--                 so that the source of the package, and its version number, are visible in compiled copies.\n--\n--   20140911.12   Minor lua cleanup.\n--                 Fixed internal reference to 'JSON.noKeyConversion' to reference 'self' instead of 'JSON'.\n--                 (Thanks to SmugMug's David Parry for these.)\n--\n--   20140418.11   JSON nulls embedded within an array were being ignored, such that\n--                     [\"1\",null,null,null,null,null,\"seven\"],\n--                 would return\n--                     {1,\"seven\"}\n--                 It's now fixed to properly return\n--                     {1, nil, nil, nil, nil, nil, \"seven\"}\n--                 Thanks to \"haddock\" for catching the error.\n--\n--   20140116.10   The user's JSON.assert() wasn't always being used. Thanks to \"blue\" for the heads up.\n--\n--   20131118.9    Update for Lua 5.3... it seems that tostring(2/1) produces \"2.0\" instead of \"2\",\n--                 and this caused some problems.\n--\n--   20131031.8    Unified the code for encode() and encode_pretty(); they had been stupidly separate,\n--                 and had of course diverged (encode_pretty didn't get the fixes that encode got, so\n--                 sometimes produced incorrect results; thanks to Mattie for the heads up).\n--\n--                 Handle encoding tables with non-positive numeric keys (unlikely, but possible).\n--\n--                 If a table has both numeric and string keys, or its numeric keys are inappropriate\n--                 (such as being non-positive or infinite), the numeric keys are turned into\n--                 string keys appropriate for a JSON object. So, as before,\n--                         JSON:encode({ \"one\", \"two\", \"three\" })\n--                 produces the array\n--                         [\"one\",\"two\",\"three\"]\n--                 but now something with mixed key types like\n--                         JSON:encode({ \"one\", \"two\", \"three\", SOMESTRING = \"some string\" }))\n--                 instead of throwing an error produces an object:\n--                         {\"1\":\"one\",\"2\":\"two\",\"3\":\"three\",\"SOMESTRING\":\"some string\"}\n--\n--                 To maintain the prior throw-an-error semantics, set\n--                      JSON.noKeyConversion = true\n--                 \n--   20131004.7    Release under a Creative Commons CC-BY license, which I should have done from day one, sorry.\n--\n--   20130120.6    Comment update: added a link to the specific page on my blog where this code can\n--                 be found, so that folks who come across the code outside of my blog can find updates\n--                 more easily.\n--\n--   20111207.5    Added support for the 'etc' arguments, for better error reporting.\n--\n--   20110731.4    More feedback from David Kolf on how to make the tests for Nan/Infinity system independent.\n--\n--   20110730.3    Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules:\n--\n--                   * When encoding lua for JSON, Sparse numeric arrays are now handled by\n--                     spitting out full arrays, such that\n--                        JSON:encode({\"one\", \"two\", [10] = \"ten\"})\n--                     returns\n--                        [\"one\",\"two\",null,null,null,null,null,null,null,\"ten\"]\n--\n--                     In 20100810.2 and earlier, only up to the first non-null value would have been retained.\n--\n--                   * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as \"1+e9999\".\n--                     Version 20100810.2 and earlier created invalid JSON in both cases.\n--\n--                   * Unicode surrogate pairs are now detected when decoding JSON.\n--\n--   20100810.2    added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding\n--\n--   20100731.1    initial public release\n--\n"
  },
  {
    "path": "src/usr/share/calla/desktop/rc.lua",
    "content": "--[[ \n--\tTODO\n--\n--\tWhy is text content changed when its color is changed? -- markdown? needs to be bg container?\n--\n--\tFeatures:\n--\tAlt+tab menu\n--\tDesktop menu\n--\n--\tSettings:\n--\tCompletely redesign settings app\n--\tAdd autostart commands to settings\n--\tAdd modkey/sessionlock to settings\n--\tAdd profile picture to settings\n--\tCreate Gtk theme from color json\n--\tImport theme (Xresources?)\n--\n--\tDesign:\n--\tRedesign preview to look more like macos (expose)\n--\tHover cursor on titlebar buttons\n--\tHover background?\n--\n--\tRefactor:\n--\tIs it finally time to make a helpers file...?\n--\tMake battery use upower\n--\tMove custom themes to cache\n--\tGet rid of color dir\n--\tDifferentiate custom themes from default\n--\n--\tLong Term:\n--\tBetter multihead support\n--]]\n\n--[[\n--\tKnown Bugs\n--\n--\tMultihead:\n--\tLocation of lockscreen promptbox depends on focused screen at startup,\n--\tdoesn't appear if laptop screen focused\n--\tSystray opens/closes for both screens, one is redundant\n--\n--\tGeneral:\n--\tPreview does not entirely reload if visible\n--\tDesktop get grid function does not account for spacing\n--\tIcon theme is not refreshed with live reload\n--\tfprintd-verify does not work after suspend (issue #173)\n--\n--\tUnknown Bugs:\n--\tMany\n--]]\n\nlocal awful = require(\"awful\")\nlocal gears = require(\"gears\")\nlocal naughty = require(\"naughty\")\n\n-- Errors\n\nnaughty.connect_signal(\"request::display_error\", function(message, startup)\n\tnaughty.notification {\n\t\turgency = \"critical\",\n\t\ttitle   = \"Error\"..(startup and \" during startup!\" or \"!\"),\n\t\tmessage = message\n\t}\nend)\n\n-- Json\n\nfunction readjson(path)\n\tlocal r = assert(io.open(path, \"r\"))\n\tlocal table = r:read(\"*all\")\n\tr:close()\n\ttable = require(\"json\"):decode(table)\n\treturn table\nend\n\nfunction writejson(path, table)\n\tlocal w = assert(io.open(path, \"w\"))\n\tw:write(require(\"json\"):encode_pretty(table, nil, { pretty = true, indent = \"\t\", align_keys = false, array_newline = true}))\n\tw:close()\nend\n\nlocal config = gears.filesystem.get_cache_dir() .. \"user.json\"\n\nlocal defaults = {\n\tbatt = \"BAT0\",\n\tcolor = \"light\",\n\tfont = \"Roboto Medium 11\",\n\tfontalt = \"Roboto Bold 11\",\n\tmod = \"Mod4\",\n\tpasswd = \"awesomewm\",\n\treboot = \"systemctl reboot\",\n\tsessionlock = false,\n\tshotdir = \"~/Pictures/Screenshots\",\n\tshutdown = \"systemctl poweroff\",\n\tterminal = \"st\"\n}\n\nif not gears.filesystem.file_readable(config) then\n\twritejson(config, defaults)\nend\n\nuser = readjson(config)\n\n-- Config\n\nrequire(\"awful.autofocus\")\nrequire(\"signal\")\nrequire(\"config\")\nrequire(\"theme\")\nrequire(\"color.desktop\")\n\n-- Startup\n\nlocal autostart = {\n\t\"picom -b --config '/usr/share/calla/compositor.conf'\",\n\t\"xsettingsd --config '/usr/share/calla/xsettingsd'\",\n\t\"nm-applet\",\n\t\"/usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1\"\n}\n\nlocal function restarted()\n\tawesome.register_xproperty(\"restarted\", \"boolean\")\n\tlocal detected = awesome.get_xproperty(\"restarted\") ~= nil\n\tawesome.set_xproperty(\"restarted\", true)\n\treturn detected\nend\n\nif not restarted() then\n\tfor _, command in ipairs(autostart) do\n\t\tawful.spawn.easy_async({ 'pkill', '--full', '--uid', os.getenv('USER'), '^' .. command }, function()\n\t\t\tawful.spawn.easy_async_with_shell(command, function() end) -- func needed to avoid callback error\n\t\tend)\n\tend\n\tif user.sessionlock then\n\t\tawesome.emit_signal(\"widget::lockscreen\")\n\tend\nend\n\n-- Theme Init\n\nawesome.emit_signal(\"live::reload\")\n"
  },
  {
    "path": "src/usr/share/calla/desktop/signal/brightness.lua",
    "content": "local awful = require(\"awful\")\n\nlocal function emit()\n\tawful.spawn.easy_async_with_shell(\"brightnessctl -m | awk -F, '{print substr($4, 0, length($4)-1)}'\", function(out)\n\t\tlocal brightness = math.floor(tonumber(out))\n\t\tawesome.emit_signal(\"signal::brightness\", brightness)\n\tend)\nend\n\nemit()\n\nlocal subscribe = [[ bash -c \"while (inotifywait -e modify /sys/class/backlight/?*/brightness -qq) do echo; done\" ]]\n\nawful.spawn.easy_async_with_shell(\"ps x | grep \\\"inotifywait -e modify /sys/class/backlight\\\" | grep -v grep | awk '{print $1}' | xargs kill\", function ()\n\tawful.spawn.with_line_callback(subscribe, {\n\t\tstdout = function() emit() end\n\t})\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/signal/desktop.lua",
    "content": "local awful = require(\"awful\")\n\nlocal emit = function(type)\n\tawesome.emit_signal(\"signal::desktop\", type) \nend\n\nemit()\n\nlocal addsubscribe = [[\n   bash -c \"\n   while inotifywait -e create -e moved_to $HOME/Desktop/ -qq; do echo; done\n\"]]\n\nlocal removesubscribe = [[\n   bash -c \"\n   while inotifywait -e delete -e moved_from $HOME/Desktop/ -qq; do echo; done\n\"]]\n\nawful.spawn.easy_async_with_shell(\"ps x | grep \\\"inotifywait -e create -e moved_to $HOME/Desktop/\\\" | grep -v grep | awk '{print $1}' | xargs kill\", function ()\n    awful.spawn.with_line_callback(addsubscribe, {\n        stdout = function() emit(\"add\") end\n    })\nend)\n\nawful.spawn.easy_async_with_shell(\"ps x | grep \\\"inotifywait -e delete -e moved_from $HOME/Desktop/\\\" | grep -v grep | awk '{print $1}' | xargs kill\", function ()\n    awful.spawn.with_line_callback(removesubscribe, {\n        stdout = function() emit(\"remove\") end\n    })\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/signal/init.lua",
    "content": "require(\"signal.volume\")\nrequire(\"signal.brightness\")\nrequire(\"signal.playerctl\")\nrequire(\"signal.desktop\")\n"
  },
  {
    "path": "src/usr/share/calla/desktop/signal/playerctl.lua",
    "content": "local awful = require(\"awful\")\n\nlocal function emit()\n\tawful.spawn.easy_async_with_shell(\"playerctl --player=%any,firefox,chromium metadata --format 'title_{{title}}album_{{album}}artist_{{artist}}cover_{{mpris:artUrl}}elapsed_{{duration(position)}}total_{{duration(mpris:length)}}' & playerctl status\", function(out)\n\t\tlocal title, album, artist, status\n\n\t\ttitle = out:match(\"title_(.*)album_\") or \"Not Playing\"\n\t\talbum = out:match(\"album_(.*)artist_\") or \"No Album\"\n\t\tartist = out:match(\"artist_(.*)cover_\") or \"No Artist\"\n\t\tcover = out:match(\"cover_file://(.*)elapsed_\") or \"None\"\n\t\telapsed = out:match(\"elapsed_(.*)total_\") or \"0:00\"\n\t\ttotal = out:match(\"total_(.-)\\n\") or \"0:00\"\n\n\t\tif out:match(\"Playing\") then\n\t\t\tstatus = true\n\t\telse\n\t\t\tstatus = false\n\t\tend\n\n\t\tawesome.emit_signal('signal::playerctl', title, album, artist, cover, elapsed, total, status)\n\tend)\nend\n\nemit()\n\nlocal subscribe = [[ bash -c \"playerctl metadata --format 'title_{{title}}album_{{album}}artist_{{artist}}cover_{{mpris:artUrl}}elapsed_{{duration(position)}}total_{{duration(mpris:length)}}' -F & playerctl status -F\" ]]\n\nawful.spawn.easy_async({ 'pkill', '--full', '--uid', os.getenv('USER'), '^playerctl' }, function()\n    awful.spawn.with_line_callback(subscribe, {\n        stdout = function() emit() end\n    })\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/signal/volume.lua",
    "content": "local awful = require(\"awful\")\n\nlocal volume_old = -1\nlocal muted_old = -1\nlocal function emit()\n\tawful.spawn.easy_async_with_shell(\"wpctl get-volume @DEFAULT_AUDIO_SINK@\", function(out)\n\t\tlocal volume = tonumber(string.match(out:match('(%d%.%d+)')*100, '(%d+)'))\n\t\tlocal muted = out:match('MUTED')\n\n\t\tif volume ~= volume_old or muted ~= muted_old then\n\t\t\tawesome.emit_signal('signal::volume', volume, muted)\n\t\t\tvolume_old = volume\n\t\t\tmuted_old = muted\n\t\tend\n\tend)\nend\n\nemit()\n\nlocal subscribe = [[ bash -c \"LANG=C pactl subscribe 2> /dev/null | grep --line-buffered \\\"Event 'change' on sink\\\"\" ]]\n\nawful.spawn.easy_async({ 'pkill', '--full', '--uid', os.getenv('USER'), '^pactl subscribe' }, function()\n\tawful.spawn.with_line_callback(subscribe, {\n\t\tstdout = function() emit() end\n\t})\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/brightness.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\nlocal brightnessbox = wibox {\n\twidth = dpi(200),\n\theight = dpi(85),\n\tontop = true,\n\tvisible = false\n}\n\nlocal percent = wibox.widget.textbox()\n\nlocal header = wibox.widget {\n\t{\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\tvalign = \"center\",\n\t\t\t\t\twidget = wibox.widget.textbox(\"Brightness\")\n\t\t\t\t},\n\t\t\t\tnil,\n\t\t\t\tpercent,\n\t\t\t\tlayout = wibox.layout.align.horizontal,\n\t\t\t},\n\t\t\ttop = dpi(5),\n\t\t\tbottom = dpi(5),\n\t\t\tleft = dpi(10),\n\t\t\tright = dpi(10),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\twidget = background({ bg = \"bgmid\" })\n\t},\n\tmargins = dpi(5),\n\twidget = wibox.container.margin\n}\n\nlocal icon = iconbox({ image = \"brightness0\" })\n\nlocal bar = wibox.widget {\n\tshape = gears.shape.rounded_rect,\n\tbar_shape = gears.shape.rounded_rect,\n\tmax_value = 100,\n\tvalue = 0,\n\twidget = live(wibox.widget.progressbar, { background_color = \"bgmid\", color = \"fg\" })\n}\n\nlocal timer = gears.timer {\n\ttimeout = 2,\n\tsingle_shot = true,\n\tcallback = function()\n\t\tbrightnessbox.visible = false\n\tend\n}\n\nbrightnessbox:setup {\n\t{\n\t\theader,\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\ticon,\n\t\t\t\t\tright = dpi(15),\n\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t},\n\t\t\t\tnil,\n\t\t\t\t{\n\t\t\t\t\tbar,\n\t\t\t\t\ttop = dpi(20),\n\t\t\t\t\tbottom = dpi(20),\n\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t},\n\t\t\t\tlayout = wibox.layout.align.horizontal\n\t\t\t},\n\t\t\tleft = dpi(15),\n\t\t\tright = dpi(15),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\tlayout = wibox.layout.align.vertical\n\t},\n\twidget = live(wibox.container.background, { bg = \"bg\", fg = \"fg\" })\n}\n\nawesome.connect_signal(\"signal::brightness\", function(brightness)\n\tpercent.text = tostring(brightness) .. \"%\"\n\tbar.value = brightness\n\tif brightness >= 75 then\n\t\ticon.image = createicon(\"brightness100\")\n\telseif brightness >= 50 then\n\t\ticon.image = createicon(\"brightness75\")\n\telseif brightness >= 25 then\n\t\ticon.image = createicon(\"brightness50\")\n\telseif brightness > 0 then\n\t\ticon.image = createicon(\"brightness25\")\n\telseif brightness == 0 then\n\t\ticon.image = createicon(\"brightness0\")\n\tend\nend)\n\nawesome.connect_signal(\"widget::brightness\", function()\n\tawesome.emit_signal(\"widget::volume:hide\")\n\n\ttimer:again()\n\n\tif client.focus and client.focus.fullscreen == true then\n\t\tawful.placement.bottom(\n\t\t\tbrightnessbox, \n\t\t\t{\n\t\t\t\tmargins = { \n\t\t\t\t\tbottom = dpi(10)\n\t\t\t\t}, \n\t\t\t\tparent = awful.screen.focused()\n\t\t\t}\n\t\t)\n\telse\n\t\tawful.placement.bottom(\n\t\t\tbrightnessbox, \n\t\t\t{\n\t\t\t\tmargins = { \n\t\t\t\t\tbottom = dpi(60)\n\t\t\t\t}, \n\t\t\t\tparent = awful.screen.focused()\n\t\t\t}\n\t\t)\n\tend\n\n\tbrightnessbox.visible = true\nend)\n\nawesome.connect_signal(\"widget::brightness:hide\", function() \n\tbrightnessbox.visible = false \nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/control/calendar.lua",
    "content": "local wibox = require(\"wibox\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\nlocal function decorate(widget, flag, date)\n\tlocal retbg\n\tif flag == \"focus\" then retbg = beautiful.fg .. \"33\" else retbg = beautiful.bgmid end\n\tlocal ret = wibox.widget {\n\t\t{\n\t\t\twidget,\n\t\t\thalign = \"center\",\n\t\t\tvalign = \"center\",\n\t\t\tfill_horizontal = true,\n\t\t\tfill_vertical = true,\n\t\t\twidget = wibox.container.place\n\t\t},\n\t\tbg = retbg,\n\t\twidget = background({ fg = \"fg\" })\n\t}\n\tawesome.connect_signal(\"live::reload\", function()\n\t\tif flag == \"focus\" then retbg = beautiful.fg .. \"33\" else retbg = beautiful.bgmid end\n\t\tret.bg = retbg\n\tend)\n\treturn ret\nend\n\nlocal calendar = wibox.widget {\n\t{\n\t\t{\n\t\t\tfont = user.font,\n\t\t\tstart_sunday = true,\n\t\t\tflex_height = true,\n\t\t\tfn_embed = decorate,\n\t\t\twidget = wibox.widget.calendar.month(os.date(\"*t\"))\n\t\t},\n\t\tmargins = dpi(10),\n\t\twidget = wibox.container.margin\n\t},\n\tforced_width = dpi(220),\n\tforced_height = dpi(220),\n\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n}\n\nreturn calendar\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/control/init.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\nlocal media = require(\"theme.control.media\")\nlocal sliders = require(\"theme.control.sliders\")\nlocal toggles = require(\"theme.control.toggles\")\nlocal system = require(\"theme.control.system\")\nlocal profile = require(\"theme.control.profile\")\nlocal calendar = require(\"theme.control.calendar\")\nlocal notifs = require(\"theme.control.notifs\")\n\nlocal controlbox = wibox {\n\twidth = dpi(470),\n\theight = dpi(360),\n\tontop = true,\n\tvisible = false,\n\twidget = {\n\t\t{\n\t\t\t{\n\t\t\t\tmedia,\n\t\t\t\t{\n\t\t\t\t\tsliders,\n\t\t\t\t\ttoggles,\n\t\t\t\t\tsystem,\n\t\t\t\t\tspacing = dpi(10),\n\t\t\t\t\tlayout = wibox.layout.fixed.vertical\n\t\t\t\t},\n\t\t\t\tspacing = dpi(10),\n\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t},\n\t\t\tmargins = dpi(10),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\twidget = background({ bg = \"bg\" })\n\t}\n}\n\nlocal infobox = wibox {\n\twidth = dpi(470),\n\theight = dpi(360),\n\tontop = true,\n\tvisible = false,\n\twidget = {\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\tprofile,\n\t\t\t\t\tcalendar,\n\t\t\t\t\tspacing = dpi(10),\n\t\t\t\t\tlayout = wibox.layout.fixed.vertical\n\t\t\t\t},\n\t\t\t\tnotifs,\n\t\t\t\tspacing = dpi(10),\n\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t},\n\t\t\tmargins = dpi(10),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\twidget = background({ bg = \"bg\" })\n\t}\n}\n\nawesome.connect_signal(\"widget::control\", function()\n\tcontrolbox.visible = not controlbox.visible\n\tinfobox.visible = not infobox.visible\n\n\tif client.focus and client.focus.fullscreen == true then\n\t\tawful.placement.bottom_right(\n\t\t\tcontrolbox,\n\t\t\t{\n\t\t\t\tmargins = {\n\t\t\t\t\tbottom = dpi(10),\n\t\t\t\t\tright = dpi(10)\n\t\t\t\t},\n\t\t\t\tparent = awful.screen.focused()\n\t\t\t}\n\t\t)\n\t\tawful.placement.bottom_right(\n\t\t\tinfobox,\n\t\t\t{\n\t\t\t\tmargins = {\n\t\t\t\t\tbottom = dpi(380),\n\t\t\t\t\tright = dpi(10)\n\t\t\t\t},\n\t\t\t\tparent = awful.screen.focused()\n\t\t\t}\n\t\t)\n\telse\n\t\tawful.placement.bottom_right(\n\t\t\tcontrolbox,\n\t\t\t{\n\t\t\t\tmargins = {\n\t\t\t\t\tbottom = dpi(60),\n\t\t\t\t\tright = dpi(20)\n\t\t\t\t},\n\t\t\t\tparent = awful.screen.focused()\n\t\t\t}\n\t\t)\n\t\tawful.placement.bottom_right(\n\t\t\tinfobox,\n\t\t\t{\n\t\t\t\tmargins = {\n\t\t\t\t\tbottom = dpi(430),\n\t\t\t\t\tright = dpi(20)\n\t\t\t\t},\n\t\t\t\tparent = awful.screen.focused()\n\t\t\t}\n\t\t)\n\tend\nend)\n\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/control/media.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\nlocal function mediabutton(args)\n\treturn hovercursor(wibox.widget {\n\t\tbuttons = { awful.button({}, 1, args.run) },\n\t\twidget = iconbox({ image = args.icon, size = dpi(24) })\n\t})\nend\n\nlocal media = wibox.widget {\n\t{\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\tid = \"cover\",\n\t\t\t\t\t\tupscale = false,\n\t\t\t\t\t\tdownscale = true,\n\t\t\t\t\t\tvalign = \"center\",\n\t\t\t\t\t\thalign = \"center\",\n\t\t\t\t\t\tclip_shape = function(cr, width, height)\n\t\t\t\t\t\t\tgears.shape.rounded_rect(cr, width, height, dpi(5))\n\t\t\t\t\t\tend,\n\t\t\t\t\t\twidget = wibox.widget.imagebox\n\t\t\t\t\t},\n\t\t\t\t\twidget = background({ bg = \"bgalt\", fg = \"fg\" })\n\t\t\t\t},\n\t\t\t\twidth = dpi(200),\n\t\t\t\theight = dpi(200),\n\t\t\t\tstrategy = \"exact\",\n\t\t\t\twidget = wibox.container.constraint\n\t\t\t},\n\t\t\tnil,\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tid = \"title\",\n\t\t\t\t\t\t\t\talign = \"center\",\n\t\t\t\t\t\t\t\twidget = wibox.widget.textbox(\"Not Playing\")\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\theight = dpi(20),\n\t\t\t\t\t\t\twidget = wibox.container.constraint\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tid = \"artist\",\n\t\t\t\t\t\t\t\talign = \"center\",\n\t\t\t\t\t\t\t\twidget = wibox.widget.textbox(\"No Artist\")\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\theight = dpi(20),\n\t\t\t\t\t\t\twidget = wibox.container.constraint\n\t\t\t\t\t\t},\n\t\t\t\t\t\tspacing = dpi(5),\n\t\t\t\t\t\tlayout = wibox.layout.fixed.vertical\n\t\t\t\t\t},\n\t\t\t\t\talign = \"center\",\n\t\t\t\t\twidget = wibox.container.place\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid = \"elapsed\",\n\t\t\t\t\t\t\twidget = wibox.widget.textbox(\"0:00\")\n\t\t\t\t\t\t},\n\t\t\t\t\t\tright = dpi(10),\n\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid = \"progress\",\n\t\t\t\t\t\t\tmax_value = 0,\n\t\t\t\t\t\t\tvalue = 0,\n\t\t\t\t\t\t\tbar_shape = gears.shape.rounded_rect,\n\t\t\t\t\t\t\tshape = gears.shape.rounded_rect,\n\t\t\t\t\t\t\tforced_height = dpi(5),\n\t\t\t\t\t\t\texpand = true,\n\t\t\t\t\t\t\twidget = live(wibox.widget.progressbar, { color = \"fg\", background_color = \"bgalt\" })\n\t\t\t\t\t\t},\n\t\t\t\t\t\tforced_height = dpi(10),\n\t\t\t\t\t\twidget = wibox.container.place\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid = \"total\",\n\t\t\t\t\t\t\twidget = wibox.widget.textbox(\"0:00\")\n\t\t\t\t\t\t},\n\t\t\t\t\t\tleft = dpi(10),\n\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t},\n\t\t\t\t\tlayout = wibox.layout.align.horizontal\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\tmediabutton({ icon = \"previous\", run = function() awful.spawn.with_shell(\"playerctl previous\") end }),\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid = \"pause\",\n\t\t\t\t\t\t\twidget = mediabutton({ icon = \"play\", run = function() awful.spawn.with_shell(\"playerctl play-pause\") end })\n\t\t\t\t\t\t},\n\t\t\t\t\t\tmediabutton({ icon = \"next\", run = function() awful.spawn.with_shell(\"playerctl next\") end }),\n\t\t\t\t\t\tspacing = dpi(5),\n\t\t\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t\t\t},\n\t\t\t\t\talign = \"center\",\n\t\t\t\t\twidget = wibox.container.place\n\t\t\t\t},\n\t\t\t\tspacing = dpi(10),\n\t\t\t\tlayout = wibox.layout.fixed.vertical\n\t\t\t},\n\t\t\tlayout = wibox.layout.align.vertical\n\t\t},\n\t\tmargins = dpi(10),\n\t\twidget = wibox.container.margin\n\t},\n\tforced_width = dpi(220),\n\tforced_height = dpi(300),\n\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n}\n\nawesome.connect_signal(\"signal::playerctl\", function(title, album, artist, cover, elapsed, total, status)\n\tmedia:get_children_by_id(\"title\")[1].text = title\n\tmedia:get_children_by_id(\"artist\")[1].text = artist\n\n\tlocal coverart = cover\n\tif cover == \"None\" then coverart = beautiful.calla end\n\tmedia:get_children_by_id(\"cover\")[1].image = coverart\n\n\tif total ~= \"\" then\n\t\tmedia:get_children_by_id(\"elapsed\")[1].text = elapsed\n\t\tmedia:get_children_by_id(\"total\")[1].text = total\n\t\tlocal elapsedseconds = tonumber(elapsed:match(\"(.*):\"))*60 + tonumber(elapsed:match(\":(.*)\"))\n\t\tlocal totalseconds = tonumber(total:match(\"(.*):\"))*60 + tonumber(total:match(\":(.*)\"))\n\t\tmedia:get_children_by_id(\"progress\")[1].value = elapsedseconds\n\t\tmedia:get_children_by_id(\"progress\")[1].max_value = totalseconds\n\tend\n\n\tif status then\n\t\tmedia:get_children_by_id(\"pause\")[1].image = createicon(\"pause\")\n\telse\n\t\tmedia:get_children_by_id(\"pause\")[1].image = createicon(\"play\")\n\tend\n\n\tawesome.connect_signal(\"live::reload\", function()\n\t\tif cover == \"None\" then coverart = beautiful.calla end\n\t\tmedia:get_children_by_id(\"cover\")[1].image = coverart\n\tend)\nend)\n\nreturn media\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/control/notifs.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal naughty = require(\"naughty\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\nlocal notifcontainer = wibox.widget {\n\tspacing = dpi(10),\n\tlayout = wibox.layout.fixed.vertical\n}\n\nlocal notifempty = wibox.widget {\n\twibox.widget.textbox(\"No Notifications\"),\n\tfill_vertical = true,\n\talign = \"center\",\n\tlayout = wibox.container.place\n}\n\nlocal function notifbutton(widget)\n\treturn hovercursor(wibox.widget {\n\t\t{\n\t\t\ticonbox({ image = widget.image }),\n\t\t\tmargins = dpi(5),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\twidget = background({ bg = \"bgalt\", fg = \"fg\" })\n\t})\nend\n\nlocal function removenotif(notif)\n\tnotifcontainer:remove_widgets(notif)\n\tif #notifcontainer.children == 0 then\n\t\tnotifempty.visible = true\n\tend\nend\n\nlocal function createnotif(n)\n\tlocal notif = wibox.widget {\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\twibox.widget.textbox(n.title),\n\t\t\t\t\t\t\t\twidth = dpi(140),\n\t\t\t\t\t\t\t\twidget = wibox.container.constraint\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tnil,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t{ id = \"remove\", widget = hovercursor(wibox.widget {\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\ticonbox({ image = \"close\" }),\n\t\t\t\t\t\t\t\t\t\tmargins = dpi(5),\n\t\t\t\t\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n\t\t\t\t\t\t\t\t})},\n\t\t\t\t\t\t\t\tvalign = \"top\",\n\t\t\t\t\t\t\t\twidget = wibox.container.place\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tlayout = wibox.layout.align.horizontal\n\t\t\t\t\t\t},\n\t\t\t\t\t\tleft = dpi(10),\n\t\t\t\t\t\tright = dpi(5),\n\t\t\t\t\t\ttop = dpi(5),\n\t\t\t\t\t\tbottom = dpi(5),\n\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t},\n\t\t\t\t\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\twibox.widget.textbox(n.message:gsub(\"'\", \"\\'\")),\n\t\t\t\t\tmargins = dpi(5),\n\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t},\n\t\t\t\tspacing = dpi(5),\n\t\t\t\tlayout = wibox.layout.fixed.vertical\n\t\t\t},\n\t\t\tmargins = dpi(10),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\twidget = background({ bg = \"bgalt\", fg = \"fg\" })\n\t}\n\n\tnotif:get_children_by_id(\"remove\")[1].buttons = { awful.button({}, 1, function() removenotif(notif) end) }\n\n\treturn notif\nend\n\nlocal notifs = wibox.widget {\n\t{\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\t{ id = \"pageup\", widget = notifbutton({ image = \"up\" }) },\n\t\t\t\t\t{ id = \"pagedown\", widget = notifbutton({ image = \"down\" }) },\n\t\t\t\t\tspacing = dpi(10),\n\t\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t\t},\n\t\t\t\tnil,\n\t\t\t\t{ id = \"clear\", widget = notifbutton({ image = \"close\" }) },\n\t\t\t\tlayout = wibox.layout.align.horizontal\n\t\t\t},\n\t\t\t{\n\t\t\t\tnotifempty,\n\t\t\t\tnotifcontainer,\n\t\t\t\tlayout = wibox.layout.stack\n\t\t\t},\n\t\t\tspacing = dpi(10),\n\t\t\tlayout = wibox.layout.fixed.vertical\n\t\t},\n\t\tmargins = dpi(10),\n\t\twidget = wibox.container.margin\n\t},\n\tforced_width = dpi(220),\n\tforced_height = dpi(340),\n\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n}\n\nlocal notifposition = 1\nlocal function notifmove(direction)\n\tif #notifcontainer.children > 0 then\n\t\tif direction == \"down\" then\n\t\t\tif notifposition < #notifcontainer.children then\n\t\t\t\tnotifcontainer.children[notifposition].visible = false\n\t\t\t\tnotifposition = notifposition + 1\n\t\t\tend\n\t\telseif direction == \"up\" then\n\t\t\tif notifposition > 1 then\n\t\t\t\tnotifposition = notifposition - 1\n\t\t\t\tnotifcontainer.children[notifposition].visible = true\n\t\t\tend\n\t\tend\n\tend\nend\n\nnotifs:get_children_by_id(\"pageup\")[1].buttons = { awful.button({}, 1, function() notifmove(\"up\") end) }\nnotifs:get_children_by_id(\"pagedown\")[1].buttons = { awful.button({}, 1, function() notifmove(\"down\") end) }\nnotifs:get_children_by_id(\"clear\")[1].buttons = { awful.button({}, 1, function() notifcontainer:reset() notifempty.visible = true notifposition = 1 end) }\n\nlocal activelen = 0\nnaughty.connect_signal(\"property::active\", function()\n\tlocal notiflen = #naughty.active\n\tif notiflen > activelen then\n\t\tnotifcontainer:insert(1, createnotif(naughty.active[notiflen]))\n\t\tnotifempty.visible = false\n\tend\n\tactivelen = notiflen\nend)\n\nreturn notifs\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/control/profile.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\nlocal margins = dpi(0)\nif beautiful.pfp == beautiful.calla then\n\tmargins = dpi(10)\nend\n\nlocal profile = wibox.widget {\n\t{\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\timage = beautiful.pfp,\n\t\t\t\t\t\tupscale = false,\n\t\t\t\t\t\tdownscale = true,\n\t\t\t\t\t\tvalign = \"center\",\n\t\t\t\t\t\thalign = \"center\",\n\t\t\t\t\t\tclip_shape = function(cr, width, height)\n\t\t\t\t\t\t\tgears.shape.rounded_rect(cr, width, height, dpi(5))\n\t\t\t\t\t\tend,\n\t\t\t\t\t\twidget = live(wibox.widget.imagebox, { image = \"pfp\" })\n\t\t\t\t\t},\n\t\t\t\t\tmargins = margins,\n\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t},\n\t\t\t\tforced_width = dpi(90),\n\t\t\t\twidget = background({ bg = \"bgalt\" })\n\t\t\t},\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\tid = \"name\",\n\t\t\t\t\t\ttext = \"User Name\",\n\t\t\t\t\t\tfont = user.font:gsub(\"%d+\", \"14\"),\n\t\t\t\t\t\twidget = wibox.widget.textbox\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid = \"host\",\n\t\t\t\t\t\ttext = \"@calla\",\n\t\t\t\t\t\twidget = wibox.widget.textbox\n\t\t\t\t\t},\n\t\t\t\t\tspacing = dpi(5),\n\t\t\t\t\tlayout = wibox.layout.fixed.vertical\n\t\t\t\t},\n\t\t\t\tvalign = \"center\",\n\t\t\t\twidget = wibox.container.place\n\t\t\t},\n\t\t\tspacing = dpi(10),\n\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t},\n\t\tmargins = dpi(10),\n\t\twidget = wibox.container.margin\n\t},\n\tforced_width = dpi(220),\n\tforced_height = dpi(110),\n\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n}\n\nawful.spawn.easy_async_with_shell(\"getent passwd $(whoami) | cut -d ':' -f 5\", function(out)\n\tprofile:get_children_by_id(\"name\")[1].text = out:gsub(\",\", \"\"):gsub(\"\\n\", \"\")\nend)\nawful.spawn.easy_async_with_shell(\"hostname\", function(out)\n\tprofile:get_children_by_id(\"host\")[1].text = \"@\" .. out:gsub(\"\\n\", \"\")\nend)\n\nreturn profile\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/control/sliders.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\nlocal function slider()\n\treturn wibox.widget {\n\t\tbar_shape = gears.shape.rounded_rect,\n\t\tbar_height = dpi(5),\n\t\tbar_color = beautiful.bgalt,\n\t\tbar_active_color = beautiful.fg,\n\t\thandle_color = beautiful.fg,\n\t\thandle_shape = gears.shape.rounded_rect,\n\t\thandle_width = dpi(10),\n\t\tforced_height = dpi(10),\n\t\tvalue = 0,\n\t\tmaximum = 100,\n\t\twidget = live(wibox.widget.slider, { bar_color = \"bgalt\", bar_active_color = \"fg\", handle_color = \"fg\" })\n\t}\nend\n\nlocal volumeslider = slider()\n\nlocal volumepercent = wibox.widget.textbox()\n\nlocal volumeicon = iconbox({ image = \"volumemute\" })\n\nlocal volumehovering = false\nlocal volumestore = 0\nlocal mutestore = false\n\nlocal function editvolume(volume, mute)\n\tif volume ~= nil then\n\t\tvolumestore = volume\n\t\tmutestore = mute\n\telse\n\t\tvolume = volumestore\n\t\tmute = mutestore\n\tend\n\tif not volumehovering then\n\t\tvolumeslider.value = volume\n\tend\n\tif mute then\n\t\tvolumepercent.text = \"Muted\"\n\t\tvolumeicon.image = createicon(\"volumemute\")\n\telse\n\t\tvolumepercent.text = tostring(volume) .. \"%\"\n\t\tif volume > 100 then\n\t\t\tvolumeicon.image = createicon(\"volumewarn\")\n\t\telseif volume >= 50 then\n\t\t\tvolumeicon.image = createicon(\"volume100\")\n\t\telseif volume >= 25 then\n\t\t\tvolumeicon.image = createicon(\"volume50\")\n\t\telseif volume > 0 then\n\t\t\tvolumeicon.image = createicon(\"volume25\")\n\t\telseif volume == 0 then\n\t\t\tvolumeicon.image = createicon(\"volume0\")\n\t\tend\n\tend\nend\n\nawesome.connect_signal(\"signal::volume\", function(volume, mute)\n\teditvolume(volume, mute)\nend)\nawesome.connect_signal(\"live::reload\", function()\n\teditvolume()\nend)\n\n-- Very hacky workaround, vol will only be changed with signal when mouse is outside of slider\nvolumeslider:connect_signal(\"mouse::enter\", function()\n\tvolumehovering = true\nend)\nvolumeslider:connect_signal(\"mouse::leave\", function()\n\tvolumehovering = false\nend)\nvolumeslider:connect_signal(\"property::value\", function(_, new)\n\tif volumehovering then\n\t\tawful.spawn.with_shell(\"wpctl set-mute @DEFAULT_AUDIO_SINK@ 0\")\n\t\tawful.spawn.with_shell(\"wpctl set-volume @DEFAULT_AUDIO_SINK@ \" .. new .. \"%\")\n\tend\nend)\n\nlocal volume = wibox.widget {\n\t{\n\t\t{\n\t\t\t{\n\t\t\t\twibox.widget.textbox(\"Volume\"),\n\t\t\t\tnil,\n\t\t\t\tvolumepercent,\n\t\t\t\tlayout = wibox.layout.align.horizontal\n\t\t\t},\n\t\t\tnil,\n\t\t\t{\n\t\t\t\tvolumeicon,\n\t\t\t\t{\n\t\t\t\t\tvolumeslider,\n\t\t\t\t\tforced_height = dpi(10),\n\t\t\t\t\twidget = wibox.container.place\n\t\t\t\t},\n\t\t\t\tspacing = dpi(10),\n\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t},\n\t\t\tlayout = wibox.layout.align.vertical\n\t\t},\n\t\tmargins = dpi(10),\n\t\twidget = wibox.container.margin\n\t},\n\tforced_height = dpi(65),\n\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n}\n\nlocal brightnessslider = slider()\n\nlocal brightnesspercent = wibox.widget.textbox()\n\nlocal brightnessicon = iconbox({ image = \"brightness0\" })\n\nlocal brightnesshovering = false\nlocal brightnessstore = 0\n\nlocal function editbrightness(brightness)\n\tif brightness ~= nil then\n\t\tbrightnessstore = brightness\n\telse\n\t\tbrightness = brightnessstore\n\tend\n\tif not brightnesshovering then\n\t\tbrightnessslider.value = brightness\n\tend\n\tbrightnesspercent.text = tostring(brightness) .. \"%\"\n\tif brightness >= 75 then\n\t\tbrightnessicon.image = createicon(\"brightness100\")\n\telseif brightness >= 50 then\n\t\tbrightnessicon.image = createicon(\"brightness75\")\n\telseif brightness >= 25 then\n\t\tbrightnessicon.image = createicon(\"brightness50\")\n\telseif brightness > 0 then\n\t\tbrightnessicon.image = createicon(\"brightness25\")\n\telseif brightness == 0 then\n\t\tbrightnessicon.image = createicon(\"brightness0\")\n\tend\nend\nawesome.connect_signal(\"signal::brightness\", function(brightness)\n\teditbrightness(brightness)\nend)\nawesome.connect_signal(\"live::reload\", function()\n\teditbrightness()\nend)\n\nbrightnessslider:connect_signal(\"mouse::enter\", function()\n\tbrightnesshovering = true\nend)\nbrightnessslider:connect_signal(\"mouse::leave\", function()\n\tbrightnesshovering = false\nend)\nbrightnessslider:connect_signal(\"property::value\", function(_, new)\n\tif brightnesshovering then\n\t\tawful.spawn.with_shell(\"brightnessctl s \" .. new .. \"%\")\n\tend\nend)\n\nlocal brightness = wibox.widget {\n\t{\n\t\t{\n\t\t\t{\n\t\t\t\twibox.widget.textbox(\"Brightness\"),\n\t\t\t\tnil,\n\t\t\t\tbrightnesspercent,\n\t\t\t\tlayout = wibox.layout.align.horizontal\n\t\t\t},\n\t\t\tnil,\n\t\t\t{\n\t\t\t\tbrightnessicon,\n\t\t\t\t{\n\t\t\t\t\tbrightnessslider,\n\t\t\t\t\tforced_height = dpi(10),\n\t\t\t\t\twidget = wibox.container.place\n\t\t\t\t},\n\t\t\t\tspacing = dpi(10),\n\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t},\n\t\t\tlayout = wibox.layout.align.vertical\n\t\t},\n\t\tmargins = dpi(10),\n\t\twidget = wibox.container.margin\n\t},\n\tforced_height = dpi(65),\n\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n}\n\nlocal sliders = wibox.widget {\n\tvolume,\n\tbrightness,\n\tspacing = dpi(10),\n\twidget = wibox.layout.fixed.vertical\n}\n\nreturn sliders\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/control/system.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\nlocal function systembutton(widget)\n\treturn button({ \n\t\t\t\timage = widget.icon, \n\t\t\t\theight = dpi(30), \n\t\t\t\twidth = dpi(32), \n\t\t\t\trun = widget.run\n\t\t\t})\nend\n\nlocal system = wibox.widget {\n\t{\n\t\t{\n\t\t\tsystembutton({ \n\t\t\t\ticon = \"settings\", \n\t\t\t\trun = function() \n\t\t\t\t\tawesome.emit_signal(\"widget::control\")\n\t\t\t\t\tawesome.emit_signal(\"widget::config\") \n\t\t\t\tend\n\t\t\t}),\n\t\t\tsystembutton({ \n\t\t\t\ticon = \"shutdown\", \n\t\t\t\trun = function() \n\t\t\t\t\tawful.spawn.with_shell(user.shutdown)\n\t\t\t\tend\n\t\t\t}),\n\t\t\tsystembutton({ \n\t\t\t\ticon = \"restart\", \n\t\t\t\trun = function() \n\t\t\t\t\tawful.spawn.with_shell(user.reboot)\n\t\t\t\tend\n\t\t\t}),\n\t\t\tsystembutton({ \n\t\t\t\ticon = \"exit\", \n\t\t\t\trun = function() \n\t\t\t\t\tawesome.quit()\n\t\t\t\tend\n\t\t\t}),\n\t\t\tsystembutton({ \n\t\t\t\ticon = \"lock\", \n\t\t\t\trun = function() \n\t\t\t\t\tawesome.emit_signal(\"widget::control\")\n\t\t\t\t\tawesome.emit_signal(\"widget::lockscreen\")\n\t\t\t\tend\n\t\t\t}),\n\t\t\tspacing = dpi(10),\n\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t},\n\t\tmargins = dpi(10),\n\t\twidget = wibox.container.margin\n\t},\n\tforced_width = dpi(220),\n\tforced_height = dpi(50),\n\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n}\n\nreturn system\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/control/toggles.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal naughty = require(\"naughty\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\nlocal function togglebutton(widget)\n\tlocal switch = function(param, cases)\n\t\tlocal case = cases[param]\n\t\tif case then return case() end\n\tend\n\n\tlocal onicon, officon\n\tlocal icon = iconbox({ image = \"error\" })\n\n\tlocal state\n\tlocal iscompositor, compositorinit\n\tif widget == \"compositor\" then iscompositor, compositorinit = true, false end\n\n\tlocal button = hovercursor(wibox.widget {\n\t\t{\n\t\t\ticon,\n\t\t\tmargins = dpi(5),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\tforced_width = dpi(95),\n\t\tforced_height = dpi(50),\n\t\twidget = background({ bg = \"bgalt\", fg = \"fg\" })\n\t})\n\n\tlocal function init()\n\t\tswitch(widget, {\n\t\t\t[\"wifi\"] = function()\n\t\t\t\tonicon, officon = \"wifion\", \"wifioff\"\n\t\t\tend,\n\t\t\t[\"bluetooth\"] = function()\n\t\t\t\tonicon, officon = \"bluetoothon\", \"bluetoothoff\"\n\t\t\tend,\n\t\t\t[\"compositor\"] = function()\n\t\t\t\tonicon, officon = \"compositoron\", \"compositoroff\"\n\t\t\tend,\n\t\t\t[\"notifs\"] = function()\n\t\t\t\tonicon, officon = \"notificationson\", \"notificationsoff\"\n\t\t\tend\n\t\t})\n\tend\n\n\tlocal function getstate()\n\t\tlocal function finish()\n\t\t\tif state then\n\t\t\t\ticon.image = createicon(onicon)\n\t\t\t\tbutton.bg = beautiful.fg .. 33\n\t\t\telse\n\t\t\t\ticon.image = createicon(officon)\n\t\t\t\tbutton.bg = beautiful.bgalt\n\t\t\tend\n\t\tend\n\n\t\tswitch(widget, {\n\t\t\t[\"wifi\"] = function()\n\t\t\t\tstate = awful.spawn.easy_async_with_shell(\"nmcli radio wifi\", function(out)\n\t\t\t\t\tif out:match(\"enabled\") then\n\t\t\t\t\t\t state = true\n\t\t\t\t\telse\n\t\t\t\t\t\t state = false\n\t\t\t\t\tend\n\t\t\t\t\tfinish()\n\t\t\t\tend)\n\t\t\tend,\n\t\t\t[\"bluetooth\"] = function()\n\t\t\t\tstate = awful.spawn.easy_async_with_shell(\"bluetoothctl show | grep 'Powered: yes'\", function(out)\n\t\t\t\t\tif out ~= \"\" then\n\t\t\t\t\t\t state = true\n\t\t\t\t\telse\n\t\t\t\t\t\t state = false\n\t\t\t\t\tend\n\t\t\t\t\tfinish()\n\t\t\t\tend)\n\t\t\tend,\n\t\t\t[\"compositor\"] = function()\n\t\t\t\tawful.spawn.easy_async_with_shell(\"ps aux | grep picom | grep -v grep\", function(out)\n\t\t\t\t\tif not compositorinit then awesome.emit_signal(\"compositor::init\") compositorinit = true end\n\t\t\t\t\tif out ~= \"\" then\n\t\t\t\t\t\t state = true\n\t\t\t\t\telse\n\t\t\t\t\t\t state = false\n\t\t\t\t\tend\n\t\t\t\t\tfinish()\n\t\t\t\tend)\n\t\t\tend,\n\t\t\t[\"notifs\"] = function()\n\t\t\t\tstate = not naughty.suspended\n\t\t\t\tfinish()\n\t\t\tend\n\t\t})\n\tend\n\n\tlocal function changestate()\n\t\tswitch(widget, {\n\t\t\t[\"wifi\"] = function()\n\t\t\t\tlocal command\n\t\t\t\tif state then\n\t\t\t\t\tcommand = \"off\"\n\t\t\t\telse\n\t\t\t\t\tcommand = \"on\"\n\t\t\t\tend\n\t\t\t\tawful.spawn.easy_async_with_shell(\"nmcli radio wifi \" .. command, function() getstate() end)\n\t\t\tend,\n\t\t\t[\"bluetooth\"] = function()\n\t\t\t\tlocal command\n\t\t\t\tif state then\n\t\t\t\t\tcommand = \"off\"\n\t\t\t\telse\n\t\t\t\t\tcommand = \"on\"\n\t\t\t\tend\n\t\t\t\tawful.spawn.easy_async_with_shell(\"bluetoothctl power \" .. command, function() getstate() end)\n\t\t\tend,\n\t\t\t[\"compositor\"] = function()\n\t\t\t\tif state then\n\t\t\t\t\tawful.spawn.easy_async_with_shell(\"killall picom\", function() getstate() end)\n\t\t\t\telse\n\t\t\t\t\tawful.spawn.easy_async_with_shell(\"picom -b --config '/usr/share/calla/compositor.conf'\", function() getstate() end)\n\t\t\t\tend\n\t\t\tend,\n\t\t\t[\"notifs\"] = function()\n\t\t\t\tnaughty.suspended = not naughty.suspended\n\t\t\t\tgetstate()\n\t\t\tend\n\t\t})\n\tend\n\n\tif iscompositor then\n\t\tawesome.connect_signal(\"compositor::init\", function()\n\t\t\tinit()\n\t\t\tgetstate()\n\t\t\tbutton.buttons = { awful.button({}, 1, changestate) }\n\t\tend)\n\telse\n\t\tinit()\n\t\tgetstate()\n\t\tbutton.buttons = { awful.button({}, 1, changestate) }\n\tend\n\tawesome.connect_signal(\"live::reload\", function()\n\t\tgetstate()\n\tend)\n\n\treturn button\nend\n\nlocal toggles = wibox.widget {\n\t{\n\t\t{\n\t\t\ttogglebutton(\"wifi\"),\n\t\t\ttogglebutton(\"bluetooth\"),\n\t\t\ttogglebutton(\"compositor\"),\n\t\t\ttogglebutton(\"notifs\"),\n\t\t\tcolumn_count = 2,\n\t\t\tspacing = dpi(10),\n\t\t\tlayout = wibox.layout.grid\n\t\t},\n\t\tmargins = dpi(10),\n\t\twidget = wibox.container.margin\n\t},\n\tforced_width = dpi(220),\n\tforced_height = dpi(130),\n\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n}\n\nreturn toggles\n\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/desktop.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\nlocal icons = \"/usr/share/icons/\" .. beautiful.icons .. \"/64x64/\"\nlocal desktopjson = gears.filesystem.get_cache_dir() .. \"desktop.json\"\nlocal lgi = require(\"lgi\")\nlocal Gtk = lgi.require(\"Gtk\", \"3.0\")\nlocal Gio = lgi.Gio\nlocal UPower = lgi.require(\"UPowerGlib\")\n\nscreen.connect_signal(\"request::desktop_decoration\", function(s)\n\n\t--[[ logic for grabbing batteries, for use in future desktop widgets\n\tlocal function listdevices()\n\t\tlocal ret = {}\n\t\tlocal devices = UPower.Client():get_devices()\n\n\t\tfor _, device in ipairs(devices) do\n\t\t\ttable.insert(ret, device:get_object_path())\n\t\tend\n\n\t\treturn ret\n\tend\n\n\tlocal function getdevice(path)\n\t\tlocal devices = UPower.Client():get_devices()\n\n\t\tfor _, device in ipairs(devices) do\n\t\t\tif device:get_object_path() == path then\n\t\t\t\treturn device\n\t\t\tend\n\t\tend\n\n\t\treturn nil\n\tend\n\n\tlocal devicepaths = listdevices()\n\n\tfor _, path in ipairs(devicepaths) do\n\t\tlocal device = getdevice(path)\n\n\t\tdevice.on_notify = function()\n\t\tend\n\tend\n\t--]]\n\n\tlocal cell = dpi(120)\n\tlocal geometry = s:get_bounding_geometry()\n\tlocal rows = math.floor((geometry.height-dpi(60))/cell)\n\tlocal cols = math.floor((geometry.width-dpi(20))/cell)\n\tlocal vspacing = math.floor((((geometry.height-dpi(60))-(rows*cell))/(rows-1))+0.5)\n\tlocal hspacing = math.floor((((geometry.width-dpi(20))-(cols*cell))/(cols-1))+0.5)\n\n\ts.grid = wibox.widget {\n\t\tforced_num_rows = rows,\n\t\tforced_num_cols = cols,\n\t\tvertical_spacing = vspacing,\n\t\thorizontal_spacing = hspacing,\n\t\torientation = \"horizontal\",\n\t\tlayout = wibox.layout.grid\n\t}\n\n\ts.manual = wibox.layout {\n\t\tlayout = wibox.layout.manual\n\t}\n\n\ts.padding = {\n\t\ttop = dpi(10),\n\t\tbottom = dpi(50),\n\t\tleft = dpi(10),\n\t\tright = dpi(10)\n\t}\n\n\ts.base = wibox {\n\t\tscreen = s,\n\t\tontop = false,\n\t\tvisible = true,\n\t\ttype = \"splash\"\n\t}\n\n\ts.desktop = wibox {\n\t\tscreen = s,\n\t\twidth = s.geometry.width-dpi(20),\n\t\theight = s.geometry.height-dpi(60),\n\t\tontop = false,\n\t\tvisible = true,\n\t\ttype = \"desktop\"\n\t}\n\n\tawful.placement.maximize(s.base)\n\n\tawful.placement.top(\n\t\ts.desktop,\n\t\t{\n\t\t\tmargins = {\n\t\t\t\ttop = dpi(10)\n\t\t\t}\n\t\t}\n\t)\n\n\ts.panel = require(\"theme.panel\")(s)\n\n\ts.base:setup {\n\t\t{\n\t\t\ts.panel,\n\t\t\tvalign = \"bottom\",\n\t\t\tcontent_fill_horizontal = true,\n\t\t\twidget = wibox.container.place\n\t\t},\n\t\twidget = live(wibox.widget.background, { bg = \"bg\" })\n\t}\n\n\ts.desktop:setup {\n\t\t{\n\t\t\tid = \"wallpaper\",\n\t\t\timage = gears.surface.crop_surface {\n\t\t\t\tsurface = gears.surface.load_uncached(beautiful.wallpaper),\n\t\t\t\tratio = (s.geometry.width-dpi(20))/(s.geometry.height-dpi(60))\n\t\t\t},\n\t\t\twidget = wibox.widget.imagebox\n\t\t},\n\t\ts.grid,\n\t\ts.manual,\n\t\tlayout = wibox.layout.stack\n\t}\n\n\tlocal function generate()\n\t\tlocal entries = {}\n\n\t\tfor entry in io.popen([[ls ~/Desktop | sed '']]):lines() do\n\t\t\tlocal label = nil\n\t\t\tlocal exec = nil\n\t\t\tlocal icon = nil\n\n\t\t\tif entry:match(\"^.+(%..+)$\") == \".desktop\" then\n\t\t\t\tfor line in io.popen(\"cat ~/Desktop/'\" .. entry .. \"'\"):lines() do\n\t\t\t\t\tif line:match(\"Name=\") and not label then\n\t\t\t\t\t\tlabel = line:gsub(\"Name=\", \"\")\n\t\t\t\t\tend\n\t\t\t\t\tif line:match(\"Exec=\") and not exec then\n\t\t\t\t\t\texec = line:gsub(\"Exec=\", \"\"):gsub(\"%%U\", \"\"):gsub(\"%%u\", \"\")\n\t\t\t\t\tend\n\t\t\t\t\tif line:match(\"CustomIcon=\") and not icon then\n\t\t\t\t\t\ticon = line:gsub(\"CustomIcon=\", \"\")\n\t\t\t\t\telseif line:match(\"Icon=\") and not icon then\n\t\t\t\t\t\ticon = line:gsub(\"Icon=\", \"\")\n\t\t\t\t\tend\n\t\t\t\tend\n\t\t\t\ttable.insert(entries, { label = label, exec = exec, icon = icon })\n\t\t\telseif os.execute(\"cd ~/Desktop'\" .. entry .. \"'\") then\n\t\t\t\tlabel = entry\n\t\t\t\ticon = \"folder\"\n\t\t\t\texec = \"gio open ~/Desktop/'\" .. entry .. \"'\"\n\t\t\t\ttable.insert(entries, { label = label, exec = exec, icon = icon })\n\t\t\telse\n\t\t\t\tlabel = entry\n\t\t\t\ticon = Gio.File.new_for_path(os.getenv(\"HOME\") .. \"/Desktop/\" .. entry):query_info(\"standard::*\", Gio.FileQueryInfoFlags.NONE):get_icon()\n\t\t\t\tfor _, name in ipairs(icon:get_names()) do\n\t\t\t\t\tif Gtk.IconTheme.get_default():has_icon(name) then\n\t\t\t\t\t\ticon = name\n\t\t\t\t\t\tbreak\n\t\t\t\t\tend\n\t\t\t\t\ticon = \"application-x-generic\"\n\t\t\t\tend\n\t\t\t\texec = \"gio open ~/Desktop/'\" .. entry .. \"'\"\n\t\t\t\ttable.insert(entries, { label = label, exec = exec, icon = icon })\n\t\t\tend\n\t\tend\n\n\t\treturn entries\n\tend\n\n\tlocal function save()\n\t\tlayout = {}\n\n\t\tfor i, widget in ipairs(s.grid.children) do\n\t\t\tlocal pos = s.grid:get_widget_position(widget)\n\n\t\t\tlayout[i] = {\n\t\t\t\trow = pos.row,\n\t\t\t\tcol = pos.col,\n\t\t\t\twidget = {\n\t\t\t\t\tlabel = widget.label,\n\t\t\t\t\texec = widget.exec,\n\t\t\t\t\ticon = widget.icon\n\t\t\t\t}\n\t\t\t}\n\t\tend\n\n\t\twritejson(desktopjson, layout)\n\tend\n\n\tlocal function gridindexat(y, x) -- gotta fix this to account for spacing\n\t\tlocal margin = dpi(10)\n\n\t\tlocal row = math.ceil((y - margin) / cell)\n\t\trow = math.min(row, rows)\n\t\trow = math.max(row, 1)\n\n\t\tlocal col = math.ceil((x - margin) / cell)\n\t\tcol = math.min(col, cols)\n\t\tcol = math.max(col, 1)\n\n\t\treturn row, col\n\tend\n\n\tlocal function exists(path)\n\t\tlocal f = io.open(path, \"r\")\n\t\tif f~=nil then io.close(f) return true else return false end\n\tend\n\n\tlocal function createdesktopicon(label, exec, icon)\n\t\tlocal image\n\t\tif exists(icons .. \"places/\" .. icon .. \".svg\") then\n\t\t\timage = icons .. \"places/\" .. icon .. \".svg\"\n\t\telseif exists(icons .. \"mimetypes/\" .. icon .. \".svg\") then\n\t\t\timage = icons .. \"mimetypes/\" .. icon .. \".svg\"\n\t\telseif exists(icons .. \"apps/\" .. icon .. \".svg\") then\n\t\t\timage = icons .. \"apps/\" .. icon .. \".svg\"\n\t\telseif exists(icon) then\n\t\t\timage = icon\n\t\telse\n\t\t\timage = icons .. \"mimetypes/application-x-generic.svg\"\n\t\tend\n\n\t\tlocal widget = hovercursor(wibox.widget {\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\timage = image,\n\t\t\t\t\t\thalign = \"center\",\n\t\t\t\t\t\twidget = wibox.widget.imagebox\n\t\t\t\t\t},\n\t\t\t\t\tstrategy = \"exact\",\n\t\t\t\t\twidth = dpi(50),\n\t\t\t\t\theight = dpi(50),\n\t\t\t\t\twidget = wibox.container.constraint\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tvalign = \"top\",\n\t\t\t\t\t\t\t\talign = \"center\",\n\t\t\t\t\t\t\t\twidget = colortext({ text = label })\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tmargins = dpi(5),\n\t\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t\t},\n\t\t\t\t\t\twidget = background({ bg = \"bgmid\" })\n\t\t\t\t\t},\n\t\t\t\t\tstrategy = \"max\",\n\t\t\t\t\twidth = dpi(100),\n\t\t\t\t\theight = dpi(50),\n\t\t\t\t\twidget = wibox.container.constraint\n\t\t\t\t},\n\t\t\t\tspacing = dpi(5),\n\t\t\t\tlayout = wibox.layout.fixed.vertical\n\t\t\t},\n\t\t\tlabel = label,\n\t\t\texec = exec,\n\t\t\ticon = icon,\n\t\t\tforced_width = cell,\n\t\t\tforced_height = cell,\n\t\t\ttop = dpi(10),\n\t\t\tleft = dpi(10),\n\t\t\tright = dpi(10),\n\t\t\twidget = wibox.container.margin\n\t\t})\n\n\t\twidget:connect_signal(\"button::press\", function(_, _, _, button)\n\t\t\tif not mousegrabber.isrunning() then\n\t\t\t\tlocal heldwidget = wibox.widget {\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\timage = image,\n\t\t\t\t\t\t\t\topacity = 0.5,\n\t\t\t\t\t\t\t\thalign = \"center\",\n\t\t\t\t\t\t\t\twidget = wibox.widget.imagebox\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tstrategy = \"exact\",\n\t\t\t\t\t\t\twidth = dpi(50),\n\t\t\t\t\t\t\theight = dpi(50),\n\t\t\t\t\t\t\twidget = wibox.container.constraint\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\topacity = 0.5,\n\t\t\t\t\t\t\t\t\t\tvalign = \"top\",\n\t\t\t\t\t\t\t\t\t\talign = \"center\",\n\t\t\t\t\t\t\t\t\t\twidget = colortext({ text = label })\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tmargins = dpi(5),\n\t\t\t\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\twidget = background({ bg = \"bgmid\" })\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tstrategy = \"max\",\n\t\t\t\t\t\t\twidth = dpi(100),\n\t\t\t\t\t\t\theight = dpi(50),\n\t\t\t\t\t\t\twidget = wibox.container.constraint\n\t\t\t\t\t\t},\n\t\t\t\t\t\tspacing = dpi(5),\n\t\t\t\t\t\tlayout = wibox.layout.fixed.vertical\n\t\t\t\t\t},\n\t\t\t\t\tforced_width = cell,\n\t\t\t\t\tforced_height = cell,\n\t\t\t\t\ttop = dpi(10),\n\t\t\t\t\tleft = dpi(10),\n\t\t\t\t\tright = dpi(10),\n\t\t\t\t\tvisible = false,\n\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t}\n\n\t\t\t\tlocal startpos = mouse.coords()\n\t\t\t\theldwidget.point = { x = startpos.x, y = startpos.y }\n\t\t\t\tlocal oldpos = s.grid:get_widget_position(widget)\n\t\t\t\ts.manual:add(heldwidget)\n\n\t\t\t\tmousegrabber.run(function(mouse)\n\t\t\t\t\tif (math.abs(mouse.x - startpos.x) > 10 or\n\t\t\t\t\t\tmath.abs(mouse.y - startpos.y) > 10) and\n\t\t\t\t\t\tmouse.buttons[1] then\n\n\t\t\t\t\t\ts.grid:remove(widget)\n\t\t\t\t\t\theldwidget.visible = true\n\n\t\t\t\t\t\ts.manual:move_widget(heldwidget, {\n\t\t\t\t\t\t\tx = mouse.x - dpi(50),\n\t\t\t\t\t\t\ty = mouse.y - dpi(50)\n\t\t\t\t\t\t})\n\t\t\t\t\tend\n\n\t\t\t\t\tif not mouse.buttons[1] then\n\t\t\t\t\t\tif button == 1 then\n\t\t\t\t\t\t\tif heldwidget.visible then\n\t\t\t\t\t\t\t\theldwidget.visible = false\n\n\t\t\t\t\t\t\t\tlocal newrow, newcol = gridindexat(\n\t\t\t\t\t\t\t\t\tmouse.y,\n\t\t\t\t\t\t\t\t\tmouse.x\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tif not s.grid:get_widgets_at(newrow, newcol) then\n\t\t\t\t\t\t\t\t\ts.grid:add_widget_at(widget, newrow, newcol)\n\t\t\t\t\t\t\t\t\tsave()\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t\ts.grid:add_widget_at(widget, oldpos.row, oldpos.col)\n\t\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\tawful.spawn.with_shell(exec)\n\t\t\t\t\t\t\t\ts.manual:reset()\n\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\tmousegrabber.stop()\n\t\t\t\t\t\tend\n\t\t\t\t\tend\n\t\t\t\t\treturn mouse.buttons[1]\n\t\t\t\tend, \"hand2\")\n\t\t\tend\n\t\tend)\n\n\t\treturn widget\n\tend\n\n\tlocal function load()\n\t\ts.grid:reset()\n\n\t\tif not gears.filesystem.file_readable(desktopjson) then\n\t\t\tlocal entries = generate()\n\t\t\tfor _, entry in ipairs(entries) do\n\t\t\t\ts.grid:add(createdesktopicon(entry.label, entry.exec, entry.icon))\n\t\t\tend\n\t\t\tsave()\n\t\t\treturn\n\t\tend\n\n\t\tlocal layout = readjson(desktopjson)\n\n\t\tfor _, entry in ipairs(layout) do\n\t\t\ts.grid:add_widget_at(createdesktopicon(entry.widget.label, entry.widget.exec, entry.widget.icon), entry.row, entry.col)\n\t\tend\n\tend\n\n\tload()\n\n\tawesome.connect_signal(\"signal::desktop\", function(type)\n\t\tlocal entries = generate()\n\t\tlocal check = false\n\n\t\tif type == \"add\" then\n\t\t\tfor _, entry in ipairs(entries) do\n\t\t\t\tfor _, widget in ipairs(s.grid.children) do\n\t\t\t\t\tif entry.label == widget.label then\n\t\t\t\t\t\tcheck = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\tend\n\t\t\t\tend\n\t\t\t\tif check == false then\n\t\t\t\t\ts.grid:add(createdesktopicon(entry.label, entry.exec, entry.icon))\n\t\t\t\tend\n\t\t\t\tcheck = false\n\t\t\tend\n\t\tend\n\n\t\tif type == \"remove\" then\n\t\t\tfor _, widget in ipairs(s.grid.children) do\n\t\t\t\tfor _, entry in ipairs(entries) do\n\t\t\t\t\tif entry.label == widget.label then\n\t\t\t\t\t\tcheck = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\tend\n\t\t\t\tend\n\t\t\t\tif check == false then\n\t\t\t\t\ts.grid:remove(widget)\n\t\t\t\tend\n\t\t\t\tcheck = false\n\t\t\tend\n\t\tend\n\n\t\tsave()\n\tend)\n\n\tawesome.connect_signal(\"live::reload\", function()\n\t\ts.desktop:get_children_by_id(\"wallpaper\")[1].image = gears.surface.crop_surface {\n\t\t\tsurface = gears.surface.load_uncached(beautiful.wallpaper),\n\t\t\tratio = (s.geometry.width-dpi(20))/(s.geometry.height-dpi(60))\n\t\t}\n\tend)\n\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/dock.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\nlocal lgi = require(\"lgi\")\nlocal Gtk = lgi.require(\"Gtk\", \"3.0\")\nlocal dockjson = gears.filesystem.get_cache_dir() .. \"dock.json\"\n\nlocal tasklist\n\nlocal pins = wibox.widget {\n\tspacing = dpi(5),\n\tlayout = wibox.layout.fixed.horizontal\n}\n\nlocal separator = wibox.widget {\n\torientation = \"vertical\",\n\tthickness = dpi(2),\n\tspan_ratio = 0.75,\n\tforced_width = dpi(5),\n\tvisible = false,\n\twidget = live(wibox.widget.separator, { color = \"bgalt\" })\n}\n\nif not gears.filesystem.file_readable(dockjson) then\n\twritejson(dockjson, {})\nend\nlocal pinned = readjson(dockjson)\n\nlocal function pin(class, exec)\n\tlocal theme = Gtk.IconTheme.get_default()\n\tlocal icon = theme:lookup_icon(class:lower(), 64, 0)\n\tif icon then\n\t\ticon = icon:get_filename()\n\telse\n\t\ticon = require(\"menubar\").utils.lookup_icon_uncached(class:lower())\n\t\tif not icon then\n\t\t\ticon = theme:lookup_icon(\"application-default-icon\", 64, 0):get_filename()\n\t\tend\n\tend\n\tlocal widget = hovercursor(wibox.widget {\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\tshape = function(cr, width, height)\n\t\t\t\t\t\t\t\tgears.shape.rounded_rect(cr, width, height, dpi(8))\n\t\t\t\t\t\t\tend,\n\t\t\t\t\tid = \"background\",\n\t\t\t\t\tbg = beautiful.bg,\n\t\t\t\t\twidget = wibox.container.background\n\t\t\t\t},\n\t\t\t\tmargins = dpi(2),\n\t\t\t\twidget = wibox.container.margin\n\t\t\t},\n\t\t\tshape = function(cr, width, height)\n\t\t\t\t\t\tgears.shape.rounded_rect(cr, width, height, dpi(10))\n\t\t\t\t\tend,\n\t\t\tid = \"foreground\",\n\t\t\tbg = beautiful.fg,\n\t\t\twidget = wibox.container.background\n\t\t},\n\t\t{\n\t\t\twibox.widget.imagebox(icon),\n\t\t\tmargins = dpi(5),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\tlayout = wibox.layout.stack\n\t})\n\n\tlocal function check()\n\t\tlocal present = false\n\t\tlocal focused = false\n\t\tif client.focus and client.focus.class == class then\n\t\t\twidget:get_children_by_id(\"background\")[1].bg = beautiful.bgalt\n\t\t\twidget:get_children_by_id(\"foreground\")[1].bg = beautiful.fg .. \"64\"\n\t\t\twidget.buttons = {\n\t\t\t\tawful.button({}, 1, function()\n\t\t\t\t\tfor _, c in ipairs(client.get()) do\n\t\t\t\t\t\tif c.class == class then\n\t\t\t\t\t\t\tc.minimized = false\n\t\t\t\t\t\t\tc:raise()\n\t\t\t\t\t\tend\n\t\t\t\t\tend\n\t\t\t\tend),\n\t\t\t\tawful.button({ \"Shift\" }, 1, function()\n\t\t\t\t\tawful.spawn.with_shell(exec)\n\t\t\t\tend),\n\t\t\t\tawful.button({}, 3, function()\n\t\t\t\t\tfor i, app in ipairs(pinned) do\n\t\t\t\t\t\tif app.class == class then\n\t\t\t\t\t\t\ttable.remove(pinned, i)\n\t\t\t\t\t\tend\n\t\t\t\t\tend\n\t\t\t\t\tpins:remove(pins:index(widget))\n\t\t\t\t\ttasklist._do_tasklist_update_now()\n\t\t\t\t\twritejson(dockjson, pinned)\n\t\t\t\tend)\n\t\t\t}\n\t\t\tpresent = true\n\t\t\tfocused = true\n\t\tend\n\t\tif not focused then\n\t\t\tfor _, c in ipairs(client.get()) do\n\t\t\t\tif c.class == class then\n\t\t\t\t\twidget:get_children_by_id(\"background\")[1].bg = beautiful.bgalt\n\t\t\t\t\twidget:get_children_by_id(\"foreground\")[1].bg = beautiful.bgalt\n\t\t\t\t\twidget.buttons = {\n\t\t\t\t\t\tawful.button({}, 1, function()\n\t\t\t\t\t\t\tc.first_tag:view_only() -- check current tag first?\n\t\t\t\t\t\t\tfor _, c in ipairs(client.get()) do\n\t\t\t\t\t\t\t\tif c.class == class then\n\t\t\t\t\t\t\t\t\tc.minimized = false\n\t\t\t\t\t\t\t\t\tc:raise()\n\t\t\t\t\t\t\t\t\tc:activate()\n\t\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\tend\n\t\t\t\t\t\tend),\n\t\t\t\t\t\tawful.button({ \"Shift\" }, 1, function()\n\t\t\t\t\t\t\tawful.spawn.with_shell(exec)\n\t\t\t\t\t\tend),\n\t\t\t\t\t\tawful.button({}, 3, function()\n\t\t\t\t\t\t\tfor i, app in ipairs(pinned) do\n\t\t\t\t\t\t\t\tif app.class == class then\n\t\t\t\t\t\t\t\t\ttable.remove(pinned, i)\n\t\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\tpins:remove(pins:index(widget))\n\t\t\t\t\t\t\ttasklist._do_tasklist_update_now()\n\t\t\t\t\t\t\twritejson(dockjson, pinned)\n\t\t\t\t\t\tend)\n\t\t\t\t\t}\n\t\t\t\t\tpresent = true\n\t\t\t\t\treturn\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\t\tif not present then\n\t\t\twidget:get_children_by_id(\"background\")[1].bg = beautiful.bg\n\t\t\twidget:get_children_by_id(\"foreground\")[1].bg = beautiful.bg\n\t\t\twidget.buttons = {\n\t\t\t\tawful.button({}, 1, function()\n\t\t\t\t\tawful.spawn.with_shell(exec)\n\t\t\t\tend),\n\t\t\t\tawful.button({}, 3, function()\n\t\t\t\t\tfor i, app in ipairs(pinned) do\n\t\t\t\t\t\tif app.class == class then\n\t\t\t\t\t\t\ttable.remove(pinned, i)\n\t\t\t\t\t\tend\n\t\t\t\t\tend\n\t\t\t\t\tpins:remove(pins:index(widget))\n\t\t\t\t\ttasklist._do_tasklist_update_now()\n\t\t\t\t\twritejson(dockjson, pinned)\n\t\t\t\tend)\n\t\t\t}\n\t\tend\n\t\twidget:emit_signal(\"widget::redraw_needed\")\n\tend\n\n\tclient.connect_signal(\"request::manage\", check)\n\tclient.connect_signal(\"request::unmanage\", check)\n\tclient.connect_signal(\"focus\", check)\n\tclient.connect_signal(\"unfocus\", check)\n\tawesome.connect_signal(\"live::reload\", check)\n\n\tcheck()\n\n\treturn widget\nend\n\nlocal function contains(table, name)\n\tfor _, app in ipairs(table) do\n\t\tif app == name then\n\t\t\treturn true\n\t\tend\n\tend\n\treturn false\nend\n\ntasklist = awful.widget.tasklist {\n\tscreen = awful.screen.focused(),\n\tfilter = awful.widget.tasklist.filter.allscreen,\n\tsource = function()\n\t\tlocal seen = {}\n\t\tlocal ret = {}\n\n\t\tfor _, c in ipairs(client.get()) do\n\t\t\tlocal exclude = false\n\t\t\tfor _, app in ipairs(pinned) do\n\t\t\t\tif c.class == app.class then\n\t\t\t\t\texclude = true\n\t\t\t\t\tbreak\n\t\t\t\tend\n\t\t\tend\n\t\t\tif not exclude and not contains(seen, c.class) or c.minimized == true then\n\t\t\t\ttable.insert(seen, c.class)\n\t\t\t\ttable.insert(ret, c)\n\t\t\tend\n\t\tend\n\n\t\tif seen[1] and pinned[1] then\n\t\t\tseparator.visible = true\n\t\telse\n\t\t\tseparator.visible = false\n\t\tend\n\n\t\treturn ret\n\tend,\n\tstyle = {\n\t\tshape = function(cr, width, height)\n\t\t\t\t\tgears.shape.rounded_rect(cr, width, height, dpi(10))\n\t\t\t\tend\n\t},\n\tlayout = {\n\t\tspacing = dpi(5),\n\t\tspacing_widget = wibox.container.background,\n\t\tlayout = wibox.layout.fixed.horizontal\n\t},\n\twidget_template = {\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\tawful.widget.clienticon,\n\t\t\t\t\tmargins = dpi(5),\n\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t},\n\t\t\t\tshape = function(cr, width, height)\n\t\t\t\t\t\t\tgears.shape.rounded_rect(cr, width, height, dpi(8))\n\t\t\t\t\t\tend,\n\t\t\t\tid = \"background\",\n\t\t\t\twidget = wibox.widget.background\n\t\t\t},\n\t\t\tmargins = dpi(2),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\tshape = function(cr, width, height)\n\t\t\t\t\tgears.shape.rounded_rect(cr, width, height, dpi(10))\n\t\t\t\tend,\n\t\tid = \"foreground\",\n\t\tbg = beautiful.fg,\n\t\twidget = wibox.container.background,\n\t\tcreate_callback = function(self, c)\n\t\t\tlocal exec\n\t\t\tif c.pid then\n\t\t\t\tawful.spawn.easy_async(\"readlink -f /proc/\" .. c.pid .. \"/exe\", function(out)\n\t\t\t\t\texec = out:gsub(\"\\n\", \"\")\n\t\t\t\tend)\n\t\t\tend\n\t\t\tself.buttons = {\n\t\t\t\tawful.button({}, 1, function()\n\t\t\t\t\tc.first_tag:view_only()\n\t\t\t\t\tc.minimized = false\n\t\t\t\t\tc:raise()\n\t\t\t\tend),\n\t\t\t\tawful.button({}, 3, function()\n\t\t\t\t\tlocal seen\n\t\t\t\t\tfor _, app in ipairs(pinned) do\n\t\t\t\t\t\tif app.class == c.class then\n\t\t\t\t\t\t\tseen = true\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\tend\n\t\t\t\t\tend\n\t\t\t\t\tif not seen then\n\t\t\t\t\t\tpins:add(pin(c.class, exec))\n\t\t\t\t\t\ttable.insert(pinned, { class = c.class, exec = exec })\n\t\t\t\t\t\ttasklist._do_tasklist_update_now()\n\t\t\t\t\t\twritejson(dockjson, pinned)\n\t\t\t\t\tend\n\t\t\t\tend)\n\t\t\t}\n\t\t\thovercursor(self)\n\n\t\t\tif client.focus == c then\n\t\t\t\tself:get_children_by_id(\"background\")[1].bg = beautiful.bgalt\n\t\t\t\tself:get_children_by_id(\"foreground\")[1].bg = beautiful.fg .. \"64\"\n\t\t\telse\n\t\t\t\tself:get_children_by_id(\"background\")[1].bg = beautiful.bgalt\n\t\t\t\tself:get_children_by_id(\"foreground\")[1].bg = beautiful.bgalt\n\t\t\tend\n\t\t\tclient.connect_signal(\"focus\", function()\n\t\t\t\tif client.focus == c then\n\t\t\t\t\tself:get_children_by_id(\"background\")[1].bg = beautiful.bgalt\n\t\t\t\t\tself:get_children_by_id(\"foreground\")[1].bg = beautiful.fg .. \"64\"\n\t\t\t\telse\n\t\t\t\t\tself:get_children_by_id(\"background\")[1].bg = beautiful.bgalt\n\t\t\t\t\tself:get_children_by_id(\"foreground\")[1].bg = beautiful.bgalt\n\t\t\t\tend\n\t\t\tend)\n\t\t\tclient.connect_signal(\"unfocus\", function()\n\t\t\t\tself:get_children_by_id(\"background\")[1].bg = beautiful.bgalt\n\t\t\t\tself:get_children_by_id(\"foreground\")[1].bg = beautiful.bgalt\n\t\t\tend)\n\t\t\tawesome.connect_signal(\"live::reload\", function()\n\t\t\t\tif client.focus == c then\n\t\t\t\t\tself:get_children_by_id(\"background\")[1].bg = beautiful.bgalt\n\t\t\t\t\tself:get_children_by_id(\"foreground\")[1].bg = beautiful.fg .. \"64\"\n\t\t\t\telse\n\t\t\t\t\tself:get_children_by_id(\"background\")[1].bg = beautiful.bgalt\n\t\t\t\t\tself:get_children_by_id(\"foreground\")[1].bg = beautiful.bgalt\n\t\t\t\tend\n\t\t\tend)\n\t\tend\n\t}\n}\n\nfor _, app in ipairs(pinned) do\n\tpins:add(pin(app.class, app.exec))\nend\n\nlocal dock = wibox.widget {\n\t{\n\t\tpins,\n\t\tseparator,\n\t\ttasklist,\n\t\tspacing = dpi(5),\n\t\tlayout = wibox.layout.fixed.horizontal\n\t},\n\thalign = \"center\",\n\twidget = wibox.container.place\n}\n\nreturn dock\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/init.lua",
    "content": "local beautiful = require(\"beautiful\")\nlocal awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal dpi = beautiful.xresources.apply_dpi\n\nbeautiful.init(gears.filesystem.get_configuration_dir() .. \"theme/theme.lua\")\n\nscreen.connect_signal(\"request::desktop_decoration\", function(s)\n\tawesome.connect_signal(\"live::reload\", function()\n\t\tawful.wallpaper {\n\t\t\tscreen = s,\n\t\t\tbg = beautiful.bg\n\t\t}\n\tend)\nend)\n\nfunction live(w, properties)\n    local widget = w()\n\n\tfor property, arg in pairs(properties) do\n\t\twidget[property] = beautiful[arg]\n\tend\n\n    awesome.connect_signal(\"live::reload\", function()\n\t\tfor property, arg in pairs(properties) do\n\t\t\twidget[property] = beautiful[arg]\n\t\tend\n        widget:emit_signal(\"widget::redraw_needed\")\n    end)\n\n    return widget\nend\n\nfunction background(properties)\n\treturn wibox.widget {\n\t\tshape = function(cr, width, height)\n\t\t\t\t\tgears.shape.rounded_rect(cr, width, height, dpi(10))\n\t\t\t\tend,\n\t\twidget = live(wibox.container.background, properties)\n\t}\nend\n\nfunction hovercursor(widget)\n\t--local oldcursor, oldwibox, oldbg\n\tlocal oldcursor, oldwibox\n\twidget:connect_signal(\"mouse::enter\", function()\n\t\tlocal wb = mouse.current_wibox\n\t\tif wb == nil then return end\n\t\t--oldcursor, oldwibox, oldbg = wb.cursor, wb, wb.bg\n\t\toldcursor, oldwibox = wb.cursor, wb\n\t\twb.cursor = \"hand2\"\n\t\t--widget.bg = beautiful.fg .. \"20\"\n\tend)\n\twidget:connect_signal(\"mouse::leave\", function()\n\t\tif oldwibox then\n\t\t\toldwibox.cursor = oldcursor\n\t\t\t--widget.bg = oldbg\n\t\t\toldwibox = nil\n\t\tend\n\tend)\n\treturn widget\nend\n\nfunction markup(args)\n\tlocal fg = beautiful[args.fg] or beautiful.fg\n\tlocal text = '<span foreground=\"' .. fg .. '\">' .. args.text .. '</span>'\n\treturn text\nend\n\nfunction colortext(args)\n\tlocal table = args or {}\n\tlocal fg = beautiful[table.fg] or beautiful.fg\n\tlocal font = table.font or user.font\n\tlocal text = table.text or \"N/A\"\n\tlocal textbox = wibox.widget {\n\t\tmarkup = markup({ text = text, fg = fg }),\n\t\tfont = font,\n\t\twidget = wibox.widget.textbox\n\t}\n\n\tawesome.connect_signal(\"live::reload\", function()\n\t\ttextbox.markup = markup({ text = text, fg = fg })\n\tend)\n\n\treturn textbox\nend\n\nfunction createicon(image)\n\tlocal icon = gears.color.recolor_image(gears.filesystem.get_configuration_dir() .. \"theme/icons/\" .. image .. \".svg\", beautiful.fg)\n\n\tawesome.connect_signal(\"live::reload\", function()\n\t\ticon = gears.color.recolor_image(gears.filesystem.get_configuration_dir() .. \"theme/icons/\" .. image .. \".svg\", beautiful.fg)\n\tend)\n\n\treturn icon\nend\n\nfunction iconbox(widget)\n\tlocal size = dpi(18)\n\tif widget.size then size = widget.size end\n\tlocal icon = wibox.widget {\n\t\timage = gears.color.recolor_image(gears.filesystem.get_configuration_dir() .. \"theme/icons/\" .. widget.image .. \".svg\", beautiful.fg),\n\t\tforced_width = size,\n\t\tforced_height = size,\n\t\tupscale = false,\n\t\tdownscale = true,\n\t\tvalign = \"center\",\n\t\thalign = \"center\",\n\t\twidget = wibox.widget.imagebox\n\t}\n\n\tawesome.connect_signal(\"live::reload\", function()\n\t\ticon.image = gears.color.recolor_image(gears.filesystem.get_configuration_dir() .. \"theme/icons/\" .. widget.image .. \".svg\", beautiful.fg)\n\tend)\n\n\treturn icon\nend\n\nfunction button(widget)\n\tlocal img = iconbox({ image = widget.image })\n\n\tif widget.size then\n\t\twidth = widget.size\n\t\theight = widget.size\n\telse\n\t\tif widget.height then\n\t\t\theight = widget.height\n\t\telse\n\t\t\theight = dpi(30)\n\t\tend\n\n\t\tif widget.width then\n\t\t\twidth = widget.width\n\t\telse\n\t\t\twidth = dpi(30)\n\t\tend\n\tend\n\n\tlocal button = hovercursor(wibox.widget {\n\t\t{\n\t\t\timg,\n\t\t\tmargins = dpi(5),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\tforced_width = width,\n\t\tforced_height = height,\n\t\tbuttons = { awful.button({}, 1, widget.run) },\n\t\twidget = background({ bg = \"bgmid\" })\n\t})\n\n\treturn button\nend\n\nrequire(\"theme.desktop\")\nrequire(\"theme.notif\")\nrequire(\"theme.title\")\nrequire(\"theme.volume\")\nrequire(\"theme.brightness\")\nrequire(\"theme.launcher\")\nrequire(\"theme.lock\")\nrequire(\"theme.settings\")\nrequire(\"theme.preview\")\nrequire(\"theme.control\")\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/launcher.lua",
    "content": "local wibox = require(\"wibox\")\nlocal awful = require(\"awful\")\nlocal gears = require(\"gears\")\nlocal Gio = require(\"lgi\").Gio\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\nlocal appicons = \"/usr/share/icons/\" .. beautiful.icons .. \"/64x64/apps/\"\n\n-- Widgets\n\nlocal launcherbox = wibox {\n\twidth = dpi(280),\n\theight = dpi(340),\n\tontop = true,\n\tvisible = false\n}\n\nlocal prompt = colortext({ text = \"Search...\" })\n\nlocal textbox = wibox.widget {\n\tforced_height = dpi(30),\n\twidget = wibox.widget.textbox\n}\n\nlocal entries = wibox.widget {\n\thomogeneous = false,\n\texpand = true,\n\tforced_num_cols = 1,\n\tlayout = wibox.layout.grid\n}\n\nlauncherbox:setup {\n\t{\n\t\t{\n\t\t\tentries,\n\t\t\tnil,\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\tprompt,\n\t\t\t\t\ttop = dpi(5),\n\t\t\t\t\tbottom = dpi(5),\n\t\t\t\t\tleft = dpi(8),\n\t\t\t\t\tright = dpi(8),\n\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t},\n\t\t\t\tforced_height = dpi(30),\n\t\t\t\twidget = background({ bg = \"bgmid\" })\n\t\t\t},\n\t\t\tforced_width = dpi(300),\n\t\t\tlayout = wibox.layout.align.vertical\n\t\t},\n\t\tmargins = dpi(10),\n\t\twidget = wibox.container.margin\n\t},\n\twidget = live(wibox.container.background, { bg = \"bg\" })\n}\n\n-- Functions\n\nlocal function next()\n\tif entryindex ~= #filtered then\n\t\tentries:get_widgets_at(entryindex, 1)[1]:get_children_by_id(\"bg\")[1].bg = nil\n\t\tentries:get_widgets_at(entryindex+1, 1)[1]:get_children_by_id(\"bg\")[1].bg = beautiful.bgmid\n\t\tentryindex = entryindex + 1\n\t\tif entryindex > startindex + 7 then\n\t\t\tentries:get_widgets_at(entryindex-8, 1)[1].visible = false\n\t\t\tentries:get_widgets_at(entryindex, 1)[1].visible = true\n\t\t\tstartindex = startindex + 1\n\t\tend\n\tend\n\tmove = true\nend\n\nlocal function back()\n\tif entryindex ~= 1 then\n\t\tentries:get_widgets_at(entryindex, 1)[1]:get_children_by_id(\"bg\")[1].bg = nil\n\t\tentries:get_widgets_at(entryindex-1, 1)[1]:get_children_by_id(\"bg\")[1].bg = beautiful.bgmid\n\t\tentryindex = entryindex - 1\n\t\tif entryindex < startindex then\n\t\t\tentries:get_widgets_at(entryindex+8, 1)[1].visible = false\n\t\t\tentries:get_widgets_at(entryindex, 1)[1].visible = true\n\t\t\tstartindex = startindex - 1\n\t\tend\n\tend\n\tmove = true\nend\n\nentries.buttons = {\n\tawful.button({}, 4, function()\n\t\tback()\n\tend),\n\tawful.button({}, 5, function()\n\t\tnext()\n\tend)\n}\n\nlocal function gen()\n\tlocal entries = {}\n\tfor _, entry in ipairs(Gio.AppInfo.get_all()) do\n\t\tif entry:should_show() then\n\t\t\tlocal name = entry:get_name():gsub(\"&\", \"&amp;\"):gsub(\"<\", \"&lt;\"):gsub(\"'\", \"&#39;\")\n\t\t\tlocal icon = entry:get_icon()\n\t\t\tif icon then\n\t\t\t\tlocal name = icon:to_string()\n\t\t\t\ticon = appicons .. name .. \".svg\"\n\t\t\t\tlocal function exists(file)\n\t\t\t\t\tlocal file=io.open(file, \"r\")\n\t\t\t\t\tif file ~= nil then \n\t\t\t\t\t\tio.close(file) \n\t\t\t\t\t\treturn true \n\t\t\t\t\telse \n\t\t\t\t\t\treturn false \n\t\t\t\t\tend\n\t\t\t\tend\n\t\t\t\tif exists(icon) then\n\t\t\t\t\ticon = appicons .. name .. \".svg\"\n\t\t\t\telse\n\t\t\t\t\ticon = appicons .. \"application-default-icon.svg\"\n\t\t\t\tend\n\t\t\telse\n\t\t\t\ticon = appicons .. \"application-default-icon.svg\"\n\t\t\tend\n\t\t\ttable.insert(\n\t\t\t\tentries,\n\t\t\t\t{ icon = icon, name = name, appinfo = entry }\n\t\t\t)\n\t\tend\n\tend\n\treturn entries\nend\n\nlocal function filter(cmd)\n\n\tfiltered = {}\n\tregfiltered = {}\n\t\n\t-- Filter entries\n\n\tfor _, entry in ipairs(unfiltered) do\n\t\tif entry.name:lower():sub(1, cmd:len()) == cmd:lower() then\n\t\t\ttable.insert(filtered, entry)\n\t\telseif entry.name:lower():match(cmd:lower()) then\n\t\t\ttable.insert(regfiltered, entry)\n\t\tend\n\tend\n\n\t-- Sort entries\n\n\ttable.sort(filtered, function(a, b) return a.name:lower() < b.name:lower() end)\n\ttable.sort(regfiltered, function(a, b) return a.name:lower() < b.name:lower() end)\n\n\t-- Merge entries\n\n\tfor i = 1, #regfiltered do\n\t\tfiltered[#filtered+1] = regfiltered[i]\n\tend\n\t\n\t-- Clear entries\n\n\tentries:reset()\n\n\t-- Fix position\n\n\tentryindex, startindex = 1, 1\n\n\t-- Add filtered entries\n\n\tfor i, entry in ipairs(filtered) do\n\t\tlocal widget = hovercursor(wibox.widget {\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\twibox.widget.imagebox(entry.icon),\n\t\t\t\t\t\tcolortext({ text = entry.name }),\n\t\t\t\t\t\tspacing = dpi(5),\n\t\t\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t\t\t},\n\t\t\t\t\tforced_height = dpi(30),\n\t\t\t\t\tmargins = dpi(5),\n\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t},\n\t\t\t\tbuttons = {\n\t\t\t\t\tawful.button({}, 1, function()\n\t\t\t\t\t\tif entryindex == i then\n\t\t\t\t\t\t\tlocal entry = filtered[entryindex]\n\t\t\t\t\t\t\tentry.appinfo:launch()\n\t\t\t\t\t\t\tawful.keygrabber.stop()\n\t\t\t\t\t\t\tlauncherbox.visible = false\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tentries:get_widgets_at(entryindex, 1)[1]:get_children_by_id(\"bg\")[1].bg = nil\n\t\t\t\t\t\t\tentryindex = i\n\t\t\t\t\t\t\tentries:get_widgets_at(entryindex, 1)[1]:get_children_by_id(\"bg\")[1].bg = beautiful.bgmid\n\t\t\t\t\t\tend\n\t\t\t\t\tend)\n\t\t\t\t},\n\t\t\t\tid = \"bg\",\n\t\t\t\tshape = function(cr, width, height)\n\t\t\t\t\t\t\tgears.shape.rounded_rect(cr, width, height, dpi(10))\n\t\t\t\t\t\tend,\n\t\t\t\twidget = wibox.container.background\n\t\t\t},\n\t\t\tbottom = dpi(5),\n\t\t\twidget = wibox.container.margin\n\t\t})\n\n\t\tif startindex <= i and i <= startindex + 7 then\n\t\t\twidget.visible = true\n\t\telse\n\t\t\twidget.visible = false\n\t\tend\n\n\t\tentries:add(widget)\n\n\t\tif i == entryindex then\n\t\t\twidget:get_children_by_id(\"bg\")[1].bg = beautiful.bgmid\n\t\tend\n\tend\n\n\tcollectgarbage(\"collect\")\n\nend\n\nlocal function open()\n\n\t-- Reset variables\n\n\tstartindex, entryindex, move = 1, 1, false\n\n\t-- Get entries\n\n\tunfiltered = gen() -- TODO: Make this local\n\tfilter(\"\")\n\n\t-- Prompt\n\n\tawful.prompt.run {\n\t\ttextbox = textbox,\n\t\tdone_callback = function() \n\t\t\tlauncherbox.visible = false \n\t\tend,\n\t\tchanged_callback = function(cmd)\n\t\t\tif cmd ~= \"\" then\n\t\t\t\tprompt.markup = markup({ text = cmd, fg = \"fg\" })\n\t\t\telse\n\t\t\t\tprompt.markup = markup({ text = \"Search...\", fg = \"fg\" })\n\t\t\tend\n\t\t\tif move == false then\t\n\t\t\t\tfilter(cmd)\n\t\t\telse\n\t\t\t\tmove = false\n\t\t\tend\n\t\tend,\n\t\texe_callback = function(cmd)\n\t\t\tprompt.markup = markup({ text = \"Search...\", fg = \"fg\" })\n\t\t\tlocal entry = filtered[entryindex]\n\t\t\tif entry then\n\t\t\t\tentry.appinfo:launch()\n\t\t\telse\n\t\t\t\tawful.spawn.with_shell(cmd)\n\t\t\tend\n\t\tend,\n\t\tkeypressed_callback = function(_, key)\n\t\t\tif key == \"Down\" then\n\t\t\t\tnext()\n\t\t\telseif key == \"Up\" then\n\t\t\t\tback()\n\t\t\tend\n\t\tend\n\t}\nend\n\nawesome.connect_signal(\"widget::launcher\", function()\n\tawesome.emit_signal(\"widget::preview:hide\")\n\n\tprompt.markup = markup({ text = \"Search...\", fg = \"fg\" })\n\tlauncherbox.visible = not launcherbox.visible\n\t\n\tif launcherbox.visible then\n\t\topen()\n\telse\n\t\tawful.keygrabber.stop()\n\tend\n\n\tif client.focus and client.focus.fullscreen == true then\n\t\tawful.placement.bottom_left(\n\t\t\tlauncherbox,\n\t\t\t{\n\t\t\t\tmargins = {\n\t\t\t\t\tbottom = dpi(10),\n\t\t\t\t\tleft = dpi(10),\n\t\t\t\t},\n\t\t\t\tparent = awful.screen.focused()\n\t\t\t}\n\t\t)\n\telse\n\t\tawful.placement.bottom_left(\n\t\t\tlauncherbox,\n\t\t\t{\n\t\t\t\tmargins = {\n\t\t\t\t\tbottom = dpi(60),\n\t\t\t\t\tleft = dpi(20),\n\t\t\t\t},\n\t\t\t\tparent = awful.screen.focused()\n\t\t\t}\n\t\t)\n\tend\nend)\n\nawesome.connect_signal(\"widget::launcher:hide\", function()\n\tlauncherbox.visible = false\n\tawful.keygrabber.stop()\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/lock.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\n-- Authentication\n\nlocal authenticate\nlocal pamexists,pam = pcall(require,\"liblua_pam\")\nif pamexists then\n\tauthenticate = function(password)\n\t\treturn pam.auth_current_user(password)\n\tend\nelse\n\tauthenticate = function(password)\n\t\treturn password == user.passwd\n\tend\nend\n\n-- Variables\n\nlocal characters = 0\n\n-- Background\n\nscreen.connect_signal(\"request::desktop_decoration\", function(s)\n\tlocal lockbackground = wibox {\n\t\tscreen = s,\n\t\tvisible = false,\n\t\tontop = true,\n\t\ttype = \"splash\",\n\t\twidget = background({ bg = \"bg\" })\n\t}\n\n\tawful.placement.maximize(lockbackground)\n\n\tlocal batterypercent = wibox.widget {\n\t\ttext = \"N/A\",\n\t\twidget = wibox.widget.textbox\n\t}\n\n\tlocal batteryicon = iconbox({ image = \"batterynone\" })\n\n\tlocal battery = wibox.widget {\n\t\t{\n\t\t\t{\n\t\t\t\tbatteryicon,\n\t\t\t\tbatterypercent,\n\t\t\t\tspacing = dpi(4),\n\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t},\n\t\t\tleft = dpi(8),\n\t\t\tright = dpi(8),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n\t}\n\n\tlocal batterystore\n\tif user.batt ~= nil then\n\t\tawful.widget.watch(\"cat /sys/class/power_supply/\" .. user.batt .. \"/capacity\", 15, function(widget, stdout)\n\t\t\tpercent = tonumber(stdout)\n\t\t\tbatterypercent.text = percent .. \"%\"\n\t\t\tif percent > 80 then\n\t\t\t\tbatterystore = \"battery100\"\n\t\t\telseif percent > 50 then\n\t\t\t\tbatterystore = \"battery80\"\n\t\t\telseif percent > 25 then\n\t\t\t\tbatterystore = \"battery50\"\n\t\t\telseif percent > 10 then\n\t\t\t\tbatterystore = \"battery25\"\n\t\t\telseif percent > 5 then\n\t\t\t\tbatterystore = \"battery10\"\n\t\t\telse\n\t\t\t\tbatterystore = \"battery0\"\n\t\t\tend\n\t\t\tbatteryicon.image = createicon(batterystore)\n\t\tend)\n\tend\n\n\tif s == awful.screen.focused() then\n\t\tlockbackground:setup {\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\twibox.widget.textbox(\"Lock Screen\"),\n\t\t\t\t\t\t\t\t\ttop = dpi(5),\n\t\t\t\t\t\t\t\t\tbottom = dpi(5),\n\t\t\t\t\t\t\t\t\tleft = dpi(8),\n\t\t\t\t\t\t\t\t\tright = dpi(8),\n\t\t\t\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tnil,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tbattery,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ticonbox({ image = \"clock\" }),\n\t\t\t\t\t\t\t\t\t\t\twibox.widget.textclock('%I:%M %p'),\n\t\t\t\t\t\t\t\t\t\t\tspacing = dpi(4),\n\t\t\t\t\t\t\t\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\tleft = dpi(8),\n\t\t\t\t\t\t\t\t\t\tright = dpi(8),\n\t\t\t\t\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tspacing = dpi(5),\n\t\t\t\t\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tlayout = wibox.layout.align.horizontal\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tbutton {\n\t\t\t\t\t\t\t\t\timage = \"shutdown\", \n\t\t\t\t\t\t\t\t\trun = function() \n\t\t\t\t\t\t\t\t\t\tawful.spawn.with_shell(user.shutdown)\n\t\t\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tbutton {\n\t\t\t\t\t\t\t\t\timage = \"restart\", \n\t\t\t\t\t\t\t\t\trun = function() \n\t\t\t\t\t\t\t\t\t\tawful.spawn.with_shell(user.reboot)\n\t\t\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tbutton {\n\t\t\t\t\t\t\t\t\timage = \"exit\", \n\t\t\t\t\t\t\t\t\trun = function() \n\t\t\t\t\t\t\t\t\t\tawesome.quit()\n\t\t\t\t\t\t\t\t\tend\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tspacing = dpi(5),\n\t\t\t\t\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\thalign = \"center\",\n\t\t\t\t\t\t\twidget = wibox.container.place\n\t\t\t\t\t\t},\n\t\t\t\t\t\tlayout = wibox.layout.stack\n\t\t\t\t\t},\n\t\t\t\t\tforced_height = dpi(50),\n\t\t\t\t\tmargins = dpi(10),\n\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t},\n\t\t\t\tvalign = \"bottom\",\n\t\t\t\tcontent_fill_horizontal = true,\n\t\t\t\twidget = wibox.container.place\n\t\t\t},\n\t\t\twidget = background({ bg = \"bg\" })\n\t\t}\n\tend\n\n\tlocal wallpaper = wibox {\n\t\tscreen = awful.screen.focused(),\n\t\twidth = s.geometry.width-dpi(20),\n\t\theight = s.geometry.height-dpi(60),\n\t\tontop = true,\n\t\tvisible = false,\n\t\ttype = \"desktop\",\n\t\twidget = wibox.widget {\n\t\t\timage = gears.surface.crop_surface {\n\t\t\t\tsurface = gears.surface.load_uncached(beautiful.wallpaper),\n\t\t\t\tratio = (s.geometry.width-dpi(20))/(s.geometry.height-dpi(60))\n\t\t\t},\n\t\t\twidget = wibox.widget.imagebox\n\t\t}\n\t}\n\n\tawful.placement.top(\n\t\twallpaper,\n\t\t{\n\t\t\tmargins = {\n\t\t\t\ttop = dpi(10)\n\t\t\t}\n\t\t}\n\t)\n\n\tawesome.connect_signal(\"live::reload\", function()\n\t\tif batterystore then batteryicon.image = createicon(batterystore) end\n\t\twallpaper.widget = wibox.widget {\n\t\t\timage = gears.surface.crop_surface {\n\t\t\t\tsurface = gears.surface.load_uncached(beautiful.wallpaper),\n\t\t\t\tratio = (s.geometry.width-dpi(20))/(s.geometry.height-dpi(60))\n\t\t\t},\n\t\t\twidget = wibox.widget.imagebox\n\t\t}\n\tend)\n\n\tawesome.connect_signal(\"lockscreen::visible\", function(v)\n\t\tlockbackground.visible = v\n\t\twallpaper.visible = v\n\tend)\nend)\n\n-- Prompt\n\nlocal prompt = wibox.widget {\n\tfont = user.font,\n\talign = \"center\",\n\twidget = colortext({ text = \"Enter Password\", fg = \"fg\" })\n}\n\nlocal promptbox = wibox {\n\twidth = dpi(350),\n\theight = dpi(135),\n\tontop = true,\n\tvisible = false\n}\n\nawful.spawn.easy_async_with_shell(\"getent passwd $(whoami) | cut -d ':' -f 5\", function(out)\n\tpromptbox:setup {\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlive(wibox.widget.imagebox, { image = \"calla\" }),\n\t\t\t\t\t\t\tforced_height = dpi(24),\n\t\t\t\t\t\t\tmargins = dpi(5),\n\t\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t\t},\n\t\t\t\t\t\twidget = background({ bg = \"bgmid\" })\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tfont = user.font:gsub(\"%d+\", \"24\"),\n\t\t\t\t\t\twidget = colortext({ text = out:gsub(\",\", \"\"):gsub(\"\\n\", \"\") })\n\t\t\t\t\t},\n\t\t\t\t\tspacing = dpi(10),\n\t\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tprompt,\n\t\t\t\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttop = dpi(10),\n\t\t\t\t\t\tbottom = dpi(10),\n\t\t\t\t\t\tleft = dpi(15),\n\t\t\t\t\t\tright = dpi(15),\n\t\t\t\t\t\tforced_height = dpi(40),\n\t\t\t\t\t\tforced_width = dpi(300),\n\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t},\n\t\t\t\t\twidget = background({ bg = \"bgmid\" })\n\t\t\t\t},\n\t\t\t\tspacing = dpi(10),\n\t\t\t\tlayout = wibox.layout.fixed.vertical\n\t\t\t},\n\t\t\tvalign = \"center\",\n\t\t\thalign = \"center\",\n\t\t\tlayout = wibox.container.place\n\t\t},\n\t\twidget = background({ bg = \"bg\" })\n\t}\nend)\n\nawful.placement.centered(promptbox)\n\nawesome.connect_signal(\"lockscreen::visible\", function(v)\n\tpromptbox.visible = v\nend)\n\n-- Reset\n\nlocal function reset()\n\tcharacters = 0\n\tprompt.markup = markup({ text = \"Enter Password\", fg = \"fg\" })\n\t--awful.spawn.with_shell(\"pkill --full --uid \" .. os.getenv(\"USER\") .. \" fprintd-verify\")\nend\n\n-- Fail\n\nlocal function fail()\n\tcharacters = 0\n\tprompt.markup = markup({ text = \"Try Again\", fg = \"fg\" })\nend\n\n-- Input\n\nlocal function grabpassword()\n\tawful.prompt.run {\n\t\thooks = {\n\t\t\t{{ }, 'Escape', function(_)\n\t\t\t\t\treset()\n\t\t\t\t\tgrabpassword()\n\t\t\t\tend\n\t\t\t}\n\t\t},\n\t\tkeypressed_callback  = function(mod, key, cmd)\n\t\t\tif #key == 1 then\n\t\t\t\tcharacters = characters + 1\n\t\t\t\tprompt.markup = markup({ text = string.rep(\"\", characters), fg = \"fg\" })\n\t\t\telseif key == \"BackSpace\" then\n\t\t\t\tif characters > 1 then\n\t\t\t\t\tcharacters = characters - 1\n\t\t\t\t\tprompt.markup = markup({ text = string.rep(\"\", characters), fg = \"fg\" })\n\t\t\t\telse\n\t\t\t\t\tcharacters = 0\n\t\t\t\t\tprompt.markup = markup({ text = \"Enter Password\", fg = \"fg\" })\n\t\t\t\tend\n\t\t\tend\n\t\tend,\n\t\texe_callback = function(input)\n\t\t\t--[[ Why doesn't this work?\n\t\t\tcharacters = 0\n\t\t\tprompt.markup = markup({ text = \"Authenticating...\", fg = \"fg\" })\n\t\t\t--]]\n\t\t\tif authenticate(input) then\n\t\t\t\treset()\n\t\t\t\tawesome.emit_signal(\"lockscreen::visible\", false)\n\t\t\telse\n\t\t\t\tfail()\n\t\t\t\tgrabpassword()\n\t\t\tend\n\t\tend,\n\t\ttextbox = wibox.widget.textbox()\n\t}\n\n\t--[[awful.spawn.easy_async(\"fprintd-verify\", function(out)\n\t\tif out:match(\"verify%-match\") then\n\t\t\tawful.keygrabber.stop()\n\t\t\treset()\n\t\t\tawesome.emit_signal(\"lockscreen::visible\", false)\n\t\telseif out:match(\"verify%-no%-match\") then\n\t\t\tawful.keygrabber.stop()\n\t\t\tfail()\n\t\t\tgrabpassword()\n\t\telseif out:match(\"already claimed\") then\n\t\t\tawful.keygrabber.stop()\n\t\t\treset()\n\t\t\tgrabpassword()\n\t\telseif out:match(\"verify%-unknown%-error\") or out:match(\"already been opened\") then\n\t\t\trequire(\"naughty\").notification{text=\"It appears that fprintd has encountered an error because of a suspend. Please enter password manually.\",urgency=\"critical\"}\n\t\tend\n\tend)--]]\nend\n\n-- Lock\n\nawesome.connect_signal(\"widget::lockscreen\", function()\n\tawesome.emit_signal(\"lockscreen::visible\", true)\n\tgrabpassword()\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/notif.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal ruled = require(\"ruled\")\nlocal naughty = require(\"naughty\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\n-- Config\n\nnaughty.config.defaults.ontop = true\nnaughty.config.defaults.screen = awful.screen.focused()\nnaughty.config.defaults.border_width = 0\nnaughty.config.defaults.position = \"top_right\"\nnaughty.config.defaults.title = \"Notification\"\n\n-- Rules\n\nruled.notification.connect_signal('request::rules', function()\n\tlocal function setrules()\n\t\t-- Critical\n\t\truled.notification.append_rule {\n\t\t\trule       = { urgency = 'critical' },\n\t\t\tproperties = { bg = beautiful.bg_normal, fg = beautiful.fg_urgent, timeout = 0 }\n\t\t}\n\n\t\t-- Normal\n\t\truled.notification.append_rule {\n\t\t\trule       = { urgency = 'normal' },\n\t\t\tproperties = { bg = beautiful.bg_normal, fg = beautiful.fg_normal, timeout = 5 }\n\t\t}\n\n\t\t-- Low\n\t\truled.notification.append_rule {\n\t\t\trule       = { urgency = 'low' },\n\t\t\tproperties = { bg = beautiful.bg_normal, fg = beautiful.fg_normal, timeout = 5 }\n\t\t}\n\tend\n\n\tsetrules()\n\n\tawesome.connect_signal(\"live::reload\", function()\n\t\tsetrules()\n\tend)\nend)\n\n-- Notification\n\nnaughty.connect_signal(\"request::display\", function(n)\n\tnaughty.layout.box {\n\t\tnotification = n,\n\t\ttype = \"notification\",\n\t\tbg = beautiful.bg_normal,\n\t\twidget_template = {\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tnaughty.widget.title,\n\t\t\t\t\t\t\t\t\t\tlayout = wibox.layout.align.horizontal\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tmargins = dpi(10),\n\t\t\t\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tshape = function(cr, width, height)\n\t\t\t\t\t\t\t\t\t\t\tgears.shape.rounded_rect(cr, width, height, dpi(10))\n\t\t\t\t\t\t\t\t\t\tend,\n\t\t\t\t\t\t\t\twidget = live(wibox.container.background, { bg = \"bgmid\" })\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tstrategy = \"min\",\n\t\t\t\t\t\t\twidth = dpi(250),\n\t\t\t\t\t\t\twidget = wibox.container.constraint\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tnaughty.widget.message,\n\t\t\t\t\t\t\t\tmargins = dpi(5),\n\t\t\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t\t},\n\t\t\t\t\t\tspacing = dpi(5),\n\t\t\t\t\t\tlayout = wibox.layout.fixed.vertical\n\t\t\t\t\t},\n\t\t\t\t\tstrategy = \"max\",\n\t\t\t\t\twidth = dpi(400),\n\t\t\t\t\twidget = wibox.container.constraint\n\t\t\t\t},\n\t\t\t\tmargins = dpi(10),\n\t\t\t\twidget = wibox.container.margin\n\t\t\t},\t\n\t\t\tid = \"background_role\",\n\t\t\twidget = naughty.container.background\n\t\t}\n\t}\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/panel.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\nlocal dock = require(\"theme.dock\")\n\nlocal function create(s)\n\nlocal menu = button {\n\timage = \"calla\",\n\trun = function()\n\t\tawesome.emit_signal('widget::launcher')\n\tend\n}\n\nlocal tray = wibox.widget {\n\twibox.widget.systray(),\n\tmargins = 0,\n\tvisible = false,\n\twidget = wibox.container.margin\n}\n\nlocal systraybutton = hovercursor(wibox.widget {\n\tbuttons = { \n\t\tawful.button({}, 1, function()\n\t\t\tawesome.emit_signal(\"widget::systray\")\n\t\tend)\n\t},\n\talign = \"center\",\n\twidget = iconbox({ image = \"left\" })\n})\n\nlocal systray = wibox.widget {\n\t{\n\t\t{\n\t\t\tsystraybutton,\n\t\t\tforced_width = dpi(30),\n\t\t\twidget = wibox.container.background\n\t\t},\n\t\ttray,\n\t\tlayout = wibox.layout.fixed.horizontal\n\t},\n\twidget = background({ bg = \"bgmid\" })\n}\n\nlocal closed = true\n\nlocal systraystore\nawesome.connect_signal(\"widget::systray\", function()\n\tif closed == true then\n\t\ttray.visible = true\n\t\ttray.margins = dpi(5)\n\t\tsystraystore = \"right\"\n\t\tclosed = false\n\telse\n\t\ttray.visible = false\n\t\ttray.margins = 0\n\t\tsystraystore = \"left\"\n\t\tclosed = true\n\tend\n\tsystraybutton.image = createicon(systraystore)\nend)\n\nlocal media = wibox.widget {\n\t{\n\t\t{\n\t\t\t{\n\t\t\t\tid = \"icon\",\n\t\t\t\twidget = iconbox({ image = \"musicon\" })\n\t\t\t},\n\t\t\t{\n\t\t\t\tid = \"title\",\n\t\t\t\twidget = wibox.widget.textbox(\"Not Playing - No Artist\")\n\t\t\t},\n\t\t\tspacing = dpi(4),\n\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t},\n\t\tleft = dpi(8),\n\t\tright = dpi(8),\n\t\twidget = wibox.container.margin\n\t},\n\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n}\n\nlocal playerstore\nawesome.connect_signal(\"signal::playerctl\", function(title, album, artist, cover, status)\n\tif string.len(title .. \" - \" .. artist) > 50 then\n\t\tmedia:get_children_by_id(\"title\")[1].text = title\n\telse\n\t\tmedia:get_children_by_id(\"title\")[1].text = title .. \" - \" .. artist\n\tend\n\tif title == \"Not Playing\" then\n\t\tstate = false\n\t\tplayerstore = \"musicoff\"\n\telse\n\t\tstate = true\n\t\tplayerstore = \"musicon\"\n\tend\n\tmedia:get_children_by_id(\"icon\")[1].image = createicon(playerstore)\nend)\n\nlocal volumepercent = wibox.widget {\n\ttext = \"N/A\",\n\twidget = wibox.widget.textbox\n}\n\nlocal volumeicon = iconbox({ image = \"volumemute\" })\n\nlocal volume = wibox.widget {\n\t{\n\t\t{\n\t\t\tvolumeicon,\n\t\t\tvolumepercent,\n\t\t\tspacing = dpi(4),\n\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t},\n\t\tleft = dpi(8),\n\t\tright = dpi(8),\n\t\twidget = wibox.container.margin\n\t},\n\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n}\n\nlocal volumestore\nawesome.connect_signal(\"signal::volume\", function(volume, mute)\n\tif mute then\n\t\tvolumepercent.text = \"Muted\"\n\t\tvolumestore = \"volumemute\"\n\telse\n\t\tvolumepercent.text = tostring(volume) .. \"%\"\n\t\tif volume > 100 then\n\t\t\tvolumestore = \"volumewarn\"\n\t\telseif volume >= 50 then\n\t\t\tvolumestore = \"volume100\"\n\t\telseif volume >= 25 then\n\t\t\tvolumestore = \"volume50\"\n\t\telseif volume > 0 then\n\t\t\tvolumestore = \"volume25\"\n\t\telseif volume == 0 then\n\t\t\tvolumestore = \"volume0\"\n\t\tend\n\tend\n\tvolumeicon.image = createicon(volumestore)\nend)\n\nlocal batterypercent = wibox.widget {\n\ttext = \"N/A\",\n\twidget = wibox.widget.textbox\n}\n\nlocal batteryicon = iconbox({ image = \"batterynone\" })\n\nlocal battery = wibox.widget {\n\t{\n\t\t{\n\t\t\tbatteryicon,\n\t\t\tbatterypercent,\n\t\t\tspacing = dpi(4),\n\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t},\n\t\tleft = dpi(8),\n\t\tright = dpi(8),\n\t\twidget = wibox.container.margin\n\t},\n\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n}\n\nlocal batterystore\nif user.batt ~= nil then\n\tawful.widget.watch(\"cat /sys/class/power_supply/\" .. user.batt .. \"/capacity\", 15, function(widget, stdout)\n\t\tpercent = tonumber(stdout)\n\t\tbatterypercent.text = percent .. \"%\"\n\t\tif percent > 80 then\n\t\t\tbatterystore = \"battery100\"\n\t\telseif percent > 50 then\n\t\t\tbatterystore = \"battery80\"\n\t\telseif percent > 25 then\n\t\t\tbatterystore = \"battery50\"\n\t\telseif percent > 10 then\n\t\t\tbatterystore = \"battery25\"\n\t\telseif percent > 5 then\n\t\t\tbatterystore = \"battery10\"\n\t\telse\n\t\t\tbatterystore = \"battery0\"\n\t\tend\n\t\tbatteryicon.image = createicon(batterystore)\n\tend)\nend\n\nlocal clock = wibox.widget {\n\t{\n\t\t{\n\t\t\ticonbox({ image = \"clock\" }),\n\t\t\twibox.widget.textclock('%I:%M %p'),\n\t\t\tspacing = dpi(4),\n\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t},\n\t\tleft = dpi(8),\n\t\tright = dpi(8),\n\t\twidget = wibox.container.margin\n\t},\n\twidget = background({ bg = \"bgmid\", fg = \"fg\" })\n}\n\nlocal taglist = awful.widget.taglist {\n\tscreen = s,\n\tfilter = awful.widget.taglist.filter.selected,\n\tstyle = {\n\t\tshape = function(cr, width, height)\n\t\t\t\t\tgears.shape.rounded_rect(cr, width, height, dpi(10))\n\t\t\t\tend\n\t},\n\twidget_template = {\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\twibox.widget.textbox(\"Workspace \"),\n\t\t\t\t\twidget = background({ fg = \"fg\" })\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tid = \"text_role\",\n\t\t\t\t\twidget = wibox.widget.textbox\n\t\t\t\t},\n\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t},\n\t\t\tleft = dpi(8),\n\t\t\tright = dpi(8),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\tid = \"background_role\",\n\t\twidget = wibox.container.background,\n\t\tcreate_callback = function(self)\n\t\t\thovercursor(self)\n\t\tend\n\t},\n\tbuttons = {\n\t\tawful.button({ }, 1, function()\n\t\t\tawesome.emit_signal(\"widget::preview\")\n\t\tend),\n\t\tawful.button({ }, 4, function(t)\n\t\t\tawful.tag.viewnext(t.screen)\n\t\tend),\n\t\tawful.button({ }, 5, function(t)\n\t\t\tawful.tag.viewprev(t.screen)\n\t\tend)\n\t}\n}\n\nlocal layouts = awful.widget.layoutbox {\n\tscreen  = s,\n\tbuttons = {\n\t\tawful.button({ }, 1, function () awful.layout.inc( 1) end),\n\t\tawful.button({ }, 3, function () awful.layout.inc(-1) end),\n\t}\n}\n\nlocal layoutbox = hovercursor(wibox.widget {\n\t{\n\t\tlayouts,\n\t\tmargins = dpi(5),\n\t\twidget = wibox.container.margin\n\t},\n\twidget = background({ bg = \"bgmid\" })\n})\n\nawesome.connect_signal(\"live::reload\", function()\n\tif systraystore then systraybutton.image = createicon(systraystore) end\n\tif playerstore then media:get_children_by_id(\"icon\")[1].image = createicon(playerstore) end\n\tif volumestore then volumeicon.image = createicon(volumestore) end\n\tif batterystore then batteryicon.image = createicon(batterystore) end\n\ttaglist._do_taglist_update_now()\n\ttag.emit_signal(\"property::layout\", awful.screen.focused().selected_tag)\nend)\n\nreturn wibox.widget {\n\t{\n\t\t{\n\t\t\t{\n\t\t\t\tmenu,\n\t\t\t\ttaglist,\n\t\t\t\tlayoutbox,\n\t\t\t\tspacing = dpi(5),\n\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t},\n\t\t\tnil,\n\t\t\t{\n\t\t\t\tsystray,\n\t\t\t\thovercursor(wibox.widget {\n\t\t\t\t\tmedia,\n\t\t\t\t\tvolume,\n\t\t\t\t\tbattery,\n\t\t\t\t\tclock,\n\t\t\t\t\tbuttons = {\n\t\t\t\t\t\tawful.button({ }, 1, function()\n\t\t\t\t\t\t\tawesome.emit_signal(\"widget::control\")\n\t\t\t\t\t\tend)\n\t\t\t\t\t},\n\t\t\t\t\tspacing = dpi(5),\n\t\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t\t}),\n\t\t\t\tspacing = dpi(5),\n\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t},\n\t\t\tlayout = wibox.layout.align.horizontal\n\t\t},\n\t\t{\n\t\t\tdock,\n\t\t\thalign = \"center\",\n\t\t\twidget = wibox.container.place\n\t\t},\n\t\tlayout = wibox.layout.stack\n\t},\n\tforced_height = dpi(50),\n\tmargins = dpi(10),\n\twidget = wibox.container.margin\n}\n\nend\n\nreturn create\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/preview.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\nlocal scale = 0.15\n\nlocal function createpreview(t, s, geometry)\n    local clientlayout = wibox.layout.manual()\n    clientlayout.forced_height = geometry.height\n    clientlayout.forced_width = geometry.width\n    for _, c in ipairs(t:clients()) do\n        if not c.hidden and not c.minimized then\n\n            local imagebox = wibox.widget {\n                resize = true,\n                forced_height = dpi(150) * scale,\n                forced_width = dpi(150) * scale,\n                widget = wibox.widget.imagebox\n            }\n\n\t\t\tif not pcall(function() imagebox.image = gears.surface.load(c.icon) end) then\n\t\t\t\timagebox.image = beautiful.calla\n\t\t\tend\n\n            local clientbox = wibox.widget({\n                {\n                    nil,\n                    {\n                        nil,\n                        imagebox,\n                        nil,\n                        expand = \"outside\",\n                        layout = wibox.layout.align.horizontal,\n                    },\n                    nil,\n                    expand = \"outside\",\n                    widget = wibox.layout.align.vertical,\n                },\n                forced_height = math.floor(c.height * scale),\n                forced_width = math.floor(c.width * scale),\n\t\t\t\tborder_width = dpi(2),\n\t\t\t\tborder_color = beautiful.bgmid,\n                widget = background({ bg = \"bg\" })\n            })\n\n            clientbox.point = {\n                x = math.floor((c.x - geometry.x) * scale),\n                y = math.floor((c.y - geometry.y) * scale),\n            }\n\n            clientlayout:add(clientbox)\n        end\n    end\n\n\tif t:clients()[1] == nil then\n\t\treturn wibox.widget {\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\t{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\twibox.widget.textbox(\"Empty\"),\n\t\t\t\t\t\t\ttop = dpi(5),\n\t\t\t\t\t\t\tbottom = dpi(5),\n\t\t\t\t\t\t\tleft = dpi(8),\n\t\t\t\t\t\t\tright = dpi(8),\n\t\t\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t\t\t},\n\t\t\t\t\t\twidget = background({ bg = \"bg\", fg = \"fg\" })\n\t\t\t\t\t},\n\t\t\t\t\tvalign = \"center\",\n\t\t\t\t\thalign = \"center\",\n\t\t\t\t\twidget = wibox.container.place\n\t\t\t\t},\n\t\t\t\tbg = beautiful.bg..\"96\",\n\t\t\t\twidget = wibox.container.background\n\t\t\t},\n\t\t\twidget = background({ bg = \"bgmid\" })\n\t\t}\n\telse\n\t\treturn wibox.widget {\n\t\t\t{\n\t\t\t\tclientlayout,\n\t\t\t\tforced_height = geometry.height,\n\t\t\t\tforced_width = geometry.width,\n\t\t\t\twidget = wibox.container.place\n\t\t\t},\n\t\t\twidget = background({ bg = \"bgmid\" })\n\t\t}\n\tend\nend\n\nlocal previewbox = wibox {\n\twidth = dpi(100),\n\theight = dpi(100),\n\tontop = true,\n\tvisible = false,\n\twidget = live(wibox.container.background, { bg = \"bg\" })\n}\n\nlocal previewlist = wibox.widget {\n\texpand = true,\n\torientation = \"horizontal\",\n\tspacing = dpi(5),\n\tlayout = wibox.layout.grid\n}\n\nawesome.connect_signal(\"widget::preview\", function()\n\tawesome.emit_signal(\"widget::launcher:hide\")\n\n\tif previewbox.visible then\n\t\tpreviewbox.visible = false\n\t\treturn\n\tend\n\n\tpreviewlist:reset()\n\n\tlocal geometry = awful.screen.focused():get_bounding_geometry()\n\tlocal tags = awful.screen.focused().tags\n\tlocal numtags\n\n\tfor i, tag in ipairs(tags) do\n\t\tnumtags = i\n\n\t\tlocal preview = wibox.widget {\n\t\t\thovercursor(createpreview(tag, tag.screen, geometry)),\n\t\t\tbuttons = {\n\t\t\t\tawful.button({}, 1, function()\n\t\t\t\t\tawesome.emit_signal(\"widget::preview\")\n\t\t\t\t\ttag:view_only()\n\t\t\t\tend)\n\t\t\t},\n\t\t\twidget = wibox.container.background\n\t\t}\n\n\t\tpreviewlist:add(preview)\n\tend\n\n\tpreviewbox.width = geometry.width * scale * numtags + (numtags + 1) * dpi(5)\n\tpreviewbox.height = (geometry.height-dpi(40)) * scale + (2 * dpi(5))\n\tpreviewbox.widget = wibox.widget {\n\t\t\t{\n\t\t\tpreviewlist,\n\t\t\tmargins = dpi(5),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\twidget = live(wibox.container.background, { bg = \"bg\" })\n\t}\n\n\tawful.placement.bottom_left(\n\t\tpreviewbox,\n\t\t{\n\t\t\tmargins = {\n\t\t\t\tbottom = dpi(60),\n\t\t\t\tleft = dpi(20),\n\t\t\t},\n\t\t\tparent = awful.screen.focused()\n\t\t}\n\t)\n\n\tpreviewbox.visible = true\nend)\n\nawesome.connect_signal(\"widget::preview:hide\", function()\n\tpreviewbox.visible = false\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/settings.lua",
    "content": "awesome.connect_signal(\"widget::config\", function()\n\nlocal dpi = require(\"beautiful\").xresources.apply_dpi\nlocal lgi = require(\"lgi\")\nlocal Gio = lgi.Gio\nlocal Gtk = lgi.require(\"Gtk\", \"3.0\")\nlocal Gdk = lgi.require(\"Gdk\", \"3.0\")\nlocal GdkPixbuf = lgi.require(\"GdkPixbuf\", \"2.0\")\nlocal GObject = lgi.require(\"GObject\", \"2.0\")\n\nlocal appID = \"io.github.stardust-kyun.calla.settings\"\nlocal appTitle = \"Settings\"\nlocal app = Gtk.Application({ application_id = appID })\n\nlocal function copytable(table)\n\tlocal copy = {}\n\tfor key, value in pairs(table) do\n\t\tcopy[key] = value\n\tend\n\treturn copy\nend\n\nlocal settings = copytable(user)\n\nlocal color\n\nlocal function genColor()\n\tcolor = readjson(require(\"gears\").filesystem.get_configuration_dir() .. \"color/\" .. settings.color .. \"/\" .. settings.color .. \".json\")\nend\n\ngenColor()\n\nlocal function settingsEntry(name, setting, default)\n\n\tlocal label = Gtk.Label({ label = name, halign = Gtk.Align.START })\n\tlocal settingsEntry = Gtk.Entry({ placeholder_text = settings[setting], width = dpi(175) })\n\tfunction settingsEntry:on_key_release_event()\n\t\tsettings[setting] = settingsEntry.text\n\tend\n\tlocal button = Gtk.Button.new_with_label(\"Default\")\n\tfunction button:on_clicked()\n\t\tsettings[setting] = default\n\t\tsettingsEntry.placeholder_text = settings[setting]\n\t\tsettingsEntry.text = \"\"\n\tend\n\tlocal grid = Gtk.Grid({\n\t\tcolumn_spacing = dpi(10),\n\t\trow_spacing = dpi(10),\n\t\thalign = Gtk.Align.CENTER,\n\n\t\t{ label, top_attach = 0, left_attach = 0, width = 2 },\n\t\t{ settingsEntry, top_attach = 1, left_attach = 0 },\n\t\t{ button, top_attach = 1, left_attach = 1 },\n\t})\n\n\treturn grid\n\nend\n\nlocal function colorEntry(name, setting, default)\n\n\tlocal label = Gtk.Label({ label = name, halign = Gtk.Align.START })\n\tlocal colorEntry = Gtk.Entry({ placeholder_text = color[setting], width = dpi(175) })\n\tfunction colorEntry:on_key_release_event()\n\t\tcolor[setting] = colorEntry.text\n\tend\n\tlocal button = Gtk.Button.new_with_label(\"Default\")\n\tfunction button:on_clicked()\n\t\tcolor[setting] = default\n\t\tcolorEntry.placeholder_text = color[setting]\n\t\tcolorEntry.text = \"\"\n\tend\n\tlocal grid = Gtk.Grid({\n\t\tcolumn_spacing = dpi(10),\n\t\trow_spacing = dpi(10),\n\t\thalign = Gtk.Align.CENTER,\n\n\t\t{ label, top_attach = 0, left_attach = 0, width = 2 },\n\t\t{ colorEntry, top_attach = 1, left_attach = 0 },\n\t\t{ button, top_attach = 1, left_attach = 1 },\n\t})\n\n\treturn grid\n\nend\n\nlocal function createColor(name, col, colname)\n\n\tlocal rgbcol = Gdk.RGBA.parse(col)\n\n\tlocal label = Gtk.Label({ label = name, halign = Gtk.Align.START })\n\tlocal button = Gtk.ColorButton({ rgba = rgbcol, show_editor = true })\n\tfunction button:on_color_set()\n\t\tlocal out = button:get_rgba():to_string()\n\n\t\tlocal red = out:match(\"%d+,\"):gsub(\",\", \"\")\n\t\tlocal green = out:match(\",%d+,\"):gsub(\",\", \"\")\n\t\tlocal blue = out:match(\",%d+%)\"):gsub(\",\", \"\"):gsub(\"%)\", \"\")\n\n\t\tlocal hex = string.format(\"#%02X%02X%02X\", red, green, blue)\n\t\t\n\t\tcolor[colname] = hex\n\tend\n\tlocal grid = Gtk.Grid({\n\t\tcolumn_spacing = dpi(10),\n\t\trow_spacing = dpi(10),\n\t\thalign = Gtk.Align.CENTER,\n\n\t\t{ label, top_attach = 0, left_attach = 0 },\n\t\t{ button, top_attach = 1, left_attach = 0 },\n\t})\n\n\treturn grid\n\nend\n\nlocal function doGeneral()\n\n\tlocal function wallpaper()\n\n\t\tlocal label = Gtk.Label({ label = \"Wallpaper\", halign = Gtk.Align.START })\n\n\t\tlocal filter = Gtk.FileFilter()\n\t\tfilter:add_pattern(\"*.png\")\n\t\tfilter:add_pattern(\"*.jpg\")\n\t\tfilter:add_pattern(\"*.JPG\")\n\t\tfilter:add_pattern(\"*.jpeg\")\n\n\t\tif settings.wallpaper ~= nil then\n\t\t\tlocal pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(settings.wallpaper:gsub(\"~\", os.getenv(\"HOME\")), 500, 500, true)\n\t\telse\n\t\t\tlocal pixbuf\n\t\tend\n\t\tlocal preview = Gtk.Image()\n\n\t\tlocal wallpaper = Gtk.FileChooserButton({ filter = filter, title = \"Choose Wallpaper\", width = dpi(175), preview_widget = preview })\n\t\tif settings.wallpaper ~= nil then\n\t\t\twallpaper:set_file(Gio.File.new_for_path(settings.wallpaper:gsub(\"~\", os.getenv(\"HOME\"))))\n\t\t\twallpaper:set_current_folder_file(Gio.File.new_for_path(settings.wallpaper:gsub(\"~\", os.getenv(\"HOME\"))))\n\t\telse\n\t\t\twallpaper:set_file(Gio.File.new_for_path(\"\"))\n\t\t\twallpaper:set_current_folder_file(Gio.File.new_for_path(os.getenv(\"HOME\")))\n\t\tend\n\t\tfunction wallpaper:on_file_set()\n\t\t\tsettings.wallpaper = self:get_filename():gsub(os.getenv(\"HOME\"), \"~\")\n\t\tend\n\t\tfunction wallpaper:on_update_preview()\n\t\t\tpixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(self:get_preview_filename(), 500, 500, true)\n\t\t\tpreview:set_from_pixbuf(pixbuf)\n\t\t\twallpaper:set_preview_widget_active(true)\n\t\tend\n\n\t\tlocal button = Gtk.Button.new_with_label(\"Default\")\n\t\tfunction button:on_clicked()\n\t\t\tsettings.wallpaper = nil\n\t\t\twallpaper:set_file(Gio.File.new_for_path(\"\"))\n\t\tend\n\n\t\tlocal grid = Gtk.Grid({\n\t\t\tcolumn_spacing = dpi(10),\n\t\t\trow_spacing = dpi(10),\n\t\t\thalign = Gtk.Align.CENTER,\n\n\t\t\t{ label, top_attach = 0, left_attach = 0, width = 2 },\n\t\t\t{ wallpaper, top_attach = 1, left_attach = 0 },\n\t\t\t{ button, top_attach = 1, left_attach = 1 },\n\t\t})\n\n\t\treturn grid\n\n\tend\n\n\tlocal function screenshot()\n\n\t\tlocal label = Gtk.Label({ label = \"Screenshot Directory\", halign = Gtk.Align.START })\n\t\tlocal button = Gtk.FileChooserButton({ title = \"Choose Screenshot Directory\", width = dpi(245), action = Gtk.FileChooserAction.SELECT_FOLDER })\n\t\tbutton:set_current_folder_file(Gio.File.new_for_path(settings.shotdir:gsub(\"~\", os.getenv(\"HOME\"))))\n\t\tfunction button:on_file_set()\n\t\t\tsettings.shotdir = self:get_filename():gsub(os.getenv(\"HOME\"), \"~\")\n\t\tend\n\n\t\tlocal grid = Gtk.Grid({\n\t\t\tcolumn_spacing = dpi(10),\n\t\t\trow_spacing = dpi(10),\n\t\t\thalign = Gtk.Align.CENTER,\n\n\t\t\t{ label, top_attach = 0, left_attach = 0 },\n\t\t\t{ button, top_attach = 1, left_attach = 0 },\n\t\t})\n\n\t\treturn grid\n\n\tend\n\n\tlocal grid = Gtk.Grid({\n\t\tcolumn_spacing = dpi(20),\n\t\trow_spacing = dpi(10),\n\t\tmargin = dpi(20),\n\t\thalign = Gtk.Align.CENTER,\n\n\t\t{ settingsEntry(\"Terminal\", \"terminal\", \"st\"), top_attach = 0, left_attach = 0 },\n\t\t{ settingsEntry(\"Shutdown\", \"shutdown\", \"systemctl poweroff\"), top_attach = 1, left_attach = 0 },\n\t\t{ settingsEntry(\"Reboot\", \"reboot\", \"systemctl reboot\"), top_attach = 2, left_attach = 0 },\n\t\t{ settingsEntry(\"Font\", \"font\", \"Roboto Medium 11\"), top_attach = 0, left_attach = 1 },\n\t\t{ settingsEntry(\"Alt Font\", \"fontalt\", \"Roboto Bold 11\"), top_attach = 1, left_attach = 1 },\n\t\t{ settingsEntry(\"Fallback Password\", \"passwd\", \"awesomewm\"), top_attach = 3, left_attach = 0 },\n\t\t{ settingsEntry(\"Battery\", \"batt\", \"BAT0\"), top_attach = 2, left_attach = 1 },\n\t\t{ wallpaper(), top_attach = 3, left_attach = 1 },\n\t\t{ settingsEntry(\"temp\", \"temp\", \"temp\"), top_attach = 4, left_attach = 1 },\n\t\t{ screenshot(), top_attach = 4, left_attach = 0 },\n\t})\n\n\treturn grid\n\nend\n\nlocal function doColor()\n\n\tgenColor()\n\n\tlocal left = Gtk.Grid({\n\t\tcolumn_spacing = dpi(10),\n\t\trow_spacing = dpi(10),\n\t\thalign = Gtk.Align.CENTER,\n\n\t\t{ createColor(\"Bg\", color.bg, \"bg\"), top_attach = 0, left_attach = 0 },\n\t\t{ createColor(\"Mid Bg\", color.bgmid, \"bgmid\"), top_attach = 1, left_attach = 0 },\n\t\t{ createColor(\"Alt Bg\", color.bgalt, \"bgalt\"), top_attach = 2, left_attach = 0 },\n\t\t{ createColor(\"Fg\", color.fg, \"fg\"), top_attach = 0, left_attach = 1 },\n\t\t{ createColor(\"Black\", color.black, \"black\"), top_attach = 1, left_attach = 1 },\n\t\t{ createColor(\"White\", color.white, \"white\"), top_attach = 2, left_attach = 1 },\n\t\t{ createColor(\"Red\", color.red, \"red\"), top_attach = 0, left_attach = 2 },\n\t\t{ createColor(\"Green\", color.green, \"green\"), top_attach = 1, left_attach = 2 },\n\t\t{ createColor(\"Yellow\", color.yellow, \"yellow\"), top_attach = 2, left_attach = 2 },\n\t\t{ createColor(\"Blue\", color.blue, \"blue\"), top_attach = 0, left_attach = 3 },\n\t\t{ createColor(\"Magenta\", color.magenta, \"magenta\"), top_attach = 1, left_attach = 3 },\n\t\t{ createColor(\"Cyan\", color.cyan, \"cyan\"), top_attach = 2, left_attach = 3 },\n\n\t\t{ colorEntry(\"Gui Theme\", \"gtk\", color.gtk), top_attach = 3, left_attach = 0, width = 4 },\n\t})\n\n\tlocal right = Gtk.Grid({\n\t\tcolumn_spacing = dpi(10),\n\t\trow_spacing = dpi(10),\n\t\thalign = Gtk.Align.CENTER,\n\n\t\t{ colorEntry(\"Compositor Radius\", \"compradius\", color.compradius), top_attach = 0, left_attach = 0 },\n\t\t{ colorEntry(\"Compositor Offset\", \"compoffset\", color.compoffset), top_attach = 1, left_attach = 0 },\n\t\t{ colorEntry(\"Compositor Opacity\", \"compopacity\", color.compopacity), top_attach = 2, left_attach = 0 },\n\t\t{ colorEntry(\"Icon Theme\", \"icons\", color.icons), top_attach = 3, left_attach = 0 },\n\t})\n\n\tlocal grid = Gtk.Grid({\n\t\tcolumn_spacing = dpi(20),\n\t\thalign = Gtk.Align.CENTER,\n\n\t\t{ left, top_attach = 0, left_attach = 0 },\n\t\t{ right, top_attach = 0, left_attach = 1 },\n\n\t})\n\n\treturn grid\nend\n\nlocal function doTheme()\n\n\tlocal function colorgen()\n\t\tlocal colors = {}\n\n\t\tfor dir in io.popen(\"ls -d \" .. require(\"gears\").filesystem.get_configuration_dir() .. \"color/*/ | rev | cut -f2 -d'/' | rev\"):lines() do\n\t\t\ttable.insert(colors, dir)\n\t\tend\n\n\t\treturn colors\n\tend\n\n\tlocal model = Gtk.ListStore.new({ GObject.Type.STRING })\n\tlocal items = colorgen()\n\tlocal currentcolor = 0\n\n\tfor i, name in ipairs(items) do\n\t\tmodel:append({ name })\n\n\t\tif name == settings.color then\n\t\t\tcurrentcolor = i-1\n\t\tend\n\tend\n\n\tlocal combo = Gtk.ComboBox({\n\t\tmodel = model,\n\t\tactive = currentcolor,\n\t\tcells = {\n\t\t\t{\n\t\t\t\tGtk.CellRendererText(),\n\t\t\t\t{ text = 1 },\n\t\t\t\talign = Gtk.Align.START\n\t\t\t}\n\t\t},\n\t\texpand = true\n\t})\n\n\tlocal savebutton = Gtk.Button.new_with_label(\"Save Theme As...\")\n\tlocal confirmbutton = Gtk.Button.new_with_label(\"Confirm\")\n\tlocal cancelbutton = Gtk.Button.new_with_label(\"Cancel\")\n\tlocal nameset = false\n\n\tlocal themeprompt = Gtk.Label({ label = \"What should this theme be called?\", halign = Gtk.Align.START })\n\tlocal themename = Gtk.Entry({ placeholder_text = \"Theme Name\", hexpand = true })\n\tlocal name\n\tfunction themename:on_key_release_event()\n\t\tname = themename.text\n\tend\n\n\tlocal wallprompt = Gtk.Label({ label = \"What wallpaper should this theme use?\", halign = Gtk.Align.START })\n\tlocal filter = Gtk.FileFilter()\n\tfilter:add_pattern(\"*.png\")\n\tfilter:add_pattern(\"*.jpg\")\n\n\tlocal preview = Gtk.Image()\n\n\tlocal wallpaper = nil\n\tlocal wallpaperbutton = Gtk.FileChooserButton({ filter = filter, title = \"Choose Wallpaper\", hexpand = true, preview_widget = preview })\n\twallpaperbutton:set_file(Gio.File.new_for_path(\"\"))\n\twallpaperbutton:set_current_folder_file(Gio.File.new_for_path(os.getenv(\"HOME\")))\n\tfunction wallpaperbutton:on_file_set()\n\t\twallpaper = self:get_filename()\n\tend\n\tfunction wallpaperbutton:on_update_preview()\n\t\tpixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(self:get_preview_filename(), 500, 500, true)\n\t\tpreview:set_from_pixbuf(pixbuf)\n\t\twallpaperbutton:set_preview_widget_active(true)\n\tend\n\n\tlocal colorscheme = Gtk.Grid({\n\t\tcolumn_spacing = dpi(10),\n\t\trow_spacing = dpi(10),\n\n\t\t{ Gtk.Label({ label = \"Color Scheme\", halign = Gtk.Align.START }), top_attach = 0, left_attach = 0 },\n\t\t{ combo, top_attach = 1, left_attach = 0 },\n\t\t{ savebutton, top_attach = 1, left_attach = 1 }\n\t})\n\n\tfunction savebutton:on_clicked()\n\t\tcolorscheme:remove(combo)\n\t\tcolorscheme:remove(savebutton)\n\t\tcolorscheme:attach(themeprompt, 0, 1, 1, 1)\n\t\tcolorscheme:attach(themename, 1, 1, 1, 1)\n\t\tcolorscheme:attach(confirmbutton, 2, 1, 1, 1)\n\t\tcolorscheme:attach(cancelbutton, 3, 1, 1, 1)\n\t\tcolorscheme:show_all()\n\tend\n\tfunction confirmbutton:on_clicked()\n\t\tif nameset == false and name ~= \"\" then\n\t\t\tnameset = true\n\t\t\tcolorscheme:remove(themeprompt)\n\t\t\tcolorscheme:remove(themename)\n\t\t\tcolorscheme:attach(wallprompt, 0, 1, 1, 1)\n\t\t\tcolorscheme:attach(wallpaperbutton, 1, 1, 1, 1)\n\t\t\tcolorscheme:show_all()\t\n\t\telseif nameset == true and wallpaper then\n\t\t\tlocal themedir = require(\"gears\").filesystem.get_configuration_dir() .. \"color/\" .. name .. \"/\"\n\t\t\tlocal path = Gio.File.new_for_path(themedir)\n\t\t\tlocal sourcepath = Gio.File.new_for_path(wallpaper)\n\t\t\tlocal targetpath = Gio.File.new_for_path(themedir .. name .. \".png\")\n\t\t\tpath:make_directory()\n\t\t\tsourcepath:copy(targetpath, Gio.FileCopyFlags.NONE, nil, nil, nil, nil)\n\t\t\tcolor.wall = targetpath:get_path():gsub(require(\"gears\").filesystem.get_configuration_dir(), \"\")\n\t\t\twritejson(require(\"gears\").filesystem.get_configuration_dir() .. \"color/\" .. name .. \"/\" .. name .. \".json\", color)\n\t\t\n\t\t\ttable.insert(items, name)\n\t\t\tmodel:append({name})\n\t\t\tcolorscheme:remove(wallprompt)\n\t\t\tcolorscheme:remove(wallpaperbutton)\n\t\t\tcolorscheme:remove(confirmbutton)\n\t\t\tcolorscheme:remove(cancelbutton)\n\t\t\tcolorscheme:attach(combo, 0, 1, 1, 1)\n\t\t\tcolorscheme:attach(savebutton, 1, 1, 1, 1)\n\t\t\tnameset = false\n\t\t\tthemename.text = \"\"\n\t\t\tcolorscheme:show_all()\n\t\tend\n\tend\n\tfunction cancelbutton:on_clicked()\n\t\tcolorscheme:remove(themeprompt)\n\t\tcolorscheme:remove(themename)\n\t\tcolorscheme:remove(wallprompt)\n\t\tcolorscheme:remove(wallpaperbutton)\n\t\tcolorscheme:remove(confirmbutton)\n\t\tcolorscheme:remove(cancelbutton)\n\t\tcolorscheme:attach(combo, 0, 1, 1, 1)\n\t\tcolorscheme:attach(savebutton, 1, 1, 1, 1)\n\t\tnameset = false\n\t\tthemename.text = \"\"\n\t\tcolorscheme:show_all()\n\tend\n\n\tlocal grid = Gtk.Grid({\n\t\tcolumn_spacing = dpi(10),\n\t\trow_spacing = dpi(10),\n\t\tmargin = dpi(20),\n\t\thalign = Gtk.Align.CENTER,\n\n\t\t{ colorscheme, top_attach = 0, left_attach = 0 },\n\t\t{ doColor(), top_attach = 1, left_attach = 0 },\n\n\t})\n\n\tfunction combo:on_changed()\n\t\tlocal n = self:get_active()\n\t\tsettings.color = items[n+1]\n\t\tgrid:remove(grid:get_child_at(0, 1))\n\t\tlocal colorwidget = doColor()\n\t\tgrid:attach(colorwidget, 0, 1, 1, 1)\n\t\tcolorwidget:show_all()\n\tend\n\n\treturn grid\n\nend\n\nlocal function restartdialog()\n\t\n\tlocal label = Gtk.Label({ label = \"Some changes require a restart. Restart now?\" })\n\tlocal cancelButton = Gtk.Button.new_with_label(\"No Thanks\")\n\tlocal restartButton = Gtk.Button.new_with_label(\"Restart\")\n\tfunction cancelButton:on_clicked()\n\t\tawesome.emit_signal(\"settings::cancel\")\n\tend\n\tfunction restartButton:on_clicked()\n\t\tawesome.restart()\n\tend\n\n\tlocal grid = Gtk.Grid({\n\t\tcolumn_spacing = dpi(10),\n\t\thalign = Gtk.Align.END,\n\t\tmargin_end = dpi(20),\n\n\t\t{ label, top_attach = 0, left_attach = 0 },\n\t\t{ cancelButton, top_attach = 0, left_attach = 1 },\n\t\t{ restartButton, top_attach = 0, left_attach = 2 },\n\t})\n\n\treturn grid\n\nend\n\nfunction app:on_startup()\n\tlocal window = Gtk.ApplicationWindow({\n\t\ttitle = appTitle,\n\t\tapplication = self,\n\t\tdefault_width = dpi(500),\n\t\tdefault_height = dpi(350)\n\t})\n\n\twindow:set_wmclass(\"settings\", \"Settings\")\n\n\tlocal stack = Gtk.Stack({\n\t\ttransition_type = Gtk.StackTransitionType.NONE\n\t})\n\n\tstack:add_titled(doGeneral(), \"general\", \"General\")\n\tstack:add_titled(doTheme(), \"theme\", \"Theme\")\n\n\tlocal saveButton = Gtk.Button({ label = \"Save\", halign = Gtk.Align.END, margin_end = dpi(20) })\n\n\tlocal box = Gtk.Box({\n\t\torientation = Gtk.Orientation.VERTICAL,\n\t\tmargin = dpi(10),\n\t\tGtk.StackSwitcher({ stack = stack, halign = Gtk.Align.CENTER }),\n\t\t{ stack, expand = true },\n\t\tsaveButton\n\t})\n\n\tfunction saveButton:on_clicked()\n\n\t\tlocal shouldrestart = false\n\n\t\tlocal userrestart = {\n\t\t\t\"font\",\n\t\t\t\"fontalt\",\n\t\t\t\"batt\",\n\t\t}\n\n\t\tfor _, property in ipairs(userrestart) do\n\t\t\tif user[property] ~= settings[property] then\n\t\t\t\tshouldrestart = true\n\t\t\t\tbreak\n\t\t\tend\n\t\tend\n\n\t\twritejson(require(\"gears\").filesystem.get_cache_dir() .. \"user.json\", settings)\n\t\tuser = copytable(settings)\n\n\t\twritejson(require(\"gears\").filesystem.get_configuration_dir() .. \"color/\" .. settings.color .. \"/\" .. settings.color .. \".json\", color)\n\n\t\tawesome.emit_signal(\"color::change\", color)\n\n\t\tif shouldrestart then\n\t\t\tbox:remove(saveButton)\n\t\t\tlocal dialog = restartdialog()\n\t\t\tbox:add(dialog)\n\t\t\tdialog:show_all()\n\t\t\tawesome.connect_signal(\"settings::cancel\", function()\n\t\t\t\tbox:remove(dialog)\n\t\t\t\tbox:add(saveButton)\n\t\t\t\tsaveButton:show_all()\n\t\t\tend)\n\t\tend\n\n\t\trequire(\"beautiful\").init(require(\"gears\").filesystem.get_configuration_dir() .. \"theme/theme.lua\")\t\n\t\tawesome.emit_signal(\"live::reload\")\n\tend\n\n\tbox:show_all()\n\twindow:add(box)\nend\n\nfunction app:on_activate()\n\tself.active_window:present()\nend\n\nreturn app:run()\n\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/theme.lua",
    "content": "local gears = require(\"gears\")\nlocal dpi = require(\"beautiful\").xresources.apply_dpi\nlocal iconpath = require(\"gears\").filesystem.get_configuration_dir() .. \"theme/icons/\"\n\nlocal color = readjson(gears.filesystem.get_configuration_dir() .. \"color/\" .. user.color .. \"/\" .. user.color .. \".json\")\n\nlocal theme = {}\n\ntheme.bg\t  = color.bg\ntheme.bgmid\t  = color.bgmid\ntheme.bgalt\t  = color.bgalt\ntheme.fg\t  = color.fg\ntheme.black   = color.black\ntheme.white\t  = color.white\ntheme.red     = color.red\ntheme.green\t  = color.green\ntheme.yellow  = color.yellow\ntheme.blue\t  = color.blue\ntheme.magenta = color.magenta\ntheme.cyan\t  = color.cyan\n\n-- Default\n\ntheme.font \t\t\t= user.font\n\ntheme.bg_normal     = color.bg\ntheme.bg_focus      = color.bgalt\ntheme.bg_urgent     = color.red\n\ntheme.fg_normal     = color.fg\ntheme.fg_focus      = color.fg\ntheme.fg_urgent     = color.red\n\n-- Titlebar\n\ntheme.titlebar_bg_normal   = color.bg\ntheme.titlebar_bg_focus    = color.bg\ntheme.titlebar_bg_urgent   = color.bg\n\ntheme.titlebar_fg_normal   = color.fg .. \"40\"\ntheme.titlebar_fg_focus    = color.fg\ntheme.titlebar_fg_urgent   = color.red\n\n-- Tasklist\n\ntheme.tasklist_bg_normal   = color.bgmid\ntheme.tasklist_bg_focus    = color.fg .. \"20\"\ntheme.tasklist_bg_urgent   = color.bgmid\ntheme.tasklist_bg_minimize = color.bg\n\n-- Taglist\n\ntheme.taglist_bg_focus    = color.bgmid\ntheme.taglist_bg_urgent   = color.bgmid\n\ntheme.taglist_fg_focus    = color.fg\ntheme.taglist_fg_urgent   = color.red\n\n-- Snap\n\ntheme.snap_bg     = color.fg\ntheme.snap_shape  = function(cr, width, height)\n\t\t\t\t\t\tgears.shape.rounded_rect(cr, width, height, dpi(15))\n\t\t\t\t\tend\ntheme.snapper_gap = dpi(15)\n\n-- Misc\n\ntheme.useless_gap           = dpi(5)\ntheme.border_width          = dpi(0)\ntheme.bg_systray    \t    = color.bgmid\ntheme.systray_icon_spacing  = dpi(5)\ntheme.notification_spacing  = dpi(20)\ntheme.tooltip_opacity       = 0\ntheme.prompt_bg_cursor\t\t= color.fg\n\n-- Wallpaper\n\ncolor.wall = gears.filesystem.get_configuration_dir() .. color.wall\ntheme.wallpaper = user.wallpaper or color.wall\n\n-- Layout icons\n\ntheme.layout_floating  = gears.color.recolor_image(iconpath .. \"floating.png\", color.fg)\ntheme.layout_tile\t   = gears.color.recolor_image(iconpath .. \"tile.png\", color.fg)\n\n-- Theme icon\n\ntheme.calla = gears.color.recolor_image(iconpath .. \"calla.png\", color.fg)\n\n-- Profile picture\n\nif gears.filesystem.file_readable(iconpath .. \"pfp.png\") then\n\ttheme.pfp = iconpath .. \"pfp.png\"\nelse\n\ttheme.pfp = theme.calla\nend\n\n-- Icon theme\n\ntheme.icons = color.icons\n\nreturn theme\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/title.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\nlocal iconpath = require(\"gears\").filesystem.get_configuration_dir() .. \"theme/icons/\"\n\nclient.connect_signal(\"request::titlebars\", function(c)\n\n\t-- Button actions\n\n    local buttons = {\n        awful.button({ }, 1, function()\n            c:activate { context = \"titlebar\", action = \"mouse_move\"  }\n        end),\n        awful.button({ }, 3, function()\n            c:activate { context = \"titlebar\", action = \"mouse_resize\"}\n        end),\n    }\n\n\t-- Widgets\n\n\tlocal icon = wibox.widget {\n\t\t{\n\t\t\tawful.titlebar.widget.iconwidget(c), \n\t\t\tbuttons = buttons,\n\t\t\tmargins = dpi(5),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\twidget = background({ bg = \"bgmid\" })\n\t}\n\n\tlocal title = wibox.widget {\n\t\t{\n\t\t\tawful.titlebar.widget.titlewidget(c), \n\t\t\tbuttons = buttons,\n\t\t\ttop = dpi(5),\n\t\t\tbottom = dpi(5),\n\t\t\tleft = dpi(10),\n\t\t\tright = dpi(10),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\tbg = beautiful.bgmid,\n\t\twidget = background({ bg = \"bgmid\" })\n\t}\n\n\tlocal function titlebutton(action, run)\n\t\tlocal img = gears.color.recolor_image(iconpath .. action .. \".png\", beautiful.bgmid)\n\t\tlocal button = wibox.widget {\n\t\t\t{\n\t\t\t\tid = \"image\",\n\t\t\t\timage = gears.color.recolor_image(img, beautiful.fg..\"40\"),\n\t\t\t\twidget = wibox.widget.imagebox\n\t\t\t},\n\t\t\tbuttons = {\n\t\t\t\tawful.button({}, 1, run)\n\t\t\t},\n\t\t\twidget = wibox.widget.background\n\t\t}\n\n\t\tlocal function update()\n\t\t\tlocal img = gears.color.recolor_image(iconpath .. action .. \".png\", beautiful.bgmid)\n\t\t\tif client.focus == c then\n\t\t\t\tbutton:get_children_by_id(\"image\")[1].image = gears.color.recolor_image(img, beautiful.fg)\n\t\t\telse\n\t\t\t\tbutton:get_children_by_id(\"image\")[1].image = gears.color.recolor_image(img, beautiful.fg..\"40\")\n\t\t\tend\n\t\tend\n\n\t\tclient.connect_signal(\"focus\", update)\n\t\tclient.connect_signal(\"unfocus\", update)\n\t\tawesome.connect_signal(\"live::reload\", update)\n\n\t\treturn button\n\tend\n\n\tlocal titlebuttons = wibox.widget {\n\t\t{\n\t\t\t{\n\t\t\t\ttitlebutton(\"minimize\", function() c.minimized = true end),\n\t\t\t\ttitlebutton(\"maximize\", function() c.maximized = not c.maximized end),\n\t\t\t\ttitlebutton(\"close\", function() c:kill() end),\n\t\t\t\tspacing = dpi(10),\n\t\t\t\twidget = wibox.layout.fixed.horizontal\n\t\t\t},\n\t\t\tmargins = dpi(10),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\twidget = background({ bg = \"bgmid\" })\n\t}\n\n\t-- Titlebar\n\n    local titlebar = awful.titlebar(c, { size = dpi(40), position = \"top\" })\n\tlocal handle = awful.titlebar(c, { size = dpi(20), position = \"bottom\" })\n\n\ttitlebar:setup {\n\t\t{\n\t\t\t{\n\t\t\t\ticon,\n\t\t\t\ttitle,\n\t\t\t\tspacing = dpi(5),\n\t\t\t\tlayout = wibox.layout.fixed.horizontal\n\t\t\t},\n\t\t\t{\n\t\t\t\tbuttons = buttons,\n\t\t\t\twidget = wibox.container.background\n\t\t\t},\n\t\t\ttitlebuttons,\n\t\t\tlayout = wibox.layout.align.horizontal\n\t\t},\n\t\tmargins = dpi(5),\n\t\twidget = wibox.container.margin\n\t}\n\thandle:setup {\n\t\tbuttons = buttons,\n\t\twidget = wibox.container.background\n\t}\n\n\tawesome.connect_signal(\"live::reload\", function()\n\t\ttitlebar:set_bg(beautiful.bg)\n\t\ttitlebar:set_fg(beautiful.fg)\n\t\thandle:set_bg(beautiful.bg)\n\tend)\n\nend)\n"
  },
  {
    "path": "src/usr/share/calla/desktop/theme/volume.lua",
    "content": "local awful = require(\"awful\")\nlocal wibox = require(\"wibox\")\nlocal gears = require(\"gears\")\nlocal beautiful = require(\"beautiful\")\nlocal dpi = beautiful.xresources.apply_dpi\n\nlocal volumebox = wibox {\n\twidth = dpi(200),\n\theight = dpi(85),\n\tontop = true,\n\tvisible = false\n}\n\nlocal percent = wibox.widget.textbox()\n\nlocal header = wibox.widget {\n\t{\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\tvalign = \"center\",\n\t\t\t\t\twidget = wibox.widget.textbox(\"Volume\")\n\t\t\t\t},\n\t\t\t\tnil,\n\t\t\t\tpercent,\n\t\t\t\tlayout = wibox.layout.align.horizontal,\n\t\t\t},\n\t\t\ttop = dpi(5),\n\t\t\tbottom = dpi(5),\n\t\t\tleft = dpi(10),\n\t\t\tright = dpi(10),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\twidget = background({ bg = \"bgmid\" })\n\t},\n\tmargins = dpi(5),\n\twidget = wibox.container.margin\n}\n\nlocal icon = iconbox({ image = \"volumemute\" })\n\nlocal bar = wibox.widget {\n\tshape = gears.shape.rounded_rect,\n\tbar_shape = gears.shape.rounded_rect,\n\tmax_value = 100,\n\tvalue = 0,\n\twidget = live(wibox.widget.progressbar, { background_color = \"bgmid\", color = \"fg\" })\n}\n\nlocal timer = gears.timer {\n\ttimeout = 2,\n\tsingle_shot = true,\n\tcallback = function()\n\t\tvolumebox.visible = false\n\tend\n}\n\nvolumebox:setup {\n\t{\n\t\theader,\n\t\t{\n\t\t\t{\n\t\t\t\t{\n\t\t\t\t\ticon,\n\t\t\t\t\tright = dpi(15),\n\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t},\n\t\t\t\tnil,\n\t\t\t\t{\n\t\t\t\t\tbar,\n\t\t\t\t\ttop = dpi(20),\n\t\t\t\t\tbottom = dpi(20),\n\t\t\t\t\twidget = wibox.container.margin\n\t\t\t\t},\n\t\t\t\tlayout = wibox.layout.align.horizontal\n\t\t\t},\n\t\t\tleft = dpi(15),\n\t\t\tright = dpi(15),\n\t\t\twidget = wibox.container.margin\n\t\t},\n\t\tlayout = wibox.layout.align.vertical\n\t},\n\twidget = live(wibox.container.background, { bg = \"bg\", fg = \"fg\" })\n}\n\nawesome.connect_signal(\"signal::volume\", function(volume, mute)\n\tif mute then\n\t\tpercent.text = \"Muted\"\n\t\ticon.image = createicon(\"volumemute\")\n\telse\n\t\tpercent.text = tostring(volume) .. \"%\"\n\t\tbar.value = volume\n\t\tif volume > 100 then\n\t\t\ticon.image = createicon(\"volumewarn\")\n\t\telseif volume >= 50 then\n\t\t\ticon.image = createicon(\"volume100\")\n\t\telseif volume >= 25 then\n\t\t\ticon.image = createicon(\"volume50\")\n\t\telseif volume > 0 then\n\t\t\ticon.image = createicon(\"volume25\")\n\t\telseif volume == 0 then\n\t\t\ticon.image = createicon(\"volume0\")\n\t\tend\n\tend\nend)\n\nawesome.connect_signal(\"widget::volume\", function()\n\tawesome.emit_signal(\"widget::brightness:hide\")\n\n\ttimer:again()\n\n\tif client.focus and client.focus.fullscreen == true then\n\t\tawful.placement.bottom(\n\t\t\tvolumebox, \n\t\t\t{\n\t\t\t\tmargins = { \n\t\t\t\t\tbottom = dpi(10)\n\t\t\t\t}, \n\t\t\t\tparent = awful.screen.focused()\n\t\t\t}\n\t\t)\n\telse\n\t\tawful.placement.bottom(\n\t\t\tvolumebox, \n\t\t\t{\n\t\t\t\tmargins = { \n\t\t\t\t\tbottom = dpi(60)\n\t\t\t\t}, \n\t\t\t\tparent = awful.screen.focused()\n\t\t\t}\n\t\t)\n\tend\n\n\tvolumebox.visible = true\nend)\n\nawesome.connect_signal(\"widget::volume:hide\", function() \n\tvolumebox.visible = false \nend)\n"
  },
  {
    "path": "src/usr/share/calla/xsettingsd",
    "content": "Net/ThemeName \"light\"\nNet/IconThemeName \"Papirus-Light\"\n"
  },
  {
    "path": "src/usr/share/themes/dark/gtk-3.0/gtk.css",
    "content": "@define-color borders #151515;\nwindow.background.chromium {\n  background: #262626;\n  color: #d0d0d0;\n}\nwindow.background.chromium button {\n  border: 1px solid #262626;\n}\nwindow.background.chromium .titlebutton {\n  border: transparent;\n}\nwindow.background.chromium entry:focus {\n  border-color: #151515;\n}\nwindow.background.chromium menu {\n  background: #1d1d1d;\n}\nwindow.background.chromium menu menuitem {\n  color: #d0d0d0;\n}\nwindow.background.chromium menu menuitem:hover {\n  background: rgba(208, 208, 208, 0.05);\n}\nwindow.background.chromium menu menuitem:disabled label {\n  color: rgba(208, 208, 208, 0.05);\n}\nwindow.background.chromium headerbar, window.background.chromium menubar {\n  background: #151515;\n}\nwindow.background.chromium headerbar label, window.background.chromium menubar label {\n  color: rgba(208, 208, 208, 0.1);\n}\nwindow.background.chromium headerbar .titlebutton, window.background.chromium menubar .titlebutton {\n  padding: 100px 80px;\n}\nwindow.background.chromium scrollbar button {\n  background: rgba(208, 208, 208, 0.05);\n  color: #d0d0d0;\n}\nwindow.background.chromium scrollbar trough {\n  background: transparent;\n}\nwindow.background.chromium scrollbar slider {\n  background: rgba(208, 208, 208, 0.05);\n}\nwindow.background.chromium scrollbar junction {\n  background: rgba(208, 208, 208, 0.05);\n}\nwindow.background.chromium textview {\n  background-color: #151515;\n  color: #d0d0d0;\n}\n\n#MozillaGtkWidget {\n  background: #151515;\n  color: #d0d0d0;\n}\n#MozillaGtkWidget menubar {\n  color: rgba(208, 208, 208, 0.1);\n  background: #151515;\n}\n#MozillaGtkWidget text {\n  background: #1d1d1d;\n}\n#MozillaGtkWidget menu {\n  background: #212121;\n}\n#MozillaGtkWidget menu :disabled {\n  color: rgba(208, 208, 208, 0.05);\n}\n#MozillaGtkWidget menu separator {\n  padding: 8px;\n}\n#MozillaGtkWidget border {\n  border: 1px solid #212121;\n}\n\n.nemo-window .nemo-places-sidebar {\n  background: #151515;\n}\n.nemo-window .primary-toolbar {\n  background: #151515;\n  padding: 0.3em;\n}\n.nemo-window menubar ~ separator {\n  min-width: 0;\n  min-height: 0;\n}\n.nemo-window .toolbar {\n  background: #151515;\n  margin: -0.1em;\n}\n.nemo-window .nemo-window-pane :selected {\n  background: rgba(208, 208, 208, 0.6);\n}\n.nemo-window .nemo-window-pane :active {\n  background: rgba(208, 208, 208, 0.6);\n}\n\nactionbar {\n  padding: 0.6em;\n  background: #151515;\n  border-top: 1px solid rgba(208, 208, 208, 0.05);\n}\n\nbutton {\n  transition: background-color 100ms ease-in;\n  background: rgba(208, 208, 208, 0.05);\n  padding: 0.6em;\n  border-radius: 0.5em;\n}\nbutton:hover:not(:active) {\n  background: rgba(208, 208, 208, 0.1);\n}\nbutton:disabled {\n  opacity: 0.3;\n}\nbutton:checked, button.suggested-action {\n  background: rgba(208, 208, 208, 0.1);\n  color: #d0d0d0;\n}\nbutton:checked:hover:not(:active), button.suggested-action:hover:not(:active) {\n  background: rgba(208, 208, 208, 0.1);\n}\nbutton.image-button:not(.text-button) {\n  padding: 0.8em;\n}\nbutton.destructive-action {\n  background: rgba(172, 65, 65, 0.6);\n  color: #d0d0d0;\n}\nbutton.destructive-action:hover:not(:active) {\n  background: rgba(172, 65, 65, 0.9);\n}\nbutton.circular {\n  border-radius: 100%;\n  padding: 0.8em;\n}\nbutton.flat {\n  background: transparent;\n}\nbutton.flat:hover {\n  background: rgba(208, 208, 208, 0.05);\n}\nbutton.flat:active, button.flat:checked {\n  background: rgba(208, 208, 208, 0.05);\n}\nlabel + button {\n  margin-left: 0.6em;\n}\nbutton.small-button image:only-child {\n  padding: 0.3em;\n}\n.linked.horizontal button:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.linked.horizontal button:not(:last-child) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.linked.vertical button:not(:first-child) {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.linked.vertical button:not(:last-child) {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\ncalendar {\n  background: #151515;\n}\ncalendar.button {\n  background: #151515;\n  color: rgba(208, 208, 208, 0.1);\n}\ncalendar.button:hover {\n  color: #d0d0d0;\n}\ncalendar.button:disabled {\n  color: rgba(208, 208, 208, 0.05);\n}\ncalendar.header {\n  background: #151515;\n}\ncalendar.highlight {\n  color: rgba(208, 208, 208, 0.1);\n}\ncalendar:selected {\n  color: #d0d0d0;\n  border-radius: 0.5em;\n  background: rgba(208, 208, 208, 0.1);\n}\ncalendar:indeterminate {\n  color: rgba(208, 208, 208, 0.05);\n}\n\ncheck {\n  -gtk-icon-source: -gtk-recolor(url(\"../assets/symbolic/check.symbolic.png\"));\n  -gtk-icon-transform: scale(0);\n  color: #d0d0d0;\n  border: 0.2em solid rgba(208, 208, 208, 0.05);\n  border-radius: 0.3em;\n  padding: 0.1em;\n  min-width: 0.7em;\n  min-height: 0.7em;\n  transition: -gtk-icon-transform 200ms;\n}\ncheck:hover {\n  border-color: rgba(208, 208, 208, 0.05);\n}\ncheck:active, check:checked {\n  background: rgba(208, 208, 208, 0.05);\n}\ncheck:checked {\n  background: rgba(208, 208, 208, 0.1);\n  -gtk-icon-transform: scale(1);\n  border-color: rgba(208, 208, 208, 0.1);\n}\ncheck:checked:active {\n  background: transparent;\n}\n\ncheckbutton label {\n  padding-left: 5px;\n}\ncheckbutton:disabled {\n  opacity: 0.3;\n}\n\ncombobox entry {\n  border-right: 0;\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\ncombobox entry:focus + button {\n  border: 0.2em solid rgba(208, 208, 208, 0.9);\n  border-left: 0;\n  margin-right: -0.2em;\n}\ncombobox entry:focus + button:hover {\n  background-clip: padding-box;\n}\ncombobox entry + button.combo {\n  border-radius: 0 0.5em 0.5em 0;\n}\ncombobox .linked button.combo {\n  padding: 0.5em;\n  border-radius: 0.5em;\n}\ncombobox .linked button.combo arrow {\n  min-width: 1em;\n  -gtk-icon-source: -gtk-icontheme(\"go-down-symbolic\");\n}\n.linked.horizontal combobox:not(:first-child) button {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.linked.horizontal combobox:not(:last-child) button {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.linked.vertical combobox:not(:first-child) button {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.linked.vertical combobox:not(:last-child) button {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\ndialog, messagedialog {\n  background: #151515;\n}\ndialog .dialog-action-box, messagedialog .dialog-action-box {\n  background: #151515;\n  padding: 0.6em;\n  margin: -0.4em;\n  margin-top: 0;\n}\ndialog fontchooser, dialog colorchooser, messagedialog fontchooser, messagedialog colorchooser {\n  padding: 0.6em;\n}\ndialog filechooser + .dialog-action-box, messagedialog filechooser + .dialog-action-box {\n  margin: 0;\n}\ndialog filechooser #pathbarbox, messagedialog filechooser #pathbarbox {\n  padding: 0.3em;\n  background: #151515;\n}\ndialog filechooser treeview, messagedialog filechooser treeview {\n  background: #151515;\n}\n\nentry {\n  padding: 0.4em;\n  border-radius: 0.5em;\n  border: 0.2em solid rgba(208, 208, 208, 0.05);\n  background: rgba(208, 208, 208, 0.05);\n  background-clip: padding-box;\n}\nentry:focus {\n  border-color: rgba(208, 208, 208, 0.1);\n}\nentry:disabled {\n  opacity: 0.3;\n}\nentry image.left {\n  margin-right: 0.6em;\n}\nentry image.right {\n  margin-left: 0.6em;\n}\nentry selection {\n  color: #d0d0d0;\n  background: rgba(208, 208, 208, 0.6);\n}\n.linked.horizontal entry:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.linked.horizontal entry:not(:last-child) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.linked.vertical entry:not(:first-child) {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.linked.vertical entry:not(:last-child) {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\nexpander title {\n  color: rgba(208, 208, 208, 0.1);\n  padding: 0.6em;\n}\nexpander title arrow {\n  -gtk-icon-source: -gtk-icontheme(\"go-next-symbolic\");\n  min-width: 1em;\n  min-height: 1em;\n  margin-right: 0.6em;\n}\nexpander title arrow:checked {\n  -gtk-icon-source: -gtk-icontheme(\"go-down-symbolic\");\n  color: #d0d0d0;\n}\nexpander title arrow:checked + label {\n  color: #d0d0d0;\n}\n\nframe > border, .frame {\n  border: 1px solid #151515;\n}\n\nscrolledwindow viewport.frame {\n  border-style: none;\n}\n\nheaderbar {\n  background: #151515;\n  padding: 0.6em;\n}\nheaderbar button.titlebutton {\n  padding: 0.8em;\n}\nheaderbar button.titlebutton:not(.appmenu) {\n  background: transparent;\n}\nheaderbar button.titlebutton.appmenu:not(:active):not(:checked) {\n  background: transparent;\n}\n\niconview {\n  background: #151515;\n  padding: 0.3em;\n}\niconview:selected {\n  color: #d0d0d0;\n  background: rgba(208, 208, 208, 0.1);\n  border-radius: 0.5em;\n}\n\ninfobar {\n  color: #d0d0d0;\n  padding: 0.3em;\n  background: #151515;\n}\ninfobar.info {\n  background: rgba(106, 159, 181, 0.6);\n}\ninfobar.warning {\n  background: rgba(244, 191, 117, 0.6);\n}\ninfobar.error {\n  background: rgba(172, 65, 65, 0.6);\n}\ninfobar.question {\n  background: rgba(208, 208, 208, 0.6);\n}\n\nlabel.dim-label, label:disabled {\n  opacity: 0.3;\n}\nlabel selection {\n  background: rgba(208, 208, 208, 0.1);\n  color: white;\n}\n\nlevelbar trough block {\n  padding: 3px;\n  border-radius: 3px;\n}\nlevelbar trough block.filled {\n  background: rgba(208, 208, 208, 0.1);\n}\nlevelbar trough block.empty {\n  background: #151515;\n}\nlevelbar.discrete block:not(:first-child) {\n  margin-left: 5px;\n}\n\nlist {\n  background: #151515;\n}\nlist row {\n  background: #151515;\n  padding: 0.6em;\n}\nlist row:hover {\n  background: #151515;\n}\nlist > label {\n  padding: 0.3em;\n}\n\nmenu {\n  background: #212121;\n}\nmenu menuitem {\n  padding: 0.6em;\n}\nmenu menuitem:hover {\n  background: rgba(208, 208, 208, 0.05);\n}\n\nmenubar {\n  background: #151515;\n}\nmenubar menuitem {\n  padding: 0.6em;\n}\nmenubar menuitem:hover {\n  background: rgba(208, 208, 208, 0.05);\n}\nmenubar menuitem check, menubar menuitem radio {\n  margin-right: 0.6em;\n}\n\nnotebook stack:only-child {\n  background: #151515;\n}\n\novershoot.top {\n  background-image: -gtk-gradient(radial, center top, 0, center top, 0.6, from(rgba(21, 21, 21, 0.2)), to(rgba(21, 21, 21, 0)));\n  background-size: 100% 60%;\n  background-repeat: no-repeat;\n  background-position: center top;\n}\novershoot.bottom {\n  background-image: -gtk-gradient(radial, center bottom, 0, center bottom, 0.6, from(rgba(21, 21, 21, 0.2)), to(rgba(21, 21, 21, 0)));\n  background-size: 100% 60%;\n  background-repeat: no-repeat;\n  background-position: center bottom;\n}\novershoot.left {\n  background-image: -gtk-gradient(radial, left center, 0, left center, 0.6, from(rgba(21, 21, 21, 0.2)), to(rgba(21, 21, 21, 0)));\n  background-size: 60% 100%;\n  background-repeat: no-repeat;\n  background-position: left center;\n}\novershoot.right {\n  background-image: -gtk-gradient(radial, right center, 0, right center, 0.6, from(rgba(21, 21, 21, 0.2)), to(rgba(21, 21, 21, 0)));\n  background-size: 60% 100%;\n  background-repeat: no-repeat;\n  background-position: right center;\n}\n\n.linked.path-bar button image:not(:only-child) {\n  margin-right: 0.6em;\n}\n.linked.path-bar button:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.linked.path-bar button:not(:last-child) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\nplacessidebar {\n  background: #151515;\n}\nplacessidebar list {\n  background: transparent;\n}\nplacessidebar list row:hover {\n  background: rgba(208, 208, 208, 0.05);\n}\nplacessidebar list row:active {\n  background: rgba(208, 208, 208, 0.05);\n}\nplacessidebar list row:selected {\n  background: rgba(208, 208, 208, 0.1);\n}\nplacessidebar list row .sidebar-icon {\n  margin-right: 0.6em;\n}\nplacessidebar.frame,\nplacessidebar .frame {\n  border: 0;\n}\n\npopover {\n  background: #212121;\n  box-shadow: 0 0 5px rgba(21, 21, 21, 0.5);\n  border-radius: 0.5em;\n  padding: 0.5em;\n}\npopover box modelbutton {\n  padding: 0.4em 1em;\n  margin: 0 -0.5em;\n  border-radius: 0.5em;\n}\npopover box modelbutton:hover {\n  background: rgba(208, 208, 208, 0.05);\n}\npopover.menu:first-child {\n  border-top-left-radius: 0;\n}\n\nprogressbar trough {\n  background: rgba(208, 208, 208, 0.05);\n}\nprogressbar trough progress {\n  background: rgba(208, 208, 208, 0.1);\n}\n\nradio {\n  -gtk-icon-source: -gtk-recolor(url(\"../assets/symbolic/radio.symbolic.png\"));\n  -gtk-icon-transform: scale(0);\n  color: #d0d0d0;\n  border: 0.2em solid rgba(208, 208, 208, 0.05);\n  border-radius: 100%;\n  padding: 0.1em;\n  min-width: 0.6em;\n  min-height: 0.6em;\n  transition: -gtk-icon-transform 200ms;\n}\nradio:hover {\n  border-color: rgba(208, 208, 208, 0.05);\n}\nradio:active, radio:checked {\n  background: rgba(208, 208, 208, 0.05);\n}\nradio:checked {\n  -gtk-icon-transform: scale(1);\n  border-color: rgba(208, 208, 208, 0.1);\n  background: rgba(208, 208, 208, 0.1);\n}\n\nradiobutton label {\n  padding-left: 5px;\n}\nradiobutton:disabled {\n  opacity: 0.38;\n}\n\nrevealer frame.app-notification border {\n  border-radius: 0.5em;\n  background: #151515;\n  padding: 0.6em;\n}\nrevealer.top frame.app-notification border {\n  margin-top: 0.6em;\n}\nrevealer.right frame.app-notification border {\n  margin-right: 0.6em;\n}\nrevealer.bottom frame.app-notification border {\n  margin-bottom: 0.6em;\n}\nrevealer.left frame.app-notification border {\n  margin-left: 0.6em;\n}\n\nrubberband, .rubberband {\n  background-color: rgba(208, 208, 208, 0.05);\n  border: 0.2em solid rgba(208, 208, 208, 0.6);\n}\n\nscale {\n  padding: 8px 0;\n}\nscale contents trough {\n  background: #151515;\n}\nscale contents trough slider {\n  background: #555555;\n  padding: 8px;\n  margin: -5px;\n  border-radius: 100%;\n}\nscale contents trough highlight {\n  background: rgba(208, 208, 208, 0.1);\n}\nscale marks mark {\n  padding-top: 5px;\n}\nscale marks mark indicator {\n  color: #151515;\n  min-height: 8px;\n  min-width: 1px;\n}\nscale:disabled {\n  opacity: 0.3;\n}\n\nscrollbar {\n  -GtkScrollbar-has-backward-stepper: false;\n  -GtkScrollbar-has-forward-stepper: false;\n}\nscrollbar slider {\n  padding: 0.2em;\n  border-radius: 1em;\n  background: rgba(208, 208, 208, 0.05);\n}\nscrollbar slider:hover {\n  background: rgba(208, 208, 208, 0.05);\n}\nscrollbar slider:active {\n  background: rgba(208, 208, 208, 0.1);\n}\n\nseparator {\n  background-image: image(rgba(208, 208, 208, 0.05));\n  background-size: 1px 1px;\n  background-position: center center;\n  min-width: 7px;\n  min-height: 7px;\n  background-repeat: repeat-x;\n  margin: -3px 0;\n}\nseparator.vertical, .horizontal > separator {\n  background-repeat: repeat-y;\n  margin: 0 -3px;\n}\n\nspinbutton button {\n  border: 0.2em solid transparent;\n}\nspinbutton:focus button {\n  border-color: rgba(208, 208, 208, 0.9);\n}\nspinbutton:focus button:hover {\n  background-clip: padding-box;\n}\nspinbutton:disabled {\n  opacity: 0.3;\n}\nspinbutton.horizontal :nth-child(3) {\n  border-radius: 0.5em 0 0 0.5em;\n  border-right: 0;\n}\nspinbutton.horizontal :nth-child(4) {\n  border-radius: 0;\n  border-width: 0.2em 0;\n}\nspinbutton.horizontal :nth-child(5) {\n  border-radius: 0 0.5em 0.5em 0;\n  border-left: 0;\n}\nspinbutton.vertical entry {\n  padding: 0.6em 0;\n}\nspinbutton.vertical :nth-child(3) {\n  border-radius: 0.5em 0.5em 0 0;\n  border-bottom: 0;\n}\nspinbutton.vertical :nth-child(4) {\n  border-radius: 0;\n  border-width: 0 0.2em;\n}\nspinbutton.vertical :nth-child(5) {\n  border-radius: 0 0 0.5em 0.5em;\n  border-top: 0;\n}\n.linked.horizontal spinbutton:not(:first-child) :nth-child(3) {\n  border-top-left-radius: 0;\n}\n.linked.horizontal spinbutton:not(:first-child) :nth-child(5) {\n  border-bottom-left-radius: 0;\n}\n.linked.horizontal spinbutton:not(:last-child) :nth-child(3) {\n  border-top-right-radius: 0;\n}\n.linked.horizontal spinbutton:not(:last-child) :nth-child(5) {\n  border-bottom-right-radius: 0;\n}\n.linked.vertical spinbutton:not(:first-child) :nth-child(3) {\n  border-top-left-radius: 0;\n}\n.linked.vertical spinbutton:not(:first-child) :nth-child(5) {\n  border-top-right-radius: 0;\n}\n.linked.vertical spinbutton:not(:last-child) :nth-child(3) {\n  border-bottom-left-radius: 0;\n}\n.linked.vertical spinbutton:not(:last-child) :nth-child(5) {\n  border-bottom-right-radius: 0;\n}\n\n@keyframes rotate {\n  to {\n    -gtk-icon-transform: rotate(1turn);\n  }\n}\nspinner {\n  -gtk-icon-source: none;\n}\nspinner:checked {\n  -gtk-icon-source: -gtk-icontheme(\"process-working-symbolic\");\n  animation: rotate 1s linear infinite;\n}\nspinner:disabled {\n  opacity: 0.3;\n}\n\nswitch {\n  background: rgba(208, 208, 208, 0.05);\n  border-radius: 1em;\n  min-width: 3em;\n  border: 0.2em solid transparent;\n}\nswitch slider {\n  background: rgba(208, 208, 208, 0.1);\n  border-radius: 100%;\n  min-width: 1.5em;\n  min-height: 1.5em;\n}\nswitch :nth-child(2) {\n  color: transparent;\n}\nswitch :nth-child(3) {\n  color: rgba(208, 208, 208, 0.1);\n}\nswitch:checked {\n  background: rgba(208, 208, 208, 0.3);\n}\nswitch:checked slider {\n  background: rgba(208, 208, 208, 0.9);\n}\nswitch:checked :nth-child(2) {\n  color: #d0d0d0;\n}\nswitch:checked :nth-child(3) {\n  color: transparent;\n}\nswitch:disabled {\n  opacity: 0.3;\n}\nswitch:hover slider {\n  box-shadow: 0 0 0 0.3em rgba(208, 208, 208, 0.05);\n}\nswitch:hover:checked slider {\n  box-shadow: 0 0 0 0.3em rgba(208, 208, 208, 0.3);\n}\n\ntabs {\n  background: #151515;\n}\n.top > tabs {\n  border-bottom: 0.2em solid #151515;\n}\n.top > tabs tab {\n  border-bottom: 0.2em solid transparent;\n  margin-bottom: -0.2em;\n}\n.right > tabs {\n  border-left: 0.2em solid #151515;\n}\n.right > tabs tab {\n  border-left: 0.2em solid transparent;\n  margin-left: -0.2em;\n}\n.bottom > tabs {\n  border-top: 0.2em solid #151515;\n}\n.bottom > tabs tab {\n  border-top: 0.2em solid transparent;\n  margin-top: -0.2em;\n}\n.left > tabs {\n  border-right: 0.2em solid #151515;\n}\n.left > tabs tab {\n  border-right: 0.2em solid transparent;\n  margin-right: -0.2em;\n}\ntabs tab {\n  padding: 0.6em;\n  color: rgba(208, 208, 208, 0.1);\n}\nheader tabs tab:checked {\n  border-color: rgba(208, 208, 208, 0.6);\n  color: #d0d0d0;\n}\nheader tabs tab:hover:not(:checked) {\n  border-color: rgba(208, 208, 208, 0.05);\n  color: #d0d0d0;\n}\n\ntextview {\n  background: rgba(208, 208, 208, 0.05);\n  padding: 0.6em 0.3em;\n}\ntextview:disabled {\n  opacity: 0.3;\n}\ntextview selection {\n  background: rgba(208, 208, 208, 0.6);\n  color: #d0d0d0;\n}\n\ntoolbar, .toolbar {\n  background: #151515;\n  padding: 0.6em;\n}\ntoolbar.osd, .toolbar.osd {\n  background: #212121;\n  padding: 0.6em;\n  border-radius: 0.5em;\n}\n\ntooltip {\n  background: #212121;\n}\ntooltip > * {\n  margin: 0.6em;\n}\ntooltip grid label {\n  margin: 0.3em 0;\n}\ntooltip grid image {\n  margin-right: 0.6em;\n}\n\ntreeview {\n  -GtkTreeView-expander-size: 16;\n  -GtkTreeView-grid-line-pattern: \"\";\n  -GtkTreeView-tree-line-pattern: \"\";\n  -GtkTreeView-horizontal-separator: 12;\n  -GtkTreeView-vertical-separator: 12;\n  border-left-color: rgba(208, 208, 208, 0.05);\n  border-top-color: rgba(208, 208, 208, 0.05);\n}\ntreeview:hover {\n  background: rgba(208, 208, 208, 0.05);\n}\ntreeview:selected {\n  background: rgba(208, 208, 208, 0.1);\n  color: #d0d0d0;\n}\ntreeview header button {\n  padding: 0.6em;\n  background: #151515;\n  border-radius: 0;\n  color: rgba(208, 208, 208, 0.1);\n}\ntreeview header button:hover:not(:active) {\n  color: #d0d0d0;\n  background: #151515;\n}\ntreeview header button:not(:last-child) {\n  border-right: 1px solid rgba(208, 208, 208, 0.05);\n}\n\nwindow, assistant {\n  background: #151515;\n  color: #d0d0d0;\n}\n\nwindow.csd decoration {\n  box-shadow: 0 0 8px 0 black;\n  margin: 8px;\n}\n\n/*# sourceMappingURL=gtk.css.map */\n"
  },
  {
    "path": "src/usr/share/themes/dark/index.theme",
    "content": "[Desktop Entry]\nType=X-GNOME-Metatheme\nName=dark\nComment=base16-defaultdark\nEncoding=UTF-8\n\n[X-GNOME-Metatheme]\nGtkTheme=dark\nIconTheme=Papirus\nCursorTheme=Breeze\nButtonLayout=menu\n"
  },
  {
    "path": "src/usr/share/themes/light/gtk-3.0/gtk.css",
    "content": "@define-color borders whitesmoke;\nwindow.background.chromium {\n  background: #d5d5d5;\n  color: #303030;\n}\nwindow.background.chromium button {\n  border: 1px solid #d5d5d5;\n}\nwindow.background.chromium .titlebutton {\n  border: transparent;\n}\nwindow.background.chromium entry:focus {\n  border-color: whitesmoke;\n}\nwindow.background.chromium menu {\n  background: #e3e3e3;\n}\nwindow.background.chromium menu menuitem {\n  color: #303030;\n}\nwindow.background.chromium menu menuitem:hover {\n  background: rgba(48, 48, 48, 0.05);\n}\nwindow.background.chromium menu menuitem:disabled label {\n  color: rgba(48, 48, 48, 0.05);\n}\nwindow.background.chromium headerbar, window.background.chromium menubar {\n  background: whitesmoke;\n}\nwindow.background.chromium headerbar label, window.background.chromium menubar label {\n  color: rgba(48, 48, 48, 0.1);\n}\nwindow.background.chromium headerbar .titlebutton, window.background.chromium menubar .titlebutton {\n  padding: 100px 80px;\n}\nwindow.background.chromium scrollbar button {\n  background: rgba(48, 48, 48, 0.05);\n  color: #303030;\n}\nwindow.background.chromium scrollbar trough {\n  background: transparent;\n}\nwindow.background.chromium scrollbar slider {\n  background: rgba(48, 48, 48, 0.05);\n}\nwindow.background.chromium scrollbar junction {\n  background: rgba(48, 48, 48, 0.05);\n}\nwindow.background.chromium textview {\n  background-color: whitesmoke;\n  color: #303030;\n}\n\n#MozillaGtkWidget {\n  background: whitesmoke;\n  color: #303030;\n}\n#MozillaGtkWidget menubar {\n  color: rgba(48, 48, 48, 0.1);\n  background: whitesmoke;\n}\n#MozillaGtkWidget text {\n  background: #e3e3e3;\n}\n#MozillaGtkWidget menu {\n  background: #d0d0d0;\n}\n#MozillaGtkWidget menu :disabled {\n  color: rgba(48, 48, 48, 0.05);\n}\n#MozillaGtkWidget menu separator {\n  padding: 8px;\n}\n#MozillaGtkWidget border {\n  border: 1px solid #d0d0d0;\n}\n\n.nemo-window .nemo-places-sidebar {\n  background: whitesmoke;\n}\n.nemo-window .primary-toolbar {\n  background: whitesmoke;\n  padding: 0.3em;\n}\n.nemo-window menubar ~ separator {\n  min-width: 0;\n  min-height: 0;\n}\n.nemo-window .toolbar {\n  background: whitesmoke;\n  margin: -0.1em;\n}\n.nemo-window .nemo-window-pane :selected {\n  background: rgba(48, 48, 48, 0.6);\n}\n.nemo-window .nemo-window-pane :active {\n  background: rgba(48, 48, 48, 0.6);\n}\n\nactionbar {\n  padding: 0.6em;\n  background: whitesmoke;\n  border-top: 1px solid rgba(48, 48, 48, 0.05);\n}\n\nbutton {\n  transition: background-color 100ms ease-in;\n  background: rgba(48, 48, 48, 0.05);\n  padding: 0.6em;\n  border-radius: 0.5em;\n}\nbutton:hover:not(:active) {\n  background: rgba(48, 48, 48, 0.1);\n}\nbutton:disabled {\n  opacity: 0.3;\n}\nbutton:checked, button.suggested-action {\n  background: rgba(48, 48, 48, 0.1);\n  color: #303030;\n}\nbutton:checked:hover:not(:active), button.suggested-action:hover:not(:active) {\n  background: rgba(48, 48, 48, 0.1);\n}\nbutton.image-button:not(.text-button) {\n  padding: 0.8em;\n}\nbutton.destructive-action {\n  background: rgba(172, 65, 65, 0.6);\n  color: #303030;\n}\nbutton.destructive-action:hover:not(:active) {\n  background: rgba(172, 65, 65, 0.9);\n}\nbutton.circular {\n  border-radius: 100%;\n  padding: 0.8em;\n}\nbutton.flat {\n  background: transparent;\n}\nbutton.flat:hover {\n  background: rgba(48, 48, 48, 0.05);\n}\nbutton.flat:active, button.flat:checked {\n  background: rgba(48, 48, 48, 0.05);\n}\nlabel + button {\n  margin-left: 0.6em;\n}\nbutton.small-button image:only-child {\n  padding: 0.3em;\n}\n.linked.horizontal button:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.linked.horizontal button:not(:last-child) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.linked.vertical button:not(:first-child) {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.linked.vertical button:not(:last-child) {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\ncalendar {\n  background: whitesmoke;\n}\ncalendar.button {\n  background: whitesmoke;\n  color: rgba(48, 48, 48, 0.1);\n}\ncalendar.button:hover {\n  color: #303030;\n}\ncalendar.button:disabled {\n  color: rgba(48, 48, 48, 0.05);\n}\ncalendar.header {\n  background: whitesmoke;\n}\ncalendar.highlight {\n  color: rgba(48, 48, 48, 0.1);\n}\ncalendar:selected {\n  color: #303030;\n  border-radius: 0.5em;\n  background: rgba(48, 48, 48, 0.1);\n}\ncalendar:indeterminate {\n  color: rgba(48, 48, 48, 0.05);\n}\n\ncheck {\n  -gtk-icon-source: -gtk-recolor(url(\"../assets/symbolic/check.symbolic.png\"));\n  -gtk-icon-transform: scale(0);\n  color: #303030;\n  border: 0.2em solid rgba(48, 48, 48, 0.05);\n  border-radius: 0.3em;\n  padding: 0.1em;\n  min-width: 0.7em;\n  min-height: 0.7em;\n  transition: -gtk-icon-transform 200ms;\n}\ncheck:hover {\n  border-color: rgba(48, 48, 48, 0.05);\n}\ncheck:active, check:checked {\n  background: rgba(48, 48, 48, 0.05);\n}\ncheck:checked {\n  background: rgba(48, 48, 48, 0.1);\n  -gtk-icon-transform: scale(1);\n  border-color: rgba(48, 48, 48, 0.1);\n}\ncheck:checked:active {\n  background: transparent;\n}\n\ncheckbutton label {\n  padding-left: 5px;\n}\ncheckbutton:disabled {\n  opacity: 0.3;\n}\n\ncombobox entry {\n  border-right: 0;\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\ncombobox entry:focus + button {\n  border: 0.2em solid rgba(48, 48, 48, 0.9);\n  border-left: 0;\n  margin-right: -0.2em;\n}\ncombobox entry:focus + button:hover {\n  background-clip: padding-box;\n}\ncombobox entry + button.combo {\n  border-radius: 0 0.5em 0.5em 0;\n}\ncombobox .linked button.combo {\n  padding: 0.5em;\n  border-radius: 0.5em;\n}\ncombobox .linked button.combo arrow {\n  min-width: 1em;\n  -gtk-icon-source: -gtk-icontheme(\"go-down-symbolic\");\n}\n.linked.horizontal combobox:not(:first-child) button {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.linked.horizontal combobox:not(:last-child) button {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.linked.vertical combobox:not(:first-child) button {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.linked.vertical combobox:not(:last-child) button {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\ndialog, messagedialog {\n  background: whitesmoke;\n}\ndialog .dialog-action-box, messagedialog .dialog-action-box {\n  background: whitesmoke;\n  padding: 0.6em;\n  margin: -0.4em;\n  margin-top: 0;\n}\ndialog fontchooser, dialog colorchooser, messagedialog fontchooser, messagedialog colorchooser {\n  padding: 0.6em;\n}\ndialog filechooser + .dialog-action-box, messagedialog filechooser + .dialog-action-box {\n  margin: 0;\n}\ndialog filechooser #pathbarbox, messagedialog filechooser #pathbarbox {\n  padding: 0.3em;\n  background: whitesmoke;\n}\ndialog filechooser treeview, messagedialog filechooser treeview {\n  background: whitesmoke;\n}\n\nentry {\n  padding: 0.4em;\n  border-radius: 0.5em;\n  border: 0.2em solid rgba(48, 48, 48, 0.05);\n  background: rgba(48, 48, 48, 0.05);\n  background-clip: padding-box;\n}\nentry:focus {\n  border-color: rgba(48, 48, 48, 0.1);\n}\nentry:disabled {\n  opacity: 0.3;\n}\nentry image.left {\n  margin-right: 0.6em;\n}\nentry image.right {\n  margin-left: 0.6em;\n}\nentry selection {\n  color: #303030;\n  background: rgba(48, 48, 48, 0.6);\n}\n.linked.horizontal entry:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.linked.horizontal entry:not(:last-child) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.linked.vertical entry:not(:first-child) {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.linked.vertical entry:not(:last-child) {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\nexpander title {\n  color: rgba(48, 48, 48, 0.1);\n  padding: 0.6em;\n}\nexpander title arrow {\n  -gtk-icon-source: -gtk-icontheme(\"go-next-symbolic\");\n  min-width: 1em;\n  min-height: 1em;\n  margin-right: 0.6em;\n}\nexpander title arrow:checked {\n  -gtk-icon-source: -gtk-icontheme(\"go-down-symbolic\");\n  color: #303030;\n}\nexpander title arrow:checked + label {\n  color: #303030;\n}\n\nframe > border, .frame {\n  border: 1px solid whitesmoke;\n}\n\nscrolledwindow viewport.frame {\n  border-style: none;\n}\n\nheaderbar {\n  background: whitesmoke;\n  padding: 0.6em;\n}\nheaderbar button.titlebutton {\n  padding: 0.8em;\n}\nheaderbar button.titlebutton:not(.appmenu) {\n  background: transparent;\n}\nheaderbar button.titlebutton.appmenu:not(:active):not(:checked) {\n  background: transparent;\n}\n\niconview {\n  background: whitesmoke;\n  padding: 0.3em;\n}\niconview:selected {\n  color: #303030;\n  background: rgba(48, 48, 48, 0.1);\n  border-radius: 0.5em;\n}\n\ninfobar {\n  color: #303030;\n  padding: 0.3em;\n  background: whitesmoke;\n}\ninfobar.info {\n  background: rgba(106, 159, 181, 0.6);\n}\ninfobar.warning {\n  background: rgba(244, 191, 117, 0.6);\n}\ninfobar.error {\n  background: rgba(172, 65, 65, 0.6);\n}\ninfobar.question {\n  background: rgba(48, 48, 48, 0.6);\n}\n\nlabel.dim-label, label:disabled {\n  opacity: 0.3;\n}\nlabel selection {\n  background: rgba(48, 48, 48, 0.1);\n  color: #b0b0b0;\n}\n\nlevelbar trough block {\n  padding: 3px;\n  border-radius: 3px;\n}\nlevelbar trough block.filled {\n  background: rgba(48, 48, 48, 0.1);\n}\nlevelbar trough block.empty {\n  background: whitesmoke;\n}\nlevelbar.discrete block:not(:first-child) {\n  margin-left: 5px;\n}\n\nlist {\n  background: whitesmoke;\n}\nlist row {\n  background: whitesmoke;\n  padding: 0.6em;\n}\nlist row:hover {\n  background: whitesmoke;\n}\nlist > label {\n  padding: 0.3em;\n}\n\nmenu {\n  background: #d0d0d0;\n}\nmenu menuitem {\n  padding: 0.6em;\n}\nmenu menuitem:hover {\n  background: rgba(48, 48, 48, 0.05);\n}\n\nmenubar {\n  background: whitesmoke;\n}\nmenubar menuitem {\n  padding: 0.6em;\n}\nmenubar menuitem:hover {\n  background: rgba(48, 48, 48, 0.05);\n}\nmenubar menuitem check, menubar menuitem radio {\n  margin-right: 0.6em;\n}\n\nnotebook stack:only-child {\n  background: whitesmoke;\n}\n\novershoot.top {\n  background-image: -gtk-gradient(radial, center top, 0, center top, 0.6, from(rgba(245, 245, 245, 0.2)), to(rgba(245, 245, 245, 0)));\n  background-size: 100% 60%;\n  background-repeat: no-repeat;\n  background-position: center top;\n}\novershoot.bottom {\n  background-image: -gtk-gradient(radial, center bottom, 0, center bottom, 0.6, from(rgba(245, 245, 245, 0.2)), to(rgba(245, 245, 245, 0)));\n  background-size: 100% 60%;\n  background-repeat: no-repeat;\n  background-position: center bottom;\n}\novershoot.left {\n  background-image: -gtk-gradient(radial, left center, 0, left center, 0.6, from(rgba(245, 245, 245, 0.2)), to(rgba(245, 245, 245, 0)));\n  background-size: 60% 100%;\n  background-repeat: no-repeat;\n  background-position: left center;\n}\novershoot.right {\n  background-image: -gtk-gradient(radial, right center, 0, right center, 0.6, from(rgba(245, 245, 245, 0.2)), to(rgba(245, 245, 245, 0)));\n  background-size: 60% 100%;\n  background-repeat: no-repeat;\n  background-position: right center;\n}\n\n.linked.path-bar button image:not(:only-child) {\n  margin-right: 0.6em;\n}\n.linked.path-bar button:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.linked.path-bar button:not(:last-child) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\nplacessidebar {\n  background: whitesmoke;\n}\nplacessidebar list {\n  background: transparent;\n}\nplacessidebar list row:hover {\n  background: rgba(48, 48, 48, 0.05);\n}\nplacessidebar list row:active {\n  background: rgba(48, 48, 48, 0.05);\n}\nplacessidebar list row:selected {\n  background: rgba(48, 48, 48, 0.1);\n}\nplacessidebar list row .sidebar-icon {\n  margin-right: 0.6em;\n}\nplacessidebar.frame,\nplacessidebar .frame {\n  border: 0;\n}\n\npopover {\n  background: #d0d0d0;\n  box-shadow: 0 0 5px rgba(245, 245, 245, 0.5);\n  border-radius: 0.5em;\n  padding: 0.5em;\n}\npopover box modelbutton {\n  padding: 0.4em 1em;\n  margin: 0 -0.5em;\n  border-radius: 0.5em;\n}\npopover box modelbutton:hover {\n  background: rgba(48, 48, 48, 0.05);\n}\npopover.menu:first-child {\n  border-top-left-radius: 0;\n}\n\nprogressbar trough {\n  background: rgba(48, 48, 48, 0.05);\n}\nprogressbar trough progress {\n  background: rgba(48, 48, 48, 0.1);\n}\n\nradio {\n  -gtk-icon-source: -gtk-recolor(url(\"../assets/symbolic/radio.symbolic.png\"));\n  -gtk-icon-transform: scale(0);\n  color: #303030;\n  border: 0.2em solid rgba(48, 48, 48, 0.05);\n  border-radius: 100%;\n  padding: 0.1em;\n  min-width: 0.6em;\n  min-height: 0.6em;\n  transition: -gtk-icon-transform 200ms;\n}\nradio:hover {\n  border-color: rgba(48, 48, 48, 0.05);\n}\nradio:active, radio:checked {\n  background: rgba(48, 48, 48, 0.05);\n}\nradio:checked {\n  -gtk-icon-transform: scale(1);\n  border-color: rgba(48, 48, 48, 0.1);\n  background: rgba(48, 48, 48, 0.1);\n}\n\nradiobutton label {\n  padding-left: 5px;\n}\nradiobutton:disabled {\n  opacity: 0.38;\n}\n\nrevealer frame.app-notification border {\n  border-radius: 0.5em;\n  background: whitesmoke;\n  padding: 0.6em;\n}\nrevealer.top frame.app-notification border {\n  margin-top: 0.6em;\n}\nrevealer.right frame.app-notification border {\n  margin-right: 0.6em;\n}\nrevealer.bottom frame.app-notification border {\n  margin-bottom: 0.6em;\n}\nrevealer.left frame.app-notification border {\n  margin-left: 0.6em;\n}\n\nrubberband, .rubberband {\n  background-color: rgba(48, 48, 48, 0.05);\n  border: 0.2em solid rgba(48, 48, 48, 0.6);\n}\n\nscale {\n  padding: 8px 0;\n}\nscale contents trough {\n  background: whitesmoke;\n}\nscale contents trough slider {\n  background: white;\n  padding: 8px;\n  margin: -5px;\n  border-radius: 100%;\n}\nscale contents trough highlight {\n  background: rgba(48, 48, 48, 0.1);\n}\nscale marks mark {\n  padding-top: 5px;\n}\nscale marks mark indicator {\n  color: whitesmoke;\n  min-height: 8px;\n  min-width: 1px;\n}\nscale:disabled {\n  opacity: 0.3;\n}\n\nscrollbar {\n  -GtkScrollbar-has-backward-stepper: false;\n  -GtkScrollbar-has-forward-stepper: false;\n}\nscrollbar slider {\n  padding: 0.2em;\n  border-radius: 1em;\n  background: rgba(48, 48, 48, 0.05);\n}\nscrollbar slider:hover {\n  background: rgba(48, 48, 48, 0.05);\n}\nscrollbar slider:active {\n  background: rgba(48, 48, 48, 0.1);\n}\n\nseparator {\n  background-image: image(rgba(48, 48, 48, 0.05));\n  background-size: 1px 1px;\n  background-position: center center;\n  min-width: 7px;\n  min-height: 7px;\n  background-repeat: repeat-x;\n  margin: -3px 0;\n}\nseparator.vertical, .horizontal > separator {\n  background-repeat: repeat-y;\n  margin: 0 -3px;\n}\n\nspinbutton button {\n  border: 0.2em solid transparent;\n}\nspinbutton:focus button {\n  border-color: rgba(48, 48, 48, 0.9);\n}\nspinbutton:focus button:hover {\n  background-clip: padding-box;\n}\nspinbutton:disabled {\n  opacity: 0.3;\n}\nspinbutton.horizontal :nth-child(3) {\n  border-radius: 0.5em 0 0 0.5em;\n  border-right: 0;\n}\nspinbutton.horizontal :nth-child(4) {\n  border-radius: 0;\n  border-width: 0.2em 0;\n}\nspinbutton.horizontal :nth-child(5) {\n  border-radius: 0 0.5em 0.5em 0;\n  border-left: 0;\n}\nspinbutton.vertical entry {\n  padding: 0.6em 0;\n}\nspinbutton.vertical :nth-child(3) {\n  border-radius: 0.5em 0.5em 0 0;\n  border-bottom: 0;\n}\nspinbutton.vertical :nth-child(4) {\n  border-radius: 0;\n  border-width: 0 0.2em;\n}\nspinbutton.vertical :nth-child(5) {\n  border-radius: 0 0 0.5em 0.5em;\n  border-top: 0;\n}\n.linked.horizontal spinbutton:not(:first-child) :nth-child(3) {\n  border-top-left-radius: 0;\n}\n.linked.horizontal spinbutton:not(:first-child) :nth-child(5) {\n  border-bottom-left-radius: 0;\n}\n.linked.horizontal spinbutton:not(:last-child) :nth-child(3) {\n  border-top-right-radius: 0;\n}\n.linked.horizontal spinbutton:not(:last-child) :nth-child(5) {\n  border-bottom-right-radius: 0;\n}\n.linked.vertical spinbutton:not(:first-child) :nth-child(3) {\n  border-top-left-radius: 0;\n}\n.linked.vertical spinbutton:not(:first-child) :nth-child(5) {\n  border-top-right-radius: 0;\n}\n.linked.vertical spinbutton:not(:last-child) :nth-child(3) {\n  border-bottom-left-radius: 0;\n}\n.linked.vertical spinbutton:not(:last-child) :nth-child(5) {\n  border-bottom-right-radius: 0;\n}\n\n@keyframes rotate {\n  to {\n    -gtk-icon-transform: rotate(1turn);\n  }\n}\nspinner {\n  -gtk-icon-source: none;\n}\nspinner:checked {\n  -gtk-icon-source: -gtk-icontheme(\"process-working-symbolic\");\n  animation: rotate 1s linear infinite;\n}\nspinner:disabled {\n  opacity: 0.3;\n}\n\nswitch {\n  background: rgba(48, 48, 48, 0.05);\n  border-radius: 1em;\n  min-width: 3em;\n  border: 0.2em solid transparent;\n}\nswitch slider {\n  background: rgba(48, 48, 48, 0.1);\n  border-radius: 100%;\n  min-width: 1.5em;\n  min-height: 1.5em;\n}\nswitch :nth-child(2) {\n  color: transparent;\n}\nswitch :nth-child(3) {\n  color: rgba(48, 48, 48, 0.1);\n}\nswitch:checked {\n  background: rgba(48, 48, 48, 0.3);\n}\nswitch:checked slider {\n  background: rgba(48, 48, 48, 0.9);\n}\nswitch:checked :nth-child(2) {\n  color: #303030;\n}\nswitch:checked :nth-child(3) {\n  color: transparent;\n}\nswitch:disabled {\n  opacity: 0.3;\n}\nswitch:hover slider {\n  box-shadow: 0 0 0 0.3em rgba(48, 48, 48, 0.05);\n}\nswitch:hover:checked slider {\n  box-shadow: 0 0 0 0.3em rgba(48, 48, 48, 0.3);\n}\n\ntabs {\n  background: whitesmoke;\n}\n.top > tabs {\n  border-bottom: 0.2em solid whitesmoke;\n}\n.top > tabs tab {\n  border-bottom: 0.2em solid transparent;\n  margin-bottom: -0.2em;\n}\n.right > tabs {\n  border-left: 0.2em solid whitesmoke;\n}\n.right > tabs tab {\n  border-left: 0.2em solid transparent;\n  margin-left: -0.2em;\n}\n.bottom > tabs {\n  border-top: 0.2em solid whitesmoke;\n}\n.bottom > tabs tab {\n  border-top: 0.2em solid transparent;\n  margin-top: -0.2em;\n}\n.left > tabs {\n  border-right: 0.2em solid whitesmoke;\n}\n.left > tabs tab {\n  border-right: 0.2em solid transparent;\n  margin-right: -0.2em;\n}\ntabs tab {\n  padding: 0.6em;\n  color: rgba(48, 48, 48, 0.1);\n}\nheader tabs tab:checked {\n  border-color: rgba(48, 48, 48, 0.6);\n  color: #303030;\n}\nheader tabs tab:hover:not(:checked) {\n  border-color: rgba(48, 48, 48, 0.05);\n  color: #303030;\n}\n\ntextview {\n  background: rgba(48, 48, 48, 0.05);\n  padding: 0.6em 0.3em;\n}\ntextview:disabled {\n  opacity: 0.3;\n}\ntextview selection {\n  background: rgba(48, 48, 48, 0.6);\n  color: #303030;\n}\n\ntoolbar, .toolbar {\n  background: whitesmoke;\n  padding: 0.6em;\n}\ntoolbar.osd, .toolbar.osd {\n  background: #d0d0d0;\n  padding: 0.6em;\n  border-radius: 0.5em;\n}\n\ntooltip {\n  background: #d0d0d0;\n}\ntooltip > * {\n  margin: 0.6em;\n}\ntooltip grid label {\n  margin: 0.3em 0;\n}\ntooltip grid image {\n  margin-right: 0.6em;\n}\n\ntreeview {\n  -GtkTreeView-expander-size: 16;\n  -GtkTreeView-grid-line-pattern: \"\";\n  -GtkTreeView-tree-line-pattern: \"\";\n  -GtkTreeView-horizontal-separator: 12;\n  -GtkTreeView-vertical-separator: 12;\n  border-left-color: rgba(48, 48, 48, 0.05);\n  border-top-color: rgba(48, 48, 48, 0.05);\n}\ntreeview:hover {\n  background: rgba(48, 48, 48, 0.05);\n}\ntreeview:selected {\n  background: rgba(48, 48, 48, 0.1);\n  color: #303030;\n}\ntreeview header button {\n  padding: 0.6em;\n  background: whitesmoke;\n  border-radius: 0;\n  color: rgba(48, 48, 48, 0.1);\n}\ntreeview header button:hover:not(:active) {\n  color: #303030;\n  background: whitesmoke;\n}\ntreeview header button:not(:last-child) {\n  border-right: 1px solid rgba(48, 48, 48, 0.05);\n}\n\nwindow, assistant {\n  background: whitesmoke;\n  color: #303030;\n}\n\nwindow.csd decoration {\n  box-shadow: 0 0 8px 0 black;\n  margin: 8px;\n}\n\n/*# sourceMappingURL=gtk.css.map */\n"
  },
  {
    "path": "src/usr/share/themes/light/index.theme",
    "content": "[Desktop Entry]\nType=X-GNOME-Metatheme\nName=light\nComment=base16-defaultlight\nEncoding=UTF-8\n\n[X-GNOME-Metatheme]\nGtkTheme=light\nIconTheme=Papirus\nCursorTheme=Breeze\nButtonLayout=menu\n"
  },
  {
    "path": "src/usr/share/themes/solarized/gtk-3.0/gtk.css",
    "content": "@define-color borders #002b36;\nwindow.background.chromium {\n  background: #074453;\n  color: #93a1a1;\n}\nwindow.background.chromium button {\n  border: 1px solid #074453;\n}\nwindow.background.chromium .titlebutton {\n  border: transparent;\n}\nwindow.background.chromium entry:focus {\n  border-color: #002b36;\n}\nwindow.background.chromium menu {\n  background: #043643;\n}\nwindow.background.chromium menu menuitem {\n  color: #93a1a1;\n}\nwindow.background.chromium menu menuitem:hover {\n  background: rgba(147, 161, 161, 0.05);\n}\nwindow.background.chromium menu menuitem:disabled label {\n  color: rgba(147, 161, 161, 0.05);\n}\nwindow.background.chromium headerbar, window.background.chromium menubar {\n  background: #002b36;\n}\nwindow.background.chromium headerbar label, window.background.chromium menubar label {\n  color: rgba(147, 161, 161, 0.1);\n}\nwindow.background.chromium headerbar .titlebutton, window.background.chromium menubar .titlebutton {\n  padding: 100px 80px;\n}\nwindow.background.chromium scrollbar button {\n  background: rgba(147, 161, 161, 0.05);\n  color: #93a1a1;\n}\nwindow.background.chromium scrollbar trough {\n  background: transparent;\n}\nwindow.background.chromium scrollbar slider {\n  background: rgba(147, 161, 161, 0.05);\n}\nwindow.background.chromium scrollbar junction {\n  background: rgba(147, 161, 161, 0.05);\n}\nwindow.background.chromium textview {\n  background-color: #002b36;\n  color: #93a1a1;\n}\n\n#MozillaGtkWidget {\n  background: #002b36;\n  color: #93a1a1;\n}\n#MozillaGtkWidget menubar {\n  color: rgba(147, 161, 161, 0.1);\n  background: #002b36;\n}\n#MozillaGtkWidget text {\n  background: #043643;\n}\n#MozillaGtkWidget menu {\n  background: #063c4a;\n}\n#MozillaGtkWidget menu :disabled {\n  color: rgba(147, 161, 161, 0.05);\n}\n#MozillaGtkWidget menu separator {\n  padding: 8px;\n}\n#MozillaGtkWidget border {\n  border: 1px solid #063c4a;\n}\n\n.nemo-window .nemo-places-sidebar {\n  background: #002b36;\n}\n.nemo-window .primary-toolbar {\n  background: #002b36;\n  padding: 0.3em;\n}\n.nemo-window menubar ~ separator {\n  min-width: 0;\n  min-height: 0;\n}\n.nemo-window .toolbar {\n  background: #002b36;\n  margin: -0.1em;\n}\n.nemo-window .nemo-window-pane :selected {\n  background: rgba(147, 161, 161, 0.6);\n}\n.nemo-window .nemo-window-pane :active {\n  background: rgba(147, 161, 161, 0.6);\n}\n\nactionbar {\n  padding: 0.6em;\n  background: #002b36;\n  border-top: 1px solid rgba(147, 161, 161, 0.05);\n}\n\nbutton {\n  transition: background-color 100ms ease-in;\n  background: rgba(147, 161, 161, 0.05);\n  padding: 0.6em;\n  border-radius: 0.5em;\n}\nbutton:hover:not(:active) {\n  background: rgba(147, 161, 161, 0.1);\n}\nbutton:disabled {\n  opacity: 0.3;\n}\nbutton:checked, button.suggested-action {\n  background: rgba(147, 161, 161, 0.1);\n  color: #93a1a1;\n}\nbutton:checked:hover:not(:active), button.suggested-action:hover:not(:active) {\n  background: rgba(147, 161, 161, 0.1);\n}\nbutton.image-button:not(.text-button) {\n  padding: 0.8em;\n}\nbutton.destructive-action {\n  background: rgba(220, 50, 47, 0.6);\n  color: #93a1a1;\n}\nbutton.destructive-action:hover:not(:active) {\n  background: rgba(220, 50, 47, 0.9);\n}\nbutton.circular {\n  border-radius: 100%;\n  padding: 0.8em;\n}\nbutton.flat {\n  background: transparent;\n}\nbutton.flat:hover {\n  background: rgba(147, 161, 161, 0.05);\n}\nbutton.flat:active, button.flat:checked {\n  background: rgba(147, 161, 161, 0.05);\n}\nlabel + button {\n  margin-left: 0.6em;\n}\nbutton.small-button image:only-child {\n  padding: 0.3em;\n}\n.linked.horizontal button:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.linked.horizontal button:not(:last-child) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.linked.vertical button:not(:first-child) {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.linked.vertical button:not(:last-child) {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\ncalendar {\n  background: #002b36;\n}\ncalendar.button {\n  background: #002b36;\n  color: rgba(147, 161, 161, 0.1);\n}\ncalendar.button:hover {\n  color: #93a1a1;\n}\ncalendar.button:disabled {\n  color: rgba(147, 161, 161, 0.05);\n}\ncalendar.header {\n  background: #002b36;\n}\ncalendar.highlight {\n  color: rgba(147, 161, 161, 0.1);\n}\ncalendar:selected {\n  color: #93a1a1;\n  border-radius: 0.5em;\n  background: rgba(147, 161, 161, 0.1);\n}\ncalendar:indeterminate {\n  color: rgba(147, 161, 161, 0.05);\n}\n\ncheck {\n  -gtk-icon-source: -gtk-recolor(url(\"../assets/symbolic/check.symbolic.png\"));\n  -gtk-icon-transform: scale(0);\n  color: #93a1a1;\n  border: 0.2em solid rgba(147, 161, 161, 0.05);\n  border-radius: 0.3em;\n  padding: 0.1em;\n  min-width: 0.7em;\n  min-height: 0.7em;\n  transition: -gtk-icon-transform 200ms;\n}\ncheck:hover {\n  border-color: rgba(147, 161, 161, 0.05);\n}\ncheck:active, check:checked {\n  background: rgba(147, 161, 161, 0.05);\n}\ncheck:checked {\n  background: rgba(147, 161, 161, 0.1);\n  -gtk-icon-transform: scale(1);\n  border-color: rgba(147, 161, 161, 0.1);\n}\ncheck:checked:active {\n  background: transparent;\n}\n\ncheckbutton label {\n  padding-left: 5px;\n}\ncheckbutton:disabled {\n  opacity: 0.3;\n}\n\ncombobox entry {\n  border-right: 0;\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\ncombobox entry:focus + button {\n  border: 0.2em solid rgba(147, 161, 161, 0.9);\n  border-left: 0;\n  margin-right: -0.2em;\n}\ncombobox entry:focus + button:hover {\n  background-clip: padding-box;\n}\ncombobox entry + button.combo {\n  border-radius: 0 0.5em 0.5em 0;\n}\ncombobox .linked button.combo {\n  padding: 0.5em;\n  border-radius: 0.5em;\n}\ncombobox .linked button.combo arrow {\n  min-width: 1em;\n  -gtk-icon-source: -gtk-icontheme(\"go-down-symbolic\");\n}\n.linked.horizontal combobox:not(:first-child) button {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.linked.horizontal combobox:not(:last-child) button {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.linked.vertical combobox:not(:first-child) button {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.linked.vertical combobox:not(:last-child) button {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\ndialog, messagedialog {\n  background: #002b36;\n}\ndialog .dialog-action-box, messagedialog .dialog-action-box {\n  background: #002b36;\n  padding: 0.6em;\n  margin: -0.4em;\n  margin-top: 0;\n}\ndialog fontchooser, dialog colorchooser, messagedialog fontchooser, messagedialog colorchooser {\n  padding: 0.6em;\n}\ndialog filechooser + .dialog-action-box, messagedialog filechooser + .dialog-action-box {\n  margin: 0;\n}\ndialog filechooser #pathbarbox, messagedialog filechooser #pathbarbox {\n  padding: 0.3em;\n  background: #002b36;\n}\ndialog filechooser treeview, messagedialog filechooser treeview {\n  background: #002b36;\n}\n\nentry {\n  padding: 0.4em;\n  border-radius: 0.5em;\n  border: 0.2em solid rgba(147, 161, 161, 0.05);\n  background: rgba(147, 161, 161, 0.05);\n  background-clip: padding-box;\n}\nentry:focus {\n  border-color: rgba(147, 161, 161, 0.1);\n}\nentry:disabled {\n  opacity: 0.3;\n}\nentry image.left {\n  margin-right: 0.6em;\n}\nentry image.right {\n  margin-left: 0.6em;\n}\nentry selection {\n  color: #93a1a1;\n  background: rgba(147, 161, 161, 0.6);\n}\n.linked.horizontal entry:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.linked.horizontal entry:not(:last-child) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n.linked.vertical entry:not(:first-child) {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n.linked.vertical entry:not(:last-child) {\n  border-bottom-left-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\nexpander title {\n  color: rgba(147, 161, 161, 0.1);\n  padding: 0.6em;\n}\nexpander title arrow {\n  -gtk-icon-source: -gtk-icontheme(\"go-next-symbolic\");\n  min-width: 1em;\n  min-height: 1em;\n  margin-right: 0.6em;\n}\nexpander title arrow:checked {\n  -gtk-icon-source: -gtk-icontheme(\"go-down-symbolic\");\n  color: #93a1a1;\n}\nexpander title arrow:checked + label {\n  color: #93a1a1;\n}\n\nframe > border, .frame {\n  border: 1px solid #002b36;\n}\n\nscrolledwindow viewport.frame {\n  border-style: none;\n}\n\nheaderbar {\n  background: #002b36;\n  padding: 0.6em;\n}\nheaderbar button.titlebutton {\n  padding: 0.8em;\n}\nheaderbar button.titlebutton:not(.appmenu) {\n  background: transparent;\n}\nheaderbar button.titlebutton.appmenu:not(:active):not(:checked) {\n  background: transparent;\n}\n\niconview {\n  background: #002b36;\n  padding: 0.3em;\n}\niconview:selected {\n  color: #93a1a1;\n  background: rgba(147, 161, 161, 0.1);\n  border-radius: 0.5em;\n}\n\ninfobar {\n  color: #93a1a1;\n  padding: 0.3em;\n  background: #002b36;\n}\ninfobar.info {\n  background: rgba(38, 139, 210, 0.6);\n}\ninfobar.warning {\n  background: rgba(181, 137, 0, 0.6);\n}\ninfobar.error {\n  background: rgba(220, 50, 47, 0.6);\n}\ninfobar.question {\n  background: rgba(147, 161, 161, 0.6);\n}\n\nlabel.dim-label, label:disabled {\n  opacity: 0.3;\n}\nlabel selection {\n  background: rgba(147, 161, 161, 0.1);\n  color: white;\n}\n\nlevelbar trough block {\n  padding: 3px;\n  border-radius: 3px;\n}\nlevelbar trough block.filled {\n  background: rgba(147, 161, 161, 0.1);\n}\nlevelbar trough block.empty {\n  background: #002b36;\n}\nlevelbar.discrete block:not(:first-child) {\n  margin-left: 5px;\n}\n\nlist {\n  background: #002b36;\n}\nlist row {\n  background: #002b36;\n  padding: 0.6em;\n}\nlist row:hover {\n  background: #002b36;\n}\nlist > label {\n  padding: 0.3em;\n}\n\nmenu {\n  background: #063c4a;\n}\nmenu menuitem {\n  padding: 0.6em;\n}\nmenu menuitem:hover {\n  background: rgba(147, 161, 161, 0.05);\n}\n\nmenubar {\n  background: #002b36;\n}\nmenubar menuitem {\n  padding: 0.6em;\n}\nmenubar menuitem:hover {\n  background: rgba(147, 161, 161, 0.05);\n}\nmenubar menuitem check, menubar menuitem radio {\n  margin-right: 0.6em;\n}\n\nnotebook stack:only-child {\n  background: #002b36;\n}\n\novershoot.top {\n  background-image: -gtk-gradient(radial, center top, 0, center top, 0.6, from(rgba(0, 43, 54, 0.2)), to(rgba(0, 43, 54, 0)));\n  background-size: 100% 60%;\n  background-repeat: no-repeat;\n  background-position: center top;\n}\novershoot.bottom {\n  background-image: -gtk-gradient(radial, center bottom, 0, center bottom, 0.6, from(rgba(0, 43, 54, 0.2)), to(rgba(0, 43, 54, 0)));\n  background-size: 100% 60%;\n  background-repeat: no-repeat;\n  background-position: center bottom;\n}\novershoot.left {\n  background-image: -gtk-gradient(radial, left center, 0, left center, 0.6, from(rgba(0, 43, 54, 0.2)), to(rgba(0, 43, 54, 0)));\n  background-size: 60% 100%;\n  background-repeat: no-repeat;\n  background-position: left center;\n}\novershoot.right {\n  background-image: -gtk-gradient(radial, right center, 0, right center, 0.6, from(rgba(0, 43, 54, 0.2)), to(rgba(0, 43, 54, 0)));\n  background-size: 60% 100%;\n  background-repeat: no-repeat;\n  background-position: right center;\n}\n\n.linked.path-bar button image:not(:only-child) {\n  margin-right: 0.6em;\n}\n.linked.path-bar button:not(:first-child) {\n  border-top-left-radius: 0;\n  border-bottom-left-radius: 0;\n}\n.linked.path-bar button:not(:last-child) {\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n}\n\nplacessidebar {\n  background: #002b36;\n}\nplacessidebar list {\n  background: transparent;\n}\nplacessidebar list row:hover {\n  background: rgba(147, 161, 161, 0.05);\n}\nplacessidebar list row:active {\n  background: rgba(147, 161, 161, 0.05);\n}\nplacessidebar list row:selected {\n  background: rgba(147, 161, 161, 0.1);\n}\nplacessidebar list row .sidebar-icon {\n  margin-right: 0.6em;\n}\nplacessidebar.frame,\nplacessidebar .frame {\n  border: 0;\n}\n\npopover {\n  background: #063c4a;\n  box-shadow: 0 0 5px rgba(0, 43, 54, 0.5);\n  border-radius: 0.5em;\n  padding: 0.5em;\n}\npopover box modelbutton {\n  padding: 0.4em 1em;\n  margin: 0 -0.5em;\n  border-radius: 0.5em;\n}\npopover box modelbutton:hover {\n  background: rgba(147, 161, 161, 0.05);\n}\npopover.menu:first-child {\n  border-top-left-radius: 0;\n}\n\nprogressbar trough {\n  background: rgba(147, 161, 161, 0.05);\n}\nprogressbar trough progress {\n  background: rgba(147, 161, 161, 0.1);\n}\n\nradio {\n  -gtk-icon-source: -gtk-recolor(url(\"../assets/symbolic/radio.symbolic.png\"));\n  -gtk-icon-transform: scale(0);\n  color: #93a1a1;\n  border: 0.2em solid rgba(147, 161, 161, 0.05);\n  border-radius: 100%;\n  padding: 0.1em;\n  min-width: 0.6em;\n  min-height: 0.6em;\n  transition: -gtk-icon-transform 200ms;\n}\nradio:hover {\n  border-color: rgba(147, 161, 161, 0.05);\n}\nradio:active, radio:checked {\n  background: rgba(147, 161, 161, 0.05);\n}\nradio:checked {\n  -gtk-icon-transform: scale(1);\n  border-color: rgba(147, 161, 161, 0.1);\n  background: rgba(147, 161, 161, 0.1);\n}\n\nradiobutton label {\n  padding-left: 5px;\n}\nradiobutton:disabled {\n  opacity: 0.38;\n}\n\nrevealer frame.app-notification border {\n  border-radius: 0.5em;\n  background: #002b36;\n  padding: 0.6em;\n}\nrevealer.top frame.app-notification border {\n  margin-top: 0.6em;\n}\nrevealer.right frame.app-notification border {\n  margin-right: 0.6em;\n}\nrevealer.bottom frame.app-notification border {\n  margin-bottom: 0.6em;\n}\nrevealer.left frame.app-notification border {\n  margin-left: 0.6em;\n}\n\nrubberband, .rubberband {\n  background-color: rgba(147, 161, 161, 0.05);\n  border: 0.2em solid rgba(147, 161, 161, 0.6);\n}\n\nscale {\n  padding: 8px 0;\n}\nscale contents trough {\n  background: #002b36;\n}\nscale contents trough slider {\n  background: #0091b6;\n  padding: 8px;\n  margin: -5px;\n  border-radius: 100%;\n}\nscale contents trough highlight {\n  background: rgba(147, 161, 161, 0.1);\n}\nscale marks mark {\n  padding-top: 5px;\n}\nscale marks mark indicator {\n  color: #002b36;\n  min-height: 8px;\n  min-width: 1px;\n}\nscale:disabled {\n  opacity: 0.3;\n}\n\nscrollbar {\n  -GtkScrollbar-has-backward-stepper: false;\n  -GtkScrollbar-has-forward-stepper: false;\n}\nscrollbar slider {\n  padding: 0.2em;\n  border-radius: 1em;\n  background: rgba(147, 161, 161, 0.05);\n}\nscrollbar slider:hover {\n  background: rgba(147, 161, 161, 0.05);\n}\nscrollbar slider:active {\n  background: rgba(147, 161, 161, 0.1);\n}\n\nseparator {\n  background-image: image(rgba(147, 161, 161, 0.05));\n  background-size: 1px 1px;\n  background-position: center center;\n  min-width: 7px;\n  min-height: 7px;\n  background-repeat: repeat-x;\n  margin: -3px 0;\n}\nseparator.vertical, .horizontal > separator {\n  background-repeat: repeat-y;\n  margin: 0 -3px;\n}\n\nspinbutton button {\n  border: 0.2em solid transparent;\n}\nspinbutton:focus button {\n  border-color: rgba(147, 161, 161, 0.9);\n}\nspinbutton:focus button:hover {\n  background-clip: padding-box;\n}\nspinbutton:disabled {\n  opacity: 0.3;\n}\nspinbutton.horizontal :nth-child(3) {\n  border-radius: 0.5em 0 0 0.5em;\n  border-right: 0;\n}\nspinbutton.horizontal :nth-child(4) {\n  border-radius: 0;\n  border-width: 0.2em 0;\n}\nspinbutton.horizontal :nth-child(5) {\n  border-radius: 0 0.5em 0.5em 0;\n  border-left: 0;\n}\nspinbutton.vertical entry {\n  padding: 0.6em 0;\n}\nspinbutton.vertical :nth-child(3) {\n  border-radius: 0.5em 0.5em 0 0;\n  border-bottom: 0;\n}\nspinbutton.vertical :nth-child(4) {\n  border-radius: 0;\n  border-width: 0 0.2em;\n}\nspinbutton.vertical :nth-child(5) {\n  border-radius: 0 0 0.5em 0.5em;\n  border-top: 0;\n}\n.linked.horizontal spinbutton:not(:first-child) :nth-child(3) {\n  border-top-left-radius: 0;\n}\n.linked.horizontal spinbutton:not(:first-child) :nth-child(5) {\n  border-bottom-left-radius: 0;\n}\n.linked.horizontal spinbutton:not(:last-child) :nth-child(3) {\n  border-top-right-radius: 0;\n}\n.linked.horizontal spinbutton:not(:last-child) :nth-child(5) {\n  border-bottom-right-radius: 0;\n}\n.linked.vertical spinbutton:not(:first-child) :nth-child(3) {\n  border-top-left-radius: 0;\n}\n.linked.vertical spinbutton:not(:first-child) :nth-child(5) {\n  border-top-right-radius: 0;\n}\n.linked.vertical spinbutton:not(:last-child) :nth-child(3) {\n  border-bottom-left-radius: 0;\n}\n.linked.vertical spinbutton:not(:last-child) :nth-child(5) {\n  border-bottom-right-radius: 0;\n}\n\n@keyframes rotate {\n  to {\n    -gtk-icon-transform: rotate(1turn);\n  }\n}\nspinner {\n  -gtk-icon-source: none;\n}\nspinner:checked {\n  -gtk-icon-source: -gtk-icontheme(\"process-working-symbolic\");\n  animation: rotate 1s linear infinite;\n}\nspinner:disabled {\n  opacity: 0.3;\n}\n\nswitch {\n  background: rgba(147, 161, 161, 0.05);\n  border-radius: 1em;\n  min-width: 3em;\n  border: 0.2em solid transparent;\n}\nswitch slider {\n  background: rgba(147, 161, 161, 0.1);\n  border-radius: 100%;\n  min-width: 1.5em;\n  min-height: 1.5em;\n}\nswitch :nth-child(2) {\n  color: transparent;\n}\nswitch :nth-child(3) {\n  color: rgba(147, 161, 161, 0.1);\n}\nswitch:checked {\n  background: rgba(147, 161, 161, 0.3);\n}\nswitch:checked slider {\n  background: rgba(147, 161, 161, 0.9);\n}\nswitch:checked :nth-child(2) {\n  color: #93a1a1;\n}\nswitch:checked :nth-child(3) {\n  color: transparent;\n}\nswitch:disabled {\n  opacity: 0.3;\n}\nswitch:hover slider {\n  box-shadow: 0 0 0 0.3em rgba(147, 161, 161, 0.05);\n}\nswitch:hover:checked slider {\n  box-shadow: 0 0 0 0.3em rgba(147, 161, 161, 0.3);\n}\n\ntabs {\n  background: #002b36;\n}\n.top > tabs {\n  border-bottom: 0.2em solid #002b36;\n}\n.top > tabs tab {\n  border-bottom: 0.2em solid transparent;\n  margin-bottom: -0.2em;\n}\n.right > tabs {\n  border-left: 0.2em solid #002b36;\n}\n.right > tabs tab {\n  border-left: 0.2em solid transparent;\n  margin-left: -0.2em;\n}\n.bottom > tabs {\n  border-top: 0.2em solid #002b36;\n}\n.bottom > tabs tab {\n  border-top: 0.2em solid transparent;\n  margin-top: -0.2em;\n}\n.left > tabs {\n  border-right: 0.2em solid #002b36;\n}\n.left > tabs tab {\n  border-right: 0.2em solid transparent;\n  margin-right: -0.2em;\n}\ntabs tab {\n  padding: 0.6em;\n  color: rgba(147, 161, 161, 0.1);\n}\nheader tabs tab:checked {\n  border-color: rgba(147, 161, 161, 0.6);\n  color: #93a1a1;\n}\nheader tabs tab:hover:not(:checked) {\n  border-color: rgba(147, 161, 161, 0.05);\n  color: #93a1a1;\n}\n\ntextview {\n  background: rgba(147, 161, 161, 0.05);\n  padding: 0.6em 0.3em;\n}\ntextview:disabled {\n  opacity: 0.3;\n}\ntextview selection {\n  background: rgba(147, 161, 161, 0.6);\n  color: #93a1a1;\n}\n\ntoolbar, .toolbar {\n  background: #002b36;\n  padding: 0.6em;\n}\ntoolbar.osd, .toolbar.osd {\n  background: #063c4a;\n  padding: 0.6em;\n  border-radius: 0.5em;\n}\n\ntooltip {\n  background: #063c4a;\n}\ntooltip > * {\n  margin: 0.6em;\n}\ntooltip grid label {\n  margin: 0.3em 0;\n}\ntooltip grid image {\n  margin-right: 0.6em;\n}\n\ntreeview {\n  -GtkTreeView-expander-size: 16;\n  -GtkTreeView-grid-line-pattern: \"\";\n  -GtkTreeView-tree-line-pattern: \"\";\n  -GtkTreeView-horizontal-separator: 12;\n  -GtkTreeView-vertical-separator: 12;\n  border-left-color: rgba(147, 161, 161, 0.05);\n  border-top-color: rgba(147, 161, 161, 0.05);\n}\ntreeview:hover {\n  background: rgba(147, 161, 161, 0.05);\n}\ntreeview:selected {\n  background: rgba(147, 161, 161, 0.1);\n  color: #93a1a1;\n}\ntreeview header button {\n  padding: 0.6em;\n  background: #002b36;\n  border-radius: 0;\n  color: rgba(147, 161, 161, 0.1);\n}\ntreeview header button:hover:not(:active) {\n  color: #93a1a1;\n  background: #002b36;\n}\ntreeview header button:not(:last-child) {\n  border-right: 1px solid rgba(147, 161, 161, 0.05);\n}\n\nwindow, assistant {\n  background: #002b36;\n  color: #93a1a1;\n}\n\nwindow.csd decoration {\n  box-shadow: 0 0 8px 0 black;\n  margin: 8px;\n}\n\n/*# sourceMappingURL=gtk.css.map */\n"
  },
  {
    "path": "src/usr/share/themes/solarized/index.theme",
    "content": "[Desktop Entry]\nType=X-GNOME-Metatheme\nName=solarized\nComment=solarized\nEncoding=UTF-8\n\n[X-GNOME-Metatheme]\nGtkTheme=solarized\nIconTheme=Papirus\nCursorTheme=Breeze\nButtonLayout=menu\n"
  },
  {
    "path": "src/usr/share/xsessions/calla.desktop",
    "content": "[Desktop Entry]\nName=Calla\nComment=Calla Desktop Environment\nExec=calla\nIcon=\nType=Application\n"
  }
]