Repository: Stardust-kyun/calla Branch: main Commit: 6d7006882f30 Files: 61 Total size: 255.7 KB Directory structure: gitextract_yiu3gkuy/ ├── .gitignore ├── DEBIAN/ │ └── control ├── PKGBUILD ├── README.md ├── build.sh └── src/ └── usr/ ├── bin/ │ └── calla └── share/ ├── calla/ │ ├── Xresources │ ├── compositor.conf │ ├── desktop/ │ │ ├── color/ │ │ │ ├── .backup/ │ │ │ │ ├── bloom/ │ │ │ │ │ └── bloom.json │ │ │ │ ├── dark/ │ │ │ │ │ ├── dark.json │ │ │ │ │ └── dark.json.bak │ │ │ │ ├── light/ │ │ │ │ │ └── light.json │ │ │ │ ├── sakura/ │ │ │ │ │ └── sakura.json │ │ │ │ ├── shore/ │ │ │ │ │ └── shore.json │ │ │ │ └── wave/ │ │ │ │ └── wave.json │ │ │ ├── dark/ │ │ │ │ └── dark.json │ │ │ ├── desktop.lua │ │ │ ├── light/ │ │ │ │ └── light.json │ │ │ ├── solarized/ │ │ │ │ └── solarized.json │ │ │ └── terminal.sh │ │ ├── config/ │ │ │ ├── bind.lua │ │ │ ├── init.lua │ │ │ ├── main.lua │ │ │ ├── rule.lua │ │ │ └── shot.lua │ │ ├── json.lua │ │ ├── rc.lua │ │ ├── signal/ │ │ │ ├── brightness.lua │ │ │ ├── desktop.lua │ │ │ ├── init.lua │ │ │ ├── playerctl.lua │ │ │ └── volume.lua │ │ └── theme/ │ │ ├── brightness.lua │ │ ├── control/ │ │ │ ├── calendar.lua │ │ │ ├── init.lua │ │ │ ├── media.lua │ │ │ ├── notifs.lua │ │ │ ├── profile.lua │ │ │ ├── sliders.lua │ │ │ ├── system.lua │ │ │ └── toggles.lua │ │ ├── desktop.lua │ │ ├── dock.lua │ │ ├── init.lua │ │ ├── launcher.lua │ │ ├── lock.lua │ │ ├── notif.lua │ │ ├── panel.lua │ │ ├── preview.lua │ │ ├── settings.lua │ │ ├── theme.lua │ │ ├── title.lua │ │ └── volume.lua │ └── xsettingsd ├── themes/ │ ├── dark/ │ │ ├── gtk-3.0/ │ │ │ └── gtk.css │ │ └── index.theme │ ├── light/ │ │ ├── gtk-3.0/ │ │ │ └── gtk.css │ │ └── index.theme │ └── solarized/ │ ├── gtk-3.0/ │ │ └── gtk.css │ └── index.theme └── xsessions/ └── calla.desktop ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ pkg/ *.tar.* *.deb package/ ================================================ FILE: DEBIAN/control ================================================ Package: calla Version: 0.3.0-5 Maintainer: Stella Depends: 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 Suggests: vim-gtk3, nemo, lollypop, firefox-esr Architecture: amd64 Homepage: https://github.com/stardust-kyun/calla Description: Calla Desktop Environment ================================================ FILE: PKGBUILD ================================================ # From @levraiardox # To https://github.com/Stardust-kyun/calla pkgname="calla" pkgver="0.3" pkgrel="1" pkgdesc="Calla desktop environement" arch=("x86_64") url="https://github.com/Stardust-kyun/calla" depends=( "xorg-server" "pipewire-pulse" "brightnessctl" "inotify-tools" "awesome-git" "picom" "maim" "papirus-icon-theme" "noto-fonts" "noto-fonts-cjk" "noto-color-emoji-fontconfig" "noto-fonts-extra" "lua-pam-git" ) optdepends=( "st: terminal", "vim-gtk3: vim with clipboard", "nemo: file manager", "network-manager-gnome: network applet", "polkit-gnome: polkit", "cbatticon: battery applet", "blueman: bluetooth applet", "xdg-user-dirs: generate home directories", "lollypop: music player", ) package() { cp -r "${srcdir}/usr" "${pkgdir}/" } ================================================ FILE: README.md ================================================

Calla

Install - Usage - Gallery - Credit - Contact
--- ![latest](.github/latest.png) --- ## Install ### Read Before Installing In 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 contact.
Installation --- # Under Construction Installation 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: - AwesomeWM git - Your distribution's package equivalents for line 4 of `DEBIAN/control` (the line that starts with `Depends:`) ---
## Usage
Settings App --- Calla 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`. ### General - Terminal - The terminal to run on `Mod+Enter` - Shutdown/Reboot - The commands to use to shutdown and reboot the system - Fallback Password - The password Calla will use if it cannot use your user's password - Font(s) - Fonts used throughout the desktop environment - Battery - The name of your system's battery (found in `/sys/class/power_supply/` - Wallpaper - If you would like to overwrite the theme's default wallpaper - Screenshot Directory - The directory that screenshots are saved to ### Theme - Color Scheme - The theme Calla will use - Colors - The colors the theme will use, in base8 - Compositor Settings - Settings for the shadow picom sets - Gui/Icon Theme - The names of the gui and icon themes the theme should use ---
## Gallery
Screenshots --- ### Apps ![apps](.github/apps.png) ### Launcher ![launcher](.github/launcher.png) ### Tag Preview ![preview](.github/preview.png) ### Volume/Brightness Popup ![volume](.github/volume.png) ### Lock Screen ![lockscreen](.github/lockscreen.png) ---
## Credit ### Thanks - [Ardox](https://github.com/LeVraiArdox) for refactoring and adding proper packaging for Arch. - [Sammy](https://github.com/TorchedSammy) for help understanding and adding live reloading. - [Crylia](https://github.com/Crylia) for massive amounts of help learning awesomewm. - [Jimmy](https://github.com/Jimmysit0) and [Petrolblue](https://github.com/petrolblue) for help with color schemes and lots of support. - And the support of many more! ### Historical Contributions - [AloneERO](https://gitlab.com/AloneER0) for help adding support for Void, Fedora, OpenSUSE, and Alpine! - [Frankfut](https://github.com/frankfutlg) for help adding support for Void and lots of help with debugging. - [Qwickdom](https://github.com/Qwickdom) for help adding support for Arch. - [Reverse](https://github.com/Reversedc) for help adding support for Debian. - [Alyssa](https://github.com/alyssa-sudo) for help adding support for Gentoo. ### References - [Sammy's Dotfiles](https://github.com/TorchedSammy/dotfiles) - [Saimoom's Dotfiles](https://github.com/saimoomedits/dotfiles) - [Smeueg's Dotfiles](https://github.com/Smeueg/Dotfiles) - [Bling](https://github.com/BlingCorp/bling) ### Projects - [Phocus](https://github.com/phocus/gtk) ## Contact You 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. ## Stars ![Star History Chart](https://api.star-history.com/svg?repos=Stardust-kyun/calla&type=Date) ================================================ FILE: build.sh ================================================ #!/usr/bin/env bash url=`cat ./.git/config | grep "url = " | sed "s/^[^=]*= //"` if [[ $url == "https://github.com/stardust-kyun/calla" ]]; then # make this case insensitive read -p " Which distro would you like to build for? (1) Debian (2) Arch (?) Select option: " dist case $dist in "1") mkdir -p package read -p "Version (0.1.0-1): " ver cp -r src/usr package/ cp -r DEBIAN package/ dpkg-deb --build package calla_$ver\_amd64.deb rm -rf package ;; "2") makepkg -f --noconfirm ;; *) echo "Something went wrong. Did you choose an option correctly?" exit esac else echo "Something went wrong. Are you inside the project directory?" fi ================================================ FILE: src/usr/bin/calla ================================================ #!/bin/sh xrdb /usr/share/calla/Xresources awesome --config /usr/share/calla/desktop/rc.lua ================================================ FILE: src/usr/share/calla/Xresources ================================================ #define BG #f5f5f5 #define FG #303030 #define BL #505050 #define WH #d0d0d0 #define R #ac4142 #define G #90a959 #define Y #f4bf75 #define B #6a9fb5 #define M #aa759f #define C #75b5aa st.font: RobotoMono:size=11 st.borderpx: 25 ! special *.foreground: FG *.background: BG *.cursorColor: FG ! black *.color0: BL *.color8: BL ! red *.color1: R *.color9: R ! green *.color2: G *.color10: G ! yellow *.color3: Y *.color11: Y ! blue *.color4: B *.color12: B ! magenta *.color5: M *.color13: M ! cyan *.color6: C *.color14: C ! white *.color7: WH *.color15: WH ================================================ FILE: src/usr/share/calla/compositor.conf ================================================ ################################# # # Shadows # ################################# # Enabled client-side shadows on windows. shadow = true; # The blur radius for shadows. (default 30) shadow-radius = 30; # The left offset for shadows. (default -30) shadow-offset-x = -30; # The top offset for shadows. (default -30) shadow-offset-y = -30; # The translucency for shadows. (default .25) shadow-opacity = .25; # The radius of corner rounding. (default 15) corner-radius = 15; # Window types to exclude from rounding. rounded-corners-exclude = [ "window_type = 'splash'" ]; # Special rules. corner-radius-rules = [ "20:window_type = 'desktop'" ]; ################################# # # Opacity # ################################# # Don't make anything transparent! inactive-opacity = 1; active-opacity = 1; frame-opacity = 1; inactive-opacity-override = false; ################################# # # Fading # ################################# # Fade windows during opacity changes. fading = true; # The time between steps in a fade in milliseconds. (default 3). fade-delta = 3; # Opacity change between steps while fading in. (default 0.03). fade-in-step = 0.03; # Opacity change between steps while fading out. (default 0.03). fade-out-step = 0.03; ================================================ FILE: src/usr/share/calla/desktop/color/.backup/bloom/bloom.json ================================================ { "bg": "#fffaf5", "bgalt": "#ebe6e1", "bgmid": "#ebe6e1", "black": "#4b4646", "blue": "#9bb9f0", "compoffset": "-25", "compopacity": ".1", "compradius": "25", "cyan": "#a0e1d2", "fg": "#4b4646", "green": "#96e6a5", "gtk": "bloom", "icons": "bloom", "magenta": "#d7a0e6", "red": "#eb8c8c", "wall": "color/bloom/bloom.png", "white": "#ebe6e1", "yellow": "#f0cd96" } ================================================ FILE: src/usr/share/calla/desktop/color/.backup/dark/dark.json ================================================ { "bg": "#151515", "bgalt": "#1a1a1a", "bgmid": "#202020", "black": "#505050", "blue": "#6a9fb5", "compoffset": "-25", "compopacity": ".5", "compradius": "25", "cyan": "#75b5aa", "fg": "#f5f5f5", "green": "#90a959", "gtk": "dark", "icons": "Papirus-Dark", "magenta": "#aa759f", "red": "#ac4142", "wall": "color/dark/dark.png", "white": "#d0d0d0", "yellow": "#f4bf75" } ================================================ FILE: src/usr/share/calla/desktop/color/.backup/dark/dark.json.bak ================================================ { "bg": "#151515", "bgalt": "#252525", "bgmid": "#1d1d1d", "black": "#505050", "blue": "#6a9fb5", "compoffset": "-25", "compopacity": ".5", "compradius": "25", "cyan": "#75b5aa", "fg": "#f5f5f5", "green": "#90a959", "gtk": "dark", "icons": "Papirus", "magenta": "#aa759f", "red": "#ac4142", "wall": "color/dark/dark.png", "white": "#d0d0d0", "yellow": "#f4bf75" } ================================================ FILE: src/usr/share/calla/desktop/color/.backup/light/light.json ================================================ { "bg": "#f5f5f5", "bgalt": "#d0d0d0", "bgmid": "#e3e3e3", "black": "#505050", "blue": "#6a9fb5", "compoffset": "-25", "compopacity": ".5", "compradius": "25", "cyan": "#75b5aa", "fg": "#303030", "green": "#90a959", "gtk": "light", "icons": "Papirus-Light", "magenta": "#aa759f", "red": "#ac4142", "wall": "color/light/light.png", "white": "#d0d0d0", "yellow": "#f4bf75" } ================================================ FILE: src/usr/share/calla/desktop/color/.backup/sakura/sakura.json ================================================ { "bg": "#000f14", "bgalt": "#0a191e", "bgmid": "#0a191e", "black": "#0a191e", "blue": "#326482", "compoffset": "-25", "compopacity": ".5", "compradius": "25", "cyan": "#327d7d", "fg": "#a0a0b4", "green": "#468264", "gtk": "sakura", "icons": "sakura", "magenta": "#645078", "red": "#824655", "wall": "color/sakura/sakura.png", "white": "#a0a0b4", "yellow": "#827d50" } ================================================ FILE: src/usr/share/calla/desktop/color/.backup/shore/shore.json ================================================ { "bg": "#19191e", "bgalt": "#2b2b33", "bgmid": "#2b2b33", "black": "#2b2b33", "blue": "#505a82", "compoffset": "-25", "compopacity": ".5", "compradius": "25", "cyan": "#5a7387", "fg": "#9999a8", "green": "#5a825a", "gtk": "shore", "icons": "shore", "magenta": "#735a87", "red": "#825a5a", "wall": "color/shore/shore.png", "white": "#9999a8", "yellow": "#968264" } ================================================ FILE: src/usr/share/calla/desktop/color/.backup/wave/wave.json ================================================ { "bg": "#f0fafa", "bgalt": "#dce6e6", "bgmid": "#dce6e6", "black": "#404040", "blue": "#83b4e6", "compoffset": "-25", "compopacity": ".1", "compradius": "25", "cyan": "#8cd7d2", "fg": "#262626", "green": "#a0e6af", "gtk": "wave", "icons": "wave", "magenta": "#e1aae1", "red": "#e68383", "wall": "color/wave/wave.png", "white": "#dce6e6", "yellow": "#ffcd96" } ================================================ FILE: src/usr/share/calla/desktop/color/dark/dark.json ================================================ { "bg": "#151515", "bgalt": "#2B2B2B", "bgmid": "#202020", "black": "#505050", "blue": "#6a9fb5", "compoffset": "-25", "compopacity": ".5", "compradius": "25", "cyan": "#75b5aa", "fg": "#f5f5f5", "green": "#90a959", "gtk": "dark", "icons": "Papirus-Dark", "magenta": "#aa759f", "red": "#ac4142", "wall": "color/dark/dark.png", "white": "#d0d0d0", "yellow": "#f4bf75" } ================================================ FILE: src/usr/share/calla/desktop/color/desktop.lua ================================================ local calla="/usr/share/calla/" local picom=calla.."compositor.conf" local xresources=calla.."Xresources" local xsettingsd=calla.."xsettingsd" local function compositor(radius, x, y, opacity) local r = assert(io.open(picom, "r")) local file = r:read("*all") r:close() file = file:gsub("shadow%-radius = .-\n", "shadow-radius = "..radius..";\n") file = file:gsub("shadow%-offset%-x = .-\n", "shadow-offset-x = "..x..";\n") file = file:gsub("shadow%-offset%-y = .-\n", "shadow-offset-y = "..y..";\n") file = file:gsub("shadow%-opacity = .-\n", "shadow-opacity = "..opacity..";\n") local w = assert(io.open(picom, "w")) w:write(file) w:close() end local function terminal(bg, fg, bl, wh, r, g, y, b, m, c) local read = assert(io.open(xresources, "r")) local file = read:read("*all") read:close() file = file:gsub("%#define BG .-\n", "#define BG "..bg.."\n") file = file:gsub("%#define FG .-\n", "#define FG "..fg.."\n") file = file:gsub("%#define BL .-\n", "#define BL "..bl.."\n") file = file:gsub("%#define WH .-\n", "#define WH "..wh.."\n") file = file:gsub("%#define R .-\n", "#define R "..r.."\n") file = file:gsub("%#define G .-\n", "#define G "..g.."\n") file = file:gsub("%#define Y .-\n", "#define Y "..y.."\n") file = file:gsub("%#define B .-\n", "#define B "..b.."\n") file = file:gsub("%#define M .-\n", "#define M "..m.."\n") file = file:gsub("%#define C .-\n", "#define C "..c.."\n") local w = assert(io.open(xresources, "w")) w:write(file) w:close() os.execute("xrdb " .. xresources) require("awful").spawn.easy_async_with_shell(require("gears").filesystem.get_configuration_dir() .. "color/terminal.sh") end local function theme(theme, icon) local r = assert(io.open(xsettingsd, "r")) local file = r:read("*all") r:close() file = file:gsub("Net/ThemeName .-\n", "Net/ThemeName \""..theme.."\"\n") file = file:gsub("Net/IconThemeName .-\n", "Net/IconThemeName \""..icon.."\"\n") local w = assert(io.open(xsettingsd, "w")) w:write(file) w:close() os.execute("xsettingsd --config '/usr/share/calla/xsettingsd' &") end awesome.connect_signal("color::change", function(color) compositor(color.compradius, color.compoffset, color.compoffset, color.compopacity) terminal(color.bg, color.fg, color.black, color.white, color.red, color.green, color.yellow, color.blue, color.magenta, color.cyan) theme(color.gtk, color.icons) end) ================================================ FILE: src/usr/share/calla/desktop/color/light/light.json ================================================ { "bg": "#f5f5f5", "bgalt": "#d0d0d0", "bgmid": "#e3e3e3", "black": "#505050", "blue": "#6a9fb5", "compoffset": "-30", "compopacity": ".25", "compradius": "30", "cyan": "#75b5aa", "fg": "#303030", "green": "#90a959", "gtk": "light", "icons": "Papirus-Light", "magenta": "#aa759f", "red": "#ac4142", "wall": "color/light/light.png", "white": "#d0d0d0", "yellow": "#f4bf75" } ================================================ FILE: src/usr/share/calla/desktop/color/solarized/solarized.json ================================================ { "bg": "#002B36", "bgalt": "#063C4A", "bgmid": "#043643", "black": "#657B83", "blue": "#268BD2", "compoffset": "-30", "compopacity": ".25", "compradius": "30", "cyan": "#2AA198", "fg": "#93A1A1", "green": "#859900", "gtk": "solarized", "icons": "Papirus-Dark", "magenta": "#6C71C4", "red": "#DC322F", "wall": "color/solarized/solarized.png", "white": "#FDF6E3", "yellow": "#B58900" } ================================================ FILE: src/usr/share/calla/desktop/color/terminal.sh ================================================ #!/usr/bin/env bash xrdb_query() { value=$(xrdb -query | grep -i "^*\.$1:" | cut -f 2) if [ -n "${value}" ]; then echo "${value}" return 0 fi return 1 } sequences="" for i in $(seq 0 15); do sequences+="\033]4;${i};$(xrdb_query "color${i}")\007" done sequences+="\\033]10;$(xrdb_query foreground)\\007" sequences+="\\033]11;$(xrdb_query background)\\007" sequences+="\\033]12;$(xrdb_query cursorColor)\\007" sequences+="\\033]708;$(xrdb_query background)\\007" for term in /dev/pts/[0-9]*; do printf "%b" "${sequences}" > "${term}" & done ================================================ FILE: src/usr/share/calla/desktop/config/bind.lua ================================================ local awful = require("awful") local mod = user.mod -- Mouse client.connect_signal("request::default_mousebindings", function() awful.mouse.append_client_mousebindings({ -- Move awful.button({ mod }, 1, function(c) c:activate { context = "mouse_click", action = "mouse_move" } end), -- Resize awful.button({ mod }, 3, function(c) c:activate { context = "mouse_click", action = "mouse_resize"} end), }) end) -- Keys awful.keyboard.append_global_keybindings({ -- Awesome awful.key( { mod, "Shift" }, "r", awesome.restart, { description = "reload awesome", group = "awesome" } ), awful.key( { mod }, "z", function() awful.layout.inc(1) end, { description = "next layout", group = "awesome" } ), awful.key( { mod, "Shift" }, "z", function() awful.layout.inc(-1) end, { description = "previous layout", group = "awesome" } ), awful.key( { mod }, "Tab", function() awful.client.focus.byidx(1) end, { description = "next window", group = "awesome" } ), awful.key( { mod, "Shift" }, "Tab", function() awful.client.focus.byidx(-1) end, { description = "previous window", group = "awesome" } ), awful.key( { mod }, "space", function() awesome.emit_signal("widget::menu") end, { description = "show menu", group = "awesome" } ), awful.key( { mod }, "d", function() awesome.emit_signal("widget::launcher") end, { description = "show launcher", group = "awesome" } ), awful.key( { mod, "Shift" }, "c", function() awesome.emit_signal("widget::config") end, { description = "show config", group = "awesome" } ), awful.key( { mod }, "BackSpace", function() awesome.emit_signal("widget::lockscreen") end, { description = "lock screen", group = "awesome" } ), awful.key( { mod }, "space", function() awesome.emit_signal("widget::control") end, { description = "control center", group = "awesome" } ), -- Programs awful.key( { mod }, "Return", function() awful.spawn.with_shell(user.terminal) end, { description = "open a terminal", group = "programs" } ), -- Screenshot awful.key( { mod }, "Print", function() awesome.emit_signal("util::screenshot", { args = "-u", time = "0" }) end, { description = "full screen", group = "screenshot" } ), awful.key( { mod, "Control" }, "Print", function() awesome.emit_signal("util::screenshot", { args = "-u", time = "5" }) end, { description = "full screen delay", group = "screenshot" } ), awful.key( { mod, "Shift" }, "Print", function() awesome.emit_signal("util::screenshot", { args = "-s -u", time = "0" }) end, { description = "part screen", group = "screenshot" } ), -- Volume awful.key( { }, "XF86AudioRaiseVolume", function() awful.spawn.with_shell("wpctl set-mute @DEFAULT_AUDIO_SINK@ 0") awful.spawn.with_shell("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+") awesome.emit_signal("widget::volume") end, { description = "raise volume", group = "volume" } ), awful.key( { }, "XF86AudioLowerVolume", function() awful.spawn.with_shell("wpctl set-mute @DEFAULT_AUDIO_SINK@ 0") awful.spawn.with_shell("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-") awesome.emit_signal("widget::volume") end, { description = "lower volume", group = "volume" } ), awful.key( { }, "XF86AudioMute", function() awful.spawn.with_shell("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle") awesome.emit_signal("widget::volume") end, { description = "mute volume", group = "volume" } ), -- Brightness awful.key( { }, "XF86MonBrightnessUp", function() awful.spawn.with_shell("brightnessctl s 5%+") awesome.emit_signal("widget::brightness") end, { description = "raise brightness", group = "brightness" } ), awful.key( { }, "XF86MonBrightnessDown", function() awful.spawn.with_shell("brightnessctl s 5%-") awesome.emit_signal("widget::brightness") end, { description = "lower brightness", group = "brightness" } ), -- Tag awful.key { modifiers = { mod }, keygroup = "numrow", description = "only view tag", group = "tag", on_press = function(index) local screen = awful.screen.focused() local tag = screen.tags[index] if tag then tag:view_only() end end, }, awful.key { modifiers = { mod, "Control" }, keygroup = "numrow", description = "move focused client to tag", group = "tag", on_press = function(index) if client.focus then local tag = client.focus.screen.tags[index] if tag then client.focus:move_to_tag(tag) end end end, }, awful.key { modifiers = { mod, "Shift" }, keygroup = "numrow", description = "move focused client to tag and follow", group = "tag", on_press = function(index) if client.focus then local tag = client.focus.screen.tags[index] if tag then client.focus:move_to_tag(tag) tag:view_only() end end end, } }) client.connect_signal("request::default_keybindings", function() awful.keyboard.append_client_keybindings({ -- Client awful.key( { mod }, "c", function(c) awful.placement.centered(c, { honor_workarea = true }) end, { description = "center window", group = "client" } ), awful.key( { mod }, "f", function(c) c.fullscreen = not c.fullscreen c:raise() end, { description = "toggle fullscreen", group = "client" } ), awful.key( { mod }, "s", function(c) c.floating = not c.floating c:raise() end, { description = "toggle floating", group = "client" } ), awful.key( { mod }, "n", function(c) client.focus.minimized = true end, { description = "minimize", group = "client" } ), awful.key( { mod }, "m", function(c) c.maximized = not c.maximized c:raise() end, { description = "toggle maximize", group = "client" } ), awful.key( { mod }, "b", function(c) c.sticky = not c.sticky c.above = not c.above c:raise() end, { description = "toggle sticky", group = "client" } ), awful.key( { mod, "Shift" }, "q", function(c) c:kill() end, { description = "close", group = "client" } ), }) end) ================================================ FILE: src/usr/share/calla/desktop/config/init.lua ================================================ require("config.main") require("config.bind") require("config.rule") require("config.shot") ================================================ FILE: src/usr/share/calla/desktop/config/main.lua ================================================ local awful = require("awful") local gears = require("gears") local beautiful = require("beautiful") -- Manage new windows client.connect_signal('manage', function(c) -- Put new windows in stack if not awesome.startup then awful.client.setslave(c) end if awesome.startup and not c.size_hints.user_position and not c.size_hints.program_position then awful.placement.no_offscreen(c) end if c.x == 0 and c.y == 0 then awful.placement.centered(c) end -- Default icon local cairo = require("lgi").cairo local defaulticon = "/usr/share/icons/" .. beautiful.icons .. "/64x64/apps/application-default-icon.svg" if c and c.valid and not c.icon then local s = gears.surface(defaulticon) local img = cairo.ImageSurface.create(cairo.Format.ARGB32, s:get_width(), s:get_height()) local cr = cairo.Context(img) cr:set_source_surface(s, 0, 0) cr:paint() c.icon = img._native end end) -- Sloppy focus client.connect_signal("mouse::enter", function(c) c:activate { context = "mouse_enter", raise = false } end) -- Layouts and tag table screen.connect_signal("request::desktop_decoration", function(s) tag.connect_signal("request::default_layouts", function() awful.layout.append_default_layouts({ awful.layout.suit.tile, awful.layout.suit.floating, }) end) awful.tag({ "1", "2", "3" }, s, awful.layout.layouts[1]) end) -- Touchpad gestures awesome.connect_signal("touchpad::gesture", function(direction) require("naughty").notification{text="test"} if direction == "left" then awful.tag.viewprev() elseif direction == "right" then awful.tag.viewnext() elseif direction == "up" then awesome.emit_signal("widget::preview") elseif direction == "down" then awesome.emit_signal("widget::preview:hide") end end) ================================================ FILE: src/usr/share/calla/desktop/config/rule.lua ================================================ local awful = require("awful") local gears = require("gears") local ruled = require("ruled") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi ruled.client.connect_signal("request::rules", function() -- New clients ruled.client.append_rule { id = "global", rule = { }, properties = { focus = awful.client.focus.filter, raise = true, screen = awful.screen.preferred, placement = awful.placement.no_offscreen } } -- Floating clients ruled.client.append_rule { id = "floating", rule_any = { instance = { "copyq", "pinentry" }, class = { "Arandr", "Gcolor3", "Blueberry.py", "SimpleScreenRecorder", "Usbimager", "Yad", "Settings" }, name = { "Event Tester", -- xev "ncmpcpp" }, }, properties = { floating = true } } -- Titlebars ruled.client.append_rule { id = "titlebars", rule_any = { type = { "normal", "dialog" } }, properties = { titlebars_enabled = true } } end) ================================================ FILE: src/usr/share/calla/desktop/config/shot.lua ================================================ local awful = require("awful") local naughty = require("naughty") local function screenshot(args) local tmp = "/tmp/" .. os.date("%F-%H%M%S") .. ".png" awful.spawn.easy_async_with_shell("sleep " .. args.time .. " && maim " .. args.args .. " " .. tmp, function() awful.spawn.easy_async_with_shell("[ -e '" .. tmp .. "' ] && echo exists", function(output) if output:match('%w+') then --[[awful.spawn.easy_async_with_shell("convert -trim " .. tmp .. " " .. tmp, function() awful.spawn.with_shell("cat " .. tmp .. " | xclip -se c -t image/png -i") awful.spawn.with_shell("cp " .. tmp .. " " .. user.shotdir) awful.spawn.with_shell("rm " .. tmp) end)--]] awful.spawn.with_shell("cat " .. tmp .. " | xclip -se c -t image/png -i") awful.spawn.with_shell("cp " .. tmp .. " " .. user.shotdir) awful.spawn.with_shell("rm " .. tmp) naughty.notification { title = "Screenshot", text = "Saved to " .. user.shotdir } else naughty.notification { title = "Screenshot", text = "Cancelled" } end end) end) end awesome.connect_signal("util::screenshot", function(args) screenshot(args) end) ================================================ FILE: src/usr/share/calla/desktop/json.lua ================================================ -- -*- coding: utf-8 -*- -- -- Simple JSON encoding and decoding in pure Lua. -- -- Copyright 2010-2017 Jeffrey Friedl -- http://regex.info/blog/ -- Latest version: http://regex.info/blog/lua/json -- -- This code is released under a Creative Commons CC-BY "Attribution" License: -- http://creativecommons.org/licenses/by/3.0/deed.en_US -- -- It can be used for any purpose so long as: -- 1) the copyright notice above is maintained -- 2) the web-page links above are maintained -- 3) the 'AUTHOR_NOTE' string below is maintained -- local VERSION = '20211016.28' -- version history at end of file local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20211016.28 ]-" -- -- The 'AUTHOR_NOTE' variable exists so that information about the source -- of the package is maintained even in compiled versions. It's also -- included in OBJDEF below mostly to quiet warnings about unused variables. -- local OBJDEF = { VERSION = VERSION, AUTHOR_NOTE = AUTHOR_NOTE, } -- -- Simple JSON encoding and decoding in pure Lua. -- JSON definition: http://www.json.org/ -- -- -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines -- -- local lua_value = JSON:decode(raw_json_text) -- -- local raw_json_text = JSON:encode(lua_table_or_value) -- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability -- -- -- -- DECODING (from a JSON string to a Lua table) -- -- -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines -- -- local lua_value = JSON:decode(raw_json_text) -- -- If the JSON text is for an object or an array, e.g. -- { "what": "books", "count": 3 } -- or -- [ "Larry", "Curly", "Moe" ] -- -- the result is a Lua table, e.g. -- { what = "books", count = 3 } -- or -- { "Larry", "Curly", "Moe" } -- -- -- The encode and decode routines accept an optional second argument, -- "etc", which is not used during encoding or decoding, but upon error -- is passed along to error handlers. It can be of any type (including nil). -- -- -- -- ERROR HANDLING DURING DECODE -- -- With most errors during decoding, this code calls -- -- JSON:onDecodeError(message, text, location, etc) -- -- with a message about the error, and if known, the JSON text being -- parsed and the byte count where the problem was discovered. You can -- replace the default JSON:onDecodeError() with your own function. -- -- The default onDecodeError() merely augments the message with data -- about the text and the location (and, an 'etc' argument had been -- provided to decode(), its value is tacked onto the message as well), -- and then calls JSON.assert(), which itself defaults to Lua's built-in -- assert(), and can also be overridden. -- -- For example, in an Adobe Lightroom plugin, you might use something like -- -- function JSON:onDecodeError(message, text, location, etc) -- LrErrors.throwUserError("Internal Error: invalid JSON data") -- end -- -- or even just -- -- function JSON.assert(message) -- LrErrors.throwUserError("Internal Error: " .. message) -- end -- -- If JSON:decode() is passed a nil, this is called instead: -- -- JSON:onDecodeOfNilError(message, nil, nil, etc) -- -- and if JSON:decode() is passed HTML instead of JSON, this is called: -- -- JSON:onDecodeOfHTMLError(message, text, nil, etc) -- -- The use of the 'etc' argument allows stronger coordination between -- decoding and error reporting, especially when you provide your own -- error-handling routines. Continuing with the the Adobe Lightroom -- plugin example: -- -- function JSON:onDecodeError(message, text, location, etc) -- local note = "Internal Error: invalid JSON data" -- if type(etc) = 'table' and etc.photo then -- note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName') -- end -- LrErrors.throwUserError(note) -- end -- -- : -- : -- -- for i, photo in ipairs(photosToProcess) do -- : -- : -- local data = JSON:decode(someJsonText, { photo = photo }) -- : -- : -- end -- -- -- -- If the JSON text passed to decode() has trailing garbage (e.g. as with the JSON "[123]xyzzy"), -- the method -- -- JSON:onTrailingGarbage(json_text, location, parsed_value, etc) -- -- is invoked, where: -- -- 'json_text' is the original JSON text being parsed, -- 'location' is the count of bytes into 'json_text' where the garbage starts (6 in the example), -- 'parsed_value' is the Lua result of what was successfully parsed ({123} in the example), -- 'etc' is as above. -- -- If JSON:onTrailingGarbage() does not abort, it should return the value decode() should return, -- or nil + an error message. -- -- local new_value, error_message = JSON:onTrailingGarbage() -- -- The default JSON:onTrailingGarbage() simply invokes JSON:onDecodeError("trailing garbage"...), -- but you can have this package ignore trailing garbage via -- -- function JSON:onTrailingGarbage(json_text, location, parsed_value, etc) -- return parsed_value -- end -- -- -- DECODING AND STRICT TYPES -- -- Because both JSON objects and JSON arrays are converted to Lua tables, -- it's not normally possible to tell which original JSON type a -- particular Lua table was derived from, or guarantee decode-encode -- round-trip equivalency. -- -- However, if you enable strictTypes, e.g. -- -- JSON = assert(loadfile "JSON.lua")() --load the routines -- JSON.strictTypes = true -- -- then the Lua table resulting from the decoding of a JSON object or -- JSON array is marked via Lua metatable, so that when re-encoded with -- JSON:encode() it ends up as the appropriate JSON type. -- -- (This is not the default because other routines may not work well with -- tables that have a metatable set, for example, Lightroom API calls.) -- -- -- DECODING AND STRICT PARSING -- -- If strictParsing is true in your JSON object, or if you set strictParsing as a decode option, -- some kinds of technically-invalid JSON that would normally be accepted are rejected with an error. -- -- For example, passing in an empty string -- -- JSON:decode("") -- -- normally succeeds with a return value of nil, but -- -- JSON:decode("", nil, { strictParsing = true }) -- -- results in an error being raised (onDecodeError is called). -- -- JSON.strictParsing = true -- JSON:decode("") -- -- achieves the same thing. -- -- -- -- ENCODING (from a lua table to a JSON string) -- -- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines -- -- local raw_json_text = JSON:encode(lua_table_or_value) -- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability -- local custom_pretty = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = "| ", align_keys = false }) -- -- On error during encoding, this code calls: -- -- JSON:onEncodeError(message, etc) -- -- which you can override in your local JSON object. Also see "HANDLING UNSUPPORTED VALUE TYPES" below. -- -- The 'etc' in the error call is the second argument to encode() and encode_pretty(), or nil if it wasn't provided. -- -- -- -- -- ENCODING OPTIONS -- -- An optional third argument, a table of options, can be provided to encode(). -- -- encode_options = { -- -- options for making "pretty" human-readable JSON (see "PRETTY-PRINTING" below) -- pretty = true, -- turn pretty formatting on -- indent = " ", -- use this indent for each level of an array/object -- align_keys = false, -- if true, align the keys in a way that sounds like it should be nice, but is actually ugly -- array_newline = false, -- if true, array elements become one to a line rather than inline -- -- -- other output-related options -- null = "\0", -- see "ENCODING JSON NULL VALUES" below -- stringsAreUtf8 = false, -- see "HANDLING UNICODE LINE AND PARAGRAPH SEPARATORS FOR JAVA" below -- } -- -- json_string = JSON:encode(mytable, etc, encode_options) -- -- -- -- For reference, the defaults are: -- -- pretty = false -- null = nil, -- stringsAreUtf8 = false, -- -- -- -- PRETTY-PRINTING -- -- Enabling the 'pretty' encode option helps generate human-readable JSON. -- -- pretty = JSON:encode(val, etc, { -- pretty = true, -- indent = " ", -- align_keys = false, -- }) -- -- encode_pretty() is also provided: it's identical to encode() except -- that encode_pretty() provides a default options table if none given in the call: -- -- { pretty = true, indent = " ", align_keys = false, array_newline = false } -- -- For example, if -- -- JSON:encode(data) -- -- produces: -- -- {"city":"Kyoto","climate":{"avg_temp":16,"humidity":"high","snowfall":"minimal"},"country":"Japan","wards":11} -- -- then -- -- JSON:encode_pretty(data) -- -- produces: -- -- { -- "city": "Kyoto", -- "climate": { -- "avg_temp": 16, -- "humidity": "high", -- "snowfall": "minimal" -- }, -- "country": "Japan", -- "wards": 11 -- } -- -- The following lines all return identical strings: -- JSON:encode_pretty(data) -- JSON:encode_pretty(data, nil, { pretty = true, indent = " ", align_keys = false, array_newline = false}) -- JSON:encode_pretty(data, nil, { pretty = true, indent = " " }) -- JSON:encode (data, nil, { pretty = true, indent = " " }) -- -- An example of setting your own indent string: -- -- JSON:encode_pretty(data, nil, { pretty = true, indent = "| " }) -- -- produces: -- -- { -- | "city": "Kyoto", -- | "climate": { -- | | "avg_temp": 16, -- | | "humidity": "high", -- | | "snowfall": "minimal" -- | }, -- | "country": "Japan", -- | "wards": 11 -- } -- -- An example of setting align_keys to true: -- -- JSON:encode_pretty(data, nil, { pretty = true, indent = " ", align_keys = true }) -- -- produces: -- -- { -- "city": "Kyoto", -- "climate": { -- "avg_temp": 16, -- "humidity": "high", -- "snowfall": "minimal" -- }, -- "country": "Japan", -- "wards": 11 -- } -- -- which I must admit is kinda ugly, sorry. This was the default for -- encode_pretty() prior to version 20141223.14. -- -- -- HANDLING UNICODE LINE AND PARAGRAPH SEPARATORS FOR JAVA -- -- If the 'stringsAreUtf8' encode option is set to true, consider Lua strings not as a sequence of bytes, -- but as a sequence of UTF-8 characters. -- -- Currently, the only practical effect of setting this option is that Unicode LINE and PARAGRAPH -- separators, if found in a string, are encoded with a JSON escape instead of being dumped as is. -- The JSON is valid either way, but encoding this way, apparently, allows the resulting JSON -- to also be valid Java. -- -- AMBIGUOUS SITUATIONS DURING THE ENCODING -- -- During the encode, if a Lua table being encoded contains both string -- and numeric keys, it fits neither JSON's idea of an object, nor its -- idea of an array. To get around this, when any string key exists (or -- when non-positive numeric keys exist), numeric keys are converted to -- strings. -- -- For example, -- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) -- produces the JSON object -- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} -- -- To prohibit this conversion and instead make it an error condition, set -- JSON.noKeyConversion = true -- -- -- ENCODING JSON NULL VALUES -- -- Lua tables completely omit keys whose value is nil, so without special handling there's -- no way to represent JSON object's null value in a Lua table. For example -- JSON:encode({ username = "admin", password = nil }) -- -- produces: -- -- {"username":"admin"} -- -- In order to actually produce -- -- {"username":"admin", "password":null} -- -- one can include a string value for a "null" field in the options table passed to encode().... -- any Lua table entry with that value becomes null in the JSON output: -- -- JSON:encode({ username = "admin", password = "xyzzy" }, -- First arg is the Lua table to encode as JSON. -- nil, -- Second arg is the 'etc' value, ignored here -- { null = "xyzzy" }) -- Third arg is th options table -- -- produces: -- -- {"username":"admin", "password":null} -- -- Just be sure to use a string that is otherwise unlikely to appear in your data. -- The string "\0" (a string with one null byte) may well be appropriate for many applications. -- -- The "null" options also applies to Lua tables that become JSON arrays. -- JSON:encode({ "one", "two", nil, nil }) -- -- produces -- -- ["one","two"] -- -- while -- -- NullPlaceholder = "\0" -- encode_options = { null = NullPlaceholder } -- JSON:encode({ "one", "two", NullPlaceholder, NullPlaceholder}, nil, encode_options) -- produces -- -- ["one","two",null,null] -- -- -- -- HANDLING LARGE AND/OR PRECISE NUMBERS -- -- -- Without special handling, numbers in JSON can lose precision in Lua. -- For example: -- -- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }') -- -- print("small: ", type(T.small), T.small) -- print("big: ", type(T.big), T.big) -- print("precise: ", type(T.precise), T.precise) -- -- produces -- -- small: number 12345 -- big: number 1.2345678901235e+28 -- precise: number 9876.6789012346 -- -- Precision is lost with both 'big' and 'precise'. -- -- This package offers ways to try to handle this better (for some definitions of "better")... -- -- The most precise method is by setting the global: -- -- JSON.decodeNumbersAsObjects = true -- -- When this is set, numeric JSON data is encoded into Lua in a form that preserves the exact -- JSON numeric presentation when re-encoded back out to JSON, or accessed in Lua as a string. -- -- This is done by encoding the numeric data with a Lua table/metatable that returns -- the possibly-imprecise numeric form when accessed numerically, but the original precise -- representation when accessed as a string. -- -- Consider the example above, with this option turned on: -- -- JSON.decodeNumbersAsObjects = true -- -- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }') -- -- print("small: ", type(T.small), T.small) -- print("big: ", type(T.big), T.big) -- print("precise: ", type(T.precise), T.precise) -- -- This now produces: -- -- small: table 12345 -- big: table 12345678901234567890123456789 -- precise: table 9876.67890123456789012345 -- -- However, within Lua you can still use the values (e.g. T.precise in the example above) in numeric -- contexts. In such cases you'll get the possibly-imprecise numeric version, but in string contexts -- and when the data finds its way to this package's encode() function, the original full-precision -- representation is used. -- -- You can force access to the string or numeric version via -- JSON:forceString() -- JSON:forceNumber() -- For example, -- local probably_okay = JSON:forceNumber(T.small) -- 'probably_okay' is a number -- -- Code the inspects the JSON-turned-Lua data using type() can run into troubles because what used to -- be a number can now be a table (e.g. as the small/big/precise example above shows). Update these -- situations to use JSON:isNumber(item), which returns nil if the item is neither a number nor one -- of these number objects. If it is either, it returns the number itself. For completeness there's -- also JSON:isString(item). -- -- If you want to try to avoid the hassles of this "number as an object" kludge for all but really -- big numbers, you can set JSON.decodeNumbersAsObjects and then also set one or both of -- JSON:decodeIntegerObjectificationLength -- JSON:decodeDecimalObjectificationLength -- They refer to the length of the part of the number before and after a decimal point. If they are -- set and their part is at least that number of digits, objectification occurs. If both are set, -- objectification occurs when either length is met. -- -- ----------------------- -- -- Even without using the JSON.decodeNumbersAsObjects option, you can encode numbers in your Lua -- table that retain high precision upon encoding to JSON, by using the JSON:asNumber() function: -- -- T = { -- imprecise = 123456789123456789.123456789123456789, -- precise = JSON:asNumber("123456789123456789.123456789123456789") -- } -- -- print(JSON:encode_pretty(T)) -- -- This produces: -- -- { -- "precise": 123456789123456789.123456789123456789, -- "imprecise": 1.2345678912346e+17 -- } -- -- -- ----------------------- -- -- A different way to handle big/precise JSON numbers is to have decode() merely return the exact -- string representation of the number instead of the number itself. This approach might be useful -- when the numbers are merely some kind of opaque object identifier and you want to work with them -- in Lua as strings anyway. -- -- This approach is enabled by setting -- -- JSON.decodeIntegerStringificationLength = 10 -- -- The value is the number of digits (of the integer part of the number) at which to stringify numbers. -- NOTE: this setting is ignored if JSON.decodeNumbersAsObjects is true, as that takes precedence. -- -- Consider our previous example with this option set to 10: -- -- JSON.decodeIntegerStringificationLength = 10 -- -- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }') -- -- print("small: ", type(T.small), T.small) -- print("big: ", type(T.big), T.big) -- print("precise: ", type(T.precise), T.precise) -- -- This produces: -- -- small: number 12345 -- big: string 12345678901234567890123456789 -- precise: number 9876.6789012346 -- -- The long integer of the 'big' field is at least JSON.decodeIntegerStringificationLength digits -- in length, so it's converted not to a Lua integer but to a Lua string. Using a value of 0 or 1 ensures -- that all JSON numeric data becomes strings in Lua. -- -- Note that unlike -- JSON.decodeNumbersAsObjects = true -- this stringification is simple and unintelligent: the JSON number simply becomes a Lua string, and that's the end of it. -- If the string is then converted back to JSON, it's still a string. After running the code above, adding -- print(JSON:encode(T)) -- produces -- {"big":"12345678901234567890123456789","precise":9876.6789012346,"small":12345} -- which is unlikely to be desired. -- -- There's a comparable option for the length of the decimal part of a number: -- -- JSON.decodeDecimalStringificationLength -- -- This can be used alone or in conjunction with -- -- JSON.decodeIntegerStringificationLength -- -- to trip stringification on precise numbers with at least JSON.decodeIntegerStringificationLength digits after -- the decimal point. (Both are ignored if JSON.decodeNumbersAsObjects is true.) -- -- This example: -- -- JSON.decodeIntegerStringificationLength = 10 -- JSON.decodeDecimalStringificationLength = 5 -- -- T = JSON:decode('{ "small":12345, "big":12345678901234567890123456789, "precise":9876.67890123456789012345 }') -- -- print("small: ", type(T.small), T.small) -- print("big: ", type(T.big), T.big) -- print("precise: ", type(T.precise), T.precise) -- -- produces: -- -- small: number 12345 -- big: string 12345678901234567890123456789 -- precise: string 9876.67890123456789012345 -- -- -- HANDLING UNSUPPORTED VALUE TYPES -- -- Among the encoding errors that might be raised is an attempt to convert a table value that has a type -- that this package hasn't accounted for: a function, userdata, or a thread. You can handle these types as table -- values (but not as table keys) if you supply a JSON:unsupportedTypeEncoder() method along the lines of the -- following example: -- -- function JSON:unsupportedTypeEncoder(value_of_unsupported_type) -- if type(value_of_unsupported_type) == 'function' then -- return "a function value" -- else -- return nil -- end -- end -- -- Your unsupportedTypeEncoder() method is actually called with a bunch of arguments: -- -- self:unsupportedTypeEncoder(value, parents, etc, options, indent, for_key) -- -- The 'value' is the function, thread, or userdata to be converted to JSON. -- -- The 'etc' and 'options' arguments are those passed to the original encode(). The other arguments are -- probably of little interest; see the source code. (Note that 'for_key' is never true, as this function -- is invoked only on table values; table keys of these types still trigger the onEncodeError method.) -- -- If your unsupportedTypeEncoder() method returns a string, it's inserted into the JSON as is. -- If it returns nil plus an error message, that error message is passed through to an onEncodeError invocation. -- If it returns only nil, processing falls through to a default onEncodeError invocation. -- -- If you want to handle everything in a simple way: -- -- function JSON:unsupportedTypeEncoder(value) -- return tostring(value) -- end -- -- -- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT -- -- assert -- onDecodeError -- onDecodeOfNilError -- onDecodeOfHTMLError -- onTrailingGarbage -- onEncodeError -- unsupportedTypeEncoder -- -- If you want to create a separate Lua JSON object with its own error handlers, -- you can reload JSON.lua or use the :new() method. -- --------------------------------------------------------------------------- local default_pretty_indent = " " local default_pretty_options = { pretty = true, indent = default_pretty_indent, align_keys = false, array_newline = false } local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject function OBJDEF:newArray(tbl) return setmetatable(tbl or {}, isArray) end function OBJDEF:newObject(tbl) return setmetatable(tbl or {}, isObject) end local function getnum(op) return type(op) == 'number' and op or op.N end local isNumber = { __tostring = function(T) return T.S end, __unm = function(op) return getnum(op) end, __concat = function(op1, op2) return tostring(op1) .. tostring(op2) end, __add = function(op1, op2) return getnum(op1) + getnum(op2) end, __sub = function(op1, op2) return getnum(op1) - getnum(op2) end, __mul = function(op1, op2) return getnum(op1) * getnum(op2) end, __div = function(op1, op2) return getnum(op1) / getnum(op2) end, __mod = function(op1, op2) return getnum(op1) % getnum(op2) end, __pow = function(op1, op2) return getnum(op1) ^ getnum(op2) end, __lt = function(op1, op2) return getnum(op1) < getnum(op2) end, __eq = function(op1, op2) return getnum(op1) == getnum(op2) end, __le = function(op1, op2) return getnum(op1) <= getnum(op2) end, } isNumber.__index = isNumber function OBJDEF:asNumber(item) if getmetatable(item) == isNumber then -- it's already a JSON number object. return item elseif type(item) == 'table' and type(item.S) == 'string' and type(item.N) == 'number' then -- it's a number-object table that lost its metatable, so give it one return setmetatable(item, isNumber) else -- the normal situation... given a number or a string representation of a number.... local holder = { S = tostring(item), -- S is the representation of the number as a string, which remains precise N = tonumber(item), -- N is the number as a Lua number. } return setmetatable(holder, isNumber) end end -- -- Given an item that might be a normal string or number, or might be an 'isNumber' object defined above, -- return the string version. This shouldn't be needed often because the 'isNumber' object should autoconvert -- to a string in most cases, but it's here to allow it to be forced when needed. -- function OBJDEF:forceString(item) if type(item) == 'table' and type(item.S) == 'string' then return item.S else return tostring(item) end end -- -- Given an item that might be a normal string or number, or might be an 'isNumber' object defined above, -- return the numeric version. -- function OBJDEF:forceNumber(item) if type(item) == 'table' and type(item.N) == 'number' then return item.N else return tonumber(item) end end -- -- If the given item is a number, return it. Otherwise, return nil. -- This, this can be used both in a conditional and to access the number when you're not sure its form. -- function OBJDEF:isNumber(item) if type(item) == 'number' then return item elseif type(item) == 'table' and type(item.N) == 'number' then return item.N else return nil end end function OBJDEF:isString(item) if type(item) == 'string' then return item elseif type(item) == 'table' and type(item.S) == 'string' then return item.S else return nil end end -- -- Some utf8 routines to deal with the fact that Lua handles only bytes -- local function top_three_bits(val) return math.floor(val / 0x20) end local function top_four_bits(val) return math.floor(val / 0x10) end local function unicode_character_bytecount_based_on_first_byte(first_byte) local W = string.byte(first_byte) if W < 0x80 then return 1 elseif (W == 0xC0) or (W == 0xC1) or (W >= 0x80 and W <= 0xBF) or (W >= 0xF5) then -- this is an error -- W can't be the start of a utf8 character return 0 elseif top_three_bits(W) == 0x06 then return 2 elseif top_four_bits(W) == 0x0E then return 3 else return 4 end end local function unicode_codepoint_as_utf8(codepoint) -- -- codepoint is a number -- if codepoint <= 127 then return string.char(codepoint) elseif codepoint <= 2047 then -- -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 -- local highpart = math.floor(codepoint / 0x40) local lowpart = codepoint - (0x40 * highpart) return string.char(0xC0 + highpart, 0x80 + lowpart) elseif codepoint <= 65535 then -- -- 1110yyyy 10yyyyxx 10xxxxxx -- local highpart = math.floor(codepoint / 0x1000) local remainder = codepoint - 0x1000 * highpart local midpart = math.floor(remainder / 0x40) local lowpart = remainder - 0x40 * midpart highpart = 0xE0 + highpart midpart = 0x80 + midpart lowpart = 0x80 + lowpart -- -- Check for an invalid character (thanks Andy R. at Adobe). -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 -- if ( highpart == 0xE0 and midpart < 0xA0 ) or ( highpart == 0xED and midpart > 0x9F ) or ( highpart == 0xF0 and midpart < 0x90 ) or ( highpart == 0xF4 and midpart > 0x8F ) then return "?" else return string.char(highpart, midpart, lowpart) end else -- -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx -- local highpart = math.floor(codepoint / 0x40000) local remainder = codepoint - 0x40000 * highpart local midA = math.floor(remainder / 0x1000) remainder = remainder - 0x1000 * midA local midB = math.floor(remainder / 0x40) local lowpart = remainder - 0x40 * midB return string.char(0xF0 + highpart, 0x80 + midA, 0x80 + midB, 0x80 + lowpart) end end function OBJDEF:onDecodeError(message, text, location, etc) if text then if location then message = string.format("%s at byte %d of: %s", message, location, text) else message = string.format("%s: %s", message, text) end end if etc ~= nil then message = message .. " (" .. OBJDEF:encode(etc) .. ")" end if self.assert then self.assert(false, message) else assert(false, message) end end function OBJDEF:onTrailingGarbage(json_text, location, parsed_value, etc) return self:onDecodeError("trailing garbage", json_text, location, etc) end OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError function OBJDEF:onEncodeError(message, etc) if etc ~= nil then message = message .. " (" .. OBJDEF:encode(etc) .. ")" end if self.assert then self.assert(false, message) else assert(false, message) end end local function grok_number(self, text, start, options) -- -- Grab the integer part -- local integer_part = text:match('^-?[1-9]%d*', start) or text:match("^-?0", start) if not integer_part then self:onDecodeError("expected number", text, start, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible end local i = start + integer_part:len() -- -- Grab an optional decimal part -- local decimal_part = text:match('^%.%d+', i) or "" i = i + decimal_part:len() -- -- Grab an optional exponential part -- local exponent_part = text:match('^[eE][-+]?%d+', i) or "" i = i + exponent_part:len() local full_number_text = integer_part .. decimal_part .. exponent_part if options.decodeNumbersAsObjects then local objectify = false if not options.decodeIntegerObjectificationLength and not options.decodeDecimalObjectificationLength then -- no options, so objectify objectify = true elseif (options.decodeIntegerObjectificationLength and (integer_part:len() >= options.decodeIntegerObjectificationLength or exponent_part:len() > 0)) or (options.decodeDecimalObjectificationLength and (decimal_part:len() >= options.decodeDecimalObjectificationLength or exponent_part:len() > 0)) then -- have options and they are triggered, so objectify objectify = true end if objectify then return OBJDEF:asNumber(full_number_text), i end -- else, fall through to try to return as a straight-up number else -- Not always decoding numbers as objects, so perhaps encode as strings? -- -- If we're told to stringify only under certain conditions, so do. -- We punt a bit when there's an exponent by just stringifying no matter what. -- I suppose we should really look to see whether the exponent is actually big enough one -- way or the other to trip stringification, but I'll be lazy about it until someone asks. -- if (options.decodeIntegerStringificationLength and (integer_part:len() >= options.decodeIntegerStringificationLength or exponent_part:len() > 0)) or (options.decodeDecimalStringificationLength and (decimal_part:len() >= options.decodeDecimalStringificationLength or exponent_part:len() > 0)) then return full_number_text, i -- this returns the exact string representation seen in the original JSON end end local as_number = tonumber(full_number_text) if not as_number then self:onDecodeError("bad number", text, start, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible end return as_number, i end local backslash_escape_conversion = { ['"'] = '"', ['/'] = "/", ['\\'] = "\\", ['b'] = "\b", ['f'] = "\f", ['n'] = "\n", ['r'] = "\r", ['t'] = "\t", } local function grok_string(self, text, start, options) if text:sub(start,start) ~= '"' then self:onDecodeError("expected string's opening quote", text, start, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible end local i = start + 1 -- +1 to bypass the initial quote local text_len = text:len() local VALUE = "" while i <= text_len do local c = text:sub(i,i) if c == '"' then return VALUE, i + 1 end if c ~= '\\' then -- should grab the next bytes as per the number of bytes for this utf8 character local byte_count = unicode_character_bytecount_based_on_first_byte(c) local next_character if byte_count == 0 then self:onDecodeError("non-utf8 sequence", text, i, options.etc) elseif byte_count == 1 then if options.strictParsing and string.byte(c) < 0x20 then self:onDecodeError("Unescaped control character", text, i+1, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible end next_character = c elseif byte_count == 2 then next_character = text:match('^(.[\128-\191])', i) elseif byte_count == 3 then next_character = text:match('^(.[\128-\191][\128-\191])', i) elseif byte_count == 4 then next_character = text:match('^(.[\128-\191][\128-\191][\128-\191])', i) end if not next_character then self:onDecodeError("incomplete utf8 sequence", text, i, options.etc) return nil, i -- in case the error method doesn't abort, return something sensible end VALUE = VALUE .. next_character i = i + byte_count else -- -- We have a backslash escape -- i = i + 1 local next_byte = text:match('^(.)', i) if next_byte == nil then -- string ended after the \ self:onDecodeError("unfinished \\ escape", text, i, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible end if backslash_escape_conversion[next_byte] then VALUE = VALUE .. backslash_escape_conversion[next_byte] i = i + 1 else -- -- The only other valid use of \ that remains is in the form of \u#### -- local hex = text:match('^u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) if hex then i = i + 5 -- bypass what we just read -- We have a Unicode codepoint. It could be standalone, or if in the proper range and -- followed by another in a specific range, it'll be a two-code surrogate pair. local codepoint = tonumber(hex, 16) if codepoint >= 0xD800 and codepoint <= 0xDBFF then -- it's a hi surrogate... see whether we have a following low local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) if lo_surrogate then i = i + 6 -- bypass the low surrogate we just read codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) else -- not a proper low, so we'll just leave the first codepoint as is and spit it out. end end VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) elseif options.strictParsing then --local next_byte = text:match('^\\(.)', i) printf("NEXT[%s]", next_byte); self:onDecodeError("illegal use of backslash escape", text, i, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible else local byte_count = unicode_character_bytecount_based_on_first_byte(next_byte) if byte_count == 0 then self:onDecodeError("non-utf8 sequence after backslash escape", text, i, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible end local next_character if byte_count == 1 then next_character = next_byte elseif byte_count == 2 then next_character = text:match('^(.[\128-\191])', i) elseif byte_count == 3 then next_character = text:match('^(.[\128-\191][\128-\191])', i) elseif byte_count == 3 then next_character = text:match('^(.[\128-\191][\128-\191][\128-\191])', i) end if next_character == nil then -- incomplete utf8 character after escape self:onDecodeError("incomplete utf8 sequence after backslash escape", text, i, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible end VALUE = VALUE .. next_character i = i + byte_count end end end end self:onDecodeError("unclosed string", text, start, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible end local function skip_whitespace(text, start) local _, match_end = text:find("^[ \n\r\t]+", start) -- [ https://datatracker.ietf.org/doc/html/rfc7158#section-2 ] if match_end then return match_end + 1 else return start end end local grok_one -- assigned later local function grok_object(self, text, start, options) if text:sub(start,start) ~= '{' then self:onDecodeError("expected '{'", text, start, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible end local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' local VALUE = self.strictTypes and self:newObject { } or { } if text:sub(i,i) == '}' then return VALUE, i + 1 end local text_len = text:len() while i <= text_len do local key, new_i = grok_string(self, text, i, options) i = skip_whitespace(text, new_i) if text:sub(i, i) ~= ':' then self:onDecodeError("expected colon", text, i, options.etc) return nil, i -- in case the error method doesn't abort, return something sensible end i = skip_whitespace(text, i + 1) local new_val, new_i = grok_one(self, text, i, options) VALUE[key] = new_val -- -- Expect now either '}' to end things, or a ',' to allow us to continue. -- i = skip_whitespace(text, new_i) local c = text:sub(i,i) if c == '}' then return VALUE, i + 1 end if text:sub(i, i) ~= ',' then self:onDecodeError("expected comma or '}'", text, i, options.etc) return nil, i -- in case the error method doesn't abort, return something sensible end i = skip_whitespace(text, i + 1) end self:onDecodeError("unclosed '{'", text, start, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible end local function grok_array(self, text, start, options) if text:sub(start,start) ~= '[' then self:onDecodeError("expected '['", text, start, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible end local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' local VALUE = self.strictTypes and self:newArray { } or { } if text:sub(i,i) == ']' then return VALUE, i + 1 end local VALUE_INDEX = 1 local text_len = text:len() while i <= text_len do local val, new_i = grok_one(self, text, i, options) -- can't table.insert(VALUE, val) here because it's a no-op if val is nil VALUE[VALUE_INDEX] = val VALUE_INDEX = VALUE_INDEX + 1 i = skip_whitespace(text, new_i) -- -- Expect now either ']' to end things, or a ',' to allow us to continue. -- local c = text:sub(i,i) if c == ']' then return VALUE, i + 1 end if text:sub(i, i) ~= ',' then self:onDecodeError("expected comma or ']'", text, i, options.etc) return nil, i -- in case the error method doesn't abort, return something sensible end i = skip_whitespace(text, i + 1) end self:onDecodeError("unclosed '['", text, start, options.etc) return nil, i -- in case the error method doesn't abort, return something sensible end grok_one = function(self, text, start, options) -- Skip any whitespace start = skip_whitespace(text, start) if start > text:len() then self:onDecodeError("unexpected end of string", text, nil, options.etc) return nil, start -- in case the error method doesn't abort, return something sensible end if text:find('^"', start) then return grok_string(self, text, start, options) elseif text:find('^[-0123456789 ]', start) then return grok_number(self, text, start, options) elseif text:find('^%{', start) then return grok_object(self, text, start, options) elseif text:find('^%[', start) then return grok_array(self, text, start, options) elseif text:find('^true', start) then return true, start + 4 elseif text:find('^false', start) then return false, start + 5 elseif text:find('^null', start) then return options.null, start + 4 else self:onDecodeError("can't parse JSON", text, start, options.etc) return nil, 1 -- in case the error method doesn't abort, return something sensible end end function OBJDEF:decode(text, etc, options) -- -- If the user didn't pass in a table of decode options, make an empty one. -- if type(options) ~= 'table' then options = {} end -- -- If they passed in an 'etc' argument, stuff it into the options. -- (If not, any 'etc' field in the options they passed in remains to be used) -- if etc ~= nil then options.etc = etc end -- -- apply global options -- if options.decodeNumbersAsObjects == nil then options.decodeNumbersAsObjects = self.decodeNumbersAsObjects end if options.decodeIntegerObjectificationLength == nil then options.decodeIntegerObjectificationLength = self.decodeIntegerObjectificationLength end if options.decodeDecimalObjectificationLength == nil then options.decodeDecimalObjectificationLength = self.decodeDecimalObjectificationLength end if options.decodeIntegerStringificationLength == nil then options.decodeIntegerStringificationLength = self.decodeIntegerStringificationLength end if options.decodeDecimalStringificationLength == nil then options.decodeDecimalStringificationLength = self.decodeDecimalStringificationLength end if options.strictParsing == nil then options.strictParsing = self.strictParsing end if type(self) ~= 'table' or self.__index ~= OBJDEF then local error_message = "JSON:decode must be called in method format" OBJDEF:onDecodeError(error_message, nil, nil, options.etc) return nil, error_message -- in case the error method doesn't abort, return something sensible end if text == nil then local error_message = "nil passed to JSON:decode()" self:onDecodeOfNilError(error_message, nil, nil, options.etc) return nil, error_message -- in case the error method doesn't abort, return something sensible elseif type(text) ~= 'string' then local error_message = "expected string argument to JSON:decode()" self:onDecodeError(string.format("%s, got %s", error_message, type(text)), nil, nil, options.etc) return nil, error_message -- in case the error method doesn't abort, return something sensible end -- If passed an empty string.... if text:match('^%s*$') then if options.strictParsing then local error_message = "empty string passed to JSON:decode()" self:onDecodeOfNilError(error_message, nil, nil, options.etc) return nil, error_message -- in case the error method doesn't abort, return something sensible else -- we'll consider it nothing, but not an error return nil end end if text:match('^%s*<') then -- Can't be JSON... we'll assume it's HTML local error_message = "HTML passed to JSON:decode()" self:onDecodeOfHTMLError(error_message, text, nil, options.etc) return nil, error_message -- in case the error method doesn't abort, return something sensible end -- -- Ensure that it's not UTF-32 or UTF-16. -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3), -- but this package can't handle them. -- if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then local error_message = "JSON package groks only UTF-8, sorry" self:onDecodeError(error_message, text, nil, options.etc) return nil, error_message -- in case the error method doesn't abort, return something sensible end -- -- Finally, go parse it -- local success, value, next_i = pcall(grok_one, self, text, 1, options) if success then local error_message = nil if next_i ~= #text + 1 then -- something's left over after we parsed the first thing.... whitespace is allowed. next_i = skip_whitespace(text, next_i) -- if we have something left over now, it's trailing garbage if next_i ~= #text + 1 then value, error_message = self:onTrailingGarbage(text, next_i, value, options.etc) end end return value, error_message else -- If JSON:onDecodeError() didn't abort out of the pcall, we'll have received -- the error message here as "value", so pass it along as an assert. local error_message = value if self.assert then self.assert(false, error_message) else assert(false, error_message) end -- ...and if we're still here (because the assert didn't throw an error), -- return a nil and throw the error message on as a second arg return nil, error_message end end local function backslash_replacement_function(c) if c == "\n" then return "\\n" elseif c == "\r" then return "\\r" elseif c == "\t" then return "\\t" elseif c == "\b" then return "\\b" elseif c == "\f" then return "\\f" elseif c == '"' then return '\\"' elseif c == '\\' then return '\\\\' elseif c == '/' then return '/' else return string.format("\\u%04x", c:byte()) end end local chars_to_be_escaped_in_JSON_string = '[' .. '"' -- class sub-pattern to match a double quote .. '%\\' -- class sub-pattern to match a backslash .. '/' -- class sub-pattern to match a forwardslash .. '%z' -- class sub-pattern to match a null .. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters .. ']' local LINE_SEPARATOR_as_utf8 = unicode_codepoint_as_utf8(0x2028) local PARAGRAPH_SEPARATOR_as_utf8 = unicode_codepoint_as_utf8(0x2029) local function json_string_literal(value, options) local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function) if options.stringsAreUtf8 then -- -- This feels really ugly to just look into a string for the sequence of bytes that we know to be a particular utf8 character, -- but utf8 was designed purposefully to make this kind of thing possible. Still, feels dirty. -- I'd rather decode the byte stream into a character stream, but it's not technically needed so -- not technically worth it. -- newval = newval:gsub(LINE_SEPARATOR_as_utf8, '\\u2028'):gsub(PARAGRAPH_SEPARATOR_as_utf8,'\\u2029') end return '"' .. newval .. '"' end local function object_or_array(self, T, etc) -- -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON -- object. If there are only numbers, it's a JSON array. -- -- If we'll be converting to a JSON object, we'll want to sort the keys so that the -- end result is deterministic. -- local string_keys = { } local number_keys = { } local number_keys_must_be_strings = false local maximum_number_key for key in pairs(T) do if type(key) == 'string' then table.insert(string_keys, key) elseif type(key) == 'number' then table.insert(number_keys, key) if key <= 0 or key >= math.huge then number_keys_must_be_strings = true elseif not maximum_number_key or key > maximum_number_key then maximum_number_key = key end elseif type(key) == 'boolean' then table.insert(string_keys, tostring(key)) else self:onEncodeError("can't encode table with a key of type " .. type(key), etc) end end if #string_keys == 0 and not number_keys_must_be_strings then -- -- An empty table, or a numeric-only array -- if #number_keys > 0 then return nil, maximum_number_key -- an array elseif tostring(T) == "JSON array" then return nil elseif tostring(T) == "JSON object" then return { } else -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects return nil end end table.sort(string_keys) local map if #number_keys > 0 then -- -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object. -- if self.noKeyConversion then self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc) end -- -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings -- map = { } for key, val in pairs(T) do map[key] = val end table.sort(number_keys) -- -- Throw numeric keys in there as strings -- for _, number_key in ipairs(number_keys) do local string_key = tostring(number_key) if map[string_key] == nil then table.insert(string_keys , string_key) map[string_key] = T[number_key] else 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) end end end return string_keys, nil, map end -- -- Encode -- -- 'options' is nil, or a table with possible keys: -- -- pretty -- If true, return a pretty-printed version. -- -- indent -- A string (usually of spaces) used to indent each nested level. -- -- align_keys -- If true, align all the keys when formatting a table. The result is uglier than one might at first imagine. -- Results are undefined if 'align_keys' is true but 'pretty' is not. -- -- array_newline -- If true, array elements are formatted each to their own line. The default is to all fall inline. -- Results are undefined if 'array_newline' is true but 'pretty' is not. -- -- null -- If this exists with a string value, table elements with this value are output as JSON null. -- -- stringsAreUtf8 -- If true, consider Lua strings not as a sequence of bytes, but as a sequence of UTF-8 characters. -- (Currently, the only practical effect of setting this option is that Unicode LINE and PARAGRAPH -- separators, if found in a string, are encoded with a JSON escape instead of as raw UTF-8. -- The JSON is valid either way, but encoding this way, apparently, allows the resulting JSON -- to also be valid Java.) -- -- local function encode_value(self, value, parents, etc, options, indent, for_key) -- -- keys in a JSON object can never be null, so we don't even consider options.null when converting a key value -- if value == nil or (not for_key and options and options.null and value == options.null) then return 'null' elseif type(value) == 'string' then return json_string_literal(value, options) elseif type(value) == 'number' then if value ~= value then -- -- NaN (Not a Number). -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option. -- return "null" elseif value >= math.huge then -- -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should -- really be a package option. Note: at least with some implementations, positive infinity -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is. -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">=" -- case first. -- return "1e+9999" elseif value <= -math.huge then -- -- Negative infinity. -- JSON has no INF, so we have to fudge the best we can. This should really be a package option. -- return "-1e+9999" else return tostring(value) end elseif type(value) == 'boolean' then return tostring(value) elseif type(value) ~= 'table' then if self.unsupportedTypeEncoder then local user_value, user_error = self:unsupportedTypeEncoder(value, parents, etc, options, indent, for_key) -- If the user's handler returns a string, use that. If it returns nil plus an error message, bail with that. -- If only nil returned, fall through to the default error handler. if type(user_value) == 'string' then return user_value elseif user_value ~= nil then self:onEncodeError("unsupportedTypeEncoder method returned a " .. type(user_value), etc) elseif user_error then self:onEncodeError(tostring(user_error), etc) end end self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc) elseif getmetatable(value) == isNumber then return tostring(value) else -- -- A table to be converted to either a JSON object or array. -- local T = value if type(options) ~= 'table' then options = {} end if type(indent) ~= 'string' then indent = "" end if parents[T] then self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) else parents[T] = true end local result_value local object_keys, maximum_number_key, map = object_or_array(self, T, etc) if maximum_number_key then -- -- An array... -- local key_indent if options.array_newline then key_indent = indent .. tostring(options.indent or "") else key_indent = indent end local ITEMS = { } for i = 1, maximum_number_key do table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, key_indent)) end if options.array_newline then result_value = "[\n" .. key_indent .. table.concat(ITEMS, ",\n" .. key_indent) .. "\n" .. indent .. "]" elseif options.pretty then result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]" else result_value = "[" .. table.concat(ITEMS, ",") .. "]" end elseif object_keys then -- -- An object -- local TT = map or T if options.pretty then local KEYS = { } local max_key_length = 0 for _, key in ipairs(object_keys) do local encoded = encode_value(self, tostring(key), parents, etc, options, indent, true) if options.align_keys then max_key_length = math.max(max_key_length, #encoded) end table.insert(KEYS, encoded) end local key_indent = indent .. tostring(options.indent or "") local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and " " or "") local FORMAT = "%s%s: %s" local COMBINED_PARTS = { } for i, key in ipairs(object_keys) do local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent) table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val)) end result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}" else local PARTS = { } for _, key in ipairs(object_keys) do local encoded_val = encode_value(self, TT[key], parents, etc, options, indent) local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent, true) table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val)) end result_value = "{" .. table.concat(PARTS, ",") .. "}" end else -- -- An empty array/object... we'll treat it as an array, though it should really be an option -- result_value = "[]" end parents[T] = false return result_value end end local function top_level_encode(self, value, etc, options) local val = encode_value(self, value, {}, etc, options) if val == nil then --PRIVATE("may need to revert to the previous public verison if I can't figure out what the guy wanted") return val else return val end end function OBJDEF:encode(value, etc, options) if type(self) ~= 'table' or self.__index ~= OBJDEF then OBJDEF:onEncodeError("JSON:encode must be called in method format", etc) end -- -- If the user didn't pass in a table of decode options, make an empty one. -- if type(options) ~= 'table' then options = {} end return top_level_encode(self, value, etc, options) end function OBJDEF:encode_pretty(value, etc, options) if type(self) ~= 'table' or self.__index ~= OBJDEF then OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc) end -- -- If the user didn't pass in a table of decode options, use the default pretty ones -- if type(options) ~= 'table' then options = default_pretty_options end return top_level_encode(self, value, etc, options) end function OBJDEF.__tostring() return "JSON encode/decode package" end OBJDEF.__index = OBJDEF function OBJDEF:new(args) local new = { } if args then for key, val in pairs(args) do new[key] = val end end return setmetatable(new, OBJDEF) end return OBJDEF:new() -- -- Version history: -- -- 20211016.28 Had forgotten to document the strictParsing option. -- -- 20211015.27 Better handle some edge-case errors [ thank you http://seriot.ch/projects/parsing_json.html ; all tests are now successful ] -- -- Added some semblance of proper UTF8 parsing, and now aborts with an error on ilformatted UTF8. -- -- Added the strictParsing option: -- Aborts with an error on unknown backslash-escape in strings -- Aborts on naked control characters in strings -- Aborts when decode is passed a whitespace-only string -- -- For completeness, when encoding a Lua string into a JSON string, escape a forward slash. -- -- String decoding should be a bit more efficient now. -- -- 20170927.26 Use option.null in decoding as well. Thanks to Max Sindwani for the bump, and sorry to Oliver Hitz -- whose first mention of it four years ago was completely missed by me. -- -- 20170823.25 Added support for JSON:unsupportedTypeEncoder(). -- Thanks to Chronos Phaenon Eosphoros (https://github.com/cpeosphoros) for the idea. -- -- 20170819.24 Added support for boolean keys in tables. -- -- 20170416.23 Added the "array_newline" formatting option suggested by yurenchen (http://www.yurenchen.com/) -- -- 20161128.22 Added: -- JSON:isString() -- JSON:isNumber() -- JSON:decodeIntegerObjectificationLength -- JSON:decodeDecimalObjectificationLength -- -- 20161109.21 Oops, had a small boo-boo in the previous update. -- -- 20161103.20 Used to silently ignore trailing garbage when decoding. Now fails via JSON:onTrailingGarbage() -- http://seriot.ch/parsing_json.php -- -- Built-in error message about "expected comma or ']'" had mistakenly referred to '[' -- -- Updated the built-in error reporting to refer to bytes rather than characters. -- -- The decode() method no longer assumes that error handlers abort. -- -- Made the VERSION string a string instead of a number -- -- 20160916.19 Fixed the isNumber.__index assignment (thanks to Jack Taylor) -- -- 20160730.18 Added JSON:forceString() and JSON:forceNumber() -- -- 20160728.17 Added concatenation to the metatable for JSON:asNumber() -- -- 20160709.16 Could crash if not passed an options table (thanks jarno heikkinen ). -- -- Made JSON:asNumber() a bit more resilient to being passed the results of itself. -- -- 20160526.15 Added the ability to easily encode null values in JSON, via the new "null" encoding option. -- (Thanks to Adam B for bringing up the issue.) -- -- Added some support for very large numbers and precise floats via -- JSON.decodeNumbersAsObjects -- JSON.decodeIntegerStringificationLength -- JSON.decodeDecimalStringificationLength -- -- Added the "stringsAreUtf8" encoding option. (Hat tip to http://lua-users.org/wiki/JsonModules ) -- -- 20141223.14 The encode_pretty() routine produced fine results for small datasets, but isn't really -- appropriate for anything large, so with help from Alex Aulbach I've made the encode routines -- more flexible, and changed the default encode_pretty() to be more generally useful. -- -- Added a third 'options' argument to the encode() and encode_pretty() routines, to control -- how the encoding takes place. -- -- Updated docs to add assert() call to the loadfile() line, just as good practice so that -- if there is a problem loading JSON.lua, the appropriate error message will percolate up. -- -- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string, -- so that the source of the package, and its version number, are visible in compiled copies. -- -- 20140911.12 Minor lua cleanup. -- Fixed internal reference to 'JSON.noKeyConversion' to reference 'self' instead of 'JSON'. -- (Thanks to SmugMug's David Parry for these.) -- -- 20140418.11 JSON nulls embedded within an array were being ignored, such that -- ["1",null,null,null,null,null,"seven"], -- would return -- {1,"seven"} -- It's now fixed to properly return -- {1, nil, nil, nil, nil, nil, "seven"} -- Thanks to "haddock" for catching the error. -- -- 20140116.10 The user's JSON.assert() wasn't always being used. Thanks to "blue" for the heads up. -- -- 20131118.9 Update for Lua 5.3... it seems that tostring(2/1) produces "2.0" instead of "2", -- and this caused some problems. -- -- 20131031.8 Unified the code for encode() and encode_pretty(); they had been stupidly separate, -- and had of course diverged (encode_pretty didn't get the fixes that encode got, so -- sometimes produced incorrect results; thanks to Mattie for the heads up). -- -- Handle encoding tables with non-positive numeric keys (unlikely, but possible). -- -- If a table has both numeric and string keys, or its numeric keys are inappropriate -- (such as being non-positive or infinite), the numeric keys are turned into -- string keys appropriate for a JSON object. So, as before, -- JSON:encode({ "one", "two", "three" }) -- produces the array -- ["one","two","three"] -- but now something with mixed key types like -- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) -- instead of throwing an error produces an object: -- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} -- -- To maintain the prior throw-an-error semantics, set -- JSON.noKeyConversion = true -- -- 20131004.7 Release under a Creative Commons CC-BY license, which I should have done from day one, sorry. -- -- 20130120.6 Comment update: added a link to the specific page on my blog where this code can -- be found, so that folks who come across the code outside of my blog can find updates -- more easily. -- -- 20111207.5 Added support for the 'etc' arguments, for better error reporting. -- -- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent. -- -- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules: -- -- * When encoding lua for JSON, Sparse numeric arrays are now handled by -- spitting out full arrays, such that -- JSON:encode({"one", "two", [10] = "ten"}) -- returns -- ["one","two",null,null,null,null,null,null,null,"ten"] -- -- In 20100810.2 and earlier, only up to the first non-null value would have been retained. -- -- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999". -- Version 20100810.2 and earlier created invalid JSON in both cases. -- -- * Unicode surrogate pairs are now detected when decoding JSON. -- -- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding -- -- 20100731.1 initial public release -- ================================================ FILE: src/usr/share/calla/desktop/rc.lua ================================================ --[[ -- TODO -- -- Why is text content changed when its color is changed? -- markdown? needs to be bg container? -- -- Features: -- Alt+tab menu -- Desktop menu -- -- Settings: -- Completely redesign settings app -- Add autostart commands to settings -- Add modkey/sessionlock to settings -- Add profile picture to settings -- Create Gtk theme from color json -- Import theme (Xresources?) -- -- Design: -- Redesign preview to look more like macos (expose) -- Hover cursor on titlebar buttons -- Hover background? -- -- Refactor: -- Is it finally time to make a helpers file...? -- Make battery use upower -- Move custom themes to cache -- Get rid of color dir -- Differentiate custom themes from default -- -- Long Term: -- Better multihead support --]] --[[ -- Known Bugs -- -- Multihead: -- Location of lockscreen promptbox depends on focused screen at startup, -- doesn't appear if laptop screen focused -- Systray opens/closes for both screens, one is redundant -- -- General: -- Preview does not entirely reload if visible -- Desktop get grid function does not account for spacing -- Icon theme is not refreshed with live reload -- fprintd-verify does not work after suspend (issue #173) -- -- Unknown Bugs: -- Many --]] local awful = require("awful") local gears = require("gears") local naughty = require("naughty") -- Errors naughty.connect_signal("request::display_error", function(message, startup) naughty.notification { urgency = "critical", title = "Error"..(startup and " during startup!" or "!"), message = message } end) -- Json function readjson(path) local r = assert(io.open(path, "r")) local table = r:read("*all") r:close() table = require("json"):decode(table) return table end function writejson(path, table) local w = assert(io.open(path, "w")) w:write(require("json"):encode_pretty(table, nil, { pretty = true, indent = " ", align_keys = false, array_newline = true})) w:close() end local config = gears.filesystem.get_cache_dir() .. "user.json" local defaults = { batt = "BAT0", color = "light", font = "Roboto Medium 11", fontalt = "Roboto Bold 11", mod = "Mod4", passwd = "awesomewm", reboot = "systemctl reboot", sessionlock = false, shotdir = "~/Pictures/Screenshots", shutdown = "systemctl poweroff", terminal = "st" } if not gears.filesystem.file_readable(config) then writejson(config, defaults) end user = readjson(config) -- Config require("awful.autofocus") require("signal") require("config") require("theme") require("color.desktop") -- Startup local autostart = { "picom -b --config '/usr/share/calla/compositor.conf'", "xsettingsd --config '/usr/share/calla/xsettingsd'", "nm-applet", "/usr/lib/policykit-1-gnome/polkit-gnome-authentication-agent-1" } local function restarted() awesome.register_xproperty("restarted", "boolean") local detected = awesome.get_xproperty("restarted") ~= nil awesome.set_xproperty("restarted", true) return detected end if not restarted() then for _, command in ipairs(autostart) do awful.spawn.easy_async({ 'pkill', '--full', '--uid', os.getenv('USER'), '^' .. command }, function() awful.spawn.easy_async_with_shell(command, function() end) -- func needed to avoid callback error end) end if user.sessionlock then awesome.emit_signal("widget::lockscreen") end end -- Theme Init awesome.emit_signal("live::reload") ================================================ FILE: src/usr/share/calla/desktop/signal/brightness.lua ================================================ local awful = require("awful") local function emit() awful.spawn.easy_async_with_shell("brightnessctl -m | awk -F, '{print substr($4, 0, length($4)-1)}'", function(out) local brightness = math.floor(tonumber(out)) awesome.emit_signal("signal::brightness", brightness) end) end emit() local subscribe = [[ bash -c "while (inotifywait -e modify /sys/class/backlight/?*/brightness -qq) do echo; done" ]] awful.spawn.easy_async_with_shell("ps x | grep \"inotifywait -e modify /sys/class/backlight\" | grep -v grep | awk '{print $1}' | xargs kill", function () awful.spawn.with_line_callback(subscribe, { stdout = function() emit() end }) end) ================================================ FILE: src/usr/share/calla/desktop/signal/desktop.lua ================================================ local awful = require("awful") local emit = function(type) awesome.emit_signal("signal::desktop", type) end emit() local addsubscribe = [[ bash -c " while inotifywait -e create -e moved_to $HOME/Desktop/ -qq; do echo; done "]] local removesubscribe = [[ bash -c " while inotifywait -e delete -e moved_from $HOME/Desktop/ -qq; do echo; done "]] awful.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 () awful.spawn.with_line_callback(addsubscribe, { stdout = function() emit("add") end }) end) awful.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 () awful.spawn.with_line_callback(removesubscribe, { stdout = function() emit("remove") end }) end) ================================================ FILE: src/usr/share/calla/desktop/signal/init.lua ================================================ require("signal.volume") require("signal.brightness") require("signal.playerctl") require("signal.desktop") ================================================ FILE: src/usr/share/calla/desktop/signal/playerctl.lua ================================================ local awful = require("awful") local function emit() awful.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) local title, album, artist, status title = out:match("title_(.*)album_") or "Not Playing" album = out:match("album_(.*)artist_") or "No Album" artist = out:match("artist_(.*)cover_") or "No Artist" cover = out:match("cover_file://(.*)elapsed_") or "None" elapsed = out:match("elapsed_(.*)total_") or "0:00" total = out:match("total_(.-)\n") or "0:00" if out:match("Playing") then status = true else status = false end awesome.emit_signal('signal::playerctl', title, album, artist, cover, elapsed, total, status) end) end emit() local 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" ]] awful.spawn.easy_async({ 'pkill', '--full', '--uid', os.getenv('USER'), '^playerctl' }, function() awful.spawn.with_line_callback(subscribe, { stdout = function() emit() end }) end) ================================================ FILE: src/usr/share/calla/desktop/signal/volume.lua ================================================ local awful = require("awful") local volume_old = -1 local muted_old = -1 local function emit() awful.spawn.easy_async_with_shell("wpctl get-volume @DEFAULT_AUDIO_SINK@", function(out) local volume = tonumber(string.match(out:match('(%d%.%d+)')*100, '(%d+)')) local muted = out:match('MUTED') if volume ~= volume_old or muted ~= muted_old then awesome.emit_signal('signal::volume', volume, muted) volume_old = volume muted_old = muted end end) end emit() local subscribe = [[ bash -c "LANG=C pactl subscribe 2> /dev/null | grep --line-buffered \"Event 'change' on sink\"" ]] awful.spawn.easy_async({ 'pkill', '--full', '--uid', os.getenv('USER'), '^pactl subscribe' }, function() awful.spawn.with_line_callback(subscribe, { stdout = function() emit() end }) end) ================================================ FILE: src/usr/share/calla/desktop/theme/brightness.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local brightnessbox = wibox { width = dpi(200), height = dpi(85), ontop = true, visible = false } local percent = wibox.widget.textbox() local header = wibox.widget { { { { { valign = "center", widget = wibox.widget.textbox("Brightness") }, nil, percent, layout = wibox.layout.align.horizontal, }, top = dpi(5), bottom = dpi(5), left = dpi(10), right = dpi(10), widget = wibox.container.margin }, widget = background({ bg = "bgmid" }) }, margins = dpi(5), widget = wibox.container.margin } local icon = iconbox({ image = "brightness0" }) local bar = wibox.widget { shape = gears.shape.rounded_rect, bar_shape = gears.shape.rounded_rect, max_value = 100, value = 0, widget = live(wibox.widget.progressbar, { background_color = "bgmid", color = "fg" }) } local timer = gears.timer { timeout = 2, single_shot = true, callback = function() brightnessbox.visible = false end } brightnessbox:setup { { header, { { { icon, right = dpi(15), widget = wibox.container.margin }, nil, { bar, top = dpi(20), bottom = dpi(20), widget = wibox.container.margin }, layout = wibox.layout.align.horizontal }, left = dpi(15), right = dpi(15), widget = wibox.container.margin }, layout = wibox.layout.align.vertical }, widget = live(wibox.container.background, { bg = "bg", fg = "fg" }) } awesome.connect_signal("signal::brightness", function(brightness) percent.text = tostring(brightness) .. "%" bar.value = brightness if brightness >= 75 then icon.image = createicon("brightness100") elseif brightness >= 50 then icon.image = createicon("brightness75") elseif brightness >= 25 then icon.image = createicon("brightness50") elseif brightness > 0 then icon.image = createicon("brightness25") elseif brightness == 0 then icon.image = createicon("brightness0") end end) awesome.connect_signal("widget::brightness", function() awesome.emit_signal("widget::volume:hide") timer:again() if client.focus and client.focus.fullscreen == true then awful.placement.bottom( brightnessbox, { margins = { bottom = dpi(10) }, parent = awful.screen.focused() } ) else awful.placement.bottom( brightnessbox, { margins = { bottom = dpi(60) }, parent = awful.screen.focused() } ) end brightnessbox.visible = true end) awesome.connect_signal("widget::brightness:hide", function() brightnessbox.visible = false end) ================================================ FILE: src/usr/share/calla/desktop/theme/control/calendar.lua ================================================ local wibox = require("wibox") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local function decorate(widget, flag, date) local retbg if flag == "focus" then retbg = beautiful.fg .. "33" else retbg = beautiful.bgmid end local ret = wibox.widget { { widget, halign = "center", valign = "center", fill_horizontal = true, fill_vertical = true, widget = wibox.container.place }, bg = retbg, widget = background({ fg = "fg" }) } awesome.connect_signal("live::reload", function() if flag == "focus" then retbg = beautiful.fg .. "33" else retbg = beautiful.bgmid end ret.bg = retbg end) return ret end local calendar = wibox.widget { { { font = user.font, start_sunday = true, flex_height = true, fn_embed = decorate, widget = wibox.widget.calendar.month(os.date("*t")) }, margins = dpi(10), widget = wibox.container.margin }, forced_width = dpi(220), forced_height = dpi(220), widget = background({ bg = "bgmid", fg = "fg" }) } return calendar ================================================ FILE: src/usr/share/calla/desktop/theme/control/init.lua ================================================ local awful = require("awful") local wibox = require("wibox") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local media = require("theme.control.media") local sliders = require("theme.control.sliders") local toggles = require("theme.control.toggles") local system = require("theme.control.system") local profile = require("theme.control.profile") local calendar = require("theme.control.calendar") local notifs = require("theme.control.notifs") local controlbox = wibox { width = dpi(470), height = dpi(360), ontop = true, visible = false, widget = { { { media, { sliders, toggles, system, spacing = dpi(10), layout = wibox.layout.fixed.vertical }, spacing = dpi(10), layout = wibox.layout.fixed.horizontal }, margins = dpi(10), widget = wibox.container.margin }, widget = background({ bg = "bg" }) } } local infobox = wibox { width = dpi(470), height = dpi(360), ontop = true, visible = false, widget = { { { { profile, calendar, spacing = dpi(10), layout = wibox.layout.fixed.vertical }, notifs, spacing = dpi(10), layout = wibox.layout.fixed.horizontal }, margins = dpi(10), widget = wibox.container.margin }, widget = background({ bg = "bg" }) } } awesome.connect_signal("widget::control", function() controlbox.visible = not controlbox.visible infobox.visible = not infobox.visible if client.focus and client.focus.fullscreen == true then awful.placement.bottom_right( controlbox, { margins = { bottom = dpi(10), right = dpi(10) }, parent = awful.screen.focused() } ) awful.placement.bottom_right( infobox, { margins = { bottom = dpi(380), right = dpi(10) }, parent = awful.screen.focused() } ) else awful.placement.bottom_right( controlbox, { margins = { bottom = dpi(60), right = dpi(20) }, parent = awful.screen.focused() } ) awful.placement.bottom_right( infobox, { margins = { bottom = dpi(430), right = dpi(20) }, parent = awful.screen.focused() } ) end end) ================================================ FILE: src/usr/share/calla/desktop/theme/control/media.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local function mediabutton(args) return hovercursor(wibox.widget { buttons = { awful.button({}, 1, args.run) }, widget = iconbox({ image = args.icon, size = dpi(24) }) }) end local media = wibox.widget { { { { { { id = "cover", upscale = false, downscale = true, valign = "center", halign = "center", clip_shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, dpi(5)) end, widget = wibox.widget.imagebox }, widget = background({ bg = "bgalt", fg = "fg" }) }, width = dpi(200), height = dpi(200), strategy = "exact", widget = wibox.container.constraint }, nil, { { { { { id = "title", align = "center", widget = wibox.widget.textbox("Not Playing") }, height = dpi(20), widget = wibox.container.constraint }, { { id = "artist", align = "center", widget = wibox.widget.textbox("No Artist") }, height = dpi(20), widget = wibox.container.constraint }, spacing = dpi(5), layout = wibox.layout.fixed.vertical }, align = "center", widget = wibox.container.place }, { { { id = "elapsed", widget = wibox.widget.textbox("0:00") }, right = dpi(10), widget = wibox.container.margin }, { { id = "progress", max_value = 0, value = 0, bar_shape = gears.shape.rounded_rect, shape = gears.shape.rounded_rect, forced_height = dpi(5), expand = true, widget = live(wibox.widget.progressbar, { color = "fg", background_color = "bgalt" }) }, forced_height = dpi(10), widget = wibox.container.place }, { { id = "total", widget = wibox.widget.textbox("0:00") }, left = dpi(10), widget = wibox.container.margin }, layout = wibox.layout.align.horizontal }, { { mediabutton({ icon = "previous", run = function() awful.spawn.with_shell("playerctl previous") end }), { id = "pause", widget = mediabutton({ icon = "play", run = function() awful.spawn.with_shell("playerctl play-pause") end }) }, mediabutton({ icon = "next", run = function() awful.spawn.with_shell("playerctl next") end }), spacing = dpi(5), layout = wibox.layout.fixed.horizontal }, align = "center", widget = wibox.container.place }, spacing = dpi(10), layout = wibox.layout.fixed.vertical }, layout = wibox.layout.align.vertical }, margins = dpi(10), widget = wibox.container.margin }, forced_width = dpi(220), forced_height = dpi(300), widget = background({ bg = "bgmid", fg = "fg" }) } awesome.connect_signal("signal::playerctl", function(title, album, artist, cover, elapsed, total, status) media:get_children_by_id("title")[1].text = title media:get_children_by_id("artist")[1].text = artist local coverart = cover if cover == "None" then coverart = beautiful.calla end media:get_children_by_id("cover")[1].image = coverart if total ~= "" then media:get_children_by_id("elapsed")[1].text = elapsed media:get_children_by_id("total")[1].text = total local elapsedseconds = tonumber(elapsed:match("(.*):"))*60 + tonumber(elapsed:match(":(.*)")) local totalseconds = tonumber(total:match("(.*):"))*60 + tonumber(total:match(":(.*)")) media:get_children_by_id("progress")[1].value = elapsedseconds media:get_children_by_id("progress")[1].max_value = totalseconds end if status then media:get_children_by_id("pause")[1].image = createicon("pause") else media:get_children_by_id("pause")[1].image = createicon("play") end awesome.connect_signal("live::reload", function() if cover == "None" then coverart = beautiful.calla end media:get_children_by_id("cover")[1].image = coverart end) end) return media ================================================ FILE: src/usr/share/calla/desktop/theme/control/notifs.lua ================================================ local awful = require("awful") local wibox = require("wibox") local naughty = require("naughty") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local notifcontainer = wibox.widget { spacing = dpi(10), layout = wibox.layout.fixed.vertical } local notifempty = wibox.widget { wibox.widget.textbox("No Notifications"), fill_vertical = true, align = "center", layout = wibox.container.place } local function notifbutton(widget) return hovercursor(wibox.widget { { iconbox({ image = widget.image }), margins = dpi(5), widget = wibox.container.margin }, widget = background({ bg = "bgalt", fg = "fg" }) }) end local function removenotif(notif) notifcontainer:remove_widgets(notif) if #notifcontainer.children == 0 then notifempty.visible = true end end local function createnotif(n) local notif = wibox.widget { { { { { { { wibox.widget.textbox(n.title), width = dpi(140), widget = wibox.container.constraint }, nil, { { id = "remove", widget = hovercursor(wibox.widget { { iconbox({ image = "close" }), margins = dpi(5), widget = wibox.container.margin }, widget = background({ bg = "bgmid", fg = "fg" }) })}, valign = "top", widget = wibox.container.place }, layout = wibox.layout.align.horizontal }, left = dpi(10), right = dpi(5), top = dpi(5), bottom = dpi(5), widget = wibox.container.margin }, widget = background({ bg = "bgmid", fg = "fg" }) }, { wibox.widget.textbox(n.message:gsub("'", "\'")), margins = dpi(5), widget = wibox.container.margin }, spacing = dpi(5), layout = wibox.layout.fixed.vertical }, margins = dpi(10), widget = wibox.container.margin }, widget = background({ bg = "bgalt", fg = "fg" }) } notif:get_children_by_id("remove")[1].buttons = { awful.button({}, 1, function() removenotif(notif) end) } return notif end local notifs = wibox.widget { { { { { { id = "pageup", widget = notifbutton({ image = "up" }) }, { id = "pagedown", widget = notifbutton({ image = "down" }) }, spacing = dpi(10), layout = wibox.layout.fixed.horizontal }, nil, { id = "clear", widget = notifbutton({ image = "close" }) }, layout = wibox.layout.align.horizontal }, { notifempty, notifcontainer, layout = wibox.layout.stack }, spacing = dpi(10), layout = wibox.layout.fixed.vertical }, margins = dpi(10), widget = wibox.container.margin }, forced_width = dpi(220), forced_height = dpi(340), widget = background({ bg = "bgmid", fg = "fg" }) } local notifposition = 1 local function notifmove(direction) if #notifcontainer.children > 0 then if direction == "down" then if notifposition < #notifcontainer.children then notifcontainer.children[notifposition].visible = false notifposition = notifposition + 1 end elseif direction == "up" then if notifposition > 1 then notifposition = notifposition - 1 notifcontainer.children[notifposition].visible = true end end end end notifs:get_children_by_id("pageup")[1].buttons = { awful.button({}, 1, function() notifmove("up") end) } notifs:get_children_by_id("pagedown")[1].buttons = { awful.button({}, 1, function() notifmove("down") end) } notifs:get_children_by_id("clear")[1].buttons = { awful.button({}, 1, function() notifcontainer:reset() notifempty.visible = true notifposition = 1 end) } local activelen = 0 naughty.connect_signal("property::active", function() local notiflen = #naughty.active if notiflen > activelen then notifcontainer:insert(1, createnotif(naughty.active[notiflen])) notifempty.visible = false end activelen = notiflen end) return notifs ================================================ FILE: src/usr/share/calla/desktop/theme/control/profile.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local margins = dpi(0) if beautiful.pfp == beautiful.calla then margins = dpi(10) end local profile = wibox.widget { { { { { { image = beautiful.pfp, upscale = false, downscale = true, valign = "center", halign = "center", clip_shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, dpi(5)) end, widget = live(wibox.widget.imagebox, { image = "pfp" }) }, margins = margins, widget = wibox.container.margin }, forced_width = dpi(90), widget = background({ bg = "bgalt" }) }, { { { id = "name", text = "User Name", font = user.font:gsub("%d+", "14"), widget = wibox.widget.textbox }, { id = "host", text = "@calla", widget = wibox.widget.textbox }, spacing = dpi(5), layout = wibox.layout.fixed.vertical }, valign = "center", widget = wibox.container.place }, spacing = dpi(10), layout = wibox.layout.fixed.horizontal }, margins = dpi(10), widget = wibox.container.margin }, forced_width = dpi(220), forced_height = dpi(110), widget = background({ bg = "bgmid", fg = "fg" }) } awful.spawn.easy_async_with_shell("getent passwd $(whoami) | cut -d ':' -f 5", function(out) profile:get_children_by_id("name")[1].text = out:gsub(",", ""):gsub("\n", "") end) awful.spawn.easy_async_with_shell("hostname", function(out) profile:get_children_by_id("host")[1].text = "@" .. out:gsub("\n", "") end) return profile ================================================ FILE: src/usr/share/calla/desktop/theme/control/sliders.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local function slider() return wibox.widget { bar_shape = gears.shape.rounded_rect, bar_height = dpi(5), bar_color = beautiful.bgalt, bar_active_color = beautiful.fg, handle_color = beautiful.fg, handle_shape = gears.shape.rounded_rect, handle_width = dpi(10), forced_height = dpi(10), value = 0, maximum = 100, widget = live(wibox.widget.slider, { bar_color = "bgalt", bar_active_color = "fg", handle_color = "fg" }) } end local volumeslider = slider() local volumepercent = wibox.widget.textbox() local volumeicon = iconbox({ image = "volumemute" }) local volumehovering = false local volumestore = 0 local mutestore = false local function editvolume(volume, mute) if volume ~= nil then volumestore = volume mutestore = mute else volume = volumestore mute = mutestore end if not volumehovering then volumeslider.value = volume end if mute then volumepercent.text = "Muted" volumeicon.image = createicon("volumemute") else volumepercent.text = tostring(volume) .. "%" if volume > 100 then volumeicon.image = createicon("volumewarn") elseif volume >= 50 then volumeicon.image = createicon("volume100") elseif volume >= 25 then volumeicon.image = createicon("volume50") elseif volume > 0 then volumeicon.image = createicon("volume25") elseif volume == 0 then volumeicon.image = createicon("volume0") end end end awesome.connect_signal("signal::volume", function(volume, mute) editvolume(volume, mute) end) awesome.connect_signal("live::reload", function() editvolume() end) -- Very hacky workaround, vol will only be changed with signal when mouse is outside of slider volumeslider:connect_signal("mouse::enter", function() volumehovering = true end) volumeslider:connect_signal("mouse::leave", function() volumehovering = false end) volumeslider:connect_signal("property::value", function(_, new) if volumehovering then awful.spawn.with_shell("wpctl set-mute @DEFAULT_AUDIO_SINK@ 0") awful.spawn.with_shell("wpctl set-volume @DEFAULT_AUDIO_SINK@ " .. new .. "%") end end) local volume = wibox.widget { { { { wibox.widget.textbox("Volume"), nil, volumepercent, layout = wibox.layout.align.horizontal }, nil, { volumeicon, { volumeslider, forced_height = dpi(10), widget = wibox.container.place }, spacing = dpi(10), layout = wibox.layout.fixed.horizontal }, layout = wibox.layout.align.vertical }, margins = dpi(10), widget = wibox.container.margin }, forced_height = dpi(65), widget = background({ bg = "bgmid", fg = "fg" }) } local brightnessslider = slider() local brightnesspercent = wibox.widget.textbox() local brightnessicon = iconbox({ image = "brightness0" }) local brightnesshovering = false local brightnessstore = 0 local function editbrightness(brightness) if brightness ~= nil then brightnessstore = brightness else brightness = brightnessstore end if not brightnesshovering then brightnessslider.value = brightness end brightnesspercent.text = tostring(brightness) .. "%" if brightness >= 75 then brightnessicon.image = createicon("brightness100") elseif brightness >= 50 then brightnessicon.image = createicon("brightness75") elseif brightness >= 25 then brightnessicon.image = createicon("brightness50") elseif brightness > 0 then brightnessicon.image = createicon("brightness25") elseif brightness == 0 then brightnessicon.image = createicon("brightness0") end end awesome.connect_signal("signal::brightness", function(brightness) editbrightness(brightness) end) awesome.connect_signal("live::reload", function() editbrightness() end) brightnessslider:connect_signal("mouse::enter", function() brightnesshovering = true end) brightnessslider:connect_signal("mouse::leave", function() brightnesshovering = false end) brightnessslider:connect_signal("property::value", function(_, new) if brightnesshovering then awful.spawn.with_shell("brightnessctl s " .. new .. "%") end end) local brightness = wibox.widget { { { { wibox.widget.textbox("Brightness"), nil, brightnesspercent, layout = wibox.layout.align.horizontal }, nil, { brightnessicon, { brightnessslider, forced_height = dpi(10), widget = wibox.container.place }, spacing = dpi(10), layout = wibox.layout.fixed.horizontal }, layout = wibox.layout.align.vertical }, margins = dpi(10), widget = wibox.container.margin }, forced_height = dpi(65), widget = background({ bg = "bgmid", fg = "fg" }) } local sliders = wibox.widget { volume, brightness, spacing = dpi(10), widget = wibox.layout.fixed.vertical } return sliders ================================================ FILE: src/usr/share/calla/desktop/theme/control/system.lua ================================================ local awful = require("awful") local wibox = require("wibox") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local function systembutton(widget) return button({ image = widget.icon, height = dpi(30), width = dpi(32), run = widget.run }) end local system = wibox.widget { { { systembutton({ icon = "settings", run = function() awesome.emit_signal("widget::control") awesome.emit_signal("widget::config") end }), systembutton({ icon = "shutdown", run = function() awful.spawn.with_shell(user.shutdown) end }), systembutton({ icon = "restart", run = function() awful.spawn.with_shell(user.reboot) end }), systembutton({ icon = "exit", run = function() awesome.quit() end }), systembutton({ icon = "lock", run = function() awesome.emit_signal("widget::control") awesome.emit_signal("widget::lockscreen") end }), spacing = dpi(10), layout = wibox.layout.fixed.horizontal }, margins = dpi(10), widget = wibox.container.margin }, forced_width = dpi(220), forced_height = dpi(50), widget = background({ bg = "bgmid", fg = "fg" }) } return system ================================================ FILE: src/usr/share/calla/desktop/theme/control/toggles.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local naughty = require("naughty") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local function togglebutton(widget) local switch = function(param, cases) local case = cases[param] if case then return case() end end local onicon, officon local icon = iconbox({ image = "error" }) local state local iscompositor, compositorinit if widget == "compositor" then iscompositor, compositorinit = true, false end local button = hovercursor(wibox.widget { { icon, margins = dpi(5), widget = wibox.container.margin }, forced_width = dpi(95), forced_height = dpi(50), widget = background({ bg = "bgalt", fg = "fg" }) }) local function init() switch(widget, { ["wifi"] = function() onicon, officon = "wifion", "wifioff" end, ["bluetooth"] = function() onicon, officon = "bluetoothon", "bluetoothoff" end, ["compositor"] = function() onicon, officon = "compositoron", "compositoroff" end, ["notifs"] = function() onicon, officon = "notificationson", "notificationsoff" end }) end local function getstate() local function finish() if state then icon.image = createicon(onicon) button.bg = beautiful.fg .. 33 else icon.image = createicon(officon) button.bg = beautiful.bgalt end end switch(widget, { ["wifi"] = function() state = awful.spawn.easy_async_with_shell("nmcli radio wifi", function(out) if out:match("enabled") then state = true else state = false end finish() end) end, ["bluetooth"] = function() state = awful.spawn.easy_async_with_shell("bluetoothctl show | grep 'Powered: yes'", function(out) if out ~= "" then state = true else state = false end finish() end) end, ["compositor"] = function() awful.spawn.easy_async_with_shell("ps aux | grep picom | grep -v grep", function(out) if not compositorinit then awesome.emit_signal("compositor::init") compositorinit = true end if out ~= "" then state = true else state = false end finish() end) end, ["notifs"] = function() state = not naughty.suspended finish() end }) end local function changestate() switch(widget, { ["wifi"] = function() local command if state then command = "off" else command = "on" end awful.spawn.easy_async_with_shell("nmcli radio wifi " .. command, function() getstate() end) end, ["bluetooth"] = function() local command if state then command = "off" else command = "on" end awful.spawn.easy_async_with_shell("bluetoothctl power " .. command, function() getstate() end) end, ["compositor"] = function() if state then awful.spawn.easy_async_with_shell("killall picom", function() getstate() end) else awful.spawn.easy_async_with_shell("picom -b --config '/usr/share/calla/compositor.conf'", function() getstate() end) end end, ["notifs"] = function() naughty.suspended = not naughty.suspended getstate() end }) end if iscompositor then awesome.connect_signal("compositor::init", function() init() getstate() button.buttons = { awful.button({}, 1, changestate) } end) else init() getstate() button.buttons = { awful.button({}, 1, changestate) } end awesome.connect_signal("live::reload", function() getstate() end) return button end local toggles = wibox.widget { { { togglebutton("wifi"), togglebutton("bluetooth"), togglebutton("compositor"), togglebutton("notifs"), column_count = 2, spacing = dpi(10), layout = wibox.layout.grid }, margins = dpi(10), widget = wibox.container.margin }, forced_width = dpi(220), forced_height = dpi(130), widget = background({ bg = "bgmid", fg = "fg" }) } return toggles ================================================ FILE: src/usr/share/calla/desktop/theme/desktop.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local icons = "/usr/share/icons/" .. beautiful.icons .. "/64x64/" local desktopjson = gears.filesystem.get_cache_dir() .. "desktop.json" local lgi = require("lgi") local Gtk = lgi.require("Gtk", "3.0") local Gio = lgi.Gio local UPower = lgi.require("UPowerGlib") screen.connect_signal("request::desktop_decoration", function(s) --[[ logic for grabbing batteries, for use in future desktop widgets local function listdevices() local ret = {} local devices = UPower.Client():get_devices() for _, device in ipairs(devices) do table.insert(ret, device:get_object_path()) end return ret end local function getdevice(path) local devices = UPower.Client():get_devices() for _, device in ipairs(devices) do if device:get_object_path() == path then return device end end return nil end local devicepaths = listdevices() for _, path in ipairs(devicepaths) do local device = getdevice(path) device.on_notify = function() end end --]] local cell = dpi(120) local geometry = s:get_bounding_geometry() local rows = math.floor((geometry.height-dpi(60))/cell) local cols = math.floor((geometry.width-dpi(20))/cell) local vspacing = math.floor((((geometry.height-dpi(60))-(rows*cell))/(rows-1))+0.5) local hspacing = math.floor((((geometry.width-dpi(20))-(cols*cell))/(cols-1))+0.5) s.grid = wibox.widget { forced_num_rows = rows, forced_num_cols = cols, vertical_spacing = vspacing, horizontal_spacing = hspacing, orientation = "horizontal", layout = wibox.layout.grid } s.manual = wibox.layout { layout = wibox.layout.manual } s.padding = { top = dpi(10), bottom = dpi(50), left = dpi(10), right = dpi(10) } s.base = wibox { screen = s, ontop = false, visible = true, type = "splash" } s.desktop = wibox { screen = s, width = s.geometry.width-dpi(20), height = s.geometry.height-dpi(60), ontop = false, visible = true, type = "desktop" } awful.placement.maximize(s.base) awful.placement.top( s.desktop, { margins = { top = dpi(10) } } ) s.panel = require("theme.panel")(s) s.base:setup { { s.panel, valign = "bottom", content_fill_horizontal = true, widget = wibox.container.place }, widget = live(wibox.widget.background, { bg = "bg" }) } s.desktop:setup { { id = "wallpaper", image = gears.surface.crop_surface { surface = gears.surface.load_uncached(beautiful.wallpaper), ratio = (s.geometry.width-dpi(20))/(s.geometry.height-dpi(60)) }, widget = wibox.widget.imagebox }, s.grid, s.manual, layout = wibox.layout.stack } local function generate() local entries = {} for entry in io.popen([[ls ~/Desktop | sed '']]):lines() do local label = nil local exec = nil local icon = nil if entry:match("^.+(%..+)$") == ".desktop" then for line in io.popen("cat ~/Desktop/'" .. entry .. "'"):lines() do if line:match("Name=") and not label then label = line:gsub("Name=", "") end if line:match("Exec=") and not exec then exec = line:gsub("Exec=", ""):gsub("%%U", ""):gsub("%%u", "") end if line:match("CustomIcon=") and not icon then icon = line:gsub("CustomIcon=", "") elseif line:match("Icon=") and not icon then icon = line:gsub("Icon=", "") end end table.insert(entries, { label = label, exec = exec, icon = icon }) elseif os.execute("cd ~/Desktop'" .. entry .. "'") then label = entry icon = "folder" exec = "gio open ~/Desktop/'" .. entry .. "'" table.insert(entries, { label = label, exec = exec, icon = icon }) else label = entry icon = Gio.File.new_for_path(os.getenv("HOME") .. "/Desktop/" .. entry):query_info("standard::*", Gio.FileQueryInfoFlags.NONE):get_icon() for _, name in ipairs(icon:get_names()) do if Gtk.IconTheme.get_default():has_icon(name) then icon = name break end icon = "application-x-generic" end exec = "gio open ~/Desktop/'" .. entry .. "'" table.insert(entries, { label = label, exec = exec, icon = icon }) end end return entries end local function save() layout = {} for i, widget in ipairs(s.grid.children) do local pos = s.grid:get_widget_position(widget) layout[i] = { row = pos.row, col = pos.col, widget = { label = widget.label, exec = widget.exec, icon = widget.icon } } end writejson(desktopjson, layout) end local function gridindexat(y, x) -- gotta fix this to account for spacing local margin = dpi(10) local row = math.ceil((y - margin) / cell) row = math.min(row, rows) row = math.max(row, 1) local col = math.ceil((x - margin) / cell) col = math.min(col, cols) col = math.max(col, 1) return row, col end local function exists(path) local f = io.open(path, "r") if f~=nil then io.close(f) return true else return false end end local function createdesktopicon(label, exec, icon) local image if exists(icons .. "places/" .. icon .. ".svg") then image = icons .. "places/" .. icon .. ".svg" elseif exists(icons .. "mimetypes/" .. icon .. ".svg") then image = icons .. "mimetypes/" .. icon .. ".svg" elseif exists(icons .. "apps/" .. icon .. ".svg") then image = icons .. "apps/" .. icon .. ".svg" elseif exists(icon) then image = icon else image = icons .. "mimetypes/application-x-generic.svg" end local widget = hovercursor(wibox.widget { { { { image = image, halign = "center", widget = wibox.widget.imagebox }, strategy = "exact", width = dpi(50), height = dpi(50), widget = wibox.container.constraint }, { { { { valign = "top", align = "center", widget = colortext({ text = label }) }, margins = dpi(5), widget = wibox.container.margin }, widget = background({ bg = "bgmid" }) }, strategy = "max", width = dpi(100), height = dpi(50), widget = wibox.container.constraint }, spacing = dpi(5), layout = wibox.layout.fixed.vertical }, label = label, exec = exec, icon = icon, forced_width = cell, forced_height = cell, top = dpi(10), left = dpi(10), right = dpi(10), widget = wibox.container.margin }) widget:connect_signal("button::press", function(_, _, _, button) if not mousegrabber.isrunning() then local heldwidget = wibox.widget { { { { image = image, opacity = 0.5, halign = "center", widget = wibox.widget.imagebox }, strategy = "exact", width = dpi(50), height = dpi(50), widget = wibox.container.constraint }, { { { { opacity = 0.5, valign = "top", align = "center", widget = colortext({ text = label }) }, margins = dpi(5), widget = wibox.container.margin }, widget = background({ bg = "bgmid" }) }, strategy = "max", width = dpi(100), height = dpi(50), widget = wibox.container.constraint }, spacing = dpi(5), layout = wibox.layout.fixed.vertical }, forced_width = cell, forced_height = cell, top = dpi(10), left = dpi(10), right = dpi(10), visible = false, widget = wibox.container.margin } local startpos = mouse.coords() heldwidget.point = { x = startpos.x, y = startpos.y } local oldpos = s.grid:get_widget_position(widget) s.manual:add(heldwidget) mousegrabber.run(function(mouse) if (math.abs(mouse.x - startpos.x) > 10 or math.abs(mouse.y - startpos.y) > 10) and mouse.buttons[1] then s.grid:remove(widget) heldwidget.visible = true s.manual:move_widget(heldwidget, { x = mouse.x - dpi(50), y = mouse.y - dpi(50) }) end if not mouse.buttons[1] then if button == 1 then if heldwidget.visible then heldwidget.visible = false local newrow, newcol = gridindexat( mouse.y, mouse.x ) if not s.grid:get_widgets_at(newrow, newcol) then s.grid:add_widget_at(widget, newrow, newcol) save() else s.grid:add_widget_at(widget, oldpos.row, oldpos.col) end else awful.spawn.with_shell(exec) s.manual:reset() end mousegrabber.stop() end end return mouse.buttons[1] end, "hand2") end end) return widget end local function load() s.grid:reset() if not gears.filesystem.file_readable(desktopjson) then local entries = generate() for _, entry in ipairs(entries) do s.grid:add(createdesktopicon(entry.label, entry.exec, entry.icon)) end save() return end local layout = readjson(desktopjson) for _, entry in ipairs(layout) do s.grid:add_widget_at(createdesktopicon(entry.widget.label, entry.widget.exec, entry.widget.icon), entry.row, entry.col) end end load() awesome.connect_signal("signal::desktop", function(type) local entries = generate() local check = false if type == "add" then for _, entry in ipairs(entries) do for _, widget in ipairs(s.grid.children) do if entry.label == widget.label then check = true break end end if check == false then s.grid:add(createdesktopicon(entry.label, entry.exec, entry.icon)) end check = false end end if type == "remove" then for _, widget in ipairs(s.grid.children) do for _, entry in ipairs(entries) do if entry.label == widget.label then check = true break end end if check == false then s.grid:remove(widget) end check = false end end save() end) awesome.connect_signal("live::reload", function() s.desktop:get_children_by_id("wallpaper")[1].image = gears.surface.crop_surface { surface = gears.surface.load_uncached(beautiful.wallpaper), ratio = (s.geometry.width-dpi(20))/(s.geometry.height-dpi(60)) } end) end) ================================================ FILE: src/usr/share/calla/desktop/theme/dock.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local lgi = require("lgi") local Gtk = lgi.require("Gtk", "3.0") local dockjson = gears.filesystem.get_cache_dir() .. "dock.json" local tasklist local pins = wibox.widget { spacing = dpi(5), layout = wibox.layout.fixed.horizontal } local separator = wibox.widget { orientation = "vertical", thickness = dpi(2), span_ratio = 0.75, forced_width = dpi(5), visible = false, widget = live(wibox.widget.separator, { color = "bgalt" }) } if not gears.filesystem.file_readable(dockjson) then writejson(dockjson, {}) end local pinned = readjson(dockjson) local function pin(class, exec) local theme = Gtk.IconTheme.get_default() local icon = theme:lookup_icon(class:lower(), 64, 0) if icon then icon = icon:get_filename() else icon = require("menubar").utils.lookup_icon_uncached(class:lower()) if not icon then icon = theme:lookup_icon("application-default-icon", 64, 0):get_filename() end end local widget = hovercursor(wibox.widget { { { { shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, dpi(8)) end, id = "background", bg = beautiful.bg, widget = wibox.container.background }, margins = dpi(2), widget = wibox.container.margin }, shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, dpi(10)) end, id = "foreground", bg = beautiful.fg, widget = wibox.container.background }, { wibox.widget.imagebox(icon), margins = dpi(5), widget = wibox.container.margin }, layout = wibox.layout.stack }) local function check() local present = false local focused = false if client.focus and client.focus.class == class then widget:get_children_by_id("background")[1].bg = beautiful.bgalt widget:get_children_by_id("foreground")[1].bg = beautiful.fg .. "64" widget.buttons = { awful.button({}, 1, function() for _, c in ipairs(client.get()) do if c.class == class then c.minimized = false c:raise() end end end), awful.button({ "Shift" }, 1, function() awful.spawn.with_shell(exec) end), awful.button({}, 3, function() for i, app in ipairs(pinned) do if app.class == class then table.remove(pinned, i) end end pins:remove(pins:index(widget)) tasklist._do_tasklist_update_now() writejson(dockjson, pinned) end) } present = true focused = true end if not focused then for _, c in ipairs(client.get()) do if c.class == class then widget:get_children_by_id("background")[1].bg = beautiful.bgalt widget:get_children_by_id("foreground")[1].bg = beautiful.bgalt widget.buttons = { awful.button({}, 1, function() c.first_tag:view_only() -- check current tag first? for _, c in ipairs(client.get()) do if c.class == class then c.minimized = false c:raise() c:activate() end end end), awful.button({ "Shift" }, 1, function() awful.spawn.with_shell(exec) end), awful.button({}, 3, function() for i, app in ipairs(pinned) do if app.class == class then table.remove(pinned, i) end end pins:remove(pins:index(widget)) tasklist._do_tasklist_update_now() writejson(dockjson, pinned) end) } present = true return end end end if not present then widget:get_children_by_id("background")[1].bg = beautiful.bg widget:get_children_by_id("foreground")[1].bg = beautiful.bg widget.buttons = { awful.button({}, 1, function() awful.spawn.with_shell(exec) end), awful.button({}, 3, function() for i, app in ipairs(pinned) do if app.class == class then table.remove(pinned, i) end end pins:remove(pins:index(widget)) tasklist._do_tasklist_update_now() writejson(dockjson, pinned) end) } end widget:emit_signal("widget::redraw_needed") end client.connect_signal("request::manage", check) client.connect_signal("request::unmanage", check) client.connect_signal("focus", check) client.connect_signal("unfocus", check) awesome.connect_signal("live::reload", check) check() return widget end local function contains(table, name) for _, app in ipairs(table) do if app == name then return true end end return false end tasklist = awful.widget.tasklist { screen = awful.screen.focused(), filter = awful.widget.tasklist.filter.allscreen, source = function() local seen = {} local ret = {} for _, c in ipairs(client.get()) do local exclude = false for _, app in ipairs(pinned) do if c.class == app.class then exclude = true break end end if not exclude and not contains(seen, c.class) or c.minimized == true then table.insert(seen, c.class) table.insert(ret, c) end end if seen[1] and pinned[1] then separator.visible = true else separator.visible = false end return ret end, style = { shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, dpi(10)) end }, layout = { spacing = dpi(5), spacing_widget = wibox.container.background, layout = wibox.layout.fixed.horizontal }, widget_template = { { { { awful.widget.clienticon, margins = dpi(5), widget = wibox.container.margin }, shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, dpi(8)) end, id = "background", widget = wibox.widget.background }, margins = dpi(2), widget = wibox.container.margin }, shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, dpi(10)) end, id = "foreground", bg = beautiful.fg, widget = wibox.container.background, create_callback = function(self, c) local exec if c.pid then awful.spawn.easy_async("readlink -f /proc/" .. c.pid .. "/exe", function(out) exec = out:gsub("\n", "") end) end self.buttons = { awful.button({}, 1, function() c.first_tag:view_only() c.minimized = false c:raise() end), awful.button({}, 3, function() local seen for _, app in ipairs(pinned) do if app.class == c.class then seen = true return end end if not seen then pins:add(pin(c.class, exec)) table.insert(pinned, { class = c.class, exec = exec }) tasklist._do_tasklist_update_now() writejson(dockjson, pinned) end end) } hovercursor(self) if client.focus == c then self:get_children_by_id("background")[1].bg = beautiful.bgalt self:get_children_by_id("foreground")[1].bg = beautiful.fg .. "64" else self:get_children_by_id("background")[1].bg = beautiful.bgalt self:get_children_by_id("foreground")[1].bg = beautiful.bgalt end client.connect_signal("focus", function() if client.focus == c then self:get_children_by_id("background")[1].bg = beautiful.bgalt self:get_children_by_id("foreground")[1].bg = beautiful.fg .. "64" else self:get_children_by_id("background")[1].bg = beautiful.bgalt self:get_children_by_id("foreground")[1].bg = beautiful.bgalt end end) client.connect_signal("unfocus", function() self:get_children_by_id("background")[1].bg = beautiful.bgalt self:get_children_by_id("foreground")[1].bg = beautiful.bgalt end) awesome.connect_signal("live::reload", function() if client.focus == c then self:get_children_by_id("background")[1].bg = beautiful.bgalt self:get_children_by_id("foreground")[1].bg = beautiful.fg .. "64" else self:get_children_by_id("background")[1].bg = beautiful.bgalt self:get_children_by_id("foreground")[1].bg = beautiful.bgalt end end) end } } for _, app in ipairs(pinned) do pins:add(pin(app.class, app.exec)) end local dock = wibox.widget { { pins, separator, tasklist, spacing = dpi(5), layout = wibox.layout.fixed.horizontal }, halign = "center", widget = wibox.container.place } return dock ================================================ FILE: src/usr/share/calla/desktop/theme/init.lua ================================================ local beautiful = require("beautiful") local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local dpi = beautiful.xresources.apply_dpi beautiful.init(gears.filesystem.get_configuration_dir() .. "theme/theme.lua") screen.connect_signal("request::desktop_decoration", function(s) awesome.connect_signal("live::reload", function() awful.wallpaper { screen = s, bg = beautiful.bg } end) end) function live(w, properties) local widget = w() for property, arg in pairs(properties) do widget[property] = beautiful[arg] end awesome.connect_signal("live::reload", function() for property, arg in pairs(properties) do widget[property] = beautiful[arg] end widget:emit_signal("widget::redraw_needed") end) return widget end function background(properties) return wibox.widget { shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, dpi(10)) end, widget = live(wibox.container.background, properties) } end function hovercursor(widget) --local oldcursor, oldwibox, oldbg local oldcursor, oldwibox widget:connect_signal("mouse::enter", function() local wb = mouse.current_wibox if wb == nil then return end --oldcursor, oldwibox, oldbg = wb.cursor, wb, wb.bg oldcursor, oldwibox = wb.cursor, wb wb.cursor = "hand2" --widget.bg = beautiful.fg .. "20" end) widget:connect_signal("mouse::leave", function() if oldwibox then oldwibox.cursor = oldcursor --widget.bg = oldbg oldwibox = nil end end) return widget end function markup(args) local fg = beautiful[args.fg] or beautiful.fg local text = '' .. args.text .. '' return text end function colortext(args) local table = args or {} local fg = beautiful[table.fg] or beautiful.fg local font = table.font or user.font local text = table.text or "N/A" local textbox = wibox.widget { markup = markup({ text = text, fg = fg }), font = font, widget = wibox.widget.textbox } awesome.connect_signal("live::reload", function() textbox.markup = markup({ text = text, fg = fg }) end) return textbox end function createicon(image) local icon = gears.color.recolor_image(gears.filesystem.get_configuration_dir() .. "theme/icons/" .. image .. ".svg", beautiful.fg) awesome.connect_signal("live::reload", function() icon = gears.color.recolor_image(gears.filesystem.get_configuration_dir() .. "theme/icons/" .. image .. ".svg", beautiful.fg) end) return icon end function iconbox(widget) local size = dpi(18) if widget.size then size = widget.size end local icon = wibox.widget { image = gears.color.recolor_image(gears.filesystem.get_configuration_dir() .. "theme/icons/" .. widget.image .. ".svg", beautiful.fg), forced_width = size, forced_height = size, upscale = false, downscale = true, valign = "center", halign = "center", widget = wibox.widget.imagebox } awesome.connect_signal("live::reload", function() icon.image = gears.color.recolor_image(gears.filesystem.get_configuration_dir() .. "theme/icons/" .. widget.image .. ".svg", beautiful.fg) end) return icon end function button(widget) local img = iconbox({ image = widget.image }) if widget.size then width = widget.size height = widget.size else if widget.height then height = widget.height else height = dpi(30) end if widget.width then width = widget.width else width = dpi(30) end end local button = hovercursor(wibox.widget { { img, margins = dpi(5), widget = wibox.container.margin }, forced_width = width, forced_height = height, buttons = { awful.button({}, 1, widget.run) }, widget = background({ bg = "bgmid" }) }) return button end require("theme.desktop") require("theme.notif") require("theme.title") require("theme.volume") require("theme.brightness") require("theme.launcher") require("theme.lock") require("theme.settings") require("theme.preview") require("theme.control") ================================================ FILE: src/usr/share/calla/desktop/theme/launcher.lua ================================================ local wibox = require("wibox") local awful = require("awful") local gears = require("gears") local Gio = require("lgi").Gio local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local appicons = "/usr/share/icons/" .. beautiful.icons .. "/64x64/apps/" -- Widgets local launcherbox = wibox { width = dpi(280), height = dpi(340), ontop = true, visible = false } local prompt = colortext({ text = "Search..." }) local textbox = wibox.widget { forced_height = dpi(30), widget = wibox.widget.textbox } local entries = wibox.widget { homogeneous = false, expand = true, forced_num_cols = 1, layout = wibox.layout.grid } launcherbox:setup { { { entries, nil, { { prompt, top = dpi(5), bottom = dpi(5), left = dpi(8), right = dpi(8), widget = wibox.container.margin }, forced_height = dpi(30), widget = background({ bg = "bgmid" }) }, forced_width = dpi(300), layout = wibox.layout.align.vertical }, margins = dpi(10), widget = wibox.container.margin }, widget = live(wibox.container.background, { bg = "bg" }) } -- Functions local function next() if entryindex ~= #filtered then entries:get_widgets_at(entryindex, 1)[1]:get_children_by_id("bg")[1].bg = nil entries:get_widgets_at(entryindex+1, 1)[1]:get_children_by_id("bg")[1].bg = beautiful.bgmid entryindex = entryindex + 1 if entryindex > startindex + 7 then entries:get_widgets_at(entryindex-8, 1)[1].visible = false entries:get_widgets_at(entryindex, 1)[1].visible = true startindex = startindex + 1 end end move = true end local function back() if entryindex ~= 1 then entries:get_widgets_at(entryindex, 1)[1]:get_children_by_id("bg")[1].bg = nil entries:get_widgets_at(entryindex-1, 1)[1]:get_children_by_id("bg")[1].bg = beautiful.bgmid entryindex = entryindex - 1 if entryindex < startindex then entries:get_widgets_at(entryindex+8, 1)[1].visible = false entries:get_widgets_at(entryindex, 1)[1].visible = true startindex = startindex - 1 end end move = true end entries.buttons = { awful.button({}, 4, function() back() end), awful.button({}, 5, function() next() end) } local function gen() local entries = {} for _, entry in ipairs(Gio.AppInfo.get_all()) do if entry:should_show() then local name = entry:get_name():gsub("&", "&"):gsub("<", "<"):gsub("'", "'") local icon = entry:get_icon() if icon then local name = icon:to_string() icon = appicons .. name .. ".svg" local function exists(file) local file=io.open(file, "r") if file ~= nil then io.close(file) return true else return false end end if exists(icon) then icon = appicons .. name .. ".svg" else icon = appicons .. "application-default-icon.svg" end else icon = appicons .. "application-default-icon.svg" end table.insert( entries, { icon = icon, name = name, appinfo = entry } ) end end return entries end local function filter(cmd) filtered = {} regfiltered = {} -- Filter entries for _, entry in ipairs(unfiltered) do if entry.name:lower():sub(1, cmd:len()) == cmd:lower() then table.insert(filtered, entry) elseif entry.name:lower():match(cmd:lower()) then table.insert(regfiltered, entry) end end -- Sort entries table.sort(filtered, function(a, b) return a.name:lower() < b.name:lower() end) table.sort(regfiltered, function(a, b) return a.name:lower() < b.name:lower() end) -- Merge entries for i = 1, #regfiltered do filtered[#filtered+1] = regfiltered[i] end -- Clear entries entries:reset() -- Fix position entryindex, startindex = 1, 1 -- Add filtered entries for i, entry in ipairs(filtered) do local widget = hovercursor(wibox.widget { { { { wibox.widget.imagebox(entry.icon), colortext({ text = entry.name }), spacing = dpi(5), layout = wibox.layout.fixed.horizontal }, forced_height = dpi(30), margins = dpi(5), widget = wibox.container.margin }, buttons = { awful.button({}, 1, function() if entryindex == i then local entry = filtered[entryindex] entry.appinfo:launch() awful.keygrabber.stop() launcherbox.visible = false else entries:get_widgets_at(entryindex, 1)[1]:get_children_by_id("bg")[1].bg = nil entryindex = i entries:get_widgets_at(entryindex, 1)[1]:get_children_by_id("bg")[1].bg = beautiful.bgmid end end) }, id = "bg", shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, dpi(10)) end, widget = wibox.container.background }, bottom = dpi(5), widget = wibox.container.margin }) if startindex <= i and i <= startindex + 7 then widget.visible = true else widget.visible = false end entries:add(widget) if i == entryindex then widget:get_children_by_id("bg")[1].bg = beautiful.bgmid end end collectgarbage("collect") end local function open() -- Reset variables startindex, entryindex, move = 1, 1, false -- Get entries unfiltered = gen() -- TODO: Make this local filter("") -- Prompt awful.prompt.run { textbox = textbox, done_callback = function() launcherbox.visible = false end, changed_callback = function(cmd) if cmd ~= "" then prompt.markup = markup({ text = cmd, fg = "fg" }) else prompt.markup = markup({ text = "Search...", fg = "fg" }) end if move == false then filter(cmd) else move = false end end, exe_callback = function(cmd) prompt.markup = markup({ text = "Search...", fg = "fg" }) local entry = filtered[entryindex] if entry then entry.appinfo:launch() else awful.spawn.with_shell(cmd) end end, keypressed_callback = function(_, key) if key == "Down" then next() elseif key == "Up" then back() end end } end awesome.connect_signal("widget::launcher", function() awesome.emit_signal("widget::preview:hide") prompt.markup = markup({ text = "Search...", fg = "fg" }) launcherbox.visible = not launcherbox.visible if launcherbox.visible then open() else awful.keygrabber.stop() end if client.focus and client.focus.fullscreen == true then awful.placement.bottom_left( launcherbox, { margins = { bottom = dpi(10), left = dpi(10), }, parent = awful.screen.focused() } ) else awful.placement.bottom_left( launcherbox, { margins = { bottom = dpi(60), left = dpi(20), }, parent = awful.screen.focused() } ) end end) awesome.connect_signal("widget::launcher:hide", function() launcherbox.visible = false awful.keygrabber.stop() end) ================================================ FILE: src/usr/share/calla/desktop/theme/lock.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi -- Authentication local authenticate local pamexists,pam = pcall(require,"liblua_pam") if pamexists then authenticate = function(password) return pam.auth_current_user(password) end else authenticate = function(password) return password == user.passwd end end -- Variables local characters = 0 -- Background screen.connect_signal("request::desktop_decoration", function(s) local lockbackground = wibox { screen = s, visible = false, ontop = true, type = "splash", widget = background({ bg = "bg" }) } awful.placement.maximize(lockbackground) local batterypercent = wibox.widget { text = "N/A", widget = wibox.widget.textbox } local batteryicon = iconbox({ image = "batterynone" }) local battery = wibox.widget { { { batteryicon, batterypercent, spacing = dpi(4), layout = wibox.layout.fixed.horizontal }, left = dpi(8), right = dpi(8), widget = wibox.container.margin }, widget = background({ bg = "bgmid", fg = "fg" }) } local batterystore if user.batt ~= nil then awful.widget.watch("cat /sys/class/power_supply/" .. user.batt .. "/capacity", 15, function(widget, stdout) percent = tonumber(stdout) batterypercent.text = percent .. "%" if percent > 80 then batterystore = "battery100" elseif percent > 50 then batterystore = "battery80" elseif percent > 25 then batterystore = "battery50" elseif percent > 10 then batterystore = "battery25" elseif percent > 5 then batterystore = "battery10" else batterystore = "battery0" end batteryicon.image = createicon(batterystore) end) end if s == awful.screen.focused() then lockbackground:setup { { { { { { { wibox.widget.textbox("Lock Screen"), top = dpi(5), bottom = dpi(5), left = dpi(8), right = dpi(8), widget = wibox.container.margin }, widget = background({ bg = "bgmid", fg = "fg" }) }, nil, { battery, { { { iconbox({ image = "clock" }), wibox.widget.textclock('%I:%M %p'), spacing = dpi(4), layout = wibox.layout.fixed.horizontal }, left = dpi(8), right = dpi(8), widget = wibox.container.margin }, widget = background({ bg = "bgmid", fg = "fg" }) }, spacing = dpi(5), layout = wibox.layout.fixed.horizontal }, layout = wibox.layout.align.horizontal }, { { button { image = "shutdown", run = function() awful.spawn.with_shell(user.shutdown) end }, button { image = "restart", run = function() awful.spawn.with_shell(user.reboot) end }, button { image = "exit", run = function() awesome.quit() end }, spacing = dpi(5), layout = wibox.layout.fixed.horizontal }, halign = "center", widget = wibox.container.place }, layout = wibox.layout.stack }, forced_height = dpi(50), margins = dpi(10), widget = wibox.container.margin }, valign = "bottom", content_fill_horizontal = true, widget = wibox.container.place }, widget = background({ bg = "bg" }) } end local wallpaper = wibox { screen = awful.screen.focused(), width = s.geometry.width-dpi(20), height = s.geometry.height-dpi(60), ontop = true, visible = false, type = "desktop", widget = wibox.widget { image = gears.surface.crop_surface { surface = gears.surface.load_uncached(beautiful.wallpaper), ratio = (s.geometry.width-dpi(20))/(s.geometry.height-dpi(60)) }, widget = wibox.widget.imagebox } } awful.placement.top( wallpaper, { margins = { top = dpi(10) } } ) awesome.connect_signal("live::reload", function() if batterystore then batteryicon.image = createicon(batterystore) end wallpaper.widget = wibox.widget { image = gears.surface.crop_surface { surface = gears.surface.load_uncached(beautiful.wallpaper), ratio = (s.geometry.width-dpi(20))/(s.geometry.height-dpi(60)) }, widget = wibox.widget.imagebox } end) awesome.connect_signal("lockscreen::visible", function(v) lockbackground.visible = v wallpaper.visible = v end) end) -- Prompt local prompt = wibox.widget { font = user.font, align = "center", widget = colortext({ text = "Enter Password", fg = "fg" }) } local promptbox = wibox { width = dpi(350), height = dpi(135), ontop = true, visible = false } awful.spawn.easy_async_with_shell("getent passwd $(whoami) | cut -d ':' -f 5", function(out) promptbox:setup { { { { { { live(wibox.widget.imagebox, { image = "calla" }), forced_height = dpi(24), margins = dpi(5), widget = wibox.container.margin }, widget = background({ bg = "bgmid" }) }, { font = user.font:gsub("%d+", "24"), widget = colortext({ text = out:gsub(",", ""):gsub("\n", "") }) }, spacing = dpi(10), layout = wibox.layout.fixed.horizontal }, { { { prompt, layout = wibox.layout.fixed.horizontal }, top = dpi(10), bottom = dpi(10), left = dpi(15), right = dpi(15), forced_height = dpi(40), forced_width = dpi(300), widget = wibox.container.margin }, widget = background({ bg = "bgmid" }) }, spacing = dpi(10), layout = wibox.layout.fixed.vertical }, valign = "center", halign = "center", layout = wibox.container.place }, widget = background({ bg = "bg" }) } end) awful.placement.centered(promptbox) awesome.connect_signal("lockscreen::visible", function(v) promptbox.visible = v end) -- Reset local function reset() characters = 0 prompt.markup = markup({ text = "Enter Password", fg = "fg" }) --awful.spawn.with_shell("pkill --full --uid " .. os.getenv("USER") .. " fprintd-verify") end -- Fail local function fail() characters = 0 prompt.markup = markup({ text = "Try Again", fg = "fg" }) end -- Input local function grabpassword() awful.prompt.run { hooks = { {{ }, 'Escape', function(_) reset() grabpassword() end } }, keypressed_callback = function(mod, key, cmd) if #key == 1 then characters = characters + 1 prompt.markup = markup({ text = string.rep("", characters), fg = "fg" }) elseif key == "BackSpace" then if characters > 1 then characters = characters - 1 prompt.markup = markup({ text = string.rep("", characters), fg = "fg" }) else characters = 0 prompt.markup = markup({ text = "Enter Password", fg = "fg" }) end end end, exe_callback = function(input) --[[ Why doesn't this work? characters = 0 prompt.markup = markup({ text = "Authenticating...", fg = "fg" }) --]] if authenticate(input) then reset() awesome.emit_signal("lockscreen::visible", false) else fail() grabpassword() end end, textbox = wibox.widget.textbox() } --[[awful.spawn.easy_async("fprintd-verify", function(out) if out:match("verify%-match") then awful.keygrabber.stop() reset() awesome.emit_signal("lockscreen::visible", false) elseif out:match("verify%-no%-match") then awful.keygrabber.stop() fail() grabpassword() elseif out:match("already claimed") then awful.keygrabber.stop() reset() grabpassword() elseif out:match("verify%-unknown%-error") or out:match("already been opened") then require("naughty").notification{text="It appears that fprintd has encountered an error because of a suspend. Please enter password manually.",urgency="critical"} end end)--]] end -- Lock awesome.connect_signal("widget::lockscreen", function() awesome.emit_signal("lockscreen::visible", true) grabpassword() end) ================================================ FILE: src/usr/share/calla/desktop/theme/notif.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local ruled = require("ruled") local naughty = require("naughty") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi -- Config naughty.config.defaults.ontop = true naughty.config.defaults.screen = awful.screen.focused() naughty.config.defaults.border_width = 0 naughty.config.defaults.position = "top_right" naughty.config.defaults.title = "Notification" -- Rules ruled.notification.connect_signal('request::rules', function() local function setrules() -- Critical ruled.notification.append_rule { rule = { urgency = 'critical' }, properties = { bg = beautiful.bg_normal, fg = beautiful.fg_urgent, timeout = 0 } } -- Normal ruled.notification.append_rule { rule = { urgency = 'normal' }, properties = { bg = beautiful.bg_normal, fg = beautiful.fg_normal, timeout = 5 } } -- Low ruled.notification.append_rule { rule = { urgency = 'low' }, properties = { bg = beautiful.bg_normal, fg = beautiful.fg_normal, timeout = 5 } } end setrules() awesome.connect_signal("live::reload", function() setrules() end) end) -- Notification naughty.connect_signal("request::display", function(n) naughty.layout.box { notification = n, type = "notification", bg = beautiful.bg_normal, widget_template = { { { { { { { { naughty.widget.title, layout = wibox.layout.align.horizontal }, margins = dpi(10), widget = wibox.container.margin }, shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, dpi(10)) end, widget = live(wibox.container.background, { bg = "bgmid" }) }, strategy = "min", width = dpi(250), widget = wibox.container.constraint }, { naughty.widget.message, margins = dpi(5), widget = wibox.container.margin }, spacing = dpi(5), layout = wibox.layout.fixed.vertical }, strategy = "max", width = dpi(400), widget = wibox.container.constraint }, margins = dpi(10), widget = wibox.container.margin }, id = "background_role", widget = naughty.container.background } } end) ================================================ FILE: src/usr/share/calla/desktop/theme/panel.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local dock = require("theme.dock") local function create(s) local menu = button { image = "calla", run = function() awesome.emit_signal('widget::launcher') end } local tray = wibox.widget { wibox.widget.systray(), margins = 0, visible = false, widget = wibox.container.margin } local systraybutton = hovercursor(wibox.widget { buttons = { awful.button({}, 1, function() awesome.emit_signal("widget::systray") end) }, align = "center", widget = iconbox({ image = "left" }) }) local systray = wibox.widget { { { systraybutton, forced_width = dpi(30), widget = wibox.container.background }, tray, layout = wibox.layout.fixed.horizontal }, widget = background({ bg = "bgmid" }) } local closed = true local systraystore awesome.connect_signal("widget::systray", function() if closed == true then tray.visible = true tray.margins = dpi(5) systraystore = "right" closed = false else tray.visible = false tray.margins = 0 systraystore = "left" closed = true end systraybutton.image = createicon(systraystore) end) local media = wibox.widget { { { { id = "icon", widget = iconbox({ image = "musicon" }) }, { id = "title", widget = wibox.widget.textbox("Not Playing - No Artist") }, spacing = dpi(4), layout = wibox.layout.fixed.horizontal }, left = dpi(8), right = dpi(8), widget = wibox.container.margin }, widget = background({ bg = "bgmid", fg = "fg" }) } local playerstore awesome.connect_signal("signal::playerctl", function(title, album, artist, cover, status) if string.len(title .. " - " .. artist) > 50 then media:get_children_by_id("title")[1].text = title else media:get_children_by_id("title")[1].text = title .. " - " .. artist end if title == "Not Playing" then state = false playerstore = "musicoff" else state = true playerstore = "musicon" end media:get_children_by_id("icon")[1].image = createicon(playerstore) end) local volumepercent = wibox.widget { text = "N/A", widget = wibox.widget.textbox } local volumeicon = iconbox({ image = "volumemute" }) local volume = wibox.widget { { { volumeicon, volumepercent, spacing = dpi(4), layout = wibox.layout.fixed.horizontal }, left = dpi(8), right = dpi(8), widget = wibox.container.margin }, widget = background({ bg = "bgmid", fg = "fg" }) } local volumestore awesome.connect_signal("signal::volume", function(volume, mute) if mute then volumepercent.text = "Muted" volumestore = "volumemute" else volumepercent.text = tostring(volume) .. "%" if volume > 100 then volumestore = "volumewarn" elseif volume >= 50 then volumestore = "volume100" elseif volume >= 25 then volumestore = "volume50" elseif volume > 0 then volumestore = "volume25" elseif volume == 0 then volumestore = "volume0" end end volumeicon.image = createicon(volumestore) end) local batterypercent = wibox.widget { text = "N/A", widget = wibox.widget.textbox } local batteryicon = iconbox({ image = "batterynone" }) local battery = wibox.widget { { { batteryicon, batterypercent, spacing = dpi(4), layout = wibox.layout.fixed.horizontal }, left = dpi(8), right = dpi(8), widget = wibox.container.margin }, widget = background({ bg = "bgmid", fg = "fg" }) } local batterystore if user.batt ~= nil then awful.widget.watch("cat /sys/class/power_supply/" .. user.batt .. "/capacity", 15, function(widget, stdout) percent = tonumber(stdout) batterypercent.text = percent .. "%" if percent > 80 then batterystore = "battery100" elseif percent > 50 then batterystore = "battery80" elseif percent > 25 then batterystore = "battery50" elseif percent > 10 then batterystore = "battery25" elseif percent > 5 then batterystore = "battery10" else batterystore = "battery0" end batteryicon.image = createicon(batterystore) end) end local clock = wibox.widget { { { iconbox({ image = "clock" }), wibox.widget.textclock('%I:%M %p'), spacing = dpi(4), layout = wibox.layout.fixed.horizontal }, left = dpi(8), right = dpi(8), widget = wibox.container.margin }, widget = background({ bg = "bgmid", fg = "fg" }) } local taglist = awful.widget.taglist { screen = s, filter = awful.widget.taglist.filter.selected, style = { shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, dpi(10)) end }, widget_template = { { { { wibox.widget.textbox("Workspace "), widget = background({ fg = "fg" }) }, { id = "text_role", widget = wibox.widget.textbox }, layout = wibox.layout.fixed.horizontal }, left = dpi(8), right = dpi(8), widget = wibox.container.margin }, id = "background_role", widget = wibox.container.background, create_callback = function(self) hovercursor(self) end }, buttons = { awful.button({ }, 1, function() awesome.emit_signal("widget::preview") end), awful.button({ }, 4, function(t) awful.tag.viewnext(t.screen) end), awful.button({ }, 5, function(t) awful.tag.viewprev(t.screen) end) } } local layouts = awful.widget.layoutbox { screen = s, buttons = { awful.button({ }, 1, function () awful.layout.inc( 1) end), awful.button({ }, 3, function () awful.layout.inc(-1) end), } } local layoutbox = hovercursor(wibox.widget { { layouts, margins = dpi(5), widget = wibox.container.margin }, widget = background({ bg = "bgmid" }) }) awesome.connect_signal("live::reload", function() if systraystore then systraybutton.image = createicon(systraystore) end if playerstore then media:get_children_by_id("icon")[1].image = createicon(playerstore) end if volumestore then volumeicon.image = createicon(volumestore) end if batterystore then batteryicon.image = createicon(batterystore) end taglist._do_taglist_update_now() tag.emit_signal("property::layout", awful.screen.focused().selected_tag) end) return wibox.widget { { { { menu, taglist, layoutbox, spacing = dpi(5), layout = wibox.layout.fixed.horizontal }, nil, { systray, hovercursor(wibox.widget { media, volume, battery, clock, buttons = { awful.button({ }, 1, function() awesome.emit_signal("widget::control") end) }, spacing = dpi(5), layout = wibox.layout.fixed.horizontal }), spacing = dpi(5), layout = wibox.layout.fixed.horizontal }, layout = wibox.layout.align.horizontal }, { dock, halign = "center", widget = wibox.container.place }, layout = wibox.layout.stack }, forced_height = dpi(50), margins = dpi(10), widget = wibox.container.margin } end return create ================================================ FILE: src/usr/share/calla/desktop/theme/preview.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local scale = 0.15 local function createpreview(t, s, geometry) local clientlayout = wibox.layout.manual() clientlayout.forced_height = geometry.height clientlayout.forced_width = geometry.width for _, c in ipairs(t:clients()) do if not c.hidden and not c.minimized then local imagebox = wibox.widget { resize = true, forced_height = dpi(150) * scale, forced_width = dpi(150) * scale, widget = wibox.widget.imagebox } if not pcall(function() imagebox.image = gears.surface.load(c.icon) end) then imagebox.image = beautiful.calla end local clientbox = wibox.widget({ { nil, { nil, imagebox, nil, expand = "outside", layout = wibox.layout.align.horizontal, }, nil, expand = "outside", widget = wibox.layout.align.vertical, }, forced_height = math.floor(c.height * scale), forced_width = math.floor(c.width * scale), border_width = dpi(2), border_color = beautiful.bgmid, widget = background({ bg = "bg" }) }) clientbox.point = { x = math.floor((c.x - geometry.x) * scale), y = math.floor((c.y - geometry.y) * scale), } clientlayout:add(clientbox) end end if t:clients()[1] == nil then return wibox.widget { { { { { wibox.widget.textbox("Empty"), top = dpi(5), bottom = dpi(5), left = dpi(8), right = dpi(8), widget = wibox.container.margin }, widget = background({ bg = "bg", fg = "fg" }) }, valign = "center", halign = "center", widget = wibox.container.place }, bg = beautiful.bg.."96", widget = wibox.container.background }, widget = background({ bg = "bgmid" }) } else return wibox.widget { { clientlayout, forced_height = geometry.height, forced_width = geometry.width, widget = wibox.container.place }, widget = background({ bg = "bgmid" }) } end end local previewbox = wibox { width = dpi(100), height = dpi(100), ontop = true, visible = false, widget = live(wibox.container.background, { bg = "bg" }) } local previewlist = wibox.widget { expand = true, orientation = "horizontal", spacing = dpi(5), layout = wibox.layout.grid } awesome.connect_signal("widget::preview", function() awesome.emit_signal("widget::launcher:hide") if previewbox.visible then previewbox.visible = false return end previewlist:reset() local geometry = awful.screen.focused():get_bounding_geometry() local tags = awful.screen.focused().tags local numtags for i, tag in ipairs(tags) do numtags = i local preview = wibox.widget { hovercursor(createpreview(tag, tag.screen, geometry)), buttons = { awful.button({}, 1, function() awesome.emit_signal("widget::preview") tag:view_only() end) }, widget = wibox.container.background } previewlist:add(preview) end previewbox.width = geometry.width * scale * numtags + (numtags + 1) * dpi(5) previewbox.height = (geometry.height-dpi(40)) * scale + (2 * dpi(5)) previewbox.widget = wibox.widget { { previewlist, margins = dpi(5), widget = wibox.container.margin }, widget = live(wibox.container.background, { bg = "bg" }) } awful.placement.bottom_left( previewbox, { margins = { bottom = dpi(60), left = dpi(20), }, parent = awful.screen.focused() } ) previewbox.visible = true end) awesome.connect_signal("widget::preview:hide", function() previewbox.visible = false end) ================================================ FILE: src/usr/share/calla/desktop/theme/settings.lua ================================================ awesome.connect_signal("widget::config", function() local dpi = require("beautiful").xresources.apply_dpi local lgi = require("lgi") local Gio = lgi.Gio local Gtk = lgi.require("Gtk", "3.0") local Gdk = lgi.require("Gdk", "3.0") local GdkPixbuf = lgi.require("GdkPixbuf", "2.0") local GObject = lgi.require("GObject", "2.0") local appID = "io.github.stardust-kyun.calla.settings" local appTitle = "Settings" local app = Gtk.Application({ application_id = appID }) local function copytable(table) local copy = {} for key, value in pairs(table) do copy[key] = value end return copy end local settings = copytable(user) local color local function genColor() color = readjson(require("gears").filesystem.get_configuration_dir() .. "color/" .. settings.color .. "/" .. settings.color .. ".json") end genColor() local function settingsEntry(name, setting, default) local label = Gtk.Label({ label = name, halign = Gtk.Align.START }) local settingsEntry = Gtk.Entry({ placeholder_text = settings[setting], width = dpi(175) }) function settingsEntry:on_key_release_event() settings[setting] = settingsEntry.text end local button = Gtk.Button.new_with_label("Default") function button:on_clicked() settings[setting] = default settingsEntry.placeholder_text = settings[setting] settingsEntry.text = "" end local grid = Gtk.Grid({ column_spacing = dpi(10), row_spacing = dpi(10), halign = Gtk.Align.CENTER, { label, top_attach = 0, left_attach = 0, width = 2 }, { settingsEntry, top_attach = 1, left_attach = 0 }, { button, top_attach = 1, left_attach = 1 }, }) return grid end local function colorEntry(name, setting, default) local label = Gtk.Label({ label = name, halign = Gtk.Align.START }) local colorEntry = Gtk.Entry({ placeholder_text = color[setting], width = dpi(175) }) function colorEntry:on_key_release_event() color[setting] = colorEntry.text end local button = Gtk.Button.new_with_label("Default") function button:on_clicked() color[setting] = default colorEntry.placeholder_text = color[setting] colorEntry.text = "" end local grid = Gtk.Grid({ column_spacing = dpi(10), row_spacing = dpi(10), halign = Gtk.Align.CENTER, { label, top_attach = 0, left_attach = 0, width = 2 }, { colorEntry, top_attach = 1, left_attach = 0 }, { button, top_attach = 1, left_attach = 1 }, }) return grid end local function createColor(name, col, colname) local rgbcol = Gdk.RGBA.parse(col) local label = Gtk.Label({ label = name, halign = Gtk.Align.START }) local button = Gtk.ColorButton({ rgba = rgbcol, show_editor = true }) function button:on_color_set() local out = button:get_rgba():to_string() local red = out:match("%d+,"):gsub(",", "") local green = out:match(",%d+,"):gsub(",", "") local blue = out:match(",%d+%)"):gsub(",", ""):gsub("%)", "") local hex = string.format("#%02X%02X%02X", red, green, blue) color[colname] = hex end local grid = Gtk.Grid({ column_spacing = dpi(10), row_spacing = dpi(10), halign = Gtk.Align.CENTER, { label, top_attach = 0, left_attach = 0 }, { button, top_attach = 1, left_attach = 0 }, }) return grid end local function doGeneral() local function wallpaper() local label = Gtk.Label({ label = "Wallpaper", halign = Gtk.Align.START }) local filter = Gtk.FileFilter() filter:add_pattern("*.png") filter:add_pattern("*.jpg") filter:add_pattern("*.JPG") filter:add_pattern("*.jpeg") if settings.wallpaper ~= nil then local pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(settings.wallpaper:gsub("~", os.getenv("HOME")), 500, 500, true) else local pixbuf end local preview = Gtk.Image() local wallpaper = Gtk.FileChooserButton({ filter = filter, title = "Choose Wallpaper", width = dpi(175), preview_widget = preview }) if settings.wallpaper ~= nil then wallpaper:set_file(Gio.File.new_for_path(settings.wallpaper:gsub("~", os.getenv("HOME")))) wallpaper:set_current_folder_file(Gio.File.new_for_path(settings.wallpaper:gsub("~", os.getenv("HOME")))) else wallpaper:set_file(Gio.File.new_for_path("")) wallpaper:set_current_folder_file(Gio.File.new_for_path(os.getenv("HOME"))) end function wallpaper:on_file_set() settings.wallpaper = self:get_filename():gsub(os.getenv("HOME"), "~") end function wallpaper:on_update_preview() pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(self:get_preview_filename(), 500, 500, true) preview:set_from_pixbuf(pixbuf) wallpaper:set_preview_widget_active(true) end local button = Gtk.Button.new_with_label("Default") function button:on_clicked() settings.wallpaper = nil wallpaper:set_file(Gio.File.new_for_path("")) end local grid = Gtk.Grid({ column_spacing = dpi(10), row_spacing = dpi(10), halign = Gtk.Align.CENTER, { label, top_attach = 0, left_attach = 0, width = 2 }, { wallpaper, top_attach = 1, left_attach = 0 }, { button, top_attach = 1, left_attach = 1 }, }) return grid end local function screenshot() local label = Gtk.Label({ label = "Screenshot Directory", halign = Gtk.Align.START }) local button = Gtk.FileChooserButton({ title = "Choose Screenshot Directory", width = dpi(245), action = Gtk.FileChooserAction.SELECT_FOLDER }) button:set_current_folder_file(Gio.File.new_for_path(settings.shotdir:gsub("~", os.getenv("HOME")))) function button:on_file_set() settings.shotdir = self:get_filename():gsub(os.getenv("HOME"), "~") end local grid = Gtk.Grid({ column_spacing = dpi(10), row_spacing = dpi(10), halign = Gtk.Align.CENTER, { label, top_attach = 0, left_attach = 0 }, { button, top_attach = 1, left_attach = 0 }, }) return grid end local grid = Gtk.Grid({ column_spacing = dpi(20), row_spacing = dpi(10), margin = dpi(20), halign = Gtk.Align.CENTER, { settingsEntry("Terminal", "terminal", "st"), top_attach = 0, left_attach = 0 }, { settingsEntry("Shutdown", "shutdown", "systemctl poweroff"), top_attach = 1, left_attach = 0 }, { settingsEntry("Reboot", "reboot", "systemctl reboot"), top_attach = 2, left_attach = 0 }, { settingsEntry("Font", "font", "Roboto Medium 11"), top_attach = 0, left_attach = 1 }, { settingsEntry("Alt Font", "fontalt", "Roboto Bold 11"), top_attach = 1, left_attach = 1 }, { settingsEntry("Fallback Password", "passwd", "awesomewm"), top_attach = 3, left_attach = 0 }, { settingsEntry("Battery", "batt", "BAT0"), top_attach = 2, left_attach = 1 }, { wallpaper(), top_attach = 3, left_attach = 1 }, { settingsEntry("temp", "temp", "temp"), top_attach = 4, left_attach = 1 }, { screenshot(), top_attach = 4, left_attach = 0 }, }) return grid end local function doColor() genColor() local left = Gtk.Grid({ column_spacing = dpi(10), row_spacing = dpi(10), halign = Gtk.Align.CENTER, { createColor("Bg", color.bg, "bg"), top_attach = 0, left_attach = 0 }, { createColor("Mid Bg", color.bgmid, "bgmid"), top_attach = 1, left_attach = 0 }, { createColor("Alt Bg", color.bgalt, "bgalt"), top_attach = 2, left_attach = 0 }, { createColor("Fg", color.fg, "fg"), top_attach = 0, left_attach = 1 }, { createColor("Black", color.black, "black"), top_attach = 1, left_attach = 1 }, { createColor("White", color.white, "white"), top_attach = 2, left_attach = 1 }, { createColor("Red", color.red, "red"), top_attach = 0, left_attach = 2 }, { createColor("Green", color.green, "green"), top_attach = 1, left_attach = 2 }, { createColor("Yellow", color.yellow, "yellow"), top_attach = 2, left_attach = 2 }, { createColor("Blue", color.blue, "blue"), top_attach = 0, left_attach = 3 }, { createColor("Magenta", color.magenta, "magenta"), top_attach = 1, left_attach = 3 }, { createColor("Cyan", color.cyan, "cyan"), top_attach = 2, left_attach = 3 }, { colorEntry("Gui Theme", "gtk", color.gtk), top_attach = 3, left_attach = 0, width = 4 }, }) local right = Gtk.Grid({ column_spacing = dpi(10), row_spacing = dpi(10), halign = Gtk.Align.CENTER, { colorEntry("Compositor Radius", "compradius", color.compradius), top_attach = 0, left_attach = 0 }, { colorEntry("Compositor Offset", "compoffset", color.compoffset), top_attach = 1, left_attach = 0 }, { colorEntry("Compositor Opacity", "compopacity", color.compopacity), top_attach = 2, left_attach = 0 }, { colorEntry("Icon Theme", "icons", color.icons), top_attach = 3, left_attach = 0 }, }) local grid = Gtk.Grid({ column_spacing = dpi(20), halign = Gtk.Align.CENTER, { left, top_attach = 0, left_attach = 0 }, { right, top_attach = 0, left_attach = 1 }, }) return grid end local function doTheme() local function colorgen() local colors = {} for dir in io.popen("ls -d " .. require("gears").filesystem.get_configuration_dir() .. "color/*/ | rev | cut -f2 -d'/' | rev"):lines() do table.insert(colors, dir) end return colors end local model = Gtk.ListStore.new({ GObject.Type.STRING }) local items = colorgen() local currentcolor = 0 for i, name in ipairs(items) do model:append({ name }) if name == settings.color then currentcolor = i-1 end end local combo = Gtk.ComboBox({ model = model, active = currentcolor, cells = { { Gtk.CellRendererText(), { text = 1 }, align = Gtk.Align.START } }, expand = true }) local savebutton = Gtk.Button.new_with_label("Save Theme As...") local confirmbutton = Gtk.Button.new_with_label("Confirm") local cancelbutton = Gtk.Button.new_with_label("Cancel") local nameset = false local themeprompt = Gtk.Label({ label = "What should this theme be called?", halign = Gtk.Align.START }) local themename = Gtk.Entry({ placeholder_text = "Theme Name", hexpand = true }) local name function themename:on_key_release_event() name = themename.text end local wallprompt = Gtk.Label({ label = "What wallpaper should this theme use?", halign = Gtk.Align.START }) local filter = Gtk.FileFilter() filter:add_pattern("*.png") filter:add_pattern("*.jpg") local preview = Gtk.Image() local wallpaper = nil local wallpaperbutton = Gtk.FileChooserButton({ filter = filter, title = "Choose Wallpaper", hexpand = true, preview_widget = preview }) wallpaperbutton:set_file(Gio.File.new_for_path("")) wallpaperbutton:set_current_folder_file(Gio.File.new_for_path(os.getenv("HOME"))) function wallpaperbutton:on_file_set() wallpaper = self:get_filename() end function wallpaperbutton:on_update_preview() pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(self:get_preview_filename(), 500, 500, true) preview:set_from_pixbuf(pixbuf) wallpaperbutton:set_preview_widget_active(true) end local colorscheme = Gtk.Grid({ column_spacing = dpi(10), row_spacing = dpi(10), { Gtk.Label({ label = "Color Scheme", halign = Gtk.Align.START }), top_attach = 0, left_attach = 0 }, { combo, top_attach = 1, left_attach = 0 }, { savebutton, top_attach = 1, left_attach = 1 } }) function savebutton:on_clicked() colorscheme:remove(combo) colorscheme:remove(savebutton) colorscheme:attach(themeprompt, 0, 1, 1, 1) colorscheme:attach(themename, 1, 1, 1, 1) colorscheme:attach(confirmbutton, 2, 1, 1, 1) colorscheme:attach(cancelbutton, 3, 1, 1, 1) colorscheme:show_all() end function confirmbutton:on_clicked() if nameset == false and name ~= "" then nameset = true colorscheme:remove(themeprompt) colorscheme:remove(themename) colorscheme:attach(wallprompt, 0, 1, 1, 1) colorscheme:attach(wallpaperbutton, 1, 1, 1, 1) colorscheme:show_all() elseif nameset == true and wallpaper then local themedir = require("gears").filesystem.get_configuration_dir() .. "color/" .. name .. "/" local path = Gio.File.new_for_path(themedir) local sourcepath = Gio.File.new_for_path(wallpaper) local targetpath = Gio.File.new_for_path(themedir .. name .. ".png") path:make_directory() sourcepath:copy(targetpath, Gio.FileCopyFlags.NONE, nil, nil, nil, nil) color.wall = targetpath:get_path():gsub(require("gears").filesystem.get_configuration_dir(), "") writejson(require("gears").filesystem.get_configuration_dir() .. "color/" .. name .. "/" .. name .. ".json", color) table.insert(items, name) model:append({name}) colorscheme:remove(wallprompt) colorscheme:remove(wallpaperbutton) colorscheme:remove(confirmbutton) colorscheme:remove(cancelbutton) colorscheme:attach(combo, 0, 1, 1, 1) colorscheme:attach(savebutton, 1, 1, 1, 1) nameset = false themename.text = "" colorscheme:show_all() end end function cancelbutton:on_clicked() colorscheme:remove(themeprompt) colorscheme:remove(themename) colorscheme:remove(wallprompt) colorscheme:remove(wallpaperbutton) colorscheme:remove(confirmbutton) colorscheme:remove(cancelbutton) colorscheme:attach(combo, 0, 1, 1, 1) colorscheme:attach(savebutton, 1, 1, 1, 1) nameset = false themename.text = "" colorscheme:show_all() end local grid = Gtk.Grid({ column_spacing = dpi(10), row_spacing = dpi(10), margin = dpi(20), halign = Gtk.Align.CENTER, { colorscheme, top_attach = 0, left_attach = 0 }, { doColor(), top_attach = 1, left_attach = 0 }, }) function combo:on_changed() local n = self:get_active() settings.color = items[n+1] grid:remove(grid:get_child_at(0, 1)) local colorwidget = doColor() grid:attach(colorwidget, 0, 1, 1, 1) colorwidget:show_all() end return grid end local function restartdialog() local label = Gtk.Label({ label = "Some changes require a restart. Restart now?" }) local cancelButton = Gtk.Button.new_with_label("No Thanks") local restartButton = Gtk.Button.new_with_label("Restart") function cancelButton:on_clicked() awesome.emit_signal("settings::cancel") end function restartButton:on_clicked() awesome.restart() end local grid = Gtk.Grid({ column_spacing = dpi(10), halign = Gtk.Align.END, margin_end = dpi(20), { label, top_attach = 0, left_attach = 0 }, { cancelButton, top_attach = 0, left_attach = 1 }, { restartButton, top_attach = 0, left_attach = 2 }, }) return grid end function app:on_startup() local window = Gtk.ApplicationWindow({ title = appTitle, application = self, default_width = dpi(500), default_height = dpi(350) }) window:set_wmclass("settings", "Settings") local stack = Gtk.Stack({ transition_type = Gtk.StackTransitionType.NONE }) stack:add_titled(doGeneral(), "general", "General") stack:add_titled(doTheme(), "theme", "Theme") local saveButton = Gtk.Button({ label = "Save", halign = Gtk.Align.END, margin_end = dpi(20) }) local box = Gtk.Box({ orientation = Gtk.Orientation.VERTICAL, margin = dpi(10), Gtk.StackSwitcher({ stack = stack, halign = Gtk.Align.CENTER }), { stack, expand = true }, saveButton }) function saveButton:on_clicked() local shouldrestart = false local userrestart = { "font", "fontalt", "batt", } for _, property in ipairs(userrestart) do if user[property] ~= settings[property] then shouldrestart = true break end end writejson(require("gears").filesystem.get_cache_dir() .. "user.json", settings) user = copytable(settings) writejson(require("gears").filesystem.get_configuration_dir() .. "color/" .. settings.color .. "/" .. settings.color .. ".json", color) awesome.emit_signal("color::change", color) if shouldrestart then box:remove(saveButton) local dialog = restartdialog() box:add(dialog) dialog:show_all() awesome.connect_signal("settings::cancel", function() box:remove(dialog) box:add(saveButton) saveButton:show_all() end) end require("beautiful").init(require("gears").filesystem.get_configuration_dir() .. "theme/theme.lua") awesome.emit_signal("live::reload") end box:show_all() window:add(box) end function app:on_activate() self.active_window:present() end return app:run() end) ================================================ FILE: src/usr/share/calla/desktop/theme/theme.lua ================================================ local gears = require("gears") local dpi = require("beautiful").xresources.apply_dpi local iconpath = require("gears").filesystem.get_configuration_dir() .. "theme/icons/" local color = readjson(gears.filesystem.get_configuration_dir() .. "color/" .. user.color .. "/" .. user.color .. ".json") local theme = {} theme.bg = color.bg theme.bgmid = color.bgmid theme.bgalt = color.bgalt theme.fg = color.fg theme.black = color.black theme.white = color.white theme.red = color.red theme.green = color.green theme.yellow = color.yellow theme.blue = color.blue theme.magenta = color.magenta theme.cyan = color.cyan -- Default theme.font = user.font theme.bg_normal = color.bg theme.bg_focus = color.bgalt theme.bg_urgent = color.red theme.fg_normal = color.fg theme.fg_focus = color.fg theme.fg_urgent = color.red -- Titlebar theme.titlebar_bg_normal = color.bg theme.titlebar_bg_focus = color.bg theme.titlebar_bg_urgent = color.bg theme.titlebar_fg_normal = color.fg .. "40" theme.titlebar_fg_focus = color.fg theme.titlebar_fg_urgent = color.red -- Tasklist theme.tasklist_bg_normal = color.bgmid theme.tasklist_bg_focus = color.fg .. "20" theme.tasklist_bg_urgent = color.bgmid theme.tasklist_bg_minimize = color.bg -- Taglist theme.taglist_bg_focus = color.bgmid theme.taglist_bg_urgent = color.bgmid theme.taglist_fg_focus = color.fg theme.taglist_fg_urgent = color.red -- Snap theme.snap_bg = color.fg theme.snap_shape = function(cr, width, height) gears.shape.rounded_rect(cr, width, height, dpi(15)) end theme.snapper_gap = dpi(15) -- Misc theme.useless_gap = dpi(5) theme.border_width = dpi(0) theme.bg_systray = color.bgmid theme.systray_icon_spacing = dpi(5) theme.notification_spacing = dpi(20) theme.tooltip_opacity = 0 theme.prompt_bg_cursor = color.fg -- Wallpaper color.wall = gears.filesystem.get_configuration_dir() .. color.wall theme.wallpaper = user.wallpaper or color.wall -- Layout icons theme.layout_floating = gears.color.recolor_image(iconpath .. "floating.png", color.fg) theme.layout_tile = gears.color.recolor_image(iconpath .. "tile.png", color.fg) -- Theme icon theme.calla = gears.color.recolor_image(iconpath .. "calla.png", color.fg) -- Profile picture if gears.filesystem.file_readable(iconpath .. "pfp.png") then theme.pfp = iconpath .. "pfp.png" else theme.pfp = theme.calla end -- Icon theme theme.icons = color.icons return theme ================================================ FILE: src/usr/share/calla/desktop/theme/title.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local iconpath = require("gears").filesystem.get_configuration_dir() .. "theme/icons/" client.connect_signal("request::titlebars", function(c) -- Button actions local buttons = { awful.button({ }, 1, function() c:activate { context = "titlebar", action = "mouse_move" } end), awful.button({ }, 3, function() c:activate { context = "titlebar", action = "mouse_resize"} end), } -- Widgets local icon = wibox.widget { { awful.titlebar.widget.iconwidget(c), buttons = buttons, margins = dpi(5), widget = wibox.container.margin }, widget = background({ bg = "bgmid" }) } local title = wibox.widget { { awful.titlebar.widget.titlewidget(c), buttons = buttons, top = dpi(5), bottom = dpi(5), left = dpi(10), right = dpi(10), widget = wibox.container.margin }, bg = beautiful.bgmid, widget = background({ bg = "bgmid" }) } local function titlebutton(action, run) local img = gears.color.recolor_image(iconpath .. action .. ".png", beautiful.bgmid) local button = wibox.widget { { id = "image", image = gears.color.recolor_image(img, beautiful.fg.."40"), widget = wibox.widget.imagebox }, buttons = { awful.button({}, 1, run) }, widget = wibox.widget.background } local function update() local img = gears.color.recolor_image(iconpath .. action .. ".png", beautiful.bgmid) if client.focus == c then button:get_children_by_id("image")[1].image = gears.color.recolor_image(img, beautiful.fg) else button:get_children_by_id("image")[1].image = gears.color.recolor_image(img, beautiful.fg.."40") end end client.connect_signal("focus", update) client.connect_signal("unfocus", update) awesome.connect_signal("live::reload", update) return button end local titlebuttons = wibox.widget { { { titlebutton("minimize", function() c.minimized = true end), titlebutton("maximize", function() c.maximized = not c.maximized end), titlebutton("close", function() c:kill() end), spacing = dpi(10), widget = wibox.layout.fixed.horizontal }, margins = dpi(10), widget = wibox.container.margin }, widget = background({ bg = "bgmid" }) } -- Titlebar local titlebar = awful.titlebar(c, { size = dpi(40), position = "top" }) local handle = awful.titlebar(c, { size = dpi(20), position = "bottom" }) titlebar:setup { { { icon, title, spacing = dpi(5), layout = wibox.layout.fixed.horizontal }, { buttons = buttons, widget = wibox.container.background }, titlebuttons, layout = wibox.layout.align.horizontal }, margins = dpi(5), widget = wibox.container.margin } handle:setup { buttons = buttons, widget = wibox.container.background } awesome.connect_signal("live::reload", function() titlebar:set_bg(beautiful.bg) titlebar:set_fg(beautiful.fg) handle:set_bg(beautiful.bg) end) end) ================================================ FILE: src/usr/share/calla/desktop/theme/volume.lua ================================================ local awful = require("awful") local wibox = require("wibox") local gears = require("gears") local beautiful = require("beautiful") local dpi = beautiful.xresources.apply_dpi local volumebox = wibox { width = dpi(200), height = dpi(85), ontop = true, visible = false } local percent = wibox.widget.textbox() local header = wibox.widget { { { { { valign = "center", widget = wibox.widget.textbox("Volume") }, nil, percent, layout = wibox.layout.align.horizontal, }, top = dpi(5), bottom = dpi(5), left = dpi(10), right = dpi(10), widget = wibox.container.margin }, widget = background({ bg = "bgmid" }) }, margins = dpi(5), widget = wibox.container.margin } local icon = iconbox({ image = "volumemute" }) local bar = wibox.widget { shape = gears.shape.rounded_rect, bar_shape = gears.shape.rounded_rect, max_value = 100, value = 0, widget = live(wibox.widget.progressbar, { background_color = "bgmid", color = "fg" }) } local timer = gears.timer { timeout = 2, single_shot = true, callback = function() volumebox.visible = false end } volumebox:setup { { header, { { { icon, right = dpi(15), widget = wibox.container.margin }, nil, { bar, top = dpi(20), bottom = dpi(20), widget = wibox.container.margin }, layout = wibox.layout.align.horizontal }, left = dpi(15), right = dpi(15), widget = wibox.container.margin }, layout = wibox.layout.align.vertical }, widget = live(wibox.container.background, { bg = "bg", fg = "fg" }) } awesome.connect_signal("signal::volume", function(volume, mute) if mute then percent.text = "Muted" icon.image = createicon("volumemute") else percent.text = tostring(volume) .. "%" bar.value = volume if volume > 100 then icon.image = createicon("volumewarn") elseif volume >= 50 then icon.image = createicon("volume100") elseif volume >= 25 then icon.image = createicon("volume50") elseif volume > 0 then icon.image = createicon("volume25") elseif volume == 0 then icon.image = createicon("volume0") end end end) awesome.connect_signal("widget::volume", function() awesome.emit_signal("widget::brightness:hide") timer:again() if client.focus and client.focus.fullscreen == true then awful.placement.bottom( volumebox, { margins = { bottom = dpi(10) }, parent = awful.screen.focused() } ) else awful.placement.bottom( volumebox, { margins = { bottom = dpi(60) }, parent = awful.screen.focused() } ) end volumebox.visible = true end) awesome.connect_signal("widget::volume:hide", function() volumebox.visible = false end) ================================================ FILE: src/usr/share/calla/xsettingsd ================================================ Net/ThemeName "light" Net/IconThemeName "Papirus-Light" ================================================ FILE: src/usr/share/themes/dark/gtk-3.0/gtk.css ================================================ @define-color borders #151515; window.background.chromium { background: #262626; color: #d0d0d0; } window.background.chromium button { border: 1px solid #262626; } window.background.chromium .titlebutton { border: transparent; } window.background.chromium entry:focus { border-color: #151515; } window.background.chromium menu { background: #1d1d1d; } window.background.chromium menu menuitem { color: #d0d0d0; } window.background.chromium menu menuitem:hover { background: rgba(208, 208, 208, 0.05); } window.background.chromium menu menuitem:disabled label { color: rgba(208, 208, 208, 0.05); } window.background.chromium headerbar, window.background.chromium menubar { background: #151515; } window.background.chromium headerbar label, window.background.chromium menubar label { color: rgba(208, 208, 208, 0.1); } window.background.chromium headerbar .titlebutton, window.background.chromium menubar .titlebutton { padding: 100px 80px; } window.background.chromium scrollbar button { background: rgba(208, 208, 208, 0.05); color: #d0d0d0; } window.background.chromium scrollbar trough { background: transparent; } window.background.chromium scrollbar slider { background: rgba(208, 208, 208, 0.05); } window.background.chromium scrollbar junction { background: rgba(208, 208, 208, 0.05); } window.background.chromium textview { background-color: #151515; color: #d0d0d0; } #MozillaGtkWidget { background: #151515; color: #d0d0d0; } #MozillaGtkWidget menubar { color: rgba(208, 208, 208, 0.1); background: #151515; } #MozillaGtkWidget text { background: #1d1d1d; } #MozillaGtkWidget menu { background: #212121; } #MozillaGtkWidget menu :disabled { color: rgba(208, 208, 208, 0.05); } #MozillaGtkWidget menu separator { padding: 8px; } #MozillaGtkWidget border { border: 1px solid #212121; } .nemo-window .nemo-places-sidebar { background: #151515; } .nemo-window .primary-toolbar { background: #151515; padding: 0.3em; } .nemo-window menubar ~ separator { min-width: 0; min-height: 0; } .nemo-window .toolbar { background: #151515; margin: -0.1em; } .nemo-window .nemo-window-pane :selected { background: rgba(208, 208, 208, 0.6); } .nemo-window .nemo-window-pane :active { background: rgba(208, 208, 208, 0.6); } actionbar { padding: 0.6em; background: #151515; border-top: 1px solid rgba(208, 208, 208, 0.05); } button { transition: background-color 100ms ease-in; background: rgba(208, 208, 208, 0.05); padding: 0.6em; border-radius: 0.5em; } button:hover:not(:active) { background: rgba(208, 208, 208, 0.1); } button:disabled { opacity: 0.3; } button:checked, button.suggested-action { background: rgba(208, 208, 208, 0.1); color: #d0d0d0; } button:checked:hover:not(:active), button.suggested-action:hover:not(:active) { background: rgba(208, 208, 208, 0.1); } button.image-button:not(.text-button) { padding: 0.8em; } button.destructive-action { background: rgba(172, 65, 65, 0.6); color: #d0d0d0; } button.destructive-action:hover:not(:active) { background: rgba(172, 65, 65, 0.9); } button.circular { border-radius: 100%; padding: 0.8em; } button.flat { background: transparent; } button.flat:hover { background: rgba(208, 208, 208, 0.05); } button.flat:active, button.flat:checked { background: rgba(208, 208, 208, 0.05); } label + button { margin-left: 0.6em; } button.small-button image:only-child { padding: 0.3em; } .linked.horizontal button:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .linked.horizontal button:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .linked.vertical button:not(:first-child) { border-top-left-radius: 0; border-top-right-radius: 0; } .linked.vertical button:not(:last-child) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } calendar { background: #151515; } calendar.button { background: #151515; color: rgba(208, 208, 208, 0.1); } calendar.button:hover { color: #d0d0d0; } calendar.button:disabled { color: rgba(208, 208, 208, 0.05); } calendar.header { background: #151515; } calendar.highlight { color: rgba(208, 208, 208, 0.1); } calendar:selected { color: #d0d0d0; border-radius: 0.5em; background: rgba(208, 208, 208, 0.1); } calendar:indeterminate { color: rgba(208, 208, 208, 0.05); } check { -gtk-icon-source: -gtk-recolor(url("../assets/symbolic/check.symbolic.png")); -gtk-icon-transform: scale(0); color: #d0d0d0; border: 0.2em solid rgba(208, 208, 208, 0.05); border-radius: 0.3em; padding: 0.1em; min-width: 0.7em; min-height: 0.7em; transition: -gtk-icon-transform 200ms; } check:hover { border-color: rgba(208, 208, 208, 0.05); } check:active, check:checked { background: rgba(208, 208, 208, 0.05); } check:checked { background: rgba(208, 208, 208, 0.1); -gtk-icon-transform: scale(1); border-color: rgba(208, 208, 208, 0.1); } check:checked:active { background: transparent; } checkbutton label { padding-left: 5px; } checkbutton:disabled { opacity: 0.3; } combobox entry { border-right: 0; border-top-right-radius: 0; border-bottom-right-radius: 0; } combobox entry:focus + button { border: 0.2em solid rgba(208, 208, 208, 0.9); border-left: 0; margin-right: -0.2em; } combobox entry:focus + button:hover { background-clip: padding-box; } combobox entry + button.combo { border-radius: 0 0.5em 0.5em 0; } combobox .linked button.combo { padding: 0.5em; border-radius: 0.5em; } combobox .linked button.combo arrow { min-width: 1em; -gtk-icon-source: -gtk-icontheme("go-down-symbolic"); } .linked.horizontal combobox:not(:first-child) button { border-top-left-radius: 0; border-bottom-left-radius: 0; } .linked.horizontal combobox:not(:last-child) button { border-top-right-radius: 0; border-bottom-right-radius: 0; } .linked.vertical combobox:not(:first-child) button { border-top-left-radius: 0; border-top-right-radius: 0; } .linked.vertical combobox:not(:last-child) button { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } dialog, messagedialog { background: #151515; } dialog .dialog-action-box, messagedialog .dialog-action-box { background: #151515; padding: 0.6em; margin: -0.4em; margin-top: 0; } dialog fontchooser, dialog colorchooser, messagedialog fontchooser, messagedialog colorchooser { padding: 0.6em; } dialog filechooser + .dialog-action-box, messagedialog filechooser + .dialog-action-box { margin: 0; } dialog filechooser #pathbarbox, messagedialog filechooser #pathbarbox { padding: 0.3em; background: #151515; } dialog filechooser treeview, messagedialog filechooser treeview { background: #151515; } entry { padding: 0.4em; border-radius: 0.5em; border: 0.2em solid rgba(208, 208, 208, 0.05); background: rgba(208, 208, 208, 0.05); background-clip: padding-box; } entry:focus { border-color: rgba(208, 208, 208, 0.1); } entry:disabled { opacity: 0.3; } entry image.left { margin-right: 0.6em; } entry image.right { margin-left: 0.6em; } entry selection { color: #d0d0d0; background: rgba(208, 208, 208, 0.6); } .linked.horizontal entry:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .linked.horizontal entry:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .linked.vertical entry:not(:first-child) { border-top-left-radius: 0; border-top-right-radius: 0; } .linked.vertical entry:not(:last-child) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } expander title { color: rgba(208, 208, 208, 0.1); padding: 0.6em; } expander title arrow { -gtk-icon-source: -gtk-icontheme("go-next-symbolic"); min-width: 1em; min-height: 1em; margin-right: 0.6em; } expander title arrow:checked { -gtk-icon-source: -gtk-icontheme("go-down-symbolic"); color: #d0d0d0; } expander title arrow:checked + label { color: #d0d0d0; } frame > border, .frame { border: 1px solid #151515; } scrolledwindow viewport.frame { border-style: none; } headerbar { background: #151515; padding: 0.6em; } headerbar button.titlebutton { padding: 0.8em; } headerbar button.titlebutton:not(.appmenu) { background: transparent; } headerbar button.titlebutton.appmenu:not(:active):not(:checked) { background: transparent; } iconview { background: #151515; padding: 0.3em; } iconview:selected { color: #d0d0d0; background: rgba(208, 208, 208, 0.1); border-radius: 0.5em; } infobar { color: #d0d0d0; padding: 0.3em; background: #151515; } infobar.info { background: rgba(106, 159, 181, 0.6); } infobar.warning { background: rgba(244, 191, 117, 0.6); } infobar.error { background: rgba(172, 65, 65, 0.6); } infobar.question { background: rgba(208, 208, 208, 0.6); } label.dim-label, label:disabled { opacity: 0.3; } label selection { background: rgba(208, 208, 208, 0.1); color: white; } levelbar trough block { padding: 3px; border-radius: 3px; } levelbar trough block.filled { background: rgba(208, 208, 208, 0.1); } levelbar trough block.empty { background: #151515; } levelbar.discrete block:not(:first-child) { margin-left: 5px; } list { background: #151515; } list row { background: #151515; padding: 0.6em; } list row:hover { background: #151515; } list > label { padding: 0.3em; } menu { background: #212121; } menu menuitem { padding: 0.6em; } menu menuitem:hover { background: rgba(208, 208, 208, 0.05); } menubar { background: #151515; } menubar menuitem { padding: 0.6em; } menubar menuitem:hover { background: rgba(208, 208, 208, 0.05); } menubar menuitem check, menubar menuitem radio { margin-right: 0.6em; } notebook stack:only-child { background: #151515; } overshoot.top { 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))); background-size: 100% 60%; background-repeat: no-repeat; background-position: center top; } overshoot.bottom { 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))); background-size: 100% 60%; background-repeat: no-repeat; background-position: center bottom; } overshoot.left { 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))); background-size: 60% 100%; background-repeat: no-repeat; background-position: left center; } overshoot.right { 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))); background-size: 60% 100%; background-repeat: no-repeat; background-position: right center; } .linked.path-bar button image:not(:only-child) { margin-right: 0.6em; } .linked.path-bar button:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .linked.path-bar button:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } placessidebar { background: #151515; } placessidebar list { background: transparent; } placessidebar list row:hover { background: rgba(208, 208, 208, 0.05); } placessidebar list row:active { background: rgba(208, 208, 208, 0.05); } placessidebar list row:selected { background: rgba(208, 208, 208, 0.1); } placessidebar list row .sidebar-icon { margin-right: 0.6em; } placessidebar.frame, placessidebar .frame { border: 0; } popover { background: #212121; box-shadow: 0 0 5px rgba(21, 21, 21, 0.5); border-radius: 0.5em; padding: 0.5em; } popover box modelbutton { padding: 0.4em 1em; margin: 0 -0.5em; border-radius: 0.5em; } popover box modelbutton:hover { background: rgba(208, 208, 208, 0.05); } popover.menu:first-child { border-top-left-radius: 0; } progressbar trough { background: rgba(208, 208, 208, 0.05); } progressbar trough progress { background: rgba(208, 208, 208, 0.1); } radio { -gtk-icon-source: -gtk-recolor(url("../assets/symbolic/radio.symbolic.png")); -gtk-icon-transform: scale(0); color: #d0d0d0; border: 0.2em solid rgba(208, 208, 208, 0.05); border-radius: 100%; padding: 0.1em; min-width: 0.6em; min-height: 0.6em; transition: -gtk-icon-transform 200ms; } radio:hover { border-color: rgba(208, 208, 208, 0.05); } radio:active, radio:checked { background: rgba(208, 208, 208, 0.05); } radio:checked { -gtk-icon-transform: scale(1); border-color: rgba(208, 208, 208, 0.1); background: rgba(208, 208, 208, 0.1); } radiobutton label { padding-left: 5px; } radiobutton:disabled { opacity: 0.38; } revealer frame.app-notification border { border-radius: 0.5em; background: #151515; padding: 0.6em; } revealer.top frame.app-notification border { margin-top: 0.6em; } revealer.right frame.app-notification border { margin-right: 0.6em; } revealer.bottom frame.app-notification border { margin-bottom: 0.6em; } revealer.left frame.app-notification border { margin-left: 0.6em; } rubberband, .rubberband { background-color: rgba(208, 208, 208, 0.05); border: 0.2em solid rgba(208, 208, 208, 0.6); } scale { padding: 8px 0; } scale contents trough { background: #151515; } scale contents trough slider { background: #555555; padding: 8px; margin: -5px; border-radius: 100%; } scale contents trough highlight { background: rgba(208, 208, 208, 0.1); } scale marks mark { padding-top: 5px; } scale marks mark indicator { color: #151515; min-height: 8px; min-width: 1px; } scale:disabled { opacity: 0.3; } scrollbar { -GtkScrollbar-has-backward-stepper: false; -GtkScrollbar-has-forward-stepper: false; } scrollbar slider { padding: 0.2em; border-radius: 1em; background: rgba(208, 208, 208, 0.05); } scrollbar slider:hover { background: rgba(208, 208, 208, 0.05); } scrollbar slider:active { background: rgba(208, 208, 208, 0.1); } separator { background-image: image(rgba(208, 208, 208, 0.05)); background-size: 1px 1px; background-position: center center; min-width: 7px; min-height: 7px; background-repeat: repeat-x; margin: -3px 0; } separator.vertical, .horizontal > separator { background-repeat: repeat-y; margin: 0 -3px; } spinbutton button { border: 0.2em solid transparent; } spinbutton:focus button { border-color: rgba(208, 208, 208, 0.9); } spinbutton:focus button:hover { background-clip: padding-box; } spinbutton:disabled { opacity: 0.3; } spinbutton.horizontal :nth-child(3) { border-radius: 0.5em 0 0 0.5em; border-right: 0; } spinbutton.horizontal :nth-child(4) { border-radius: 0; border-width: 0.2em 0; } spinbutton.horizontal :nth-child(5) { border-radius: 0 0.5em 0.5em 0; border-left: 0; } spinbutton.vertical entry { padding: 0.6em 0; } spinbutton.vertical :nth-child(3) { border-radius: 0.5em 0.5em 0 0; border-bottom: 0; } spinbutton.vertical :nth-child(4) { border-radius: 0; border-width: 0 0.2em; } spinbutton.vertical :nth-child(5) { border-radius: 0 0 0.5em 0.5em; border-top: 0; } .linked.horizontal spinbutton:not(:first-child) :nth-child(3) { border-top-left-radius: 0; } .linked.horizontal spinbutton:not(:first-child) :nth-child(5) { border-bottom-left-radius: 0; } .linked.horizontal spinbutton:not(:last-child) :nth-child(3) { border-top-right-radius: 0; } .linked.horizontal spinbutton:not(:last-child) :nth-child(5) { border-bottom-right-radius: 0; } .linked.vertical spinbutton:not(:first-child) :nth-child(3) { border-top-left-radius: 0; } .linked.vertical spinbutton:not(:first-child) :nth-child(5) { border-top-right-radius: 0; } .linked.vertical spinbutton:not(:last-child) :nth-child(3) { border-bottom-left-radius: 0; } .linked.vertical spinbutton:not(:last-child) :nth-child(5) { border-bottom-right-radius: 0; } @keyframes rotate { to { -gtk-icon-transform: rotate(1turn); } } spinner { -gtk-icon-source: none; } spinner:checked { -gtk-icon-source: -gtk-icontheme("process-working-symbolic"); animation: rotate 1s linear infinite; } spinner:disabled { opacity: 0.3; } switch { background: rgba(208, 208, 208, 0.05); border-radius: 1em; min-width: 3em; border: 0.2em solid transparent; } switch slider { background: rgba(208, 208, 208, 0.1); border-radius: 100%; min-width: 1.5em; min-height: 1.5em; } switch :nth-child(2) { color: transparent; } switch :nth-child(3) { color: rgba(208, 208, 208, 0.1); } switch:checked { background: rgba(208, 208, 208, 0.3); } switch:checked slider { background: rgba(208, 208, 208, 0.9); } switch:checked :nth-child(2) { color: #d0d0d0; } switch:checked :nth-child(3) { color: transparent; } switch:disabled { opacity: 0.3; } switch:hover slider { box-shadow: 0 0 0 0.3em rgba(208, 208, 208, 0.05); } switch:hover:checked slider { box-shadow: 0 0 0 0.3em rgba(208, 208, 208, 0.3); } tabs { background: #151515; } .top > tabs { border-bottom: 0.2em solid #151515; } .top > tabs tab { border-bottom: 0.2em solid transparent; margin-bottom: -0.2em; } .right > tabs { border-left: 0.2em solid #151515; } .right > tabs tab { border-left: 0.2em solid transparent; margin-left: -0.2em; } .bottom > tabs { border-top: 0.2em solid #151515; } .bottom > tabs tab { border-top: 0.2em solid transparent; margin-top: -0.2em; } .left > tabs { border-right: 0.2em solid #151515; } .left > tabs tab { border-right: 0.2em solid transparent; margin-right: -0.2em; } tabs tab { padding: 0.6em; color: rgba(208, 208, 208, 0.1); } header tabs tab:checked { border-color: rgba(208, 208, 208, 0.6); color: #d0d0d0; } header tabs tab:hover:not(:checked) { border-color: rgba(208, 208, 208, 0.05); color: #d0d0d0; } textview { background: rgba(208, 208, 208, 0.05); padding: 0.6em 0.3em; } textview:disabled { opacity: 0.3; } textview selection { background: rgba(208, 208, 208, 0.6); color: #d0d0d0; } toolbar, .toolbar { background: #151515; padding: 0.6em; } toolbar.osd, .toolbar.osd { background: #212121; padding: 0.6em; border-radius: 0.5em; } tooltip { background: #212121; } tooltip > * { margin: 0.6em; } tooltip grid label { margin: 0.3em 0; } tooltip grid image { margin-right: 0.6em; } treeview { -GtkTreeView-expander-size: 16; -GtkTreeView-grid-line-pattern: ""; -GtkTreeView-tree-line-pattern: ""; -GtkTreeView-horizontal-separator: 12; -GtkTreeView-vertical-separator: 12; border-left-color: rgba(208, 208, 208, 0.05); border-top-color: rgba(208, 208, 208, 0.05); } treeview:hover { background: rgba(208, 208, 208, 0.05); } treeview:selected { background: rgba(208, 208, 208, 0.1); color: #d0d0d0; } treeview header button { padding: 0.6em; background: #151515; border-radius: 0; color: rgba(208, 208, 208, 0.1); } treeview header button:hover:not(:active) { color: #d0d0d0; background: #151515; } treeview header button:not(:last-child) { border-right: 1px solid rgba(208, 208, 208, 0.05); } window, assistant { background: #151515; color: #d0d0d0; } window.csd decoration { box-shadow: 0 0 8px 0 black; margin: 8px; } /*# sourceMappingURL=gtk.css.map */ ================================================ FILE: src/usr/share/themes/dark/index.theme ================================================ [Desktop Entry] Type=X-GNOME-Metatheme Name=dark Comment=base16-defaultdark Encoding=UTF-8 [X-GNOME-Metatheme] GtkTheme=dark IconTheme=Papirus CursorTheme=Breeze ButtonLayout=menu ================================================ FILE: src/usr/share/themes/light/gtk-3.0/gtk.css ================================================ @define-color borders whitesmoke; window.background.chromium { background: #d5d5d5; color: #303030; } window.background.chromium button { border: 1px solid #d5d5d5; } window.background.chromium .titlebutton { border: transparent; } window.background.chromium entry:focus { border-color: whitesmoke; } window.background.chromium menu { background: #e3e3e3; } window.background.chromium menu menuitem { color: #303030; } window.background.chromium menu menuitem:hover { background: rgba(48, 48, 48, 0.05); } window.background.chromium menu menuitem:disabled label { color: rgba(48, 48, 48, 0.05); } window.background.chromium headerbar, window.background.chromium menubar { background: whitesmoke; } window.background.chromium headerbar label, window.background.chromium menubar label { color: rgba(48, 48, 48, 0.1); } window.background.chromium headerbar .titlebutton, window.background.chromium menubar .titlebutton { padding: 100px 80px; } window.background.chromium scrollbar button { background: rgba(48, 48, 48, 0.05); color: #303030; } window.background.chromium scrollbar trough { background: transparent; } window.background.chromium scrollbar slider { background: rgba(48, 48, 48, 0.05); } window.background.chromium scrollbar junction { background: rgba(48, 48, 48, 0.05); } window.background.chromium textview { background-color: whitesmoke; color: #303030; } #MozillaGtkWidget { background: whitesmoke; color: #303030; } #MozillaGtkWidget menubar { color: rgba(48, 48, 48, 0.1); background: whitesmoke; } #MozillaGtkWidget text { background: #e3e3e3; } #MozillaGtkWidget menu { background: #d0d0d0; } #MozillaGtkWidget menu :disabled { color: rgba(48, 48, 48, 0.05); } #MozillaGtkWidget menu separator { padding: 8px; } #MozillaGtkWidget border { border: 1px solid #d0d0d0; } .nemo-window .nemo-places-sidebar { background: whitesmoke; } .nemo-window .primary-toolbar { background: whitesmoke; padding: 0.3em; } .nemo-window menubar ~ separator { min-width: 0; min-height: 0; } .nemo-window .toolbar { background: whitesmoke; margin: -0.1em; } .nemo-window .nemo-window-pane :selected { background: rgba(48, 48, 48, 0.6); } .nemo-window .nemo-window-pane :active { background: rgba(48, 48, 48, 0.6); } actionbar { padding: 0.6em; background: whitesmoke; border-top: 1px solid rgba(48, 48, 48, 0.05); } button { transition: background-color 100ms ease-in; background: rgba(48, 48, 48, 0.05); padding: 0.6em; border-radius: 0.5em; } button:hover:not(:active) { background: rgba(48, 48, 48, 0.1); } button:disabled { opacity: 0.3; } button:checked, button.suggested-action { background: rgba(48, 48, 48, 0.1); color: #303030; } button:checked:hover:not(:active), button.suggested-action:hover:not(:active) { background: rgba(48, 48, 48, 0.1); } button.image-button:not(.text-button) { padding: 0.8em; } button.destructive-action { background: rgba(172, 65, 65, 0.6); color: #303030; } button.destructive-action:hover:not(:active) { background: rgba(172, 65, 65, 0.9); } button.circular { border-radius: 100%; padding: 0.8em; } button.flat { background: transparent; } button.flat:hover { background: rgba(48, 48, 48, 0.05); } button.flat:active, button.flat:checked { background: rgba(48, 48, 48, 0.05); } label + button { margin-left: 0.6em; } button.small-button image:only-child { padding: 0.3em; } .linked.horizontal button:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .linked.horizontal button:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .linked.vertical button:not(:first-child) { border-top-left-radius: 0; border-top-right-radius: 0; } .linked.vertical button:not(:last-child) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } calendar { background: whitesmoke; } calendar.button { background: whitesmoke; color: rgba(48, 48, 48, 0.1); } calendar.button:hover { color: #303030; } calendar.button:disabled { color: rgba(48, 48, 48, 0.05); } calendar.header { background: whitesmoke; } calendar.highlight { color: rgba(48, 48, 48, 0.1); } calendar:selected { color: #303030; border-radius: 0.5em; background: rgba(48, 48, 48, 0.1); } calendar:indeterminate { color: rgba(48, 48, 48, 0.05); } check { -gtk-icon-source: -gtk-recolor(url("../assets/symbolic/check.symbolic.png")); -gtk-icon-transform: scale(0); color: #303030; border: 0.2em solid rgba(48, 48, 48, 0.05); border-radius: 0.3em; padding: 0.1em; min-width: 0.7em; min-height: 0.7em; transition: -gtk-icon-transform 200ms; } check:hover { border-color: rgba(48, 48, 48, 0.05); } check:active, check:checked { background: rgba(48, 48, 48, 0.05); } check:checked { background: rgba(48, 48, 48, 0.1); -gtk-icon-transform: scale(1); border-color: rgba(48, 48, 48, 0.1); } check:checked:active { background: transparent; } checkbutton label { padding-left: 5px; } checkbutton:disabled { opacity: 0.3; } combobox entry { border-right: 0; border-top-right-radius: 0; border-bottom-right-radius: 0; } combobox entry:focus + button { border: 0.2em solid rgba(48, 48, 48, 0.9); border-left: 0; margin-right: -0.2em; } combobox entry:focus + button:hover { background-clip: padding-box; } combobox entry + button.combo { border-radius: 0 0.5em 0.5em 0; } combobox .linked button.combo { padding: 0.5em; border-radius: 0.5em; } combobox .linked button.combo arrow { min-width: 1em; -gtk-icon-source: -gtk-icontheme("go-down-symbolic"); } .linked.horizontal combobox:not(:first-child) button { border-top-left-radius: 0; border-bottom-left-radius: 0; } .linked.horizontal combobox:not(:last-child) button { border-top-right-radius: 0; border-bottom-right-radius: 0; } .linked.vertical combobox:not(:first-child) button { border-top-left-radius: 0; border-top-right-radius: 0; } .linked.vertical combobox:not(:last-child) button { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } dialog, messagedialog { background: whitesmoke; } dialog .dialog-action-box, messagedialog .dialog-action-box { background: whitesmoke; padding: 0.6em; margin: -0.4em; margin-top: 0; } dialog fontchooser, dialog colorchooser, messagedialog fontchooser, messagedialog colorchooser { padding: 0.6em; } dialog filechooser + .dialog-action-box, messagedialog filechooser + .dialog-action-box { margin: 0; } dialog filechooser #pathbarbox, messagedialog filechooser #pathbarbox { padding: 0.3em; background: whitesmoke; } dialog filechooser treeview, messagedialog filechooser treeview { background: whitesmoke; } entry { padding: 0.4em; border-radius: 0.5em; border: 0.2em solid rgba(48, 48, 48, 0.05); background: rgba(48, 48, 48, 0.05); background-clip: padding-box; } entry:focus { border-color: rgba(48, 48, 48, 0.1); } entry:disabled { opacity: 0.3; } entry image.left { margin-right: 0.6em; } entry image.right { margin-left: 0.6em; } entry selection { color: #303030; background: rgba(48, 48, 48, 0.6); } .linked.horizontal entry:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .linked.horizontal entry:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .linked.vertical entry:not(:first-child) { border-top-left-radius: 0; border-top-right-radius: 0; } .linked.vertical entry:not(:last-child) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } expander title { color: rgba(48, 48, 48, 0.1); padding: 0.6em; } expander title arrow { -gtk-icon-source: -gtk-icontheme("go-next-symbolic"); min-width: 1em; min-height: 1em; margin-right: 0.6em; } expander title arrow:checked { -gtk-icon-source: -gtk-icontheme("go-down-symbolic"); color: #303030; } expander title arrow:checked + label { color: #303030; } frame > border, .frame { border: 1px solid whitesmoke; } scrolledwindow viewport.frame { border-style: none; } headerbar { background: whitesmoke; padding: 0.6em; } headerbar button.titlebutton { padding: 0.8em; } headerbar button.titlebutton:not(.appmenu) { background: transparent; } headerbar button.titlebutton.appmenu:not(:active):not(:checked) { background: transparent; } iconview { background: whitesmoke; padding: 0.3em; } iconview:selected { color: #303030; background: rgba(48, 48, 48, 0.1); border-radius: 0.5em; } infobar { color: #303030; padding: 0.3em; background: whitesmoke; } infobar.info { background: rgba(106, 159, 181, 0.6); } infobar.warning { background: rgba(244, 191, 117, 0.6); } infobar.error { background: rgba(172, 65, 65, 0.6); } infobar.question { background: rgba(48, 48, 48, 0.6); } label.dim-label, label:disabled { opacity: 0.3; } label selection { background: rgba(48, 48, 48, 0.1); color: #b0b0b0; } levelbar trough block { padding: 3px; border-radius: 3px; } levelbar trough block.filled { background: rgba(48, 48, 48, 0.1); } levelbar trough block.empty { background: whitesmoke; } levelbar.discrete block:not(:first-child) { margin-left: 5px; } list { background: whitesmoke; } list row { background: whitesmoke; padding: 0.6em; } list row:hover { background: whitesmoke; } list > label { padding: 0.3em; } menu { background: #d0d0d0; } menu menuitem { padding: 0.6em; } menu menuitem:hover { background: rgba(48, 48, 48, 0.05); } menubar { background: whitesmoke; } menubar menuitem { padding: 0.6em; } menubar menuitem:hover { background: rgba(48, 48, 48, 0.05); } menubar menuitem check, menubar menuitem radio { margin-right: 0.6em; } notebook stack:only-child { background: whitesmoke; } overshoot.top { 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))); background-size: 100% 60%; background-repeat: no-repeat; background-position: center top; } overshoot.bottom { 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))); background-size: 100% 60%; background-repeat: no-repeat; background-position: center bottom; } overshoot.left { 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))); background-size: 60% 100%; background-repeat: no-repeat; background-position: left center; } overshoot.right { 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))); background-size: 60% 100%; background-repeat: no-repeat; background-position: right center; } .linked.path-bar button image:not(:only-child) { margin-right: 0.6em; } .linked.path-bar button:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .linked.path-bar button:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } placessidebar { background: whitesmoke; } placessidebar list { background: transparent; } placessidebar list row:hover { background: rgba(48, 48, 48, 0.05); } placessidebar list row:active { background: rgba(48, 48, 48, 0.05); } placessidebar list row:selected { background: rgba(48, 48, 48, 0.1); } placessidebar list row .sidebar-icon { margin-right: 0.6em; } placessidebar.frame, placessidebar .frame { border: 0; } popover { background: #d0d0d0; box-shadow: 0 0 5px rgba(245, 245, 245, 0.5); border-radius: 0.5em; padding: 0.5em; } popover box modelbutton { padding: 0.4em 1em; margin: 0 -0.5em; border-radius: 0.5em; } popover box modelbutton:hover { background: rgba(48, 48, 48, 0.05); } popover.menu:first-child { border-top-left-radius: 0; } progressbar trough { background: rgba(48, 48, 48, 0.05); } progressbar trough progress { background: rgba(48, 48, 48, 0.1); } radio { -gtk-icon-source: -gtk-recolor(url("../assets/symbolic/radio.symbolic.png")); -gtk-icon-transform: scale(0); color: #303030; border: 0.2em solid rgba(48, 48, 48, 0.05); border-radius: 100%; padding: 0.1em; min-width: 0.6em; min-height: 0.6em; transition: -gtk-icon-transform 200ms; } radio:hover { border-color: rgba(48, 48, 48, 0.05); } radio:active, radio:checked { background: rgba(48, 48, 48, 0.05); } radio:checked { -gtk-icon-transform: scale(1); border-color: rgba(48, 48, 48, 0.1); background: rgba(48, 48, 48, 0.1); } radiobutton label { padding-left: 5px; } radiobutton:disabled { opacity: 0.38; } revealer frame.app-notification border { border-radius: 0.5em; background: whitesmoke; padding: 0.6em; } revealer.top frame.app-notification border { margin-top: 0.6em; } revealer.right frame.app-notification border { margin-right: 0.6em; } revealer.bottom frame.app-notification border { margin-bottom: 0.6em; } revealer.left frame.app-notification border { margin-left: 0.6em; } rubberband, .rubberband { background-color: rgba(48, 48, 48, 0.05); border: 0.2em solid rgba(48, 48, 48, 0.6); } scale { padding: 8px 0; } scale contents trough { background: whitesmoke; } scale contents trough slider { background: white; padding: 8px; margin: -5px; border-radius: 100%; } scale contents trough highlight { background: rgba(48, 48, 48, 0.1); } scale marks mark { padding-top: 5px; } scale marks mark indicator { color: whitesmoke; min-height: 8px; min-width: 1px; } scale:disabled { opacity: 0.3; } scrollbar { -GtkScrollbar-has-backward-stepper: false; -GtkScrollbar-has-forward-stepper: false; } scrollbar slider { padding: 0.2em; border-radius: 1em; background: rgba(48, 48, 48, 0.05); } scrollbar slider:hover { background: rgba(48, 48, 48, 0.05); } scrollbar slider:active { background: rgba(48, 48, 48, 0.1); } separator { background-image: image(rgba(48, 48, 48, 0.05)); background-size: 1px 1px; background-position: center center; min-width: 7px; min-height: 7px; background-repeat: repeat-x; margin: -3px 0; } separator.vertical, .horizontal > separator { background-repeat: repeat-y; margin: 0 -3px; } spinbutton button { border: 0.2em solid transparent; } spinbutton:focus button { border-color: rgba(48, 48, 48, 0.9); } spinbutton:focus button:hover { background-clip: padding-box; } spinbutton:disabled { opacity: 0.3; } spinbutton.horizontal :nth-child(3) { border-radius: 0.5em 0 0 0.5em; border-right: 0; } spinbutton.horizontal :nth-child(4) { border-radius: 0; border-width: 0.2em 0; } spinbutton.horizontal :nth-child(5) { border-radius: 0 0.5em 0.5em 0; border-left: 0; } spinbutton.vertical entry { padding: 0.6em 0; } spinbutton.vertical :nth-child(3) { border-radius: 0.5em 0.5em 0 0; border-bottom: 0; } spinbutton.vertical :nth-child(4) { border-radius: 0; border-width: 0 0.2em; } spinbutton.vertical :nth-child(5) { border-radius: 0 0 0.5em 0.5em; border-top: 0; } .linked.horizontal spinbutton:not(:first-child) :nth-child(3) { border-top-left-radius: 0; } .linked.horizontal spinbutton:not(:first-child) :nth-child(5) { border-bottom-left-radius: 0; } .linked.horizontal spinbutton:not(:last-child) :nth-child(3) { border-top-right-radius: 0; } .linked.horizontal spinbutton:not(:last-child) :nth-child(5) { border-bottom-right-radius: 0; } .linked.vertical spinbutton:not(:first-child) :nth-child(3) { border-top-left-radius: 0; } .linked.vertical spinbutton:not(:first-child) :nth-child(5) { border-top-right-radius: 0; } .linked.vertical spinbutton:not(:last-child) :nth-child(3) { border-bottom-left-radius: 0; } .linked.vertical spinbutton:not(:last-child) :nth-child(5) { border-bottom-right-radius: 0; } @keyframes rotate { to { -gtk-icon-transform: rotate(1turn); } } spinner { -gtk-icon-source: none; } spinner:checked { -gtk-icon-source: -gtk-icontheme("process-working-symbolic"); animation: rotate 1s linear infinite; } spinner:disabled { opacity: 0.3; } switch { background: rgba(48, 48, 48, 0.05); border-radius: 1em; min-width: 3em; border: 0.2em solid transparent; } switch slider { background: rgba(48, 48, 48, 0.1); border-radius: 100%; min-width: 1.5em; min-height: 1.5em; } switch :nth-child(2) { color: transparent; } switch :nth-child(3) { color: rgba(48, 48, 48, 0.1); } switch:checked { background: rgba(48, 48, 48, 0.3); } switch:checked slider { background: rgba(48, 48, 48, 0.9); } switch:checked :nth-child(2) { color: #303030; } switch:checked :nth-child(3) { color: transparent; } switch:disabled { opacity: 0.3; } switch:hover slider { box-shadow: 0 0 0 0.3em rgba(48, 48, 48, 0.05); } switch:hover:checked slider { box-shadow: 0 0 0 0.3em rgba(48, 48, 48, 0.3); } tabs { background: whitesmoke; } .top > tabs { border-bottom: 0.2em solid whitesmoke; } .top > tabs tab { border-bottom: 0.2em solid transparent; margin-bottom: -0.2em; } .right > tabs { border-left: 0.2em solid whitesmoke; } .right > tabs tab { border-left: 0.2em solid transparent; margin-left: -0.2em; } .bottom > tabs { border-top: 0.2em solid whitesmoke; } .bottom > tabs tab { border-top: 0.2em solid transparent; margin-top: -0.2em; } .left > tabs { border-right: 0.2em solid whitesmoke; } .left > tabs tab { border-right: 0.2em solid transparent; margin-right: -0.2em; } tabs tab { padding: 0.6em; color: rgba(48, 48, 48, 0.1); } header tabs tab:checked { border-color: rgba(48, 48, 48, 0.6); color: #303030; } header tabs tab:hover:not(:checked) { border-color: rgba(48, 48, 48, 0.05); color: #303030; } textview { background: rgba(48, 48, 48, 0.05); padding: 0.6em 0.3em; } textview:disabled { opacity: 0.3; } textview selection { background: rgba(48, 48, 48, 0.6); color: #303030; } toolbar, .toolbar { background: whitesmoke; padding: 0.6em; } toolbar.osd, .toolbar.osd { background: #d0d0d0; padding: 0.6em; border-radius: 0.5em; } tooltip { background: #d0d0d0; } tooltip > * { margin: 0.6em; } tooltip grid label { margin: 0.3em 0; } tooltip grid image { margin-right: 0.6em; } treeview { -GtkTreeView-expander-size: 16; -GtkTreeView-grid-line-pattern: ""; -GtkTreeView-tree-line-pattern: ""; -GtkTreeView-horizontal-separator: 12; -GtkTreeView-vertical-separator: 12; border-left-color: rgba(48, 48, 48, 0.05); border-top-color: rgba(48, 48, 48, 0.05); } treeview:hover { background: rgba(48, 48, 48, 0.05); } treeview:selected { background: rgba(48, 48, 48, 0.1); color: #303030; } treeview header button { padding: 0.6em; background: whitesmoke; border-radius: 0; color: rgba(48, 48, 48, 0.1); } treeview header button:hover:not(:active) { color: #303030; background: whitesmoke; } treeview header button:not(:last-child) { border-right: 1px solid rgba(48, 48, 48, 0.05); } window, assistant { background: whitesmoke; color: #303030; } window.csd decoration { box-shadow: 0 0 8px 0 black; margin: 8px; } /*# sourceMappingURL=gtk.css.map */ ================================================ FILE: src/usr/share/themes/light/index.theme ================================================ [Desktop Entry] Type=X-GNOME-Metatheme Name=light Comment=base16-defaultlight Encoding=UTF-8 [X-GNOME-Metatheme] GtkTheme=light IconTheme=Papirus CursorTheme=Breeze ButtonLayout=menu ================================================ FILE: src/usr/share/themes/solarized/gtk-3.0/gtk.css ================================================ @define-color borders #002b36; window.background.chromium { background: #074453; color: #93a1a1; } window.background.chromium button { border: 1px solid #074453; } window.background.chromium .titlebutton { border: transparent; } window.background.chromium entry:focus { border-color: #002b36; } window.background.chromium menu { background: #043643; } window.background.chromium menu menuitem { color: #93a1a1; } window.background.chromium menu menuitem:hover { background: rgba(147, 161, 161, 0.05); } window.background.chromium menu menuitem:disabled label { color: rgba(147, 161, 161, 0.05); } window.background.chromium headerbar, window.background.chromium menubar { background: #002b36; } window.background.chromium headerbar label, window.background.chromium menubar label { color: rgba(147, 161, 161, 0.1); } window.background.chromium headerbar .titlebutton, window.background.chromium menubar .titlebutton { padding: 100px 80px; } window.background.chromium scrollbar button { background: rgba(147, 161, 161, 0.05); color: #93a1a1; } window.background.chromium scrollbar trough { background: transparent; } window.background.chromium scrollbar slider { background: rgba(147, 161, 161, 0.05); } window.background.chromium scrollbar junction { background: rgba(147, 161, 161, 0.05); } window.background.chromium textview { background-color: #002b36; color: #93a1a1; } #MozillaGtkWidget { background: #002b36; color: #93a1a1; } #MozillaGtkWidget menubar { color: rgba(147, 161, 161, 0.1); background: #002b36; } #MozillaGtkWidget text { background: #043643; } #MozillaGtkWidget menu { background: #063c4a; } #MozillaGtkWidget menu :disabled { color: rgba(147, 161, 161, 0.05); } #MozillaGtkWidget menu separator { padding: 8px; } #MozillaGtkWidget border { border: 1px solid #063c4a; } .nemo-window .nemo-places-sidebar { background: #002b36; } .nemo-window .primary-toolbar { background: #002b36; padding: 0.3em; } .nemo-window menubar ~ separator { min-width: 0; min-height: 0; } .nemo-window .toolbar { background: #002b36; margin: -0.1em; } .nemo-window .nemo-window-pane :selected { background: rgba(147, 161, 161, 0.6); } .nemo-window .nemo-window-pane :active { background: rgba(147, 161, 161, 0.6); } actionbar { padding: 0.6em; background: #002b36; border-top: 1px solid rgba(147, 161, 161, 0.05); } button { transition: background-color 100ms ease-in; background: rgba(147, 161, 161, 0.05); padding: 0.6em; border-radius: 0.5em; } button:hover:not(:active) { background: rgba(147, 161, 161, 0.1); } button:disabled { opacity: 0.3; } button:checked, button.suggested-action { background: rgba(147, 161, 161, 0.1); color: #93a1a1; } button:checked:hover:not(:active), button.suggested-action:hover:not(:active) { background: rgba(147, 161, 161, 0.1); } button.image-button:not(.text-button) { padding: 0.8em; } button.destructive-action { background: rgba(220, 50, 47, 0.6); color: #93a1a1; } button.destructive-action:hover:not(:active) { background: rgba(220, 50, 47, 0.9); } button.circular { border-radius: 100%; padding: 0.8em; } button.flat { background: transparent; } button.flat:hover { background: rgba(147, 161, 161, 0.05); } button.flat:active, button.flat:checked { background: rgba(147, 161, 161, 0.05); } label + button { margin-left: 0.6em; } button.small-button image:only-child { padding: 0.3em; } .linked.horizontal button:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .linked.horizontal button:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .linked.vertical button:not(:first-child) { border-top-left-radius: 0; border-top-right-radius: 0; } .linked.vertical button:not(:last-child) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } calendar { background: #002b36; } calendar.button { background: #002b36; color: rgba(147, 161, 161, 0.1); } calendar.button:hover { color: #93a1a1; } calendar.button:disabled { color: rgba(147, 161, 161, 0.05); } calendar.header { background: #002b36; } calendar.highlight { color: rgba(147, 161, 161, 0.1); } calendar:selected { color: #93a1a1; border-radius: 0.5em; background: rgba(147, 161, 161, 0.1); } calendar:indeterminate { color: rgba(147, 161, 161, 0.05); } check { -gtk-icon-source: -gtk-recolor(url("../assets/symbolic/check.symbolic.png")); -gtk-icon-transform: scale(0); color: #93a1a1; border: 0.2em solid rgba(147, 161, 161, 0.05); border-radius: 0.3em; padding: 0.1em; min-width: 0.7em; min-height: 0.7em; transition: -gtk-icon-transform 200ms; } check:hover { border-color: rgba(147, 161, 161, 0.05); } check:active, check:checked { background: rgba(147, 161, 161, 0.05); } check:checked { background: rgba(147, 161, 161, 0.1); -gtk-icon-transform: scale(1); border-color: rgba(147, 161, 161, 0.1); } check:checked:active { background: transparent; } checkbutton label { padding-left: 5px; } checkbutton:disabled { opacity: 0.3; } combobox entry { border-right: 0; border-top-right-radius: 0; border-bottom-right-radius: 0; } combobox entry:focus + button { border: 0.2em solid rgba(147, 161, 161, 0.9); border-left: 0; margin-right: -0.2em; } combobox entry:focus + button:hover { background-clip: padding-box; } combobox entry + button.combo { border-radius: 0 0.5em 0.5em 0; } combobox .linked button.combo { padding: 0.5em; border-radius: 0.5em; } combobox .linked button.combo arrow { min-width: 1em; -gtk-icon-source: -gtk-icontheme("go-down-symbolic"); } .linked.horizontal combobox:not(:first-child) button { border-top-left-radius: 0; border-bottom-left-radius: 0; } .linked.horizontal combobox:not(:last-child) button { border-top-right-radius: 0; border-bottom-right-radius: 0; } .linked.vertical combobox:not(:first-child) button { border-top-left-radius: 0; border-top-right-radius: 0; } .linked.vertical combobox:not(:last-child) button { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } dialog, messagedialog { background: #002b36; } dialog .dialog-action-box, messagedialog .dialog-action-box { background: #002b36; padding: 0.6em; margin: -0.4em; margin-top: 0; } dialog fontchooser, dialog colorchooser, messagedialog fontchooser, messagedialog colorchooser { padding: 0.6em; } dialog filechooser + .dialog-action-box, messagedialog filechooser + .dialog-action-box { margin: 0; } dialog filechooser #pathbarbox, messagedialog filechooser #pathbarbox { padding: 0.3em; background: #002b36; } dialog filechooser treeview, messagedialog filechooser treeview { background: #002b36; } entry { padding: 0.4em; border-radius: 0.5em; border: 0.2em solid rgba(147, 161, 161, 0.05); background: rgba(147, 161, 161, 0.05); background-clip: padding-box; } entry:focus { border-color: rgba(147, 161, 161, 0.1); } entry:disabled { opacity: 0.3; } entry image.left { margin-right: 0.6em; } entry image.right { margin-left: 0.6em; } entry selection { color: #93a1a1; background: rgba(147, 161, 161, 0.6); } .linked.horizontal entry:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .linked.horizontal entry:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .linked.vertical entry:not(:first-child) { border-top-left-radius: 0; border-top-right-radius: 0; } .linked.vertical entry:not(:last-child) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } expander title { color: rgba(147, 161, 161, 0.1); padding: 0.6em; } expander title arrow { -gtk-icon-source: -gtk-icontheme("go-next-symbolic"); min-width: 1em; min-height: 1em; margin-right: 0.6em; } expander title arrow:checked { -gtk-icon-source: -gtk-icontheme("go-down-symbolic"); color: #93a1a1; } expander title arrow:checked + label { color: #93a1a1; } frame > border, .frame { border: 1px solid #002b36; } scrolledwindow viewport.frame { border-style: none; } headerbar { background: #002b36; padding: 0.6em; } headerbar button.titlebutton { padding: 0.8em; } headerbar button.titlebutton:not(.appmenu) { background: transparent; } headerbar button.titlebutton.appmenu:not(:active):not(:checked) { background: transparent; } iconview { background: #002b36; padding: 0.3em; } iconview:selected { color: #93a1a1; background: rgba(147, 161, 161, 0.1); border-radius: 0.5em; } infobar { color: #93a1a1; padding: 0.3em; background: #002b36; } infobar.info { background: rgba(38, 139, 210, 0.6); } infobar.warning { background: rgba(181, 137, 0, 0.6); } infobar.error { background: rgba(220, 50, 47, 0.6); } infobar.question { background: rgba(147, 161, 161, 0.6); } label.dim-label, label:disabled { opacity: 0.3; } label selection { background: rgba(147, 161, 161, 0.1); color: white; } levelbar trough block { padding: 3px; border-radius: 3px; } levelbar trough block.filled { background: rgba(147, 161, 161, 0.1); } levelbar trough block.empty { background: #002b36; } levelbar.discrete block:not(:first-child) { margin-left: 5px; } list { background: #002b36; } list row { background: #002b36; padding: 0.6em; } list row:hover { background: #002b36; } list > label { padding: 0.3em; } menu { background: #063c4a; } menu menuitem { padding: 0.6em; } menu menuitem:hover { background: rgba(147, 161, 161, 0.05); } menubar { background: #002b36; } menubar menuitem { padding: 0.6em; } menubar menuitem:hover { background: rgba(147, 161, 161, 0.05); } menubar menuitem check, menubar menuitem radio { margin-right: 0.6em; } notebook stack:only-child { background: #002b36; } overshoot.top { 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))); background-size: 100% 60%; background-repeat: no-repeat; background-position: center top; } overshoot.bottom { 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))); background-size: 100% 60%; background-repeat: no-repeat; background-position: center bottom; } overshoot.left { 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))); background-size: 60% 100%; background-repeat: no-repeat; background-position: left center; } overshoot.right { 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))); background-size: 60% 100%; background-repeat: no-repeat; background-position: right center; } .linked.path-bar button image:not(:only-child) { margin-right: 0.6em; } .linked.path-bar button:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .linked.path-bar button:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } placessidebar { background: #002b36; } placessidebar list { background: transparent; } placessidebar list row:hover { background: rgba(147, 161, 161, 0.05); } placessidebar list row:active { background: rgba(147, 161, 161, 0.05); } placessidebar list row:selected { background: rgba(147, 161, 161, 0.1); } placessidebar list row .sidebar-icon { margin-right: 0.6em; } placessidebar.frame, placessidebar .frame { border: 0; } popover { background: #063c4a; box-shadow: 0 0 5px rgba(0, 43, 54, 0.5); border-radius: 0.5em; padding: 0.5em; } popover box modelbutton { padding: 0.4em 1em; margin: 0 -0.5em; border-radius: 0.5em; } popover box modelbutton:hover { background: rgba(147, 161, 161, 0.05); } popover.menu:first-child { border-top-left-radius: 0; } progressbar trough { background: rgba(147, 161, 161, 0.05); } progressbar trough progress { background: rgba(147, 161, 161, 0.1); } radio { -gtk-icon-source: -gtk-recolor(url("../assets/symbolic/radio.symbolic.png")); -gtk-icon-transform: scale(0); color: #93a1a1; border: 0.2em solid rgba(147, 161, 161, 0.05); border-radius: 100%; padding: 0.1em; min-width: 0.6em; min-height: 0.6em; transition: -gtk-icon-transform 200ms; } radio:hover { border-color: rgba(147, 161, 161, 0.05); } radio:active, radio:checked { background: rgba(147, 161, 161, 0.05); } radio:checked { -gtk-icon-transform: scale(1); border-color: rgba(147, 161, 161, 0.1); background: rgba(147, 161, 161, 0.1); } radiobutton label { padding-left: 5px; } radiobutton:disabled { opacity: 0.38; } revealer frame.app-notification border { border-radius: 0.5em; background: #002b36; padding: 0.6em; } revealer.top frame.app-notification border { margin-top: 0.6em; } revealer.right frame.app-notification border { margin-right: 0.6em; } revealer.bottom frame.app-notification border { margin-bottom: 0.6em; } revealer.left frame.app-notification border { margin-left: 0.6em; } rubberband, .rubberband { background-color: rgba(147, 161, 161, 0.05); border: 0.2em solid rgba(147, 161, 161, 0.6); } scale { padding: 8px 0; } scale contents trough { background: #002b36; } scale contents trough slider { background: #0091b6; padding: 8px; margin: -5px; border-radius: 100%; } scale contents trough highlight { background: rgba(147, 161, 161, 0.1); } scale marks mark { padding-top: 5px; } scale marks mark indicator { color: #002b36; min-height: 8px; min-width: 1px; } scale:disabled { opacity: 0.3; } scrollbar { -GtkScrollbar-has-backward-stepper: false; -GtkScrollbar-has-forward-stepper: false; } scrollbar slider { padding: 0.2em; border-radius: 1em; background: rgba(147, 161, 161, 0.05); } scrollbar slider:hover { background: rgba(147, 161, 161, 0.05); } scrollbar slider:active { background: rgba(147, 161, 161, 0.1); } separator { background-image: image(rgba(147, 161, 161, 0.05)); background-size: 1px 1px; background-position: center center; min-width: 7px; min-height: 7px; background-repeat: repeat-x; margin: -3px 0; } separator.vertical, .horizontal > separator { background-repeat: repeat-y; margin: 0 -3px; } spinbutton button { border: 0.2em solid transparent; } spinbutton:focus button { border-color: rgba(147, 161, 161, 0.9); } spinbutton:focus button:hover { background-clip: padding-box; } spinbutton:disabled { opacity: 0.3; } spinbutton.horizontal :nth-child(3) { border-radius: 0.5em 0 0 0.5em; border-right: 0; } spinbutton.horizontal :nth-child(4) { border-radius: 0; border-width: 0.2em 0; } spinbutton.horizontal :nth-child(5) { border-radius: 0 0.5em 0.5em 0; border-left: 0; } spinbutton.vertical entry { padding: 0.6em 0; } spinbutton.vertical :nth-child(3) { border-radius: 0.5em 0.5em 0 0; border-bottom: 0; } spinbutton.vertical :nth-child(4) { border-radius: 0; border-width: 0 0.2em; } spinbutton.vertical :nth-child(5) { border-radius: 0 0 0.5em 0.5em; border-top: 0; } .linked.horizontal spinbutton:not(:first-child) :nth-child(3) { border-top-left-radius: 0; } .linked.horizontal spinbutton:not(:first-child) :nth-child(5) { border-bottom-left-radius: 0; } .linked.horizontal spinbutton:not(:last-child) :nth-child(3) { border-top-right-radius: 0; } .linked.horizontal spinbutton:not(:last-child) :nth-child(5) { border-bottom-right-radius: 0; } .linked.vertical spinbutton:not(:first-child) :nth-child(3) { border-top-left-radius: 0; } .linked.vertical spinbutton:not(:first-child) :nth-child(5) { border-top-right-radius: 0; } .linked.vertical spinbutton:not(:last-child) :nth-child(3) { border-bottom-left-radius: 0; } .linked.vertical spinbutton:not(:last-child) :nth-child(5) { border-bottom-right-radius: 0; } @keyframes rotate { to { -gtk-icon-transform: rotate(1turn); } } spinner { -gtk-icon-source: none; } spinner:checked { -gtk-icon-source: -gtk-icontheme("process-working-symbolic"); animation: rotate 1s linear infinite; } spinner:disabled { opacity: 0.3; } switch { background: rgba(147, 161, 161, 0.05); border-radius: 1em; min-width: 3em; border: 0.2em solid transparent; } switch slider { background: rgba(147, 161, 161, 0.1); border-radius: 100%; min-width: 1.5em; min-height: 1.5em; } switch :nth-child(2) { color: transparent; } switch :nth-child(3) { color: rgba(147, 161, 161, 0.1); } switch:checked { background: rgba(147, 161, 161, 0.3); } switch:checked slider { background: rgba(147, 161, 161, 0.9); } switch:checked :nth-child(2) { color: #93a1a1; } switch:checked :nth-child(3) { color: transparent; } switch:disabled { opacity: 0.3; } switch:hover slider { box-shadow: 0 0 0 0.3em rgba(147, 161, 161, 0.05); } switch:hover:checked slider { box-shadow: 0 0 0 0.3em rgba(147, 161, 161, 0.3); } tabs { background: #002b36; } .top > tabs { border-bottom: 0.2em solid #002b36; } .top > tabs tab { border-bottom: 0.2em solid transparent; margin-bottom: -0.2em; } .right > tabs { border-left: 0.2em solid #002b36; } .right > tabs tab { border-left: 0.2em solid transparent; margin-left: -0.2em; } .bottom > tabs { border-top: 0.2em solid #002b36; } .bottom > tabs tab { border-top: 0.2em solid transparent; margin-top: -0.2em; } .left > tabs { border-right: 0.2em solid #002b36; } .left > tabs tab { border-right: 0.2em solid transparent; margin-right: -0.2em; } tabs tab { padding: 0.6em; color: rgba(147, 161, 161, 0.1); } header tabs tab:checked { border-color: rgba(147, 161, 161, 0.6); color: #93a1a1; } header tabs tab:hover:not(:checked) { border-color: rgba(147, 161, 161, 0.05); color: #93a1a1; } textview { background: rgba(147, 161, 161, 0.05); padding: 0.6em 0.3em; } textview:disabled { opacity: 0.3; } textview selection { background: rgba(147, 161, 161, 0.6); color: #93a1a1; } toolbar, .toolbar { background: #002b36; padding: 0.6em; } toolbar.osd, .toolbar.osd { background: #063c4a; padding: 0.6em; border-radius: 0.5em; } tooltip { background: #063c4a; } tooltip > * { margin: 0.6em; } tooltip grid label { margin: 0.3em 0; } tooltip grid image { margin-right: 0.6em; } treeview { -GtkTreeView-expander-size: 16; -GtkTreeView-grid-line-pattern: ""; -GtkTreeView-tree-line-pattern: ""; -GtkTreeView-horizontal-separator: 12; -GtkTreeView-vertical-separator: 12; border-left-color: rgba(147, 161, 161, 0.05); border-top-color: rgba(147, 161, 161, 0.05); } treeview:hover { background: rgba(147, 161, 161, 0.05); } treeview:selected { background: rgba(147, 161, 161, 0.1); color: #93a1a1; } treeview header button { padding: 0.6em; background: #002b36; border-radius: 0; color: rgba(147, 161, 161, 0.1); } treeview header button:hover:not(:active) { color: #93a1a1; background: #002b36; } treeview header button:not(:last-child) { border-right: 1px solid rgba(147, 161, 161, 0.05); } window, assistant { background: #002b36; color: #93a1a1; } window.csd decoration { box-shadow: 0 0 8px 0 black; margin: 8px; } /*# sourceMappingURL=gtk.css.map */ ================================================ FILE: src/usr/share/themes/solarized/index.theme ================================================ [Desktop Entry] Type=X-GNOME-Metatheme Name=solarized Comment=solarized Encoding=UTF-8 [X-GNOME-Metatheme] GtkTheme=solarized IconTheme=Papirus CursorTheme=Breeze ButtonLayout=menu ================================================ FILE: src/usr/share/xsessions/calla.desktop ================================================ [Desktop Entry] Name=Calla Comment=Calla Desktop Environment Exec=calla Icon= Type=Application