diff --git a/.busted b/.busted new file mode 100644 index 0000000..e8e9cfb --- /dev/null +++ b/.busted @@ -0,0 +1,13 @@ +if os.getenv "LOCAL_LUA_DEBUGGER_VSCODE" == "1" then + require("lldebugger").start() +end + +local function lua_module_paths(module_base_path) + return (module_base_path .. "/?.lua;") .. (module_base_path .. "/?/init.lua;") +end + +return { + _all = { + lpath = lua_module_paths "src", + }, +} diff --git a/.cspell.json b/.cspell.json new file mode 100644 index 0000000..c505535 --- /dev/null +++ b/.cspell.json @@ -0,0 +1,70 @@ +{ + "words": [ + "Aire-One", + "autofocus", + "awesomerc", + "byidx", + "capi", + "closebutton", + "confdir", + "conffile", + "currenttags", + "dbus", + "drawin", + "dryrun", + "fatalwarnings", + "floatingbutton", + "fullscreen", + "getmaster", + "halign", + "iconwidget", + "imagebox", + "incmwfact", + "incncol", + "incnmaster", + "jumpto", + "keyboardlayout", + "keygrabber", + "keygroup", + "layoutbox", + "ldoc", + "lldebugger", + "lpath", + "luacheck", + "luacheckrc", + "luamon", + "luamonrc", + "luarocks", + "maximizedbutton", + "modkey", + "mousebinding", + "mousebindings", + "mousegrabber", + "noreset", + "numpad", + "numrow", + "ontop", + "ontopbutton", + "pkill", + "rcfile", + "rockspec", + "stickybutton", + "Stylua", + "systray", + "taglist", + "tasklist", + "textbox", + "textclock", + "titlebar", + "titlebars", + "titlewidget", + "unminimize", + "valign", + "viewnext", + "viewprev", + "viewtoggle", + "wibar", + "wibox", + "xephyr" + ] +} diff --git a/.editorconfig b/.editorconfig index c1e2c64..21b11e1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,8 +5,17 @@ root = true [*] indent_style = space -indent_size = 4 +indent_size = 3 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true + +[*.json] +indent_size = 2 + +[*.sh] +indent_size = 4 + +[Makefile] +indent_style = tab diff --git a/.luacheckrc b/.luacheckrc index 8094e43..7e6bfc4 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,10 +1,28 @@ --- Only allow symbols available in all Lua versions -std = "min" +std = "lua54" --- Get rid of "unused argument self"-warnings -self = false +files[".luamonrc"].std = { + globals = { + "ext", + "lang", + }, +} + +files["awesomerc-dev-1.rockspec"].ignore = { + "631", -- Line is too long. +} + +include_files = { + ".busted", + ".luacheckrc", + ".luamonrc", + "*.rockspec", + "src/**/*.lua", +} + +exclude_files = { + "src/awesome-wm-nice", -- Vendored code with its own .luacheckrc +} --- Global objects defined by the C code read_globals = { "awesome", "button", @@ -21,13 +39,9 @@ read_globals = { "math.atan2", } --- screen may not be read-only, because newer luacheck versions complain about --- screen[1].tags[1].selected = true. --- The same happens with the following code: --- local tags = mouse.screen.tags --- tags[7].index = 4 --- client may not be read-only due to client.focus. -globals = { "screen", "mouse", "root", "client" } - --- Enable cache (uses .luacheckcache relative to this rc file). -cache = true +globals = { + "screen", + "mouse", + "root", + "client", +} diff --git a/.luamonrc b/.luamonrc new file mode 100644 index 0000000..6275058 --- /dev/null +++ b/.luamonrc @@ -0,0 +1,2 @@ +ext = { "lua" } +lang = "./scripts/run.sh runAwesome" diff --git a/.stylua.toml b/.stylua.toml new file mode 100644 index 0000000..8440490 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1,6 @@ +indent_type = "Spaces" +indent_width = 3 +call_parentheses = "None" + +[sort_requires] +enabled = true diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 28a1bfe..6a07a7e 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,10 @@ { - "recommendations": [ - "editorconfig.editorconfig", - "tomblind.local-lua-debugger-vscode", - "sumneko.lua", - "johnnymorganz.stylua", - "dwenegar.vscode-luacheck" - ] + "recommendations": [ + "streetsidesoftware.code-spell-checker", + "editorconfig.editorconfig", + "tomblind.local-lua-debugger-vscode", + "johnnymorganz.stylua", + "dwenegar.vscode-luacheck", + "sumneko.lua" + ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index f8145a4..0d21972 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,19 +1,17 @@ { - "version": "0.2.0", - "configurations": [ - { - "type": "lua-local", - "request": "launch", - "name": "Debug with Xephyr", - "program": { - "command": "${workspaceFolder}/start-xephyr.sh" - }, - "args": [ - "/usr/bin/Xephyr", - "/usr/bin/awesome", - "${workspaceFolder}/init.lua" - ], - "postDebugTask": "Terminate All Tasks" - } - ] + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lua-local", + "request": "launch", + "name": "Debug with Xephyr", + "program": { + "command": "${workspaceFolder}/scripts/run.sh" + }, + "args": ["debug"] + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index ea79314..6b444e2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,20 @@ { - "editor.formatOnSave": true, - "editor.formatOnPaste": true, - "[markdown]": { - "editor.wordWrap": "on", - "editor.renderWhitespace": "all", - "editor.acceptSuggestionOnEnter": "off" - } + "Lua.runtime.path": [ + "/usr/share/awesome/lib/?.lua", + "/usr/share/awesome/lib/?/init.lua", + "${workspaceFolder}/src/?.lua", + "${workspaceFolder}/src/init.lua", + "${workspaceFolder}/src/?/init.lua" + ], + "[lua]": { + "editor.defaultFormatter": "JohnnyMorganz.stylua" + }, + "stylua.targetReleaseVersion": "latest", + "files.associations": { + ".busted": "lua", + ".luacheckrc": "lua", + ".luamonrc": "lua", + "*.rockspec": "lua" + }, + "Lua.runtime.version": "Lua 5.4" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index dd690d8..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "Terminate All Tasks", - "command": "${workspaceFolder}/start-xephyr.sh stop", - "type": "shell", - "problemMatcher": [] - } - ], - "inputs": [ - { - "id": "terminate", - "type": "command", - "command": "workbench.action.tasks.terminate", - "args": "terminateAll" - } - ] -} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3e52060 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +dev: + scripts/run.sh start + +test: + luarocks test + +# Install the rock in the local tree (aka user home directory) +deploy: + luarocks --local build --force + mkdir -p ~/.config/awesome + cp -rv config/awesome ~/.config + +try-current: + scripts/run.sh try-current + +luacheck: + luacheck . + +stylua: + stylua --check . + +# ldoc-dryrun: +# $(eval TMP := $(shell mktemp -d)) +# ldoc --fatalwarnings --dir $(TMP) . +# rm -rf $(TMP) + +cspell: + cspell lint . + +lint-rockspec: + luarocks lint awesomerc-dev-1.rockspec + +# For now: +# - we don't run ldoc-dryrun because we don't have documentation +# - we don't run cspell because there are too many problems I don't want to fix now since the code will change +# lint: luacheck stylua ldoc-dryrun cspell lint-rockspec +lint: luacheck stylua lint-rockspec diff --git a/awesomerc-dev-1.rockspec b/awesomerc-dev-1.rockspec new file mode 100644 index 0000000..7d6a7d7 --- /dev/null +++ b/awesomerc-dev-1.rockspec @@ -0,0 +1,77 @@ +rockspec_format = "3.0" + +package = "awesomerc" +version = "dev-1" + +source = { + url = "git://gitea.aireone.xyz/Aire-One/awesomerc.git", +} + +description = { + homepage = "https://gitea.aireone.xyz/Aire-One/awesomerc", + license = "GPL 2.0", -- TODO: git add the LICENSE file + -- Vendored awesome-wm-nice is licensed by mut-ex under the MIT license. +} + +dependencies = { + "awesome-slot", + "awesome-battery_widget", +} + +build = { + type = "builtin", + modules = { + ["awesomerc.configuration.applications"] = "src/awesomerc/configuration/applications.lua", + ["awesomerc.configuration.bindings.client_keybindings"] = "src/awesomerc/configuration/bindings/client_keybindings.lua", + ["awesomerc.configuration.bindings.client_mousebindings"] = "src/awesomerc/configuration/bindings/client_mousebindings.lua", + ["awesomerc.configuration.bindings.global_keybindings"] = "src/awesomerc/configuration/bindings/global_keybindings.lua", + ["awesomerc.configuration.bindings.global_mousebindings"] = "src/awesomerc/configuration/bindings/global_mousebindings.lua", + ["awesomerc.configuration.bindings.init"] = "src/awesomerc/configuration/bindings/init.lua", + ["awesomerc.configuration.bindings.utils"] = "src/awesomerc/configuration/bindings/utils.lua", + ["awesomerc.configuration.init"] = "src/awesomerc/configuration/init.lua", + ["awesomerc.configuration.menu.init"] = "src/awesomerc/configuration/menu/init.lua", + ["awesomerc.configuration.menu.myawesomemenu"] = "src/awesomerc/configuration/menu/myawesomemenu.lua", + ["awesomerc.configuration.menu.mymainmenu"] = "src/awesomerc/configuration/menu/mymainmenu.lua", + ["awesomerc.configuration.prompt_commands"] = "src/awesomerc/configuration/prompt_commands.lua", + ["awesomerc.configuration.rules.client.firefox"] = "src/awesomerc/configuration/rules/client/firefox.lua", + ["awesomerc.configuration.rules.client.floating"] = "src/awesomerc/configuration/rules/client/floating.lua", + ["awesomerc.configuration.rules.client.global"] = "src/awesomerc/configuration/rules/client/global.lua", + ["awesomerc.configuration.rules.client.init"] = "src/awesomerc/configuration/rules/client/init.lua", + ["awesomerc.configuration.rules.client.titlebars"] = "src/awesomerc/configuration/rules/client/titlebars.lua", + ["awesomerc.configuration.rules.init"] = "src/awesomerc/configuration/rules/init.lua", + ["awesomerc.configuration.rules.notification.global"] = "src/awesomerc/configuration/rules/notification/global.lua", + ["awesomerc.configuration.rules.notification.init"] = "src/awesomerc/configuration/rules/notification/init.lua", + ["awesomerc.configuration.tag_layouts"] = "src/awesomerc/configuration/tag_layouts.lua", + ["awesomerc.slots.init"] = "src/awesomerc/slots/init.lua", + ["awesomerc.theme.init"] = "src/awesomerc/theme/init.lua", + ["awesomerc.ui.desktop_decoration.bar.init"] = "src/awesomerc/ui/desktop_decoration/bar/init.lua", + ["awesomerc.ui.desktop_decoration.bar.widgets.battery"] = "src/awesomerc/ui/desktop_decoration/bar/widgets/battery.lua", + ["awesomerc.ui.desktop_decoration.bar.widgets.init"] = "src/awesomerc/ui/desktop_decoration/bar/widgets/init.lua", + ["awesomerc.ui.desktop_decoration.bar.widgets.prompt"] = "src/awesomerc/ui/desktop_decoration/bar/widgets/prompt.lua", + ["awesomerc.ui.desktop_decoration.init"] = "src/awesomerc/ui/desktop_decoration/init.lua", + ["awesomerc.ui.hotkeys_popup.init"] = "src/awesomerc/ui/hotkeys_popup/init.lua", + ["awesomerc.ui.menu.mymainmenu"] = "src/awesomerc/ui/menu/mymainmenu.lua", + ["awesomerc.ui.titlebar.init"] = "src/awesomerc/ui/titlebar/init.lua", + ["awesomerc.init"] = "src/awesomerc/init.lua", + ["awesome-legacy.autofocus.init"] = "src/awesome-legacy/autofocus/init.lua", + ["awesome-legacy.manage_error.init"] = "src/awesome-legacy/manage_error/init.lua", + ["awesome-legacy.sloppy_focus.init"] = "src/awesome-legacy/sloppy_focus/init.lua", + ["awesome-legacy.init"] = "src/awesome-legacy/init.lua", + ["MyTagListWidget.TagItem_widget"] = "src/MyTagListWidget/TagItem_widget.lua", + ["MyTagListWidget.init"] = "src/MyTagListWidget/init.lua", + ["MyTagListWidget.mockup"] = "src/MyTagListWidget/mockup.lua", + ["MyTagListWidget.tests.widget-testrc"] = "src/MyTagListWidget/tests/widget-testrc.lua", + ["awesome-wm-nice.colors"] = "src/awesome-wm-nice/colors.lua", + ["awesome-wm-nice.config"] = "src/awesome-wm-nice/config.lua", + ["awesome-wm-nice.init"] = "src/awesome-wm-nice/init.lua", + ["awesome-wm-nice.shade"] = "src/awesome-wm-nice/shade.lua", + ["awesome-wm-nice.shapes"] = "src/awesome-wm-nice/shapes.lua", + ["awesome-wm-nice.table"] = "src/awesome-wm-nice/table.lua", + ["awesome-wm-nice.utils"] = "src/awesome-wm-nice/utils.lua", + ["awesome-wm-nice.widgets"] = "src/awesome-wm-nice/widgets.lua", + }, +} + +test = { + type = "busted", +} diff --git a/theme/assets/icons/apps.svg b/config/awesome/assets/icons/apps.svg similarity index 100% rename from theme/assets/icons/apps.svg rename to config/awesome/assets/icons/apps.svg diff --git a/theme/assets/icons/battery-outline.svg b/config/awesome/assets/icons/battery-outline.svg similarity index 100% rename from theme/assets/icons/battery-outline.svg rename to config/awesome/assets/icons/battery-outline.svg diff --git a/theme/assets/icons/home-circle.svg b/config/awesome/assets/icons/home-circle.svg similarity index 100% rename from theme/assets/icons/home-circle.svg rename to config/awesome/assets/icons/home-circle.svg diff --git a/config/awesome/assets/wallpapers/poke.jpg b/config/awesome/assets/wallpapers/poke.jpg new file mode 100644 index 0000000..3ed8003 Binary files /dev/null and b/config/awesome/assets/wallpapers/poke.jpg differ diff --git a/config/awesome/rc.lua b/config/awesome/rc.lua new file mode 100644 index 0000000..2d0ea0b --- /dev/null +++ b/config/awesome/rc.lua @@ -0,0 +1,9 @@ +-- awesome_mode: api-level=4:screen=on + +if os.getenv "LOCAL_LUA_DEBUGGER_VSCODE" == "1" then + require("lldebugger").start() +end + +require "luarocks.loader" + +require "awesomerc" diff --git a/configuration/bindings/client_keybindings.lua b/configuration/bindings/client_keybindings.lua deleted file mode 100644 index 1b25a43..0000000 --- a/configuration/bindings/client_keybindings.lua +++ /dev/null @@ -1,113 +0,0 @@ -local akey = require "awful.key" -local aclient = require "awful.client" - -local utils = require "rc.configuration.bindings.utils" - -local client_keybindings = { - - akey { - modifiers = { utils.mods.modkey }, - key = "f", - description = "toggle fullscreen", - group = utils.groups.client, - on_press = function(client) - client.fullscreen = not client.fullscreen - client:raise() - end, - }, - - akey { - modifiers = { utils.mods.modkey }, - key = "c", - description = "close", - group = utils.groups.client, - on_press = function(client) - client:kill() - end, - }, - - akey { - modifiers = { utils.mods.modkey, utils.mods.control }, - key = "space", - description = "toggle floating", - group = utils.groups.client, - on_press = aclient.floating.toggle, - }, - - akey { - modifiers = { utils.mods.modkey, utils.mods.control }, - key = "Return", - description = "move to master", - group = utils.groups.client, - on_press = function(client) - client:swap(aclient.getmaster()) - end, - }, - - akey { - modifiers = { utils.mods.modkey }, - key = "o", - description = "move to screen", - group = utils.groups.client, - on_press = function(client) - client:move_to_screen() - end, - }, - - akey { - modifiers = { utils.mods.modkey }, - key = "t", - description = "toggle keep on top", - group = utils.groups.client, - on_press = function(client) - client.ontop = not client.ontop - end, - }, - - akey { - modifiers = { utils.mods.modkey }, - key = "n", - description = "minimize", - group = utils.groups.client, - on_press = function(client) - -- The client currently has the input focus, so it cannot be - -- minimized, since minimized clients can't have the focus. - client.minimized = true - end, - }, - - akey { - modifiers = { utils.mods.modkey }, - key = "m", - description = "(un)maximize", - group = utils.groups.client, - on_press = function(client) - client.maximized = not client.maximized - client:raise() - end, - }, - - akey { - modifiers = { utils.mods.modkey, utils.mods.control }, - key = "m", - description = "(un)maximize vertically", - group = utils.groups.client, - on_press = function(client) - client.maximized_vertical = not client.maximized_vertical - client:raise() - end, - }, - - akey { - modifiers = { utils.mods.modkey, utils.mods.shift }, - key = "m", - description = "(un)maximize horizontally", - group = utils.groups.client, - on_press = function(client) - client.maximized_horizontal = not client.maximized_horizontal - client:raise() - end, - }, -} - -return client_keybindings diff --git a/configuration/bindings/client_mousebindings.lua b/configuration/bindings/client_mousebindings.lua deleted file mode 100644 index 8817c86..0000000 --- a/configuration/bindings/client_mousebindings.lua +++ /dev/null @@ -1,39 +0,0 @@ -local abutton = require "awful.button" - -local utils = require "rc.configuration.bindings.utils" - -local mousebindings = { - abutton { - modifiers = {}, - button = abutton.names.LEFT, - on_press = function(client) - client:activate { - context = "mouse_click", - } - end, - }, - - abutton { - modifiers = { utils.mods.modkey }, - button = abutton.names.LEFT, - on_press = function(client) - client:activate { - context = "mouse_click", - action = "mouse_move", - } - end, - }, - - abutton { - modifiers = { utils.mods.modkey }, - button = abutton.names.RIGHT, - on_press = function(client) - client:activate { - context = "mouse_click", - action = "mouse_resize", - } - end, - }, -} - -return mousebindings diff --git a/configuration/bindings/global_keybindings.lua b/configuration/bindings/global_keybindings.lua deleted file mode 100644 index f66e589..0000000 --- a/configuration/bindings/global_keybindings.lua +++ /dev/null @@ -1,223 +0,0 @@ -local aclient = require "awful.client" -local akey = require "awful.key" -local aprompt = require "awful.prompt" -local ascreen = require "awful.screen" -local aspawn = require "awful.spawn" -local atag = require "awful.tag" -local autil = require "awful.util" - -local menubar = require "menubar" - -local applications = require "rc.configuration.applications" -local desktop_bar = require "rc.ui.desktop_decoration.bar" -local hotkeys_popup = require "rc.ui.hotkeys_popup" -local mymainmenu = require "rc.ui.menu.mymainmenu" - -local utils = require "rc.configuration.bindings.utils" - -local capi = { - awesome = _G.awesome, - client = _G.client, -} - -local global_keybindings = { - - -- Awesome - - akey { - modifiers = { utils.mods.modkey }, - key = "s", - description = "show help", - group = utils.groups.awesome, - on_press = function() - hotkeys_popup.show_help() - end, - }, - - akey { - modifiers = { utils.mods.modkey }, - key = "w", - description = "show main menu", - group = utils.groups.awesome, - on_press = function() - mymainmenu():show() - end, - }, - - akey { - modifiers = { utils.mods.modkey, utils.mods.control }, - key = "r", - description = "reload awesome", - group = utils.groups.awesome, - on_press = capi.awesome.restart, - }, - - akey { - modifiers = { utils.mods.modkey, utils.mods.shift }, - key = "q", - description = "quit awesome", - group = utils.groups.awesome, - on_press = capi.awesome.quit, - }, - - akey { - modifiers = { utils.mods.modkey }, - key = "x", - description = "lua execute prompt", - group = utils.groups.awesome, - on_press = function() - aprompt.run { - prompt = "Run Lua code: ", - textbox = desktop_bar(ascreen.focused()).promptbox.widget, - exe_callback = autil.eval, - history_path = autil.get_cache_dir() .. "/history_eval", - } - end, - }, - - -- Launcher - - akey { - modifiers = { utils.mods.modkey }, - key = "Return", - description = "open a terminal", - group = utils.groups.launcher, - on_press = function() - aspawn(applications.open_terminal()) - end, - }, - - akey { - modifiers = { utils.mods.modkey }, - key = "r", - description = "run prompt", - group = utils.groups.launcher, - on_press = function() - desktop_bar(ascreen.focused()).promptbox:run() - end, - }, - - akey { - modifiers = { utils.mods.modkey }, - key = "p", - description = "show the menubar", - group = utils.groups.launcher, - on_press = function() - menubar.show() - end, - }, - - -- Client focus - - akey { - modifiers = { utils.mods.modkey }, - key = "j", - group = "client", - description = "Focus next client by index", - on_press = function() - aclient.focus.byidx(1) - end, - }, - - akey { - modifiers = { utils.mods.modkey }, - key = "k", - group = "client", - description = "Focus previous by index", - on_press = function() - aclient.focus.byidx(-1) - end, - }, - - -- Layout manipulation - - akey { - modifiers = { utils.mods.modkey, utils.mods.shift }, - key = "j", - group = "client", - description = "Swap with next client", - on_press = function() - aclient.swap.byidx(1) - end, - }, - - akey { - modifiers = { utils.mods.modkey, utils.mods.shift }, - key = "k", - group = "client", - description = "Swap with previous client", - on_press = function() - aclient.swap.byidx(-1) - end, - }, - - akey { - modifiers = { utils.mods.modkey, utils.mods.shift }, - key = "Right", - description = "Increase master width factor", - group = "client", - on_press = function() - atag.incmwfact(0.01) - end, - }, - - akey { - modifiers = { utils.mods.modkey, utils.mods.shift }, - key = "Left", - description = "Decrease master width factor", - group = "client", - on_press = function() - atag.incmwfact(-0.01) - end, - }, - - -- Tags manipulation - - akey { - modifiers = { utils.mods.modkey }, - keygroup = akey.keygroup.NUMROW, - description = "only view tag", - group = "tag", - on_press = function(index) - local screen = ascreen.focused() - local tag = screen.tags[index] - - if tag then - tag:view_only() - end - end, - }, - - akey { - modifiers = { utils.mods.modkey, utils.mods.control }, - keygroup = akey.keygroup.NUMROW, - description = "toggle tag", - group = "tag", - on_press = function(index) - local screen = ascreen.focused() - local tag = screen.tags[index] - - if tag then - atag.viewtoggle(tag) - end - end, - }, - - akey { - modifiers = { utils.mods.modkey, utils.mods.shift }, - keygroup = akey.keygroup.NUMROW, - description = "move focused client to tag", - group = "tag", - on_press = function(index) - local screen = ascreen.focused() - local client = capi.client.focus - local tag = screen.tags[index] - - if client and tag then - client:move_to_tag(tag) - end - end, - }, -} - -return global_keybindings diff --git a/configuration/bindings/global_mousebindings.lua b/configuration/bindings/global_mousebindings.lua deleted file mode 100644 index eac992f..0000000 --- a/configuration/bindings/global_mousebindings.lua +++ /dev/null @@ -1,28 +0,0 @@ -local abutton = require "awful.button" -local atag = require "awful.tag" - -local mymainmenu = require "rc.ui.menu.mymainmenu" - -local global_mousebindings = { - abutton { - modifiers = {}, - button = abutton.names.RIGHT, - on_press = function() - mymainmenu():toggle() - end, - }, - - abutton { - modifiers = {}, - button = abutton.names.SCROLL_UP, - on_press = tag.viewprev, - }, - - abutton { - modifiers = {}, - button = abutton.names.SCROLL_DOWN, - on_press = atag.viewnext, - }, -} - -return global_mousebindings diff --git a/configuration/bindings/init.lua b/configuration/bindings/init.lua deleted file mode 100644 index 9ce68b2..0000000 --- a/configuration/bindings/init.lua +++ /dev/null @@ -1,13 +0,0 @@ -local bindings = {} - -bindings.client_keybindings = - require "rc.configuration.bindings.client_keybindings" -bindings.client_mousebindings = - require "rc.configuration.bindings.client_mousebindings" -bindings.global_keybindings = - require "rc.configuration.bindings.global_keybindings" -bindings.global_mousebindings = - require "rc.configuration.bindings.global_mousebindings" -bindings.utils = require "rc.configuration.bindings.utils" - -return bindings diff --git a/configuration/bindings/utils.lua b/configuration/bindings/utils.lua deleted file mode 100644 index 584e52a..0000000 --- a/configuration/bindings/utils.lua +++ /dev/null @@ -1,16 +0,0 @@ -local mods = { - control = "Control", - modkey = "Mod4", - shift = "Shift", -} - -local groups = { - client = "client", - awesome = "awesome", - launcher = "launcher", -} - -return { - mods = mods, - groups = groups, -} diff --git a/configuration/init.lua b/configuration/init.lua deleted file mode 100644 index 6c84ac8..0000000 --- a/configuration/init.lua +++ /dev/null @@ -1,10 +0,0 @@ -local rc_configuration = {} - -rc_configuration.applications = require "rc.configuration.applications" -rc_configuration.bindings = require "rc.configuration.bindings" -rc_configuration.menu = require "rc.configuration.menu" -rc_configuration.rules = require "rc.configuration.rules" -rc_configuration.prompt_commands = require "rc.configuration.prompt_commands" -rc_configuration.tag_layouts = require "rc.configuration.tag_layouts" - -return rc_configuration diff --git a/configuration/menu/init.lua b/configuration/menu/init.lua deleted file mode 100644 index ee9131a..0000000 --- a/configuration/menu/init.lua +++ /dev/null @@ -1,6 +0,0 @@ -local configuration_menu = {} - -configuration_menu.myawesomemenu = require "rc.configuration.menu.myawesomemenu" -configuration_menu.mymainmenu = require "rc.configuration.menu.mymainmenu" - -return configuration_menu diff --git a/configuration/menu/myawesomemenu.lua b/configuration/menu/myawesomemenu.lua deleted file mode 100644 index 0bc8b37..0000000 --- a/configuration/menu/myawesomemenu.lua +++ /dev/null @@ -1,31 +0,0 @@ -local applications = require "rc.configuration.applications" -local hotkeys_popup = require "rc.ui.hotkeys_popup" - -local capi = { - awesome = _G.awesome, -} - -local myawesomemenu = { - { - "hotkeys", - function() - hotkeys_popup.show_help() - end, - }, - { "manual", applications.open_man "awesome" }, - { "edit config", applications.open_editor(capi.awesome.conffile) }, - { - "restart", - function() - capi.awesome.restart() - end, - }, - { - "quit", - function() - capi.awesome.quit() - end, - }, -} - -return myawesomemenu diff --git a/configuration/menu/mymainmenu.lua b/configuration/menu/mymainmenu.lua deleted file mode 100644 index 5062750..0000000 --- a/configuration/menu/mymainmenu.lua +++ /dev/null @@ -1,11 +0,0 @@ -local beautiful = require "beautiful" - -local applications = require "rc.configuration.applications" -local myawesomemenu = require "rc.configuration.menu.myawesomemenu" - -local mymainmenu = { - { "awesome", myawesomemenu, beautiful.awesome_icon }, - { "open terminal", applications.terminal }, -} - -return mymainmenu diff --git a/configuration/prompt_commands.lua b/configuration/prompt_commands.lua deleted file mode 100644 index 0bd7a15..0000000 --- a/configuration/prompt_commands.lua +++ /dev/null @@ -1,50 +0,0 @@ -local atag = require "awful.tag" -local layout_suit = require "awful.layout.suit" - -local commands = {} - -commands["o"] = { - callback = function(parameters) - local tag_name = parameters[1] or "New-Tag" - - atag.add(tag_name, { - layout = layout_suit.tile, - }):view_only() - end, -} - -commands["O"] = { - callback = function(parameters) - local aspawn = require "awful.spawn" - - local application = parameters[1] - local tag_name = parameters[2] or application - - local t = atag.add(tag_name, { - layout = layout_suit.tile, - volatile = true, - }) - t:view_only() - aspawn(application, { tag = t }) - end, -} - -commands["q"] = { - callback = function() - local ascreen = require "awful.screen" - - local tags = ascreen.focused().selected_tags - - for _, tag in ipairs(tags) do - tag.volatile = true - - for _, client in ipairs(tag:clients()) do - client:kill() - end - - tag:delete() - end - end, -} - -return commands diff --git a/configuration/rules/client/firefox.lua b/configuration/rules/client/firefox.lua deleted file mode 100644 index 35df7a3..0000000 --- a/configuration/rules/client/firefox.lua +++ /dev/null @@ -1,7 +0,0 @@ -local firefox_rule = { - id = "firefox", - rule = { class = "Firefox" }, - properties = { screen = 1, tag = "2" }, -} - -return firefox_rule diff --git a/configuration/rules/client/floating.lua b/configuration/rules/client/floating.lua deleted file mode 100644 index 239820e..0000000 --- a/configuration/rules/client/floating.lua +++ /dev/null @@ -1,28 +0,0 @@ -local floating_rule = { - id = "floating", - rule_any = { - instance = { "copyq", "pinentry" }, - class = { - "Arandr", - "Blueman-manager", - "Gpick", - "Kruler", - "Sxiv", - "Tor Browser", - "Wpa_gui", - "veromix", - "xtightvncviewer", - }, - name = { - "Event Tester", -- xev. - }, - role = { - "AlarmWindow", -- Thunderbird's calendar. - "ConfigManager", -- Thunderbird's about:config. - "pop-up", -- e.g. Google Chrome's (detached) Developer Tools. - }, - }, - properties = { floating = true }, -} - -return floating_rule diff --git a/configuration/rules/client/global.lua b/configuration/rules/client/global.lua deleted file mode 100644 index 5d812c4..0000000 --- a/configuration/rules/client/global.lua +++ /dev/null @@ -1,16 +0,0 @@ -local aclient = require "awful.client" -local aplacement = require "awful.placement" -local ascreen = require "awful.screen" - -local global_rule = { - id = "global", - rule = {}, - properties = { - focus = aclient.focus.filter, - raise = true, - screen = ascreen.preferred, - placement = aplacement.no_overlap + aplacement.no_offscreen, - }, -} - -return global_rule diff --git a/configuration/rules/client/init.lua b/configuration/rules/client/init.lua deleted file mode 100644 index f0bb4d7..0000000 --- a/configuration/rules/client/init.lua +++ /dev/null @@ -1,8 +0,0 @@ -local client_rules = {} - -client_rules.global = require "rc.configuration.rules.client.global" -client_rules.floating = require "rc.configuration.rules.client.floating" -client_rules.titlebar = require "rc.configuration.rules.client.titlebars" --- client_rules.firefox = require 'rc.configuration.rules.client.firefox' - -return client_rules diff --git a/configuration/rules/client/titlebars.lua b/configuration/rules/client/titlebars.lua deleted file mode 100644 index 99e4d1b..0000000 --- a/configuration/rules/client/titlebars.lua +++ /dev/null @@ -1,9 +0,0 @@ -local titlebar_rule = { - id = "titlebars", - rule_any = { - type = { "normal", "dialog" }, - }, - properties = { titlebars_enabled = true }, -} - -return titlebar_rule diff --git a/configuration/rules/init.lua b/configuration/rules/init.lua deleted file mode 100644 index 02d2a8f..0000000 --- a/configuration/rules/init.lua +++ /dev/null @@ -1,6 +0,0 @@ -local rules = {} - -rules.client = require "rc.configuration.rules.client" -rules.notification = require "rc.configuration.rules.notification" - -return rules diff --git a/configuration/rules/notification/global.lua b/configuration/rules/notification/global.lua deleted file mode 100644 index 21f2c6f..0000000 --- a/configuration/rules/notification/global.lua +++ /dev/null @@ -1,11 +0,0 @@ -local ascreen = require "awful.screen" - -local global_rule = { - rule = {}, - properties = { - screen = ascreen.preferred, - implicit_timeout = 5, - }, -} - -return global_rule diff --git a/configuration/rules/notification/init.lua b/configuration/rules/notification/init.lua deleted file mode 100644 index 36f35c1..0000000 --- a/configuration/rules/notification/init.lua +++ /dev/null @@ -1,5 +0,0 @@ -local notification_rules = {} - -notification_rules.global = require "rc.configuration.rules.notification.global" - -return notification_rules diff --git a/configuration/tag_layouts.lua b/configuration/tag_layouts.lua deleted file mode 100644 index 5fbbea2..0000000 --- a/configuration/tag_layouts.lua +++ /dev/null @@ -1,19 +0,0 @@ -local alayout = require "awful.layout" - -local tag_layouts = { - alayout.suit.floating, - alayout.suit.tile, - alayout.suit.tile.left, - alayout.suit.tile.bottom, - alayout.suit.tile.top, - alayout.suit.fair, - alayout.suit.fair.horizontal, - alayout.suit.spiral, - alayout.suit.spiral.dwindle, - alayout.suit.max, - alayout.suit.max.fullscreen, - alayout.suit.magnifier, - alayout.suit.corner.nw, -} - -return tag_layouts diff --git a/init.lua b/init.lua deleted file mode 100644 index 00fba18..0000000 --- a/init.lua +++ /dev/null @@ -1,153 +0,0 @@ --- awesome_mode: api-level=4:screen=on - -if os.getenv "LOCAL_LUA_DEBUGGER_VSCODE" == "1" then - require("lldebugger").start() -end - --- If LuaRocks is installed, make sure that packages installed through it are --- found (e.g. lgi). If LuaRocks is not installed, do nothing. -pcall(require, "luarocks.loader") - -local gtimer = require "gears.timer" -local legacy = require "awesome-legacy" -local naughty = require "naughty" -local ruled = require "ruled" -local slot = require "awesome-slot" - --- Load global awesome components from the C API -local capi = { - client = _G.client, - screen = _G.screen, - tag = _G.tag, -} - --- Beautiful needs to be initialized as soon as possible to make theme --- variables available to the configuration module. -legacy.beautiful { - base = "default", - theme = require "rc.theme", -} - -legacy.manage_error() -legacy.autofocus() -legacy.sloppy_focus() - -local configuration = require "rc.configuration" -local my_slots = require "rc.slots" - --- This needs to be run after awesome has completed C API initialization and --- the `root` object is available. -gtimer.delayed_call(function() - legacy.global_mouse_bindings(configuration.bindings.global_mousebindings) - legacy.global_keybindings(configuration.bindings.global_keybindings) -end) - --- luacheck: ignore unused variable load_wallpaper -local load_wallpaper = slot { - id = "LOAD_WALLPAPER", - connect = true, - target = capi.screen, - signal = "request::wallpaper", - slot = my_slots.wallpaper, -} - --- luacheck: ignore unused variable default_layout -local default_layout = slot { - id = "DEFAULT_LAYOUTS", - connect = true, - target = capi.tag, - signal = "request::default_layouts", - slot = slot.slots.tag.default_layouts, - slot_params = { - layouts = configuration.tag_layouts, - }, -} - --- luacheck: ignore unused variable create_tag -local create_tag = slot { - id = "CREATE_TAGS", - connect = true, - target = capi.screen, - signal = "request::desktop_decoration", - slot = my_slots.create_tags, -} - --- luacheck: ignore unused variable desktop_decoration -local desktop_decoration = slot { - id = "DESKTOP_DECORATION", - connect = true, - target = capi.screen, - signal = "request::desktop_decoration", - slot = my_slots.build_desktop_decoration, -} - --- luacheck: ignore unused variable client_mousebinding -local client_mousebinding = slot { - id = "CLIENT_MOUSE_BINDINGS", - connect = true, - target = capi.client, - signal = "request::default_mousebindings", - slot = slot.slots.client.append_mousebindings, - slot_params = { - mousebindings = configuration.bindings.client_mousebindings, - }, -} - --- luacheck: ignore unused variable client_keybinding -local client_keybinding = slot { - id = "CLIENT_KEY_BINDINGS", - connect = true, - target = capi.client, - signal = "request::default_keybindings", - slot = slot.slots.client.append_keybindings, - slot_params = { - keybindings = configuration.bindings.client_keybindings, - }, -} - --- luacheck: ignore unused variable ruled_client -local ruled_client = slot { - id = "RULED_CLIENT", - connect = true, - target = ruled.client, - signal = "request::rules", - slot = slot.slots.ruled.append_client_rules, - slot_params = { - rules = configuration.rules.client, - }, -} - --- luacheck: ignore unused variable client_titlebar -local client_titlebar = slot { - id = "CLIENT_TITLEBAR", - connect = true, - target = capi.client, - signal = "request::titlebars", - slot = my_slots.build_client_titlebars, -} - --- luacheck: ignore unused variable ruled_notification -local ruled_notification = slot { - id = "RULED_NOTIFICATION", - connect = true, - target = ruled.notification, - signal = "request::rules", - slot = slot.slots.ruled.append_notification_rules, - slot_params = { - rules = configuration.rules.notification, - }, -} - --- luacheck: ignore unused variable naughty_display -local naughty_display = slot { - id = "NAUGHTY_DISPLAY", - connect = true, - target = naughty, - signal = "request::display", - slot = my_slots.naughty_display, -} - -naughty.notify { - title = "Aire-One dots", - message = "You successfully ran the Aire-One default rc!", -} diff --git a/scripts/run.sh b/scripts/run.sh new file mode 100755 index 0000000..acf2c20 --- /dev/null +++ b/scripts/run.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env sh + +xephyr=/usr/bin/Xephyr +awesome=/usr/bin/awesome + +# TODO: configurable +confdir=./src +rcfile=./config/awesome/rc.lua +screen=1600x900 + +display=:1.0 + +startXephyr() { + $xephyr $display -ac -br -noreset -screen $screen >/dev/null 2>&1 & +} + +runAwesome() { + DISPLAY=$display $awesome \ + --config $rcfile \ + --search $confdir +} + +case $1 in + start) + startXephyr + luamon . + pkill Xephyr + ;; + runAwesome) + runAwesome + ;; + debug) + startXephyr + sleep 1 # wait for Xephyr to be ready + runAwesome + ;; + try-current) + startXephyr + sleep 1 # wait for Xephyr to be ready + DISPLAY=$display $awesome + ;; + *) + echo "Need command" + ;; +esac diff --git a/slots/init.lua b/slots/init.lua deleted file mode 100644 index 59056bf..0000000 --- a/slots/init.lua +++ /dev/null @@ -1,123 +0,0 @@ -local abutton = require "awful.button" -local atitlebar = require "awful.titlebar" -local beautiful = require "beautiful" -local lalign = require "wibox.layout.align" -local lfixed = require "wibox.layout.fixed" -local lflex = require "wibox.layout.flex" -local naughty = require "naughty" - -local slots = {} - -function slots.wallpaper(screen) - local awallpaper = require "awful.wallpaper" - local beautiful = require "beautiful" - local gcolor = require "gears.color" - local gsurface = require "gears.surface" - local imagebox = require "wibox.widget.imagebox" - local cairo = require("lgi").cairo - - local screen_geo = screen.geometry - local source = cairo.ImageSurface( - cairo.Format.RGB32, - screen_geo.width, - screen_geo.height - ) - local cr = cairo.Context(source) - - -- Load base image - local image_surface = gsurface.load_uncached(beautiful.wallpaper) - local w, h = gsurface.get_size(image_surface) - cr:scale(screen_geo.width / w, screen_geo.height / h) - cr:set_source_surface(image_surface, 0, 0) - cr:paint() - - -- Add color layer - local color_pattern = gcolor.create_linear_pattern { - from = { 0, 0 }, - to = { screen.width, screen.height }, - stops = { - { 0, "#26323840" }, - }, - } - cr:set_source(color_pattern) - cr:paint() - - awallpaper { - screen = screen, - widget = { - image = source, - widget = imagebox, - }, - } -end - -function slots.create_tags(screen) - local atag = require "awful.tag" - local gtimer = require "gears.timer" - local home_layout = require "MyTagLayout.home_layout" - - local first_tag = atag.add("home", { - screen = screen, - icon = beautiful.icon_hometag, - layout = home_layout, - master_width_factor = beautiful.hometag_master_width_factor, - }) - - gtimer.delayed_call(function() - local spawn = require "awful.spawn" - local apps = require "configuration.applications" - - first_tag:view_only() - spawn(apps.open_terminal(), { screen = screen, tag = first_tag }) - end) -end - -function slots.build_desktop_decoration(screen) - local desktop_bar = require "rc.ui.desktop_decoration.bar" - - desktop_bar(screen) -end - -function slots.build_client_titlebars(client) - -- Mouse buttons bindings for the titlebar - local buttons = { - abutton({}, 1, function() - client:activate { context = "titlebar", action = "mouse_move" } - end), - abutton({}, 3, function() - client:activate { context = "titlebar", action = "mouse_resize" } - end), - } - - -- Titlebar UI - atitlebar(client).widget = { - { -- Left - atitlebar.widget.iconwidget(client), - buttons = buttons, - layout = lfixed.horizontal, - }, - { -- Middle - { -- Title - align = "center", - widget = atitlebar.widget.titlewidget(client), - }, - buttons = buttons, - layout = lflex.horizontal, - }, - { -- Right - atitlebar.widget.floatingbutton(client), - atitlebar.widget.maximizedbutton(client), - atitlebar.widget.stickybutton(client), - atitlebar.widget.ontopbutton(client), - atitlebar.widget.closebutton(client), - layout = lfixed.horizontal(), - }, - layout = lalign.horizontal, - } -end - -function slots.naughty_display(notification) - naughty.layout.box { notification = notification } -end - -return slots diff --git a/spec/default_spec.lua b/spec/default_spec.lua new file mode 100644 index 0000000..dcf5eb0 --- /dev/null +++ b/spec/default_spec.lua @@ -0,0 +1,5 @@ +describe("default", function() + it("should work", function() + assert.are.equal(1, 1) + end) +end) diff --git a/src/MyTagListWidget/README.md b/src/MyTagListWidget/README.md new file mode 100644 index 0000000..0117394 --- /dev/null +++ b/src/MyTagListWidget/README.md @@ -0,0 +1,59 @@ +# My Tag List Widget + +The standard Awesome wm API already contains a Tag List Widget implementation. However, this implementation doesn't match my needs, so here is my attempt to create my own Tag List Widget. + +It is a personnal project. + +# Mockup + +Here are some references I'm writing to define what I think my widget should looks like. This section will introduice a mockup of the widget in a taskbar. + +This mockup concept have its implementation example in the script `mockup.lua`. + +## The taskbar + +The widget is designed to be used in a taskbar. Here, I'll only briefly talk about the bar, keep in mind inspiration for this come from *literally **any*** desktop environment and OS you can try. + +Basically, the bar will look like this: + +``` + +-----------------------------+ + | X X X X | + +-----------------------------+ +``` + +Every X symbols is an item of the Tag List Widget. + +## Items + +*(I call them "items" because I didn't find a better word :s)* + +In a clasical tasklist, an item is an icon of a runing application on your desktop. Here, I want to use the *Tag* system to introduice a new way to think about it. + +Every tag will be shown as a single icon. This icon should show to the user a numerous informations about the tag itself and its associated clients. In one look, the user should be able to identify tags, which are selected, how may extra-clients there is, if a client is flaged as `urgent`. + +Here is a discomposition of an item: + +``` + +---------+ +---------+ +---------+ +---------+ + | | | | | *| | | + | O | | O | | O | | O | + | | | ------- | | | | + + + | + +---------+ +---------+ +---------+ +---------+ + Simple Selected Urgent Clients +``` + +*The frame is used to delimite the size of the item. It is not aimed to be drawned.* + +Legend: + +* `O` : the icon on the center of the item +* `-----` : the bottom line, shown when the tag is selected +* `*` : the notification dot drawn when the tag is marked "urgent" +* `+` : some dots to show how many additionals clients are on the tag + +# References + +Most of the code is based on the awful standard library and the module `awful.widget.taglist.lua`. + +Special thanks to u/EmpressNoodle [for the inspiration](https://www.reddit.com/r/unixporn/comments/a900p7/awesome_mechanical_love/) and the good example of implementation on github (https://github.com/elenapan/dotfiles/blob/master/config/awesome/noodle/icon_taglist.lua). diff --git a/src/MyTagListWidget/TagItem_widget.lua b/src/MyTagListWidget/TagItem_widget.lua new file mode 100644 index 0000000..06f5da3 --- /dev/null +++ b/src/MyTagListWidget/TagItem_widget.lua @@ -0,0 +1,139 @@ +----------------- +-- MyTagListWidget - TagItem_widget +-- +-- Definition of a TagItem_widget. +-- A TagItem_widget is the widget representing a tag in the TagList widget. +-- +-- @author : Aire-One (Aire-One@github.com ; Aire-One@gitlab.com) +-- @copyright (C) 2019 Aire-One +----------------- + +local aplacement = require "awful.placement" +local beautiful = require "beautiful" +local empty_widget = require("wibox.widget.base").empty_widget +local gshape = require "gears.shape" +local wibox = require "wibox" + +--- Set the widget's image. +-- @tparam TagItem_widget self The widget itself. +-- @tparam Image image The image to use. +local set_image = function(self, image) + self.internal_role.icon_role.image = image +end + +--- Toogle the widget's selected status. +-- Toogle the selected status will whether show the selected_line_role widget +-- or the additionals_clients_role widget. +-- @tparam TagItem_widget self The widget itself. +-- @tparam bool selected Is the widget selected? +local toogle_selected = function(self, selected) + self.internal_role.selected_line_role.visible = selected + self.internal_role.additionals_clients_role.visible = not selected +end + +--- Toogle the widget's notification dot. +-- @tparam TagItem_widget self The widget itself. +-- @tparam bool show Show the notification dot? +local toogle_notification_dot = function(self, show) + self.internal_role.notification_dot_role.visible = show +end + +--- Set the number of additionals clients shown in the widget. +-- @tparam TagItem_widget self The widget itself. +-- @tparam Number count The number of additionals clients. +-- (Should be >= 0 and not too high) +local set_additional_client_count = function(self, count) + count = count >= 0 and count or 0 + + while #self.internal_role.additionals_clients_role.children < count do + self.internal_role.additionals_clients_role:add(wibox.widget { + layout = wibox.container.background, + wibox.widget.base.empty_widget(), + bg = "#ECEFF1", + shape = gshape.circle, + forced_height = 3, + forced_width = 3, + }) + end + + while #self.internal_role.additionals_clients_role.children > count do + self.internal_role.additionals_clients_role:remove(1) + end +end + +local tagItem_widget = {} + +--- Create a new tag item widget instance. +-- @tparam Tag tag The associated tag. +-- @treturn TagItem_widget The created widget. +tagItem_widget.new = function(tag) + -- TODO: remove all constants - prefere use a `params` table to initialize them. + local w = wibox.widget { + id = "container_role", + layout = wibox.container.margin, + { + id = "internal_role", + layout = wibox.layout.manual, + { + id = "selected_line_role", + layout = wibox.container.margin, + forced_height = 2, + point = aplacement.maximize_horizontally + aplacement.bottom, + right = 2, + left = 2, + { + layout = wibox.container.background, + empty_widget(), + bg = "#ECEFF1", + shape = gshape.rounded_bar, + }, + }, + { + id = "icon_role", + widget = wibox.widget.imagebox, + resize = true, + image = tag.icon or beautiful.awesome_icon, + forced_height = 16, + forced_width = 16, + point = aplacement.centered, + }, + { + id = "notification_dot_role", + layout = wibox.container.background, + visible = false, + wibox.widget.base.empty_widget(), + bg = "#F44336", + -- shape_border_width = 1, + -- shape_border_color = '#B71C1C', + shape = gshape.circle, + forced_height = 6, + forced_width = 6, + point = aplacement.top_right, + }, + { + id = "additionals_clients_role", + layout = wibox.layout.flex.horizontal, + visible = false, + forced_height = 3, + spacing = 2, + point = aplacement.bottom, + }, + }, + } + + -- it seems that setting the margins in the hierarchy doesn't work 🤷‍♂️ + -- w:set_margins(0) + w.margins = 0 + + -- Save the tag as an internal property + w.tag = tag + + w.set_image = set_image + w.toogle_selected = toogle_selected + w.toogle_notification_dot = toogle_notification_dot + w.set_additional_client_count = set_additional_client_count + + return w +end + +return tagItem_widget diff --git a/src/MyTagListWidget/init.lua b/src/MyTagListWidget/init.lua new file mode 100644 index 0000000..80f27ab --- /dev/null +++ b/src/MyTagListWidget/init.lua @@ -0,0 +1,209 @@ +----------------- +-- MyTagListWidget +-- +-- My implementationof the tag list widget for the Awesome WM. +-- +-- @author : Aire-One (Aire-One@github.com ; Aire-One@gitlab.com) +-- @copyright (C) 2019 Aire-One +----------------- + +local awful = require "awful" +local beautiful = require "beautiful" +local client = client -- luacheck: ignore client +local gsurface = require "gears.surface" +local gtable = require "gears.table" +local wibox = require "wibox" + +local TagItem_widget = require "MyTagListWidget.TagItem_widget" + +--- Get the tag widget associated with a specific tag. +-- @tparam MyTagListWidget taglist The tag list widget to look up. +-- @tparam Tag tag The tag to look for. +-- @treturn[1] widget The tag widget (nil if none). +-- @treturn[1] number The position of the widget in the list. +-- @treturn[2] nil If the tag does not have its widget. +local get_tagwidget = function(taglist, tag) + for i, child in ipairs(taglist.children) do + if gtable.hasitem(child, tag) then + return child, i + end + end + + return nil +end + +--- Create a new tag icon widget. +-- @tparam Tag tag The targeted tag. +-- @treturn widget The created widget. +local create_tagwidget = function(tag) + local w = TagItem_widget.new(tag) + + w:buttons(gtable.join( + -- Left click - Focus Tag + awful.button({}, 1, function() + tag:view_only() + end), + -- Right click - Show options + awful.button({}, 3, function() + -- TODO : design a menu + print("OPTION FOR " .. tag.name) + end) + )) + + return w +end + +--- Manage a client update. +-- Do everything the widget should check when client is updated. +-- @tparam MyTagListWidget self The MyTagListWidget instance to update. +-- @tparam client client The client which trigger the update. +local client_update = function(self, client) end -- luacheck: ignore + +--- Manage an icon update. +-- @tparam MyTagListWidget self The MyTagListWidget instance to update. +-- @tparam Tag tag The targeted tag. +local update_icon = function(self, tag) + -- TODO : this function shouldn't retrive the widget + local w = get_tagwidget(self, tag) + if not w then + return + end + + -- by default, we should use the predefined tag icon + -- then master client icon + -- then default to awesome_icon + if tag.icon then + w:set_image(tag.icon) + elseif #tag:clients() > 0 then + local attach_icon = function() + w:set_image(gsurface(tag:clients()[1].icon)) + end + -- first client should be masters + -- so usually, we should prioritize firsts clients + tag:clients()[1]:connect_signal("property::icon", attach_icon) + for i = 2, #tag:clients() do + tag:clients()[i]:disconnect_signal("property::icon", attach_icon) + end + else + w:set_image(beautiful.awesome_icon) + end +end + +--- Manage the additionals clients count on a tagItem_widget. +-- @tparam MyTagListWidget self The MyTagListWidget instance to update. +-- @tparam Tag tag The targeted tag. +local update_additionalsclient = function(self, tag) + -- TODO : this function shouldn't retrive the widget + local w = get_tagwidget(self, tag) + if not w then + return + end + + -- TODO : better additionals clients count computation + -- (detect important clients and not count them) + w:set_additional_client_count(#tag:clients() - tag.master_count) +end + +--- Update the widget. +-- @tparam MyTagListWidget self The MyTagListWidget instance to update. +local update_widget = function(self) + for i, tag in ipairs(self.screen.tags) do + -- w is the TagItem_widget associated with the tag. + local w = get_tagwidget(self, tag) + + -- If none exists, create it. + if not w then + w = create_tagwidget(tag) + self:insert(i, w) -- add the widget at its position in the layout + end + + w:toogle_selected(tag.selected) + w:toogle_notification_dot(awful.tag.getproperty(tag, "urgent")) + update_icon(self, tag) + if not tag.selected then + update_additionalsclient(self, tag) + end + end + + -- Remove deleted tags: + for i, wtag in ipairs(self.children) do + if wtag.tag.screen ~= self.screen then + self:remove(i) + end + end +end + +local my_taglist_widget = {} + +--- MyTagListWidget Constructor. +-- Build a new instance of MyTagListWidget. +-- @tparam table params Parameters to build the Widget. +-- @tparam screen params.screen Screen where the widget will be. +-- @treturn MyTagListWidget The new widget instance. +my_taglist_widget.new = function(params) + -- TODO : use more beautiful theming properties with params + + local self = wibox.widget { + layout = wibox.layout.flex.horizontal, + screen = params.screen, + spacing = 2, + max_widget_size = 20, + } + + -- Add publics methods + self.force_update_widget = update_widget + + -- Add buttons events + self:buttons(gtable.join( + -- Scroll - Cycle through tags + awful.button({}, 4, function() + awful.tag.viewprev() + end), + awful.button({}, 5, function() + awful.tag.viewnext() + end) + )) + + -- Attach signals to update the widget + -- TODO : specialize signal events (ie not always call UPDATE_EVERYTHING when a shorter function can do the job) + client.connect_signal("manage", function() + self:force_update_widget() + end) + client.connect_signal("unmanage", function() + self:force_update_widget() + end) + client.connect_signal("tagged", function() + self:force_update_widget() + end) + client.connect_signal("untagged", function() + self:force_update_widget() + end) + client.connect_signal("screen", function() + self:force_update_widget() + end) + awful.tag.attached_connect_signal(self.screen, "property::selected", function() + self:force_update_widget() + end) + awful.tag.attached_connect_signal(self.screen, "property::hide", function() + self:force_update_widget() + end) + awful.tag.attached_connect_signal(self.screen, "property::activated", function() + self:force_update_widget() + end) + awful.tag.attached_connect_signal(self.screen, "property::screen", function() + self:force_update_widget() + end) + awful.tag.attached_connect_signal(self.screen, "property::index", function() + self:force_update_widget() + end) + awful.tag.attached_connect_signal(self.screen, "property::urgent", function() + self:force_update_widget() + end) + + -- Build the widget with an update + update_widget(self) + + return self +end + +return my_taglist_widget diff --git a/src/MyTagListWidget/mockup.lua b/src/MyTagListWidget/mockup.lua new file mode 100644 index 0000000..689913e --- /dev/null +++ b/src/MyTagListWidget/mockup.lua @@ -0,0 +1,281 @@ +----------------- +-- MyTagListWidget mockup +-- +-- This file define a mockup of the tag list widget in a taskbar +-- as I wish to create my own. +-- +-- This mockup will create a fake taskbar with a fake Tag List Widget to help +-- me for the conceptualisation of the design for my Tag List Widget. +-- +--

The taskbar

+-- Basically, the bar will look like this: +-- +-- +---------------------+ +-- | X X X X | +-- +---------------------+ +-- +-- Every X symbols is an item of the Tag List Widget. +-- +--

Items

+-- An item represente a Tag and is discomposable like this: +-- +-- +---------+ +---------+ +---------+ +---------+ +-- | | | | | *| | | +-- | O | | O | | O | | O | +-- | | | ------- | | | | + + + | +-- +---------+ +---------+ +---------+ +---------+ +-- Simple Selected Urgent Clients +-- +-- The frame is used to delimite the size of the item. +-- It is not aimed to be drawned. +-- +-- * O : the icon on the center of the item +-- * ----- : the bottom line, shown when the tag is selected +-- * \* : a notification dot drawn when the tag is marked "urgent" +-- * \+ : some dots to show how many additionals clients are on the tag +-- +-- @author : Aire-One (Aire-One@github.com ; Aire-One@gitlab.com) +-- @copyright (C) 2019 Aire-One +----------------- + +-- require user rc.lua as a base config +-- user rc.lua file should be on a place loaded by awesomewm +require "rc" + +local awful = require "awful" +local gears = require "gears" +local wibox = require "wibox" + +-- Configs +local bar_height = 20 -- used by the wibox embedding the widget +local bar_padding = 0 -- used by the wibox embedding the widget +local item_size = 20 -- size of a tag icon placeholder +local icon_size = 16 -- size of the icon itself +local selected_line_height = 3 -- size of the bar below the icon +local notification_dot_size = 6 -- size of the notification dot (request::urgent) + +local screen = awful.screen.focused() + +-- the taskbar is symbolized by this wibox +screen.bar = wibox { + type = "dock", + ontop = true, + visible = true, + x = 50, + y = 200, + width = 400, + height = bar_height, +} + +screen.bar:setup { + -- The bar is a container.margin + layout = wibox.container.margin, + top = bar_padding / 2, + bottom = bar_padding / 2, + right = bar_padding, + left = bar_padding, + { + -- The Tag List widget + layout = wibox.layout.flex.horizontal, + spacing = 2, + max_widget_size = item_size, -- each widget will be a rectangular box + -- Every Tag is represented by this widget + { + -- first a container.margin + layout = wibox.container.margin, + top = 0, + bottom = 0, + right = 0, + left = 0, + { + -- The internal widget design + layout = wibox.layout.manual, + { + -- simple icon + -- use a predefined icon depending on the tag name + -- use a client (master client?) icon + -- use the tag name as a string (widget.textbox) ??? + widget = wibox.widget.imagebox, + resize = true, + image = "/usr/share/icons/Adwaita/16x16/places/user-home-symbolic.symbolic.png", + forced_height = icon_size, + forced_width = icon_size, + point = awful.placement.centered, + }, + }, + }, + { + -- first a container.margin + layout = wibox.container.margin, + top = 0, + bottom = 0, + right = 0, + left = 0, + { + -- The internal widget design + layout = wibox.layout.manual, + { + -- underline (request::selected) + -- should be placed below the tag icon + layout = wibox.container.background, + wibox.widget.base.empty_widget(), + bg = "#eeeeee", + shape = gears.shape.rounded_bar, + forced_height = selected_line_height, + point = awful.placement.maximize_horizontally + awful.placement.bottom, + }, + { + widget = wibox.widget.imagebox, + resize = true, + image = "/usr/share/icons/hicolor/16x16/apps/atom.png", + forced_height = icon_size, + forced_width = icon_size, + point = awful.placement.centered, + }, + }, + }, + { + -- first a container.margin + layout = wibox.container.margin, + top = 0, + bottom = 0, + right = 0, + left = 0, + { + -- The internal widget design + layout = wibox.layout.manual, + { + widget = wibox.widget.imagebox, + resize = true, + image = "/usr/share/icons/hicolor/16x16/apps/thunderbird.png", + forced_height = icon_size, + forced_width = icon_size, + point = awful.placement.centered, + }, + { + -- Notification dot (request::urgent) + -- should be placed on the top of the icon + layout = wibox.container.background, + wibox.widget.base.empty_widget(), + bg = "#ff0000", + shape_border_width = 2, + shape_border_color = "#8b0000", + shape = gears.shape.circle, + forced_height = notification_dot_size, + forced_width = notification_dot_size, + point = awful.placement.top_right, + }, + }, + }, + { + -- first a container.margin + layout = wibox.container.margin, + top = 0, + bottom = 0, + right = 0, + left = 0, + { + -- The internal widget design + layout = wibox.layout.manual, + { + -- simple icon + -- use a predefined icon depending on the tag name + -- use a client (master client?) icon + -- use the tag name as a string (widget.textbox) ??? + widget = wibox.widget.imagebox, + resize = true, + image = "/usr/share/icons/Adwaita/16x16/apps/utilities-terminal-symbolic.symbolic.png", + forced_height = icon_size, + forced_width = icon_size, + point = awful.placement.centered, + }, + { + -- number of clients on the tag + layout = wibox.layout.flex.horizontal, + { + layout = wibox.container.background, + wibox.widget.base.empty_widget(), + bg = "#eeeeee", + shape = gears.shape.circle, + forced_height = 3, + forced_width = 3, + }, + { + layout = wibox.container.background, + wibox.widget.base.empty_widget(), + bg = "#eeeeee", + shape = gears.shape.circle, + forced_height = 3, + forced_width = 3, + }, + { + layout = wibox.container.background, + wibox.widget.base.empty_widget(), + bg = "#eeeeee", + shape = gears.shape.circle, + forced_height = 3, + forced_width = 3, + }, + { + layout = wibox.container.background, + wibox.widget.base.empty_widget(), + bg = "#eeeeee", + shape = gears.shape.circle, + forced_height = 3, + forced_width = 3, + }, + forced_height = selected_line_height, + spacing = 2, + point = awful.placement.bottom, + }, + }, + }, + { + -- first a container.margin + layout = wibox.container.margin, + top = 0, + bottom = 0, + right = 0, + left = 0, + { + -- The internal widget design + layout = wibox.layout.manual, + { + -- simple icon + -- use a predefined icon depending on the tag name + -- use a client (master client?) icon + -- use the tag name as a string (widget.textbox) ??? + widget = wibox.widget.imagebox, + resize = true, + image = "/usr/share/icons/Adwaita/16x16/apps/utilities-terminal-symbolic.symbolic.png", + forced_height = icon_size, + forced_width = icon_size, + point = awful.placement.centered, + }, + { + -- number of clients on the tag + layout = wibox.layout.flex.horizontal, + { + layout = wibox.container.background, + wibox.widget.base.empty_widget(), + bg = "#eeeeee", + shape = gears.shape.circle, + forced_height = 3, + forced_width = 3, + }, + { + layout = wibox.container.background, + wibox.widget.base.empty_widget(), + bg = "#eeeeee", + shape = gears.shape.circle, + forced_height = 3, + forced_width = 3, + }, + forced_height = selected_line_height, + spacing = 2, + point = awful.placement.bottom, + }, + }, + }, + }, +} diff --git a/src/MyTagListWidget/tests/widget-testrc.lua b/src/MyTagListWidget/tests/widget-testrc.lua new file mode 100644 index 0000000..027b3a6 --- /dev/null +++ b/src/MyTagListWidget/tests/widget-testrc.lua @@ -0,0 +1,34 @@ +----------------- +-- tests/widget-testrc.lua +-- +-- Author : Aire-One (Aire-One@github.com ; Aire-One@gitlab.com) +-- Copyright (C) 2018 Aire-One +----------------- + +-- require user rc.lua as a base config +-- user rc.lua file should be on a place loaded by awesomewm +require "rc" + +-- Code to test the widget + +local awful = require "awful" +local wibox = require "wibox" + +local MyTagListWidget = require "MyTagListWidget" + +-- build the widget on the current screen +local widget = MyTagListWidget.new { screen = awful.screen.focused() } + +-- wibox to host the MyTagListWidget instance +awful.screen.w = wibox { + type = "dock", + ontop = true, + visible = true, + opacity = 1, + width = 200, + height = 30, +} +awful.screen.w:setup { + layout = wibox.container.background, + widget, +} diff --git a/src/awesome-legacy/autofocus/init.lua b/src/awesome-legacy/autofocus/init.lua new file mode 100644 index 0000000..88dba49 --- /dev/null +++ b/src/awesome-legacy/autofocus/init.lua @@ -0,0 +1,7 @@ +----- +-- autofocus module +-- +-- TODO: remove this module as it's now deprecated. +----- + +return require "awful.autofocus" diff --git a/src/awesome-legacy/init.lua b/src/awesome-legacy/init.lua new file mode 100644 index 0000000..dc2b778 --- /dev/null +++ b/src/awesome-legacy/init.lua @@ -0,0 +1,66 @@ +----- +-- legacy module +-- +-- This modules aims to hold all the legacy features from rc.lua +----- + +local awesome_legacy = {} + +function awesome_legacy.autofocus() + return require "awesome-legacy.autofocus" +end + +function awesome_legacy.manage_error() + return require "awesome-legacy.manage_error" +end + +function awesome_legacy.sloppy_focus() + return require "awesome-legacy.sloppy_focus" +end + +function awesome_legacy.global_mouse_bindings(mousebindings) + local amouse = require "awful.mouse" + + amouse.append_global_mousebindings(mousebindings) +end + +function awesome_legacy.global_keybindings(keybindings) + local akeyboard = require "awful.keyboard" + + akeyboard.append_global_keybindings(keybindings) +end + +function awesome_legacy.beautiful(args) + local os = os + local dofile = dofile + + local beautiful = require "beautiful" + local gfs = require "gears.filesystem" + local gstring = require "gears.string" + local gtable = require "gears.table" + local protected_call = require "gears.protected_call" + + local default_theme = {} + + if args.base then + if type(args.base) == "string" then + -- Replace the '~' by the fullpath to the home directory. + local theme_path = args.base:gsub("^~/", os.getenv "HOME" .. "/") + + -- Complete the path for standard themes + if not gstring.startswith(theme_path, "/") then + theme_path = gfs.get_themes_dir() .. args.base .. "/theme.lua" + end + + -- Execute the file to get the theme table + default_theme = protected_call(dofile, theme_path) + elseif type(args.base) == "table" then + default_theme = args.base + end + end + + local theme = gtable.crush(default_theme, args.theme) + beautiful.init(theme) +end + +return awesome_legacy diff --git a/src/awesome-legacy/manage_error/init.lua b/src/awesome-legacy/manage_error/init.lua new file mode 100644 index 0000000..304db75 --- /dev/null +++ b/src/awesome-legacy/manage_error/init.lua @@ -0,0 +1,15 @@ +----- +-- Error module +----- + +local naughty = require "naughty" + +-- Check if awesome encountered an error during startup and fell back to +-- another config (This code will only ever execute for the fallback config) +naughty.connect_signal("request::display_error", function(message, startup) + naughty.notification { + urgency = "critical", + title = "Oops, an error happened" .. (startup and " during startup!" or "!"), + message = message, + } +end) diff --git a/src/awesome-legacy/sloppy_focus/init.lua b/src/awesome-legacy/sloppy_focus/init.lua new file mode 100644 index 0000000..499785d --- /dev/null +++ b/src/awesome-legacy/sloppy_focus/init.lua @@ -0,0 +1,15 @@ +----- +-- Sloppy focus module +----- + +local capi = { + client = _G.client, +} + +-- Enable sloppy focus, so that focus follows mouse. +capi.client.connect_signal("mouse::enter", function(client) + client:activate { + context = "mouse_enter", + raise = false, + } +end) diff --git a/src/awesome-wm-nice/.editorconfig b/src/awesome-wm-nice/.editorconfig new file mode 100644 index 0000000..c1e2c64 --- /dev/null +++ b/src/awesome-wm-nice/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/src/awesome-wm-nice/.gitignore b/src/awesome-wm-nice/.gitignore new file mode 100644 index 0000000..51a4bc8 --- /dev/null +++ b/src/awesome-wm-nice/.gitignore @@ -0,0 +1 @@ +color_rules \ No newline at end of file diff --git a/src/awesome-wm-nice/.luacheckrc b/src/awesome-wm-nice/.luacheckrc new file mode 100644 index 0000000..8094e43 --- /dev/null +++ b/src/awesome-wm-nice/.luacheckrc @@ -0,0 +1,33 @@ +-- Only allow symbols available in all Lua versions +std = "min" + +-- Get rid of "unused argument self"-warnings +self = false + +-- Global objects defined by the C code +read_globals = { + "awesome", + "button", + "dbus", + "drawable", + "drawin", + "key", + "keygrabber", + "mousegrabber", + "selection", + "tag", + "window", + "table.unpack", + "math.atan2", +} + +-- screen may not be read-only, because newer luacheck versions complain about +-- screen[1].tags[1].selected = true. +-- The same happens with the following code: +-- local tags = mouse.screen.tags +-- tags[7].index = 4 +-- client may not be read-only due to client.focus. +globals = { "screen", "mouse", "root", "client" } + +-- Enable cache (uses .luacheckcache relative to this rc file). +cache = true diff --git a/src/awesome-wm-nice/LICENSE.md b/src/awesome-wm-nice/LICENSE.md new file mode 100644 index 0000000..09a1a32 --- /dev/null +++ b/src/awesome-wm-nice/LICENSE.md @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2020 mut-ex + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/src/awesome-wm-nice/README.md b/src/awesome-wm-nice/README.md new file mode 100644 index 0000000..8d145ff --- /dev/null +++ b/src/awesome-wm-nice/README.md @@ -0,0 +1,257 @@ +# :thumbsup:nice + +

+If you would like to show your appreciation for this project,
please consider a donation :)

+ +PayPal donation link +

+ +**N.B. This branch is for Awesome v4.3 git. [You can find the branch for Awesome v4.3 stable here](https://github.com/mut-ex/awesome-wm-nice/tree/awesome-4v3-stable)** + +nice is an easy to use, highly configurable extension for **[Awesome WM](https://awesomewm.org/)** that adds beautiful window decorations (and extra functionality!) to clients. It... + +* ...adds a **subtle 3D look**, and soft, **rounded anti-aliased, corners** to windows +* ...picks the window **decoration color based on the client content for a seamless look** , and adjusts the window title text color accordingly +* ...**auto-generates titlebar buttons** (and their states) for you based on the colors your pick *or* you can let it pick the colors for you! +* ...allows you to customize which titlebar buttons to include, their order, and their layout +* ...adds the **ability to maximize/unmaximize** floating windows by **double clicking the titlebar**, and of course, **moving them by clicking and holding** +* ...adds the ability to **"roll up"** and **"roll down"** the client window like a **window shade**! Scroll up over the titlebar to **instantly hide the window contents but keep the title bar** right where it is. And then either scroll down or click the titlebar to make the window contents visible again! + +![Preview](https://raw.githubusercontent.com/mut-ex/awesome-wm-nice/master/preview.png) + +## Getting Started + +### Prerequisites + +* You need **[Awesome WM](https://awesomewm.org/)** with a working basic configuration. **This branch is for Awesome v4.3 git. [You can find the branch for Awesome v4.3 stable here](https://github.com/mut-ex/awesome-wm-nice/tree/awesome-4v3-stable)** + +* You also need **[picom](https://github.com/yshui/picom)**. Make sure you have `shadow-ignore-shaped = false` in your configuration otherwise picom will not draw shadows. My recommended shadow settings are given below: + + ``` + shadow = true; + shadow-radius = 40; + shadow-opacity = .55; + shadow-offset-x = -40; + shadow-offset-y = -20; + shadow-exclude = [ + "_NET_WM_WINDOW_TYPE:a = '_NET_WM_WINDOW_TYPE_NOTIFICATION'", + "_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'", + "_GTK_FRAME_EXTENTS@:c" + ]; + shadow-ignore-shaped = false + ``` + +* For **GTK** applications add the following line to **~/.config/gtk-3.0/settings.ini** under the **[Settings]** section to hide client-side window control buttons: + + ``` + gtk-decoration-layout=menu: + ``` + +* Within you Awesome configuration, make sure that you do not already have code in place that request default titlebars for clients. Something like this: + +```lua +client.connect_signal("request::titlebars", function(c) ... end) -- Remove this +``` + +* Additionally, nice only adds window decorations to clients that have the `titlebars_enabled` property set to true. So configure your client rules accordingly. + +### Installation + +The easiest and quickest way to get started is by cloning this repository to your awesome configuration directory + +```shell +$ cd ~/.config/awesome +$ git clone https://github.com/mut-ex/awesome-wm-nice.git nice +``` + + + +## Usage + +To use nice, you first need to load the module. To do that, put the following line right after `beautiful.init(...)` + +```lua +local nice = require("nice") +nice() +``` + +If you are fine using the default configuration, you are all done! + +nice will automatically detect and change the window decoration color to match the client. However... + +* To pick the window decoration color yourself, right-click the titlebar and select **'Manually Pick Color'** +* To update the window decoration colors, right-click on the titlebar and select **'Redo Window Decorations'** +* Scroll-up with your mouse over the titlebar to "roll up" the window shade. Scroll-down over the titlebar, or left-click to "roll down" the window shade +* nice saves its color rules in the **color_rules** file within the module directory. If you wish you can manually edit it, or delete the file if you want to start again. + + + +## Configuration + +You can override the defaults by passing your own configuration. For example + +```lua +local nice = require("nice") +nice { + titlebar_color = "#00ff00", + + -- You only need to pass the parameter you are changing + context_menu_theme = { + width = 300, + }, + + -- Swap the designated buttons for resizing, and opening the context menu + mb_resize = nice.MB_MIDDLE, + mb_contextmenu = nice.MB_RIGHT, +} +``` + +Below you will find further details explaining the configuration parameters for nice. + +| Parameter | Type | Description | Default | +| --------------------- | :--: | ----------- | ------------------- | +| `titlebar_height` | integer | The height of the titlebar | `38` | +| `titlebar_radius` | integer | The radius of the top left and top right corners of the titlebar. Should be `>= 3` and `<= titlebar_height` | `9` | +| `titlebar_color` | string | The default color of the titlebar and window decorations. Should be a hex color string | `"#1e1e24"` | +| `titlebar_padding_left` | integer | The padding on the left side of the titlebar | `0` | +| `titlebar_padding_right` | integer | The padding on the right side of the titlebar | `0` | +| `titlebar_font` | string | The font and font size for text within the titlebar. See the default value for an example of the format | `"Sans 11"` | +| `win_shade_enabled` | boolean | Whether the window shade feature should be enabled | `true` | +| `no_titlebar_maximized` | boolean | Whether the titlebar should be hidden for maximized windows | `false` | +| `mb_move` | integer or named constant | Mouse button to move a window. | `nice.MB_LEFT` | +| `mb_contextmenu` | integer or named constant | Mouse button to open the nice context menu | `nice.MB_MIDDLE` | +| `mb_resize` | integer or named constant | Mouse button to resize a window | `nice.MB_RIGHT` | +| `mb_win_shade_rollup` | integer or named constant | Mouse button to roll up/hide window contents | `nice.MB_SCROLL_UP` | +| `mb_win_shade_rolldown` | integer or named constant | Mouse button to roll down/show window contents | `nice.MB_SCROLL_DOWN` | +| `button_size` | integer | The size (diameter) of the titlebar buttons | 16 | +| `button_margin_horizontal` | integer | The horizontal margin around each titlebar button. `button_margin_left` and `button_margin_right`can override this parameter. | 5 | +| `button_margin_vertical` | integer | The vertical margin above and below each titlebar button. `button_margin_top` and `button_margin_bottom` can override this parameter. | nil | +| `button_margin_top` | integer | The margin above each titlebar button | 2 | +| `button_margin_bottom` | integer | The margin below each titlebar button | nil | +| `button_margin_left` | integer | The margin to the left of each titlebar button | 0 | +| `button_margin_right` | integer | The margin to the right of each titlebar button | 0 | +| `tooltips_enabled` | boolean | If tooltip hints should be shown when the mouse cursor is hovered over a titlebar button | nil | +| `close_color` | string | The base color for the close button | "#ee4266" | +| `minimize_color` | string | The base color for the minimize button | "#ffb400" | +| `maximize_color` | string | The base color for the maximize button | "#4cbb17" | +| `floating_color` | string | The base color for the floating mode toggle button | "#f6a2ed" | +| `ontop_color` | string | The base color for the on top mode toggle button | "#f6a2ed" | +| `sticky_color` | string | The base color for the sticky mode toggle button | "#f6a2ed" | + +In addition to the above mentioned parameters, there some more parameters that require a little more explanation: + +### titlebar_items + +`titlebar_items` — Specifies the titlebar items to include + +* It should be a table with the following keys: + * `left` — Specifies the item(s) to place on the left side of the titlebar + * `middle` — Specifies the item(s) to place in the middle of the titlebar + * `right` — Specifies the items(s) to place on the right side of the titlebar +* Multiple items should be passed as an array of identifiers. For a single item simply passing the identifier is sufficient +* Valid titlebar item identifiers are: + * `"close"` + * `"minimize"` + * `"maximize"` + * `"floating"` + * `"ontop"` + * `"sticky"` + * `"title"` +* Default value for `titlebar_items` is: + +```lua +titlebar_items = { + left = {"close", "minimize", "maximize"}, + middle = "title", + right = {"sticky", "ontop", "floating"}, +} +``` + +### context_menu_theme + +`context_menu_theme` — Specifies theming parameters for the context (default right-click) menu + +* It should be a table with the following keys: + * `​bg_focus` — Background color of focused menu item + * `bg_normal` — Background color of not-focused menu items + * `​border_color` — Color of the border around the entire menu + * `​border_width` — Width of the border around the entire menu + * `​fg_focus` — Foreground color of focused menu item + * `​fg_normal` — Foreground color of not-focused menu items + * `​font` — Font used for menu text + * `​height` — Height of each menu list item + * `​width` — Width of the menu +* Default value for `context_menu_theme` is: + +```lua +context_menu_theme = { + bg_focus = "#aed9e0", + bg_normal = "#5e6472", + border_color = "#00000000", + border_width = 0, + fg_focus = "#242424", + fg_normal = "#fefefa", + font = "Sans 11", + height = 27.5, + width = 250, +} +``` + +### tooltip_messages + +`tooltip_messages` — Specifies the hints that are shown when the mouse cursor is hovered over a titlebar button + +* It should be a table with the following keys: + * `close` — Text shown when hovering over the close button + * `minimize` — Text shown when hovering over the minimize button + * `maximize_active` — Text shown when hovering over the maximize button when the window is maximized + * `maximize_inactive` — Text shown when hovering over the maximize button when the window is not maximized + * `floating_active` — Text shown when hovering over the floating button when the window is floating + * `floating_inactive` — Text shown when hovering over the floating button when the window is tiled + * `ontop_active` — Text shown when hovering over the ontop button when the window is set to be above other windows + * `ontop_inactive` — Text shown when hovering over the ontop button when the window is not set to be above other windows + * `sticky_active` — Text shown when hovering over the sticky button when the window is set to be available on all tags + * `sticky_inactive` — Text shown when hovering over the sticky button when the window is not to be available on all tags + +The default value for `tooltip_messages` is: + +```lua +tooltip_messages = { + close = "close", + minimize = "minimize", + maximize_active = "unmaximize", + maximize_inactive = "maximize", + floating_active = "enable tiling mode", + floating_inactive = "enable floating mode", + ontop_active = "don't keep above other windows", + ontop_inactive = "keep above other windows", + sticky_active = "disable sticky mode", + sticky_inactive = "enable sticky mode", +} +``` + + + +## Using + +nice will automatically detect and change the window decoration color to match the client. However... + +* If nice doesn't pick the right color or you want to specify it yourself, right-click the titlebar and select 'Manually Pick Color' +* If the client theme changes (for example if you change your terminal emulator colors), to update the window decoration colors, right-click on the titlebar and select 'Redo Window Decorations' +* Scroll-up with your mouse over the titlebar to "roll-up" the window shade. Scroll-down over the titlebar, or left-click to "roll-down" the window shade +* nice saves its color rules in the color_rules file within the module directory. If you wish you can manually edit it, or delete the file if you want to start again. + + + +## Issues + +If you face any bugs or issues (or have a feature request), please feel free to open an issue on here + + + +## License + +[![License](http://img.shields.io/:license-mit-blue.svg)](http://doge.mit-license.org) + + + diff --git a/src/awesome-wm-nice/colors.lua b/src/awesome-wm-nice/colors.lua new file mode 100644 index 0000000..f29096b --- /dev/null +++ b/src/awesome-wm-nice/colors.lua @@ -0,0 +1,164 @@ +-- => Colors +-- Provides utility functions for handling colors +-- ============================================================ +local math = math +local floor = math.floor +local max = math.max +local min = math.min +local random = math.random +local gcolor = require "gears.color" +local parse_color = gcolor.parse_color + +-- Returns a value that is clipped to interval edges if it falls outside the interval +local function clip(num, min_num, max_num) + return max(min(num, max_num), min_num) +end + +-- Converts the given hex color to normalized rgba +local function hex2rgb(color) + -- color = color:gsub("#", "") + -- local strlen = color:len() + -- if strlen == 6 then + -- return tonumber("0x" .. color:sub(1, 2)) / 255, + -- tonumber("0x" .. color:sub(3, 4)) / 255, + -- tonumber("0x" .. color:sub(5, 6)) / 255, 1 + -- end + -- if strlen == 8 then + -- return tonumber("0x" .. color:sub(1, 2)) / 255, + -- tonumber("0x" .. color:sub(3, 4)) / 255, + -- tonumber("0x" .. color:sub(5, 6)) / 255, + -- tonumber("0x" .. color:sub(7, 8)) / 255 + -- end + return parse_color(color) +end + +-- Converts the given hex color to hsv +local function hex2hsv(color) + local r, g, b = hex2rgb(color) + local C_max = max(r, g, b) + local C_min = min(r, g, b) + local delta = C_max - C_min + local H, S, V + if delta == 0 then + H = 0 + elseif C_max == r then + H = 60 * (((g - b) / delta) % 6) + elseif C_max == g then + H = 60 * (((b - r) / delta) + 2) + elseif C_max == b then + H = 60 * (((r - g) / delta) + 4) + end + if C_max == 0 then + S = 0 + else + S = delta / C_max + end + V = C_max + return H, S * 100, V * 100 +end + +-- Converts the given hsv color to hex +local function hsv2hex(H, S, V) + S = S / 100 + V = V / 100 + if H > 360 then + H = 360 + end + if H < 0 then + H = 0 + end + local C = V * S + local X = C * (1 - math.abs(((H / 60) % 2) - 1)) + local m = V - C + local r_, g_, b_ = 0, 0, 0 + if H >= 0 and H < 60 then + r_, g_, b_ = C, X, 0 + elseif H >= 60 and H < 120 then + r_, g_, b_ = X, C, 0 + elseif H >= 120 and H < 180 then + r_, g_, b_ = 0, C, X + elseif H >= 180 and H < 240 then + r_, g_, b_ = 0, X, C + elseif H >= 240 and H < 300 then + r_, g_, b_ = X, 0, C + elseif H >= 300 and H < 360 then + r_, g_, b_ = C, 0, X + end + local r, g, b = (r_ + m) * 255, (g_ + m) * 255, (b_ + m) * 255 + return ("#%02x%02x%02x"):format(floor(r), floor(g), floor(b)) +end + +-- Calculates the relative luminance of the given color +local function relative_luminance(color) + local r, g, b = hex2rgb(color) + local function from_sRGB(u) + return u <= 0.0031308 and 25 * u / 323 or ((200 * u + 11) / 211) ^ (12 / 5) + end + return 0.2126 * from_sRGB(r) + 0.7152 * from_sRGB(g) + 0.0722 * from_sRGB(b) +end + +-- Calculates the contrast ratio between the two given colors +local function contrast_ratio(fg, bg) + return (relative_luminance(fg) + 0.05) / (relative_luminance(bg) + 0.05) +end + +-- Returns true if the contrast between the two given colors is suitable +local function is_contrast_acceptable(fg, bg) + return contrast_ratio(fg, bg) >= 7 and true +end + +-- Returns a bright-ish, saturated-ish, color of random hue +local function rand_hex(lb_angle, ub_angle) + return hsv2hex(random(lb_angle or 0, ub_angle or 360), 70, 90) +end + +-- Rotates the hue of the given hex color by the specified angle (in degrees) +local function rotate_hue(color, angle) + local H, S, V = hex2hsv(color) + angle = clip(angle or 0, 0, 360) + H = (H + angle) % 360 + return hsv2hex(H, S, V) +end + +-- Lightens a given hex color by the specified amount +local function lighten(color, amount) + local r, g, b + r, g, b = hex2rgb(color) + r = 255 * r + g = 255 * g + b = 255 * b + r = r + floor(2.55 * amount) + g = g + floor(2.55 * amount) + b = b + floor(2.55 * amount) + r = r > 255 and 255 or r + g = g > 255 and 255 or g + b = b > 255 and 255 or b + return ("#%02x%02x%02x"):format(r, g, b) +end + +-- Darkens a given hex color by the specified amount +local function darken(color, amount) + local r, g, b + r, g, b = hex2rgb(color) + r = 255 * r + g = 255 * g + b = 255 * b + r = max(0, r - floor(r * (amount / 100))) + g = max(0, g - floor(g * (amount / 100))) + b = max(0, b - floor(b * (amount / 100))) + return ("#%02x%02x%02x"):format(r, g, b) +end + +return { + clip = clip, + hex2rgb = hex2rgb, + hex2hsv = hex2hsv, + hsv2hex = hsv2hex, + relative_luminance = relative_luminance, + contrast_ratio = contrast_ratio, + is_contrast_acceptable = is_contrast_acceptable, + rand_hex = rand_hex, + rotate_hue = rotate_hue, + lighten = lighten, + darken = darken, +} diff --git a/src/awesome-wm-nice/config.ld b/src/awesome-wm-nice/config.ld new file mode 100644 index 0000000..2484d5d --- /dev/null +++ b/src/awesome-wm-nice/config.ld @@ -0,0 +1,22 @@ +project = "awesome-wm-nice" +title = "An Awesome WM module that add MacOS-like window decorations" + +all = false +dir = "doc" +format = "markdown" +pretty = "lua" +prettify_files = true +backtick_references = true +merge = true +use_markdown_titles = true +wrap = true +sort_modules = true +not_luadoc = true + +file = { + "init.lua", +} + +-- Define some new ldoc tags from the AwesomeWM doc +new_type("constructorfct", "Constructor", false, "Parameters") +new_type("staticfct", "Static functions", false, "Parameters") diff --git a/src/awesome-wm-nice/config.lua b/src/awesome-wm-nice/config.lua new file mode 100644 index 0000000..38290ca --- /dev/null +++ b/src/awesome-wm-nice/config.lua @@ -0,0 +1,95 @@ +local abutton = require "awful.button" +local gtable = require "gears.table" + +local config = { mt = {}, _private = {} } + +-- Titlebar +config._private.titlebar_height = 38 +config._private.titlebar_radius = 9 +config._private.titlebar_color = "#1E1E24" +config._private.titlebar_margin_left = 0 +config._private.titlebar_margin_right = 0 +config._private.titlebar_font = "Sans 11" +config._private.titlebar_items = { + left = { "close", "minimize", "maximize" }, + middle = "title", + right = { "sticky", "ontop", "floating" }, +} +config._private.context_menu_theme = { + bg_focus = "#aed9e0", + bg_normal = "#5e6472", + border_color = "#00000000", + border_width = 0, + fg_focus = "#242424", + fg_normal = "#fefefa", + font = "Sans 11", + height = 27.5, + width = 250, +} +config._private.win_shade_enabled = true +config._private.no_titlebar_maximized = false +config._private.mb_move = abutton.names.LEFT +config._private.mb_contextmenu = abutton.names.MIDDLE +config._private.mb_resize = abutton.names.RIGHT +config._private.mb_win_shade_rollup = abutton.names.SCROLL_UP +config._private.mb_win_shade_rolldown = abutton.names.SCROLL_DOWN + +-- Titlebar Items +config._private.button_size = 16 +config._private.button_margin_horizontal = 5 +-- _private.button_margin_vertical +config._private.button_margin_top = 2 +-- _private.button_margin_bottom = 0 +-- _private.button_margin_left = 0 +-- _private.button_margin_right = 0 +config._private.tooltips_enabled = true +config._private.tooltip_messages = { + close = "close", + minimize = "minimize", + maximize_active = "unmaximize", + maximize_inactive = "maximize", + floating_active = "enable tiling mode", + floating_inactive = "enable floating mode", + ontop_active = "don't keep above other windows", + ontop_inactive = "keep above other windows", + sticky_active = "disable sticky mode", + sticky_inactive = "enable sticky mode", +} +config._private.close_color = "#ee4266" +config._private.minimize_color = "#ffb400" +config._private.maximize_color = "#4CBB17" +config._private.floating_color = "#f6a2ed" +config._private.ontop_color = "#f6a2ed" +config._private.sticky_color = "#f6a2ed" + +function config.init(args) + -- properties that are table + local table_args = { + titlebar_items = true, + context_menu_theme = true, + tooltip_messages = true, + } + + -- Apply changes to our _private properties + if args then + for prop, value in pairs(args) do + if table_args[prop] == true then + gtable.crush(config._private[prop], value) + elseif prop == "titlebar_radius" then + config._private[prop] = math.max(3, value) + else + config._private[prop] = value + end + end + end +end + +function config.mt:__index(k) + return config._private[k] +end + +function config.mt:__newindex(k, v) + config._private[k] = v +end + +return setmetatable(config, config.mt) diff --git a/src/awesome-wm-nice/init.lua b/src/awesome-wm-nice/init.lua new file mode 100644 index 0000000..33fd1ac --- /dev/null +++ b/src/awesome-wm-nice/init.lua @@ -0,0 +1,472 @@ +--[[ +███╗ ██╗██╗ ██████╗███████╗ +████╗ ██║██║██╔════╝██╔════╝ +██╔██╗ ██║██║██║ █████╗ +██║╚██╗██║██║██║ ██╔══╝ +██║ ╚████║██║╚██████╗███████╗ +╚═╝ ╚═══╝╚═╝ ╚═════╝╚══════╝ +Author: mu-tex +License: MIT +Repository: https://github.com/mut-ex/awesome-wm-nice +]] + +local awful = require "awful" +local abutton = awful.button +local wibox = require "wibox" +-- Widgets +local imagebox = wibox.widget.imagebox +-- Layouts +local wlayout = wibox.layout +local wlayout_align_horizontal = wlayout.align.horizontal +local wlayout_flex_horizontal = wlayout.flex.horizontal +-- Containers +local wcontainer = wibox.container +local wcontainer_background = wcontainer.background +local wcontainer_margin = wcontainer.margin +-- Gears +local gtimer = require "gears.timer" +local gtimer_weak_start_new = gtimer.weak_start_new +local gtable = require "gears.table" +-- ------------------------------------------------------------ + +-- => Math + standard Lua methods +-- ============================================================ +local math = math +local abs = math.abs + +-- ------------------------------------------------------------ + +-- => LGI +-- ============================================================ +local lgi = require "lgi" +local gdk = lgi.require("Gdk", "3.0") +-- ------------------------------------------------------------ + +-- => nice +-- ============================================================ +-- Config +local config = require "awesome-wm-nice.config" +-- Colors +local colors = require "awesome-wm-nice.colors" +local color_darken = colors.darken +local color_lighten = colors.lighten +local relative_luminance = colors.relative_luminance +-- Client Shade +local shade = require "awesome-wm-nice.shade" +-- Shapes +local shapes = require "awesome-wm-nice.shapes" +local create_corner_top_left = shapes.create_corner_top_left +local create_edge_left = shapes.create_edge_left +local create_edge_top_middle = shapes.create_edge_top_middle +local gradient = shapes.duotone_gradient_vertical +-- Utils +local utils = require "awesome-wm-nice.utils" +-- Widgets builder +local widgets = require "awesome-wm-nice.widgets" +-- ------------------------------------------------------------ + +gdk.init {} + +-- => Local settings +-- ============================================================ +local bottom_edge_height = 3 +local double_click_jitter_tolerance = 4 +local double_click_time_window_ms = 250 +local stroke_inner_bottom_lighten_mul = 0.4 +local stroke_inner_sides_lighten_mul = 0.4 +local stroke_outer_top_darken_mul = 0.7 +local titlebar_gradient_c1_lighten = 1 +local titlebar_gradient_c2_offset = 0.5 + +-- ------------------------------------------------------------ + +local nice = {} + +-- => Defaults +-- ============================================================ + +-- ------------------------------------------------------------ + +-- => Saving and loading of color rules +-- ============================================================ +local t = require "awesome-wm-nice.table" + +-- Load the color rules or create an empty table if there aren't any +local gfilesys = require "gears.filesystem" +local config_dir = gfilesys.get_configuration_dir() +local color_rules_filename = "color_rules" +local color_rules_filepath = config_dir .. "/nice/" .. color_rules_filename +config.color_rules = t.load(color_rules_filepath) or {} + +-- Saves the contents of config.color_rules table to file +local function save_color_rules() + t.save(config.color_rules, color_rules_filepath) +end + +-- Adds a color rule entry to the color_rules table for the given client and saves to file +local function set_color_rule(c, color) + config.color_rules[c.instance] = color + save_color_rules() +end + +-- Fetches the color rule for the given client instance +local function get_color_rule(c) + return config.color_rules[c.instance] +end +-- ------------------------------------------------------------ + +function nice.get_titlebar_mouse_bindings(c) + local shade_enabled = config.win_shade_enabled + -- Add functionality for double click to (un)maximize, and single click and hold to move + local clicks = 0 + local tolerance = double_click_jitter_tolerance + local buttons = { + abutton({}, config.mb_move, function() + local cx, cy = _G.mouse.coords().x, _G.mouse.coords().y + local delta = double_click_time_window_ms / 1000 + clicks = clicks + 1 + if clicks == 2 then + local nx, ny = _G.mouse.coords().x, _G.mouse.coords().y + -- The second click is only counted as a double click if it is + -- within the neighborhood of the first click's position, and + -- occurs within the set time window + if abs(cx - nx) <= tolerance and abs(cy - ny) <= tolerance then + if shade_enabled then + shade.shade_roll_down(c) + end + c.maximized = not c.maximized + end + else + if shade_enabled and c._nice_window_shade_up then + -- shade.shade_roll_down(c) + awful.mouse.wibox.move(c._nice_window_shade) + else + c:activate { context = "titlebar", action = "mouse_move" } + end + end + -- Start a timer to clear the click count + gtimer_weak_start_new(delta, function() + clicks = 0 + end) + end), + abutton({}, config.mb_contextmenu, function() + local menu_items = {} + local function add_item(text, callback) + menu_items[#menu_items + 1] = { text, callback } + end + -- TODO: Add client control options as menu entries for options that haven't had their buttons added + add_item("Redo Window Decorations", function() + c._nice_base_color = utils.get_dominant_color(c) + set_color_rule(c, c._nice_base_color) + nice.add_window_decoration(c) + end) + add_item("Manually Pick Color", function() + _G.mousegrabber.run(function(m) + if m.buttons[1] then + c._nice_base_color = utils.get_pixel_at(m.x, m.y) + set_color_rule(c, c._nice_base_color) + nice.add_window_decoration(c) + return false + end + return true + end, "crosshair") + end) + add_item("Nevermind...", function() end) + if c._nice_right_click_menu then + c._nice_right_click_menu:hide() + end + c._nice_right_click_menu = awful.menu { + items = menu_items, + theme = config.context_menu_theme, + } + c._nice_right_click_menu:show() + end), + abutton({}, config.mb_resize, function() + c:activate { context = "mouse_click", action = "mouse_resize" } + end), + } + + if config.win_shade_enabled then + buttons[#buttons + 1] = abutton({}, config.mb_win_shade_rollup, function() + shade.shade_roll_up(c) + end) + buttons[#buttons + 1] = abutton({}, config.mb_win_shade_rolldown, function() + shade.shade_roll_down(c) + end) + end + return buttons +end + +-- ------------------------------------------------------------ + +-- Puts all the pieces together and decorates the given client +function nice.add_window_decoration(c) + local client_color = c._nice_base_color + local client_geometry = c:geometry() + + -- Closures to avoid repitition + local lighten = function(amount) + return color_lighten(client_color, amount) + end + local darken = function(amount) + return color_darken(client_color, amount) + end + -- > Color computations + local luminance = relative_luminance(client_color) + local lighten_amount = utils.rel_lighten(luminance) + local darken_amount = utils.rel_darken(luminance) + -- Inner strokes + local stroke_color_inner_top = lighten(lighten_amount) + local stroke_color_inner_sides = lighten(lighten_amount * stroke_inner_sides_lighten_mul) + local stroke_color_inner_bottom = lighten(lighten_amount * stroke_inner_bottom_lighten_mul) + -- Outer strokes + local stroke_color_outer_top = darken(darken_amount * stroke_outer_top_darken_mul) + local stroke_color_outer_sides = darken(darken_amount) + local stroke_color_outer_bottom = darken(darken_amount) + local titlebar_height = config.titlebar_height + local background_fill_top = + gradient(lighten(titlebar_gradient_c1_lighten), client_color, titlebar_height, 0, titlebar_gradient_c2_offset) + -- The top left corner of the titlebar + local corner_top_left_img = create_corner_top_left { + background_source = background_fill_top, + color = client_color, + height = titlebar_height, + radius = config.titlebar_radius, + stroke_offset_inner = 1.5, + stroke_width_inner = 1, + stroke_offset_outer = 0.5, + stroke_width_outer = 1, + stroke_source_inner = gradient(stroke_color_inner_top, stroke_color_inner_sides, titlebar_height), + stroke_source_outer = gradient(stroke_color_outer_top, stroke_color_outer_sides, titlebar_height), + } + -- The top right corner of the titlebar + local corner_top_right_img = shapes.flip(corner_top_left_img, "horizontal") + + -- The middle part of the titlebar + local top_edge = create_edge_top_middle { + background_source = background_fill_top, + color = client_color, + height = titlebar_height, + stroke_color_inner = stroke_color_inner_top, + stroke_color_outer = stroke_color_outer_top, + stroke_offset_inner = 1.25, + stroke_offset_outer = 0.5, + stroke_width_inner = 2, + stroke_width_outer = 1, + width = client_geometry.width, + } + -- Create the titlebar + local titlebar = awful.titlebar(c, { size = titlebar_height, bg = "transparent" }) + -- Arrange the graphics + titlebar.widget = { + imagebox(corner_top_left_img, false), + { + { + { + utils.create_titlebar_items(c, config.titlebar_items.left), + widget = wcontainer_margin, + left = config.titlebar_margin_left, + }, + { + utils.create_titlebar_items(c, config.titlebar_items.middle), + buttons = nice.get_titlebar_mouse_bindings(c), + layout = wlayout_flex_horizontal, + }, + { + utils.create_titlebar_items(c, config.titlebar_items.right), + widget = wcontainer_margin, + right = config.titlebar_margin_right, + }, + layout = wlayout_align_horizontal, + }, + widget = wcontainer_background, + bgimage = top_edge, + }, + imagebox(corner_top_right_img, false), + layout = wlayout_align_horizontal, + } + + local resize_button = { + abutton({}, 1, function() + c:activate { context = "mouse_click", action = "mouse_resize" } + end), + } + + -- The left side border + local left_border_img = create_edge_left { + client_color = client_color, + height = client_geometry.height, + stroke_offset_outer = 0.5, + stroke_width_outer = 1, + stroke_color_outer = stroke_color_outer_sides, + stroke_offset_inner = 1.5, + stroke_width_inner = 1.5, + inner_stroke_color = stroke_color_inner_sides, + } + -- The right side border + local right_border_img = shapes.flip(left_border_img, "horizontal") + local left_side_border = awful.titlebar(c, { + position = "left", + size = 2, + bg = client_color, + widget = wcontainer_background, + }) + left_side_border:setup { + buttons = resize_button, + widget = wcontainer_background, + bgimage = left_border_img, + } + local right_side_border = awful.titlebar(c, { + position = "right", + size = 2, + bg = client_color, + widget = wcontainer_background, + }) + right_side_border:setup { + widget = wcontainer_background, + bgimage = right_border_img, + buttons = resize_button, + } + local corner_bottom_left_img = shapes.flip( + create_corner_top_left { + color = client_color, + radius = bottom_edge_height, + height = bottom_edge_height, + background_source = background_fill_top, + stroke_offset_inner = 1.5, + stroke_offset_outer = 0.5, + stroke_source_outer = gradient( + stroke_color_outer_bottom, + stroke_color_outer_sides, + bottom_edge_height, + 0, + 0.25 + ), + stroke_source_inner = gradient(stroke_color_inner_bottom, stroke_color_inner_sides, bottom_edge_height), + stroke_width_inner = 1.5, + stroke_width_outer = 2, + }, + "vertical" + ) + local corner_bottom_right_img = shapes.flip(corner_bottom_left_img, "horizontal") + local bottom_edge = shapes.flip( + create_edge_top_middle { + color = client_color, + height = bottom_edge_height, + background_source = background_fill_top, + stroke_color_inner = stroke_color_inner_bottom, + stroke_color_outer = stroke_color_outer_bottom, + stroke_offset_inner = 1.25, + stroke_offset_outer = 0.5, + stroke_width_inner = 1, + stroke_width_outer = 1, + width = client_geometry.width, + }, + "vertical" + ) + local bottom = awful.titlebar(c, { + size = bottom_edge_height, + bg = "transparent", + position = "bottom", + }) + bottom.widget = wibox.widget { + imagebox(corner_bottom_left_img, false), + -- {widget = wcontainer_background, bgimage = bottom_edge}, + imagebox(bottom_edge, false), + + imagebox(corner_bottom_right_img, false), + layout = wlayout_align_horizontal, + buttons = resize_button, + } + if config.win_shade_enabled then + shade.add_window_shade(c, titlebar.widget, bottom.widget) + end + + if config.no_titlebar_maximized then + c:connect_signal("property::maximized", function() + if c.maximized then + local curr_screen_workarea = client.focus.screen.workarea + awful.titlebar.hide(c) + c.shape = nil + c:geometry { + x = curr_screen_workarea.x, + y = curr_screen_workarea.y, + width = curr_screen_workarea.width, + height = curr_screen_workarea.height, + } + else + awful.titlebar.show(c) + -- Shape the client + c.shape = shapes.rounded_rect { + tl = config.titlebar_radius, + tr = config.titlebar_radius, + bl = 4, + br = 4, + } + end + end) + end + + -- Clean up + collectgarbage "collect" +end + +function nice.apply_client_shape(c) + c.shape = shapes.rounded_rect { + tl = config.titlebar_radius, + tr = config.titlebar_radius, + bl = 4, + br = 4, + } +end + +function nice.initialize(args) + config.init(args) + + utils.validate_mb_bindings(config) + + _G.client.connect_signal("request::titlebars", function(c) + -- Callback + c._cb_add_window_decorations = function() + gtimer_weak_start_new(0.25, function() + c._nice_base_color = utils.get_dominant_color(c) + set_color_rule(c, c._nice_base_color) + nice.add_window_decoration(c) + -- table.save(config, config_dir .. "/nice/private") + c:disconnect_signal("request::activate", c._cb_add_window_decorations) + end) + end -- _cb_add_window_decorations + + -- Check if a color rule already exists... + local base_color = get_color_rule(c) + if base_color then + -- If so, use that color rule + c._nice_base_color = base_color + nice.add_window_decoration(c) + else + -- Otherwise use the default titlebar temporarily + c._nice_base_color = config.titlebar_color + nice.add_window_decoration(c) + -- Connect a signal to determine the client color and then re-decorate it + c:connect_signal("request::activate", c._cb_add_window_decorations) + end + + -- Shape the client + nice.apply_client_shape(c) + end) + + -- Force the window decoration to be re-created when the client size change. + _G.client.connect_signal("property::size", function(c) + nice.add_window_decoration(c) + end) +end + +return gtable.join(nice, { + colors = colors, + config = config, + shade = shade, + shapes = shapes, + table = t, + utils = utils, + widgets = widgets, +}) diff --git a/src/awesome-wm-nice/shade.lua b/src/awesome-wm-nice/shade.lua new file mode 100644 index 0000000..4c37de7 --- /dev/null +++ b/src/awesome-wm-nice/shade.lua @@ -0,0 +1,79 @@ +local config = require "awesome-wm-nice.config" +local shapes = require "awesome-wm-nice.shapes" +local wibox = require "wibox" +local wlayout_manual = require "wibox.layout.manual" + +local shade = {} + +-- Legacy global variables +local bottom_edge_height = 3 + +-- Adds a window shade to the given client +function shade.add_window_shade(c, src_top, src_bottom) + local geo = c:geometry() + local w = wibox() + w.width = geo.width + w.background = "transparent" + w.x = geo.x + w.y = geo.y + w.height = config.titlebar_height + bottom_edge_height + w.ontop = true + w.visible = false + w.shape = shapes.rounded_rect { + tl = config.titlebar_radius, + tr = config.titlebar_radius, + bl = 4, + br = 4, + } + -- Need to use a manual layout because layout fixed seems to introduce a thin gap + src_top.point = { x = 0, y = 0 } + src_top.forced_width = geo.width + src_bottom.point = { x = 0, y = config.titlebar_height } + w.widget = { src_top, src_bottom, layout = wlayout_manual } + -- Clean up resources when a client is killed + c:connect_signal("request::unmanage", function() + if c._nice_window_shade then + c._nice_window_shade.visible = false + c._nice_window_shade = nil + end + -- Clean up + collectgarbage "collect" + end) + c._nice_window_shade_up = false + c._nice_window_shade = w +end + +-- Shows the window contents +function shade.shade_roll_down(c) + if not c._nice_window_shade_up then + return + end + c:geometry { x = c._nice_window_shade.x, y = c._nice_window_shade.y } + c:activate() + c._nice_window_shade.visible = false + c._nice_window_shade_up = false +end + +-- Hides the window contents +function shade.shade_roll_up(c) + if c._nice_window_shade_up then + return + end + local w = c._nice_window_shade + local geo = c:geometry() + w.x = geo.x + w.y = geo.y + w.width = geo.width + c.minimized = true + w.visible = true + w.ontop = true + c._nice_window_shade_up = true +end + +-- Toggles the window shade state +function shade.shade_toggle(c) + c.minimized = not c.minimized + c._nice_window_shade.visible = c.minimized +end + +return shade diff --git a/src/awesome-wm-nice/shapes.lua b/src/awesome-wm-nice/shapes.lua new file mode 100644 index 0000000..294f5b9 --- /dev/null +++ b/src/awesome-wm-nice/shapes.lua @@ -0,0 +1,247 @@ +-- => Shapes +-- Provides utility functions for handling cairo shapes and geometry +-- ============================================================ +-- +local colors = require "awesome-wm-nice.colors" +local lgi = require "lgi" +local hex2rgb = colors.hex2rgb +local cairo = lgi.cairo +local math = math +local rad = math.rad + +-- Returns a shape function for a rounded rectangle with independently configurable corner radii +local function rounded_rect(args) + local r1 = args.tl or 0 + local r2 = args.bl or 0 + local r3 = args.br or 0 + local r4 = args.tr or 0 + return function(cr, width, height) + cr:new_sub_path() + cr:arc(width - r1, r1, r1, rad(-90), rad(0)) + cr:arc(width - r2, height - r2, r2, rad(0), rad(90)) + cr:arc(r3, height - r3, r3, rad(90), rad(180)) + cr:arc(r4, r4, r4, rad(180), rad(270)) + cr:close_path() + end +end + +-- Returns a circle of the specified size filled with the specified color +local function circle_filled(color, size) + color = color or "#fefefa" + local surface = cairo.ImageSurface.create("ARGB32", size, size) + local cr = cairo.Context.create(surface) + cr:arc(size / 2, size / 2, size / 2, rad(0), rad(360)) + cr:set_source_rgba(hex2rgb(color)) + cr.antialias = cairo.Antialias.BEST + cr:fill() + -- cr:arc( + -- size / 2, size / 2, size / 2 - 0.5, rad(135), rad(270)) + -- cr:set_source_rgba(hex2rgb(darken(color, 25))) + -- cr.line_width = 1 + -- cr:stroke() + + return surface +end + +-- Returns a vertical gradient pattern going from cololr_1 -> color_2 +local function duotone_gradient_vertical(color_1, color_2, height, offset_1, offset_2) + local fill_pattern = cairo.Pattern.create_linear(0, 0, 0, height) + local r, g, b, a + r, g, b, a = hex2rgb(color_1) + fill_pattern:add_color_stop_rgba(offset_1 or 0, r, g, b, a) + r, g, b, a = hex2rgb(color_2) + fill_pattern:add_color_stop_rgba(offset_2 or 1, r, g, b, a) + return fill_pattern +end + +-- Returns a horizontal gradient pattern going from cololr_1 -> color_2 +local function duotone_gradient_horizontal(color, width) + local fill_pattern = cairo.Pattern.create_linear(0, 0, width, 0) + local r, g, b, a + r, g, b, a = hex2rgb(color) + fill_pattern:add_color_stop_rgba(0, r, g, b, a) + r, g, b, a = hex2rgb(color) + fill_pattern:add_color_stop_rgba(0.5, r, g, b, a) + r, g, b, a = hex2rgb "#00000000" + fill_pattern:add_color_stop_rgba(0.6, r, g, b, a) + r, g, b, a = hex2rgb(color) + fill_pattern:add_color_stop_rgba(0.7, r, g, b, a) + r, g, b, a = hex2rgb(color) + fill_pattern:add_color_stop_rgba(1, r, g, b, a) + return fill_pattern +end + +-- Flips the given surface around the specified axis +local function flip(surface, axis) + local width = surface:get_width() + local height = surface:get_height() + local flipped = cairo.ImageSurface.create("ARGB32", width, height) + local cr = cairo.Context.create(flipped) + local source_pattern = cairo.Pattern.create_for_surface(surface) + if axis == "horizontal" then + source_pattern.matrix = cairo.Matrix { xx = -1, yy = 1, x0 = width } + elseif axis == "vertical" then + source_pattern.matrix = cairo.Matrix { xx = 1, yy = -1, y0 = height } + elseif axis == "both" then + source_pattern.matrix = cairo.Matrix { + xx = -1, + yy = -1, + x0 = width, + y0 = height, + } + end + cr.source = source_pattern + cr:rectangle(0, 0, width, height) + cr:paint() + + return flipped +end + +-- Draws the left corner of the titlebar +local function create_corner_top_left(args) + local radius = args.radius + local height = args.height + local surface = cairo.ImageSurface.create("ARGB32", radius, height) + local cr = cairo.Context.create(surface) + -- Create the corner shape and fill it with a gradient + local radius_offset = 1 -- To soften the corner + cr:move_to(0, height) + cr:line_to(0, radius - radius_offset) + cr:arc(radius + radius_offset, radius + radius_offset, radius, rad(180), rad(270)) + cr:line_to(radius, height) + cr:close_path() + cr.source = args.background_source + cr.antialias = cairo.Antialias.BEST + cr:fill() + -- Next add the subtle 3D look + local function add_stroke(nargs) + local arc_radius = nargs.radius + local offset_x = nargs.offset_x + local offset_y = nargs.offset_y + cr:new_sub_path() + cr:move_to(offset_x, height) + cr:line_to(offset_x, arc_radius + offset_y) + cr:arc(arc_radius + offset_x, arc_radius + offset_y, arc_radius, rad(180), rad(270)) + cr.source = nargs.source + cr.line_width = nargs.width + cr.antialias = cairo.Antialias.BEST + cr:stroke() + end + -- Outer dark stroke + add_stroke { + offset_x = args.stroke_offset_outer, + offset_y = args.stroke_offset_outer, + radius = radius + 0.5, + source = args.stroke_source_outer, + width = args.stroke_width_outer, + } + -- Inner light stroke + add_stroke { + offset_x = args.stroke_offset_inner, + offset_y = args.stroke_offset_inner, + radius = radius, + width = args.stroke_width_inner, + source = args.stroke_source_inner, + } + + return surface +end + +-- Draws the middle of the titlebar +local function create_edge_top_middle(args) + local height = args.height + local width = args.width + local surface = cairo.ImageSurface.create("ARGB32", width, height) + local cr = cairo.Context.create(surface) + -- Create the background shape and fill it with a gradient + cr:rectangle(0, 0, width, height) + cr.source = args.background_source + cr:fill() + -- Then add the light and dark strokes for that 3D look + local function add_stroke(stroke_width, stroke_offset, stroke_color) + cr:new_sub_path() + cr:move_to(0, stroke_offset) + cr:line_to(width, stroke_offset) + cr.line_width = stroke_width + cr:set_source_rgb(hex2rgb(stroke_color)) + cr:stroke() + end + -- Inner light stroke + add_stroke( + args.stroke_width_inner, -- 2 + args.stroke_offset_inner, -- 1.25 + args.stroke_color_inner -- color_lighten(client_color, utils.rel_lighten(relative_luminance(client_color))) + ) + -- Outer dark stroke + add_stroke( + args.stroke_width_outer, -- 1 + args.stroke_offset_outer, -- 0.5 + args.stroke_color_outer -- color_darken(client_color, utils.rel_darken(relative_luminance(client_color)) * 0.7) + ) + + return surface +end + +local function create_edge_left(args) + local height = args.height + local width = 2 + -- height = height or 1080 + local surface = cairo.ImageSurface.create("ARGB32", width, height) + local cr = cairo.Context.create(surface) + cr:rectangle(0, 0, 2, args.height) + cr:set_source_rgb(hex2rgb(args.client_color)) + cr:fill() + -- Inner light stroke + cr:new_sub_path() + cr:move_to(args.stroke_offset_inner, 0) -- 1/5 + cr:line_to(args.stroke_offset_inner, height) + cr.line_width = args.stroke_width_inner -- 1.5 + cr:set_source_rgb(hex2rgb(args.inner_stroke_color)) + cr:stroke() + -- Outer dark stroke + cr:new_sub_path() + cr:move_to(args.stroke_offset_outer, 0) + cr:line_to(args.stroke_offset_outer, height) + cr.line_width = args.stroke_width_outer -- 1 + cr:set_source_rgb(hex2rgb(args.stroke_color_outer)) + cr:stroke() + + return surface +end + +local function set_font(cr, font) + cr:set_font_size(font.size) + cr:select_font_face(font.font or "Inter", font.italic and 1 or 0, font.bold and 1 or 0) +end + +local function text_label(args) + local surface = cairo.ImageSurface.create("ARGB32", 1, 1) + local cr = cairo.Context.create(surface) + set_font(cr, args.font) + local text = args.text + local kern = args.font.kerning or 0 + local ext = cr:text_extents(text) + surface = cairo.ImageSurface.create("ARGB32", ext.width + string.len(text) * kern, ext.height) + cr = cairo.Context.create(surface) + set_font(cr, args.font) + cr:move_to(0, ext.height) + cr:set_source_rgb(hex2rgb(args.color)) + -- cr:show_text(text) + text:gsub(".", function(c) + -- do something with c + cr:show_text(c) + cr:rel_move_to(kern, 0) + end) + return surface +end + +return { + rounded_rect = rounded_rect, + circle_filled = circle_filled, + duotone_gradient_vertical = duotone_gradient_vertical, + flip = flip, + create_corner_top_left = create_corner_top_left, + create_edge_top_middle = create_edge_top_middle, + create_edge_left = create_edge_left, + text_label = text_label, +} diff --git a/stylua.toml b/src/awesome-wm-nice/stylua.toml similarity index 100% rename from stylua.toml rename to src/awesome-wm-nice/stylua.toml diff --git a/src/awesome-wm-nice/table.lua b/src/awesome-wm-nice/table.lua new file mode 100644 index 0000000..7305b7f --- /dev/null +++ b/src/awesome-wm-nice/table.lua @@ -0,0 +1,108 @@ +--[[ + Courtesy of: http://lua-users.org/wiki/SaveTableToFile +]] +local function exportstring(s) + return string.format("%q", s) +end + +-- The Save Function +local function save(tbl, filename) + local charS, charE = " ", "\n" + local file, err = io.open(filename, "wb") + if err then + return err + end + + -- Initialize variables for save procedure + local tables, lookup = { tbl }, { [tbl] = 1 } + file:write("return {" .. charE) + + for idx, t in ipairs(tables) do + file:write("-- Table: {" .. idx .. "}" .. charE) + file:write("{" .. charE) + local thandled = {} + + for i, v in ipairs(t) do + thandled[i] = true + local stype = type(v) + -- only handle value + if stype == "table" then + if not lookup[v] then + table.insert(tables, v) + lookup[v] = #tables + end + file:write(charS .. "{" .. lookup[v] .. "}," .. charE) + elseif stype == "string" then + file:write(charS .. exportstring(v) .. "," .. charE) + elseif stype == "number" then + file:write(charS .. tostring(v) .. "," .. charE) + end + end + + for i, v in pairs(t) do + -- escape handled values + if not thandled[i] then + local str = "" + local stype = type(i) + -- handle index + if stype == "table" then + if not lookup[i] then + table.insert(tables, i) + lookup[i] = #tables + end + str = charS .. "[{" .. lookup[i] .. "}]=" + elseif stype == "string" then + str = charS .. "[" .. exportstring(i) .. "]=" + elseif stype == "number" then + str = charS .. "[" .. tostring(i) .. "]=" + end + + if str ~= "" then + stype = type(v) + -- handle value + if stype == "table" then + if not lookup[v] then + table.insert(tables, v) + lookup[v] = #tables + end + file:write(str .. "{" .. lookup[v] .. "}," .. charE) + elseif stype == "string" then + file:write(str .. exportstring(v) .. "," .. charE) + elseif stype == "number" then + file:write(str .. tostring(v) .. "," .. charE) + end + end + end + end + file:write("}," .. charE) + end + file:write "}" + file:close() +end + +-- The Load Function +local function load(sfile) + local ftables, err = loadfile(sfile) + if err then + return nil, err + end + local tables = ftables() + for idx = 1, #tables do + local tolinki = {} + for i, v in pairs(tables[idx]) do + if type(v) == "table" then + tables[idx][i] = tables[v[1]] + end + if type(i) == "table" and tables[i[1]] then + table.insert(tolinki, { i, tables[i[1]] }) + end + end + -- link indices + for _, v in ipairs(tolinki) do + tables[idx][v[2]], tables[idx][v[1]] = tables[idx][v[1]], nil + end + end + return tables[1] +end + +return { save = save, load = load } diff --git a/src/awesome-wm-nice/utils.lua b/src/awesome-wm-nice/utils.lua new file mode 100644 index 0000000..d4b781d --- /dev/null +++ b/src/awesome-wm-nice/utils.lua @@ -0,0 +1,149 @@ +local gsurface = require "gears.surface" +local lgi = require "lgi" +local wibox = require "wibox" +local widgets = require "awesome-wm-nice.widgets" +local wlayout_fixed = require "wibox.layout.fixed" + +local gdk = lgi.require("Gdk", "3.0") + +local utils = {} + +function utils.rel_lighten(lum) + return lum * 90 + 10 +end + +function utils.rel_darken(lum) + return -(lum * 70) + 100 +end + +-- Returns the hex color for the pixel at the given coordinates on the screen +function utils.get_pixel_at(x, y) + local pixbuf = gdk.pixbuf_get_from_window(gdk.get_default_root_window(), x, y, 1, 1) + local bytes = pixbuf:get_pixels() + return "#" .. bytes:gsub(".", function(c) + return ("%02x"):format(c:byte()) + end) +end + +-- Determines the dominant color of the client's top region +function utils.get_dominant_color(client) + local color + -- gsurface(client.content):write_to_png( + -- "/home/mutex/nice/" .. client.class .. "_" .. client.instance .. ".png") + local pb + local bytes + local tally = {} + local content = gsurface(client.content) + local cgeo = client:geometry() + local x_offset = 2 + local y_offset = 2 + local x_lim = math.floor(cgeo.width / 2) + for x_pos = 0, x_lim, 2 do + for y_pos = 0, 8, 1 do + pb = gdk.pixbuf_get_from_surface(content, x_offset + x_pos, y_offset + y_pos, 1, 1) + bytes = pb:get_pixels() + color = "#" .. bytes:gsub(".", function(c) + return ("%02x"):format(c:byte()) + end) + if not tally[color] then + tally[color] = 1 + else + tally[color] = tally[color] + 1 + end + end + end + local mode + local mode_c = 0 + for kolor, kount in pairs(tally) do + if kount > mode_c then + mode_c = kount + mode = kolor + end + end + color = mode + return color +end + +-- Returns a titlebar item +function utils.get_titlebar_item(c, name) + if name == "close" then + return widgets.create_titlebar_button(c, name, function() + c:kill() + end) + elseif name == "maximize" then + return widgets.create_titlebar_button(c, name, function() + c.maximized = not c.maximized + end, "maximized") + elseif name == "minimize" then + return widgets.create_titlebar_button(c, name, function() + c.minimized = true + end) + elseif name == "ontop" then + return widgets.create_titlebar_button(c, name, function() + c.ontop = not c.ontop + end, "ontop") + elseif name == "floating" then + return widgets.create_titlebar_button(c, name, function() + c.floating = not c.floating + if c.floating then + c.maximized = false + end + end, "floating") + elseif name == "sticky" then + return widgets.create_titlebar_button(c, name, function() + c.sticky = not c.sticky + return c.sticky + end, "sticky") + elseif name == "title" then + return widgets.create_titlebar_title(c) + end +end + +-- Creates titlebar items for a given group of item names +-- group can be a string (=item name) or a table (= array of "item-name"s) +function utils.create_titlebar_items(c, group) + if not group then + return nil + end + if type(group) == "string" then + return utils.get_titlebar_item(c, group) + end + local titlebar_group_items = wibox.widget { + layout = wlayout_fixed.horizontal, + } + local item + for _, name in ipairs(group) do + item = utils.get_titlebar_item(c, name) + if item then + titlebar_group_items:add(item) + end + end + return titlebar_group_items +end + +function utils.validate_mb_bindings(private) + local action_mbs = { + "mb_move", + "mb_contextmenu", + "mb_resize", + "mb_win_shade_rollup", + "mb_win_shade_rolldown", + } + local mb_specified = { false, false, false, false, false } + local mb + local mb_conflict_test + for _, action_mb in ipairs(action_mbs) do + mb = private[action_mb] + if mb then + assert(mb >= 1 and mb <= 5, "Invalid mouse button specified!") + mb_conflict_test = mb_specified[mb] + if not mb_conflict_test then + mb_specified[mb] = action_mb + else + error(("%s and %s can not be bound to the same mouse button"):format(action_mb, mb_conflict_test)) + end + end + end +end + +return utils diff --git a/src/awesome-wm-nice/widgets.lua b/src/awesome-wm-nice/widgets.lua new file mode 100644 index 0000000..9ce3450 --- /dev/null +++ b/src/awesome-wm-nice/widgets.lua @@ -0,0 +1,201 @@ +local abutton = require "awful.button" +local atooltip = require "awful.tooltip" +local colors = require "awesome-wm-nice.colors" +local config = require "awesome-wm-nice.config" +local get_font_height = require("beautiful").get_font_height +local imagebox = require "wibox.widget.imagebox" +local shapes = require "awesome-wm-nice.shapes" +local textbox = require "wibox.widget.textbox" +local wcontainer_constraint = require "wibox.container.constraint" +local wcontainer_margin = require "wibox.container.margin" +local wcontainer_place = require "wibox.container.place" +local wibox = require "wibox" + +local widgets = {} +local cache = {} + +-- Legacy global variables +local title_color_dark = "#242424" +local title_color_light = "#fefefa" +local title_unfocused_opacity = 0.7 + +-- Returns a color that is analogous to the last color returned +-- To make sure that the "randomly" generated colors look cohesive, only the +-- first color is truly random, the rest are generated by offseting the hue by +-- +33 degrees +local next_color = colors.rand_hex() +local function get_next_color() + local prev_color = next_color + next_color = colors.rotate_hue(prev_color, 33) + return prev_color +end + +-- Returns (or generates) a button image based on the given params +function widgets.create_button_image(name, is_focused, event, is_on) + local focus_state = is_focused and "focused" or "unfocused" + local key_img + -- If it is a toggle button, then the key has an extra param + if is_on ~= nil then + local toggle_state = is_on and "on" or "off" + key_img = ("%s_%s_%s_%s"):format(name, toggle_state, focus_state, event) + else + key_img = ("%s_%s_%s"):format(name, focus_state, event) + end + -- If an image already exists, then we are done + if cache[key_img] then + return cache[key_img] + end + -- The color key just has _color at the end + local key_color = key_img .. "_color" + -- If the user hasn't provided a color, then we have to generate one + if not cache[key_color] then + local key_base_color = name .. "_color" + -- Maybe the user has at least provided a base color? If not we just pick a pesudo-random color + local base_color = config[key_base_color] or get_next_color() + cache[key_base_color] = base_color + local button_color = base_color + local H = colors.hex2hsv(base_color) + -- Unfocused buttons are desaturated and darkened (except when they are being hovered over) + if not is_focused and event ~= "hover" then + button_color = colors.hsv2hex(H, 0, 50) + end + -- Then the color is lightened if the button is being hovered over, or + -- darkened if it is being pressed, otherwise it is left as is + button_color = (event == "hover") and colors.lighten(button_color, 25) + or (event == "press") and colors.darken(button_color, 25) + or button_color + -- Save the generate color because why not lol + cache[key_color] = button_color + end + local button_size = config.button_size + -- If it is a toggle button, we create an outline instead of a filled shape if it is in off state + -- config[key_img] = (is_on ~= nil and is_on == false) and + -- shapes.circle_outline( + -- config[key_color], button_size, + -- config.button_border_width) or + -- shapes.circle_filled( + -- config[key_color], button_size) + cache[key_img] = shapes.circle_filled(cache[key_color], button_size) + return cache[key_img] +end + +-- Creates a titlebar button widget +function widgets.create_titlebar_button(c, name, button_callback, property) + local button_img = imagebox(nil, false) + if config.tooltips_enabled then + local tooltip = atooltip { + timer_function = function() + local prop = name .. (property and (c[property] and "_active" or "_inactive") or "") + return config.tooltip_messages[prop] + end, + delay_show = 0.5, + margins_leftright = 12, + margins_topbottom = 6, + timeout = 0.25, + align = "bottom_right", + } + tooltip:add_to_object(button_img) + end + local is_on, is_focused + local event = "normal" + local function update() + is_focused = c.active + -- If the button is for a property that can be toggled + if property then + is_on = c[property] + button_img.image = widgets.create_button_image(name, is_focused, event, is_on) + else + button_img.image = widgets.create_button_image(name, is_focused, event) + end + end + -- Update the button when the client gains/loses focus + c:connect_signal("unfocus", update) + c:connect_signal("focus", update) + -- If the button is for a property that can be toggled, update it accordingly + if property then + c:connect_signal("property::" .. property, update) + end + -- Update the button on mouse hover/leave + button_img:connect_signal("mouse::enter", function() + event = "hover" + update() + end) + button_img:connect_signal("mouse::leave", function() + event = "normal" + update() + end) + -- The button is updated on both click and release, but the call back is executed on release + button_img.buttons = abutton({}, abutton.names.LEFT, function() + event = "press" + update() + end, function() + if button_callback then + event = "normal" + button_callback() + else + event = "hover" + end + update() + end) + button_img.id = "button_image" + update() + return wibox.widget { + widget = wcontainer_place, + { + widget = wcontainer_margin, + top = config.button_margin_top or config.button_margin_vertical or config.button_margin, + bottom = config.button_margin_bottom or config.button_margin_vertical or config.button_margin, + left = config.button_margin_left or config.button_margin_horizontal or config.button_margin, + right = config.button_margin_right or config.button_margin_horizontal or config.button_margin, + { + button_img, + widget = wcontainer_constraint, + height = config.button_size, + width = config.button_size, + strategy = "exact", + }, + }, + } +end + +-- Returns a titlebar widget for the given client +function widgets.create_titlebar_title(c) + local client_color = c._nice_base_color + + local title_widget = wibox.widget { + align = "center", + ellipsize = "middle", + opacity = c.active and 1 or title_unfocused_opacity, + valign = "center", + widget = textbox, + } + + local function update() + local text_color = colors.is_contrast_acceptable(title_color_light, client_color) and title_color_light + or title_color_dark + title_widget.markup = ("%s"):format( + text_color, + config.titlebar_font, + c.name + ) + end + c:connect_signal("property::name", update) + c:connect_signal("unfocus", function() + title_widget.opacity = title_unfocused_opacity + end) + c:connect_signal("focus", function() + title_widget.opacity = 1 + end) + update() + local titlebar_font_height = get_font_height(config.titlebar_font) + local leftover_space = config.titlebar_height - titlebar_font_height + local margin_vertical = leftover_space > 1 and leftover_space / 2 or 0 + return { + title_widget, + widget = wcontainer_margin, + top = margin_vertical, + bottom = margin_vertical, + } +end + +return widgets diff --git a/configuration/applications.lua b/src/awesomerc/configuration/applications.lua similarity index 64% rename from configuration/applications.lua rename to src/awesomerc/configuration/applications.lua index bf4cc29..d10204f 100644 --- a/configuration/applications.lua +++ b/src/awesomerc/configuration/applications.lua @@ -7,15 +7,15 @@ applications.browser = "firefox-nightly" applications.web = "qutebrowser" applications.open_terminal = function() - return applications.terminal .. " -e tmux" + return applications.terminal .. " -e tmux" end applications.open_editor = function(file) - return applications.terminal .. " -e " .. applications.editor .. " " .. file + return applications.terminal .. " -e " .. applications.editor .. " " .. file end applications.open_man = function(file) - return applications.terminal .. " -e man " .. file + return applications.terminal .. " -e man " .. file end return applications diff --git a/src/awesomerc/configuration/bindings/client_keybindings.lua b/src/awesomerc/configuration/bindings/client_keybindings.lua new file mode 100644 index 0000000..02ed748 --- /dev/null +++ b/src/awesomerc/configuration/bindings/client_keybindings.lua @@ -0,0 +1,113 @@ +local aclient = require "awful.client" +local akey = require "awful.key" + +local utils = require "awesomerc.configuration.bindings.utils" + +local client_keybindings = { + + akey { + modifiers = { utils.mods.modkey }, + key = "f", + description = "toggle fullscreen", + group = utils.groups.client, + on_press = function(client) + client.fullscreen = not client.fullscreen + client:raise() + end, + }, + + akey { + modifiers = { utils.mods.modkey }, + key = "c", + description = "close", + group = utils.groups.client, + on_press = function(client) + client:kill() + end, + }, + + akey { + modifiers = { utils.mods.modkey, utils.mods.control }, + key = "space", + description = "toggle floating", + group = utils.groups.client, + on_press = aclient.floating.toggle, + }, + + akey { + modifiers = { utils.mods.modkey, utils.mods.control }, + key = "Return", + description = "move to master", + group = utils.groups.client, + on_press = function(client) + client:swap(aclient.getmaster()) + end, + }, + + akey { + modifiers = { utils.mods.modkey }, + key = "o", + description = "move to screen", + group = utils.groups.client, + on_press = function(client) + client:move_to_screen() + end, + }, + + akey { + modifiers = { utils.mods.modkey }, + key = "t", + description = "toggle keep on top", + group = utils.groups.client, + on_press = function(client) + client.ontop = not client.ontop + end, + }, + + akey { + modifiers = { utils.mods.modkey }, + key = "n", + description = "minimize", + group = utils.groups.client, + on_press = function(client) + -- The client currently has the input focus, so it cannot be + -- minimized, since minimized clients can't have the focus. + client.minimized = true + end, + }, + + akey { + modifiers = { utils.mods.modkey }, + key = "m", + description = "(un)maximize", + group = utils.groups.client, + on_press = function(client) + client.maximized = not client.maximized + client:raise() + end, + }, + + akey { + modifiers = { utils.mods.modkey, utils.mods.control }, + key = "m", + description = "(un)maximize vertically", + group = utils.groups.client, + on_press = function(client) + client.maximized_vertical = not client.maximized_vertical + client:raise() + end, + }, + + akey { + modifiers = { utils.mods.modkey, utils.mods.shift }, + key = "m", + description = "(un)maximize horizontally", + group = utils.groups.client, + on_press = function(client) + client.maximized_horizontal = not client.maximized_horizontal + client:raise() + end, + }, +} + +return client_keybindings diff --git a/src/awesomerc/configuration/bindings/client_mousebindings.lua b/src/awesomerc/configuration/bindings/client_mousebindings.lua new file mode 100644 index 0000000..ee5776e --- /dev/null +++ b/src/awesomerc/configuration/bindings/client_mousebindings.lua @@ -0,0 +1,39 @@ +local abutton = require "awful.button" + +local utils = require "awesomerc.configuration.bindings.utils" + +local mousebindings = { + abutton { + modifiers = {}, + button = abutton.names.LEFT, + on_press = function(client) + client:activate { + context = "mouse_click", + } + end, + }, + + abutton { + modifiers = { utils.mods.modkey }, + button = abutton.names.LEFT, + on_press = function(client) + client:activate { + context = "mouse_click", + action = "mouse_move", + } + end, + }, + + abutton { + modifiers = { utils.mods.modkey }, + button = abutton.names.RIGHT, + on_press = function(client) + client:activate { + context = "mouse_click", + action = "mouse_resize", + } + end, + }, +} + +return mousebindings diff --git a/src/awesomerc/configuration/bindings/global_keybindings.lua b/src/awesomerc/configuration/bindings/global_keybindings.lua new file mode 100644 index 0000000..216bd25 --- /dev/null +++ b/src/awesomerc/configuration/bindings/global_keybindings.lua @@ -0,0 +1,223 @@ +local aclient = require "awful.client" +local akey = require "awful.key" +local aprompt = require "awful.prompt" +local ascreen = require "awful.screen" +local aspawn = require "awful.spawn" +local atag = require "awful.tag" +local autil = require "awful.util" + +local menubar = require "menubar" + +local applications = require "awesomerc.configuration.applications" +local desktop_bar = require "awesomerc.ui.desktop_decoration.bar" +local hotkeys_popup = require "awesomerc.ui.hotkeys_popup" +local mymainmenu = require "awesomerc.ui.menu.mymainmenu" + +local utils = require "awesomerc.configuration.bindings.utils" + +local capi = { + awesome = _G.awesome, + client = _G.client, +} + +local global_keybindings = { + + -- Awesome + + akey { + modifiers = { utils.mods.modkey }, + key = "s", + description = "show help", + group = utils.groups.awesome, + on_press = function() + hotkeys_popup.show_help() + end, + }, + + akey { + modifiers = { utils.mods.modkey }, + key = "w", + description = "show main menu", + group = utils.groups.awesome, + on_press = function() + mymainmenu():show() + end, + }, + + akey { + modifiers = { utils.mods.modkey, utils.mods.control }, + key = "r", + description = "reload awesome", + group = utils.groups.awesome, + on_press = capi.awesome.restart, + }, + + akey { + modifiers = { utils.mods.modkey, utils.mods.shift }, + key = "q", + description = "quit awesome", + group = utils.groups.awesome, + on_press = capi.awesome.quit, + }, + + akey { + modifiers = { utils.mods.modkey }, + key = "x", + description = "lua execute prompt", + group = utils.groups.awesome, + on_press = function() + aprompt.run { + prompt = "Run Lua code: ", + textbox = desktop_bar(ascreen.focused()).promptbox.widget, + exe_callback = autil.eval, + history_path = autil.get_cache_dir() .. "/history_eval", + } + end, + }, + + -- Launcher + + akey { + modifiers = { utils.mods.modkey }, + key = "Return", + description = "open a terminal", + group = utils.groups.launcher, + on_press = function() + aspawn(applications.open_terminal()) + end, + }, + + akey { + modifiers = { utils.mods.modkey }, + key = "r", + description = "run prompt", + group = utils.groups.launcher, + on_press = function() + desktop_bar(ascreen.focused()).promptbox:run() + end, + }, + + akey { + modifiers = { utils.mods.modkey }, + key = "p", + description = "show the menubar", + group = utils.groups.launcher, + on_press = function() + menubar.show() + end, + }, + + -- Client focus + + akey { + modifiers = { utils.mods.modkey }, + key = "j", + group = "client", + description = "Focus next client by index", + on_press = function() + aclient.focus.byidx(1) + end, + }, + + akey { + modifiers = { utils.mods.modkey }, + key = "k", + group = "client", + description = "Focus previous by index", + on_press = function() + aclient.focus.byidx(-1) + end, + }, + + -- Layout manipulation + + akey { + modifiers = { utils.mods.modkey, utils.mods.shift }, + key = "j", + group = "client", + description = "Swap with next client", + on_press = function() + aclient.swap.byidx(1) + end, + }, + + akey { + modifiers = { utils.mods.modkey, utils.mods.shift }, + key = "k", + group = "client", + description = "Swap with previous client", + on_press = function() + aclient.swap.byidx(-1) + end, + }, + + akey { + modifiers = { utils.mods.modkey, utils.mods.shift }, + key = "Right", + description = "Increase master width factor", + group = "client", + on_press = function() + atag.incmwfact(0.01) + end, + }, + + akey { + modifiers = { utils.mods.modkey, utils.mods.shift }, + key = "Left", + description = "Decrease master width factor", + group = "client", + on_press = function() + atag.incmwfact(-0.01) + end, + }, + + -- Tags manipulation + + akey { + modifiers = { utils.mods.modkey }, + keygroup = akey.keygroup.NUMROW, + description = "only view tag", + group = "tag", + on_press = function(index) + local screen = ascreen.focused() + local tag = screen.tags[index] + + if tag then + tag:view_only() + end + end, + }, + + akey { + modifiers = { utils.mods.modkey, utils.mods.control }, + keygroup = akey.keygroup.NUMROW, + description = "toggle tag", + group = "tag", + on_press = function(index) + local screen = ascreen.focused() + local tag = screen.tags[index] + + if tag then + atag.viewtoggle(tag) + end + end, + }, + + akey { + modifiers = { utils.mods.modkey, utils.mods.shift }, + keygroup = akey.keygroup.NUMROW, + description = "move focused client to tag", + group = "tag", + on_press = function(index) + local screen = ascreen.focused() + local client = capi.client.focus + local tag = screen.tags[index] + + if client and tag then + client:move_to_tag(tag) + end + end, + }, +} + +return global_keybindings diff --git a/src/awesomerc/configuration/bindings/global_mousebindings.lua b/src/awesomerc/configuration/bindings/global_mousebindings.lua new file mode 100644 index 0000000..de11e88 --- /dev/null +++ b/src/awesomerc/configuration/bindings/global_mousebindings.lua @@ -0,0 +1,28 @@ +local abutton = require "awful.button" +local atag = require "awful.tag" + +local mymainmenu = require "awesomerc.ui.menu.mymainmenu" + +local global_mousebindings = { + abutton { + modifiers = {}, + button = abutton.names.RIGHT, + on_press = function() + mymainmenu():toggle() + end, + }, + + abutton { + modifiers = {}, + button = abutton.names.SCROLL_UP, + on_press = tag.viewprev, + }, + + abutton { + modifiers = {}, + button = abutton.names.SCROLL_DOWN, + on_press = atag.viewnext, + }, +} + +return global_mousebindings diff --git a/src/awesomerc/configuration/bindings/init.lua b/src/awesomerc/configuration/bindings/init.lua new file mode 100644 index 0000000..0ccff30 --- /dev/null +++ b/src/awesomerc/configuration/bindings/init.lua @@ -0,0 +1,9 @@ +local bindings = {} + +bindings.client_keybindings = require "awesomerc.configuration.bindings.client_keybindings" +bindings.client_mousebindings = require "awesomerc.configuration.bindings.client_mousebindings" +bindings.global_keybindings = require "awesomerc.configuration.bindings.global_keybindings" +bindings.global_mousebindings = require "awesomerc.configuration.bindings.global_mousebindings" +bindings.utils = require "awesomerc.configuration.bindings.utils" + +return bindings diff --git a/src/awesomerc/configuration/bindings/utils.lua b/src/awesomerc/configuration/bindings/utils.lua new file mode 100644 index 0000000..c2d3f76 --- /dev/null +++ b/src/awesomerc/configuration/bindings/utils.lua @@ -0,0 +1,16 @@ +local mods = { + control = "Control", + modkey = "Mod4", + shift = "Shift", +} + +local groups = { + client = "client", + awesome = "awesome", + launcher = "launcher", +} + +return { + mods = mods, + groups = groups, +} diff --git a/src/awesomerc/configuration/init.lua b/src/awesomerc/configuration/init.lua new file mode 100644 index 0000000..16178e4 --- /dev/null +++ b/src/awesomerc/configuration/init.lua @@ -0,0 +1,10 @@ +local rc_configuration = {} + +rc_configuration.applications = require "awesomerc.configuration.applications" +rc_configuration.bindings = require "awesomerc.configuration.bindings" +rc_configuration.menu = require "awesomerc.configuration.menu" +rc_configuration.rules = require "awesomerc.configuration.rules" +rc_configuration.prompt_commands = require "awesomerc.configuration.prompt_commands" +rc_configuration.tag_layouts = require "awesomerc.configuration.tag_layouts" + +return rc_configuration diff --git a/src/awesomerc/configuration/menu/init.lua b/src/awesomerc/configuration/menu/init.lua new file mode 100644 index 0000000..baa692a --- /dev/null +++ b/src/awesomerc/configuration/menu/init.lua @@ -0,0 +1,6 @@ +local configuration_menu = {} + +configuration_menu.myawesomemenu = require "awesomerc.configuration.menu.myawesomemenu" +configuration_menu.mymainmenu = require "awesomerc.configuration.menu.mymainmenu" + +return configuration_menu diff --git a/src/awesomerc/configuration/menu/myawesomemenu.lua b/src/awesomerc/configuration/menu/myawesomemenu.lua new file mode 100644 index 0000000..099576b --- /dev/null +++ b/src/awesomerc/configuration/menu/myawesomemenu.lua @@ -0,0 +1,31 @@ +local applications = require "awesomerc.configuration.applications" +local hotkeys_popup = require "awesomerc.ui.hotkeys_popup" + +local capi = { + awesome = _G.awesome, +} + +local myawesomemenu = { + { + "hotkeys", + function() + hotkeys_popup.show_help() + end, + }, + { "manual", applications.open_man "awesome" }, + { "edit config", applications.open_editor(capi.awesome.conffile) }, + { + "restart", + function() + capi.awesome.restart() + end, + }, + { + "quit", + function() + capi.awesome.quit() + end, + }, +} + +return myawesomemenu diff --git a/src/awesomerc/configuration/menu/mymainmenu.lua b/src/awesomerc/configuration/menu/mymainmenu.lua new file mode 100644 index 0000000..95a5010 --- /dev/null +++ b/src/awesomerc/configuration/menu/mymainmenu.lua @@ -0,0 +1,11 @@ +local beautiful = require "beautiful" + +local applications = require "awesomerc.configuration.applications" +local myawesomemenu = require "awesomerc.configuration.menu.myawesomemenu" + +local mymainmenu = { + { "awesome", myawesomemenu, beautiful.awesome_icon }, + { "open terminal", applications.terminal }, +} + +return mymainmenu diff --git a/src/awesomerc/configuration/prompt_commands.lua b/src/awesomerc/configuration/prompt_commands.lua new file mode 100644 index 0000000..bbd45b3 --- /dev/null +++ b/src/awesomerc/configuration/prompt_commands.lua @@ -0,0 +1,52 @@ +local atag = require "awful.tag" +local layout_suit = require "awful.layout.suit" + +local commands = {} + +commands["o"] = { + callback = function(parameters) + local tag_name = parameters[1] or "New-Tag" + + atag + .add(tag_name, { + layout = layout_suit.tile, + }) + :view_only() + end, +} + +commands["O"] = { + callback = function(parameters) + local aspawn = require "awful.spawn" + + local application = parameters[1] + local tag_name = parameters[2] or application + + local t = atag.add(tag_name, { + layout = layout_suit.tile, + volatile = true, + }) + t:view_only() + aspawn(application, { tag = t }) + end, +} + +commands["q"] = { + callback = function() + local ascreen = require "awful.screen" + + local tags = ascreen.focused().selected_tags + + for _, tag in ipairs(tags) do + tag.volatile = true + + for _, client in ipairs(tag:clients()) do + client:kill() + end + + tag:delete() + end + end, +} + +return commands diff --git a/src/awesomerc/configuration/rules/client/firefox.lua b/src/awesomerc/configuration/rules/client/firefox.lua new file mode 100644 index 0000000..0954049 --- /dev/null +++ b/src/awesomerc/configuration/rules/client/firefox.lua @@ -0,0 +1,7 @@ +local firefox_rule = { + id = "firefox", + rule = { class = "Firefox" }, + properties = { screen = 1, tag = "2" }, +} + +return firefox_rule diff --git a/src/awesomerc/configuration/rules/client/floating.lua b/src/awesomerc/configuration/rules/client/floating.lua new file mode 100644 index 0000000..f307fa3 --- /dev/null +++ b/src/awesomerc/configuration/rules/client/floating.lua @@ -0,0 +1,28 @@ +local floating_rule = { + id = "floating", + rule_any = { + instance = { "copyq", "pinentry" }, + class = { + "Arandr", + "Blueman-manager", + "Gpick", + "Kruler", + "Sxiv", + "Tor Browser", + "Wpa_gui", + "veromix", + "xtightvncviewer", + }, + name = { + "Event Tester", -- xev. + }, + role = { + "AlarmWindow", -- Thunderbird's calendar. + "ConfigManager", -- Thunderbird's about:config. + "pop-up", -- e.g. Google Chrome's (detached) Developer Tools. + }, + }, + properties = { floating = true }, +} + +return floating_rule diff --git a/src/awesomerc/configuration/rules/client/global.lua b/src/awesomerc/configuration/rules/client/global.lua new file mode 100644 index 0000000..f8dee74 --- /dev/null +++ b/src/awesomerc/configuration/rules/client/global.lua @@ -0,0 +1,16 @@ +local aclient = require "awful.client" +local aplacement = require "awful.placement" +local ascreen = require "awful.screen" + +local global_rule = { + id = "global", + rule = {}, + properties = { + focus = aclient.focus.filter, + raise = true, + screen = ascreen.preferred, + placement = aplacement.no_overlap + aplacement.no_offscreen, + }, +} + +return global_rule diff --git a/src/awesomerc/configuration/rules/client/init.lua b/src/awesomerc/configuration/rules/client/init.lua new file mode 100644 index 0000000..eb81f5f --- /dev/null +++ b/src/awesomerc/configuration/rules/client/init.lua @@ -0,0 +1,8 @@ +local client_rules = {} + +client_rules.global = require "awesomerc.configuration.rules.client.global" +client_rules.floating = require "awesomerc.configuration.rules.client.floating" +client_rules.titlebar = require "awesomerc.configuration.rules.client.titlebars" +-- client_rules.firefox = require 'awesomerc.configuration.rules.client.firefox' + +return client_rules diff --git a/src/awesomerc/configuration/rules/client/titlebars.lua b/src/awesomerc/configuration/rules/client/titlebars.lua new file mode 100644 index 0000000..631d885 --- /dev/null +++ b/src/awesomerc/configuration/rules/client/titlebars.lua @@ -0,0 +1,9 @@ +local titlebar_rule = { + id = "titlebars", + rule_any = { + type = { "normal", "dialog" }, + }, + properties = { titlebars_enabled = true }, +} + +return titlebar_rule diff --git a/src/awesomerc/configuration/rules/init.lua b/src/awesomerc/configuration/rules/init.lua new file mode 100644 index 0000000..962edb0 --- /dev/null +++ b/src/awesomerc/configuration/rules/init.lua @@ -0,0 +1,6 @@ +local rules = {} + +rules.client = require "awesomerc.configuration.rules.client" +rules.notification = require "awesomerc.configuration.rules.notification" + +return rules diff --git a/src/awesomerc/configuration/rules/notification/global.lua b/src/awesomerc/configuration/rules/notification/global.lua new file mode 100644 index 0000000..1e3149f --- /dev/null +++ b/src/awesomerc/configuration/rules/notification/global.lua @@ -0,0 +1,11 @@ +local ascreen = require "awful.screen" + +local global_rule = { + rule = {}, + properties = { + screen = ascreen.preferred, + implicit_timeout = 5, + }, +} + +return global_rule diff --git a/src/awesomerc/configuration/rules/notification/init.lua b/src/awesomerc/configuration/rules/notification/init.lua new file mode 100644 index 0000000..b31518c --- /dev/null +++ b/src/awesomerc/configuration/rules/notification/init.lua @@ -0,0 +1,5 @@ +local notification_rules = {} + +notification_rules.global = require "awesomerc.configuration.rules.notification.global" + +return notification_rules diff --git a/src/awesomerc/configuration/tag_layouts.lua b/src/awesomerc/configuration/tag_layouts.lua new file mode 100644 index 0000000..46776be --- /dev/null +++ b/src/awesomerc/configuration/tag_layouts.lua @@ -0,0 +1,19 @@ +local alayout = require "awful.layout" + +local tag_layouts = { + alayout.suit.floating, + alayout.suit.tile, + alayout.suit.tile.left, + alayout.suit.tile.bottom, + alayout.suit.tile.top, + alayout.suit.fair, + alayout.suit.fair.horizontal, + alayout.suit.spiral, + alayout.suit.spiral.dwindle, + alayout.suit.max, + alayout.suit.max.fullscreen, + alayout.suit.magnifier, + alayout.suit.corner.nw, +} + +return tag_layouts diff --git a/src/awesomerc/init.lua b/src/awesomerc/init.lua new file mode 100644 index 0000000..8ea0644 --- /dev/null +++ b/src/awesomerc/init.lua @@ -0,0 +1,162 @@ +local gtimer = require "gears.timer" -- cspell:ignore gtimer +local legacy = require "awesome-legacy" +local naughty = require "naughty" +local ruled = require "ruled" +local slot = require "awesome-slot" + +-- Load global awesome components from the C API +local capi = { + client = _G.client, + screen = _G.screen, + tag = _G.tag, +} + +-- Beautiful needs to be initialized as soon as possible to make theme +-- variables available to the configuration module. +legacy.beautiful { + base = "default", + theme = require "awesomerc.theme", +} + +legacy.manage_error() +legacy.autofocus() +legacy.sloppy_focus() + +local configuration = require "awesomerc.configuration" +local my_slots = require "awesomerc.slots" + +-- This needs to be run after awesome has completed C API initialization and +-- the `root` object is available. +gtimer.delayed_call(function() + legacy.global_mouse_bindings(configuration.bindings.global_mousebindings) + legacy.global_keybindings(configuration.bindings.global_keybindings) +end) + +-- luacheck: ignore unused variable load_wallpaper +local load_wallpaper = slot { + id = "LOAD_WALLPAPER", + connect = true, + target = capi.screen, + signal = "request::wallpaper", + slot = my_slots.wallpaper, +} + +-- luacheck: ignore unused variable default_layout +local default_layout = slot { + id = "DEFAULT_LAYOUTS", + connect = true, + target = capi.tag, + signal = "request::default_layouts", + slot = slot.slots.tag.default_layouts, + slot_params = { + layouts = configuration.tag_layouts, + }, +} + +-- luacheck: ignore unused variable create_tag +local create_tag = slot { + id = "CREATE_TAGS", + connect = true, + target = capi.screen, + signal = "request::desktop_decoration", + slot = my_slots.create_tags, +} + +-- luacheck: ignore unused variable desktop_decoration +local desktop_decoration = slot { + id = "DESKTOP_DECORATION", + connect = true, + target = capi.screen, + signal = "request::desktop_decoration", + slot = my_slots.build_desktop_decoration, +} + +-- luacheck: ignore unused variable client_mousebinding +local client_mousebinding = slot { + id = "CLIENT_MOUSE_BINDINGS", + connect = true, + target = capi.client, + signal = "request::default_mousebindings", + slot = slot.slots.client.append_mousebindings, + slot_params = { + mousebindings = configuration.bindings.client_mousebindings, + }, +} + +-- luacheck: ignore unused variable client_keybinding +local client_keybinding = slot { + id = "CLIENT_KEY_BINDINGS", + connect = true, + target = capi.client, + signal = "request::default_keybindings", + slot = slot.slots.client.append_keybindings, + slot_params = { + keybindings = configuration.bindings.client_keybindings, + }, +} + +-- luacheck: ignore unused variable ruled_client +local ruled_client = slot { + id = "RULED_CLIENT", + connect = true, + target = ruled.client, + signal = "request::rules", + slot = slot.slots.ruled.append_client_rules, + slot_params = { + rules = configuration.rules.client, + }, +} + +-- luacheck: ignore unused variable client_titlebar +local client_titlebar = slot { + id = "CLIENT_TITLEBAR", + connect = true, + target = capi.client, + signal = "request::titlebars", + slot = my_slots.build_titlebars, +} + +gtimer.delayed_call(function() + local nice_config = require "awesome-wm-nice.config" + local nice_utils = require "awesome-wm-nice.utils" + + nice_config.init() + nice_utils.validate_mb_bindings(nice_config) +end) + +-- luacheck: ignore unused variable client_shape +local client_shape = slot { + id = "CLIENT_SHAPE", + connect = true, + target = capi.client, + signal = "request::manage", + slot = my_slots.client_shape, +} + +-- luacheck: ignore unused variable ruled_notification +local ruled_notification = slot { + id = "RULED_NOTIFICATION", + connect = true, + target = ruled.notification, + signal = "request::rules", + slot = slot.slots.ruled.append_notification_rules, + slot_params = { + rules = configuration.rules.notification, + }, +} + +-- luacheck: ignore unused variable naughty_display +local naughty_display = slot { + id = "NAUGHTY_DISPLAY", + connect = true, + target = naughty, + signal = "request::display", + slot = my_slots.naughty_display, +} + +gtimer.delayed_call(function() + naughty.notify { + title = "Aire-One dots", + message = "Welcome to the Aire-One rc!", + } +end) diff --git a/src/awesomerc/slots/init.lua b/src/awesomerc/slots/init.lua new file mode 100644 index 0000000..4d95cbf --- /dev/null +++ b/src/awesomerc/slots/init.lua @@ -0,0 +1,86 @@ +local alayout = require "awful.layout" +local atag = require "awful.tag" +local awallpaper = require "awful.wallpaper" +local beautiful = require "beautiful" +local desktop_bar = require "awesomerc.ui.desktop_decoration.bar" +local gcolor = require "gears.color" +local gsurface = require "gears.surface" +local gtimer = require "gears.timer" +local imagebox = require "wibox.widget.imagebox" +local lgi = require "lgi" +local naughty = require "naughty" +local nice_shapes = require "awesome-wm-nice.shapes" +local titlebar = require "awesomerc.ui.titlebar" + +local cairo = lgi.cairo + +local slots = {} + +function slots.wallpaper(screen) + local screen_geo = screen.geometry + local source = cairo.ImageSurface(cairo.Format.RGB32, screen_geo.width, screen_geo.height) + local cr = cairo.Context(source) + + -- Load base image + local image_surface = gsurface.load_uncached(beautiful.wallpaper) + local w, h = gsurface.get_size(image_surface) + cr:scale(screen_geo.width / w, screen_geo.height / h) + cr:set_source_surface(image_surface, 0, 0) + cr:paint() + + -- Add color layer + local color_pattern = gcolor.create_linear_pattern { + from = { 0, 0 }, + to = { screen.width, screen.height }, + stops = { + { 0, "#26323840" }, + }, + } + cr:set_source(color_pattern) + cr:paint() + + awallpaper { + screen = screen, + widget = { + image = source, + widget = imagebox, + }, + } +end + +function slots.create_tags(screen) + local first_tag = atag.add("home", { + screen = screen, + layout = alayout.suit.tile, + icon = beautiful.icon_hometag, + gap = beautiful.gaps_hometag, + }) + + gtimer.delayed_call(function() + first_tag:view_only() + -- spawn(apps.open_terminal(), { screen = screen, tag = first_tag }) + end) +end + +function slots.build_desktop_decoration(screen) + desktop_bar(screen) +end + +function slots.build_titlebars(client) + titlebar(client) +end + +function slots.client_shape(client) + client.shape = nice_shapes.rounded_rect { + tl = beautiful.client_corner_radius_top, + tr = beautiful.client_corner_radius_top, + bl = beautiful.client_corner_radius_bottom, + br = beautiful.client_corner_radius_bottom, + } +end + +function slots.naughty_display(notification) + naughty.layout.box { notification = notification } +end + +return slots diff --git a/theme/init.lua b/src/awesomerc/theme/init.lua similarity index 82% rename from theme/init.lua rename to src/awesomerc/theme/init.lua index 0b5235e..9bf5b82 100644 --- a/theme/init.lua +++ b/src/awesomerc/theme/init.lua @@ -1,17 +1,18 @@ local gfs = require "gears.filesystem" local config_dir = gfs.get_configuration_dir() -local theme_dir = config_dir .. "theme/" -local assets_dir = theme_dir .. "assets/" +local assets_dir = config_dir .. "assets/" local icons_dir = assets_dir .. "icons/" local theme = {} --- Basic theme.font = "Noto Mono 9" +theme.border_width = 0 ---- Tags -theme.hometag_master_width_factor = 0.65 +--- Clients +theme.client_corner_radius_top = 9 +theme.client_corner_radius_bottom = 4 --- Icons theme.icon_hometag = icons_dir .. "home-circle.svg" @@ -32,7 +33,7 @@ theme.wibar_bg = "#0000" --- Spacing and margins -- Naming convention: --- __ +-- __ theme.spacing = 8 theme.margin = 4 theme.padding = 4 @@ -51,11 +52,13 @@ theme.bar_box_padding_x = theme.padding * 2 theme.bar_box_padding_y = theme.padding theme.bar_box_spacing = theme.spacing +theme.gaps_hometag = theme.padding + --- Widgets specific theme.bg_systray = "#455A64" theme.systray_icon_spacing = 3 --- Wallpaper -theme.wallpaper = assets_dir .. "wallpapers/pizza.jpg" +theme.wallpaper = assets_dir .. "wallpapers/poke.jpg" return theme diff --git a/src/awesomerc/ui/desktop_decoration/bar/init.lua b/src/awesomerc/ui/desktop_decoration/bar/init.lua new file mode 100644 index 0000000..1c06964 --- /dev/null +++ b/src/awesomerc/ui/desktop_decoration/bar/init.lua @@ -0,0 +1,241 @@ +local awful = require "awful" +local awibar = require "awful.wibar" +local battery_widget = require "awesome-battery_widget" +local beautiful = require "beautiful" + +local container_background = require "wibox.container.background" +local container_margin = require "wibox.container.margin" +local container_place = require "wibox.container.place" + +local gshape = require "gears.shape" + +local layout_align = require "wibox.layout.align" +local layout_fixed = require "wibox.layout.fixed" + +local systray = require "wibox.widget.systray" +local textclock = require "wibox.widget.textclock" +local widget = require "wibox.widget" + +local mycommands = require "awesomerc.configuration.prompt_commands" +local mymainmenu = require "awesomerc.ui.menu.mymainmenu" +local mytaglist = require "MyTagListWidget" + +local bar_widgets = require "awesomerc.ui.desktop_decoration.bar.widgets" +local mybattery = bar_widgets.battery +local myprompt = bar_widgets.prompt + +local capi = { + screen = _G.screen, +} + +local abs = math.abs +local dpi = beautiful.xresources.apply_dpi + +local function get_screen_id(screen) + local s = capi.screen[screen or 1] + + return s.index +end + +--- Build a widget box in the bar. +-- A widget box is a combinaision of "shaped" `wibox.container/background` and +-- `wibox.container.margin` to create this nice round-colored-buttons I like. +-- @tparam args table +-- @tparam args.screen The screen where the widget will be drawn. +-- @tparam args.widget The widget to draw. +-- @tparam[opt] args.bg The box's background. +-- @tpram[opt] args.fg The box's foreground (used mainly for textbox text color). +-- @treturn wibox.container.background The builded widget. +local function build_widget(args) + local screen_id = get_screen_id(args.screen) + + -- Callback for the shape function. + -- If the widget is almost a square: draw a circle. Otherwise, draw a + -- rounded_bar. + local shape_callback = function(cr, width, height) + local shape = gshape.circle + + -- 10 is an arbitrary value I found after some tests :shrug: + if abs(width - height) > 10 then + shape = gshape.rounded_bar + end + + return shape(cr, width, height) + end + + local box_widget = widget { + { + widget = container_margin, + draw_empty = false, + top = dpi(beautiful.bar_box_padding_y, screen_id), + bottom = dpi(beautiful.bar_box_padding_y, screen_id), + right = dpi(beautiful.bar_box_padding_x, screen_id), + left = dpi(beautiful.bar_box_padding_x, screen_id), + args.widget, + }, + bg = args.bg, + fg = args.fg, + shape = shape_callback, + widget = container_background, + } + + return box_widget +end + +local bar = { _private = { instances = {} }, mt = {} } + +bar.widgets = bar_widgets + +--- Get the bar instance for a given screen. +-- If no instance was found, we build a new one. +-- @tparam screen screen|integer The bar's screen. +-- @treturn wibox.wibar +function bar:instance(screen) + local screen_id = get_screen_id(screen) + local instance = self._private.instances[screen_id] + + if not instance then + instance = bar.new(screen) + self._private.instances[screen_id] = instance + end + + return instance +end + +function bar.new(screen) + local my_bar = {} + + my_bar.launcher = build_widget { + screen = screen, + bg = "#2196F3", + widget = awful.widget.launcher { + image = beautiful.icon_apps, + menu = mymainmenu(), + }, + } + + my_bar.textclock = build_widget { + screen = screen, + bg = "#FF5722", + fg = "#ECEFF1", + widget = textclock "%l:%M %p", + } + + my_bar.promptbox = myprompt { + commands = mycommands, + } + + -- This widget needs to be reworded and integrated inside the project. + my_bar.taglist = mytaglist.new { + screen = screen, + } + + my_bar.battery = build_widget { + screen = screen, + bg = "#673AB7", + widget = mybattery { + screen = screen, + device_path = battery_widget.get_BAT0_device_path(), + color = "#ECEFF1", + }, + } + + my_bar.systray = build_widget { + screen = screen, + bg = "#455A64", + widget = systray(), + } + + my_bar.wibar = awibar { + screen = screen, + position = "bottom", + height = dpi(beautiful.bar_height, screen), + width = beautiful.bar_width, -- Width is a percentage of the screen size + } + + my_bar.wibar:setup { + -- Bar margins + { + -- Physical bar + { + -- Bar paddings + { + -- Bar content + { + -- Left side of the bar, align on the left + { + -- box margins + { + -- Left widget boxes + my_bar.launcher, + my_bar.promptbox, + spacing = dpi(beautiful.bar_box_spacing, screen), + layout = layout_fixed.horizontal, + }, + top = dpi(beautiful.bar_box_margin_y, screen), + bottom = dpi(beautiful.bar_box_margin_y, screen), + right = dpi(beautiful.bar_box_margin_x, screen), + left = dpi(beautiful.bar_box_margin_x, screen), + widget = container_margin, + }, + halign = "left", + widget = container_place, + }, + expand = "outside", + layout = layout_align.horizontal, + + -- Middle widget is the custom taglist + -- it doesn't need box/margins/... + { + my_bar.taglist, + widget = container_place, + }, + { + -- Right side of the bar, align on the right + { + -- box margins + { + -- Right widget boxes + my_bar.systray, + my_bar.battery, + my_bar.textclock, + spacing = dpi(beautiful.bar_box_spacing, screen), + layout = layout_fixed.horizontal, + }, + top = dpi(beautiful.bar_box_margin_y, screen), + bottom = dpi(beautiful.bar_box_margin_y, screen), + right = dpi(beautiful.bar_box_margin_x, screen), + left = dpi(beautiful.bar_box_margin_x, screen), + widget = container_margin, + }, + halign = "right", + widget = container_place, + }, + }, + top = dpi(beautiful.bar_padding_y, screen), + bottom = dpi(beautiful.bar_padding_y, screen), + right = dpi(beautiful.bar_padding_x, screen), + left = dpi(beautiful.bar_padding_x, screen), + widget = container_margin, + }, + bg = beautiful.bar_bg, + shape = function(cr, width, height) + gshape.rounded_rect(cr, width, height, 10) + end, + widget = container_background, + }, + top = dpi(beautiful.bar_margin_y, screen), + bottom = dpi(beautiful.bar_margin_y, screen), + right = dpi(beautiful.bar_margin_x, screen), + left = dpi(beautiful.bar_margin_x, screen), + widget = container_margin, + } + + return my_bar +end + +function bar.mt:__call(...) + return self:instance(...) +end + +return setmetatable(bar, bar.mt) diff --git a/ui/desktop_decoration/bar/widgets/battery.lua b/src/awesomerc/ui/desktop_decoration/bar/widgets/battery.lua similarity index 58% rename from ui/desktop_decoration/bar/widgets/battery.lua rename to src/awesomerc/ui/desktop_decoration/bar/widgets/battery.lua index 0c1aee9..0cddec6 100644 --- a/ui/desktop_decoration/bar/widgets/battery.lua +++ b/src/awesomerc/ui/desktop_decoration/bar/widgets/battery.lua @@ -17,7 +17,7 @@ local beautiful = require "beautiful" local gcolor = require "gears.color" local imagebox = require "wibox.widget.imagebox" -local battery_widget = require "battery-widget" +local battery_widget = require "awesome-battery_widget" local my_battery = {} local mt = {} @@ -27,25 +27,25 @@ local mt = {} -- @tparam gears.color|string color The color of the drawing. -- @treturn cairo.ImageSurface The generated surface for the drawing. function my_battery.draw_battery(percentage, color) - local svg_handle = rsvg.Handle.new_from_file(beautiful.icon_battery_outline) - if not svg_handle then - return - end - local surface = cairo.ImageSurface.create(cairo.Format.ARGB32, 24, 24) - local cr = cairo.Context(surface) - svg_handle:render_cairo(cr) + local svg_handle = rsvg.Handle.new_from_file(beautiful.icon_battery_outline) + if not svg_handle then + return + end + local surface = cairo.ImageSurface.create(cairo.Format.ARGB32, 24, 24) + local cr = cairo.Context(surface) + svg_handle:render_cairo(cr) - local max_height = 14 - local x = 8 - local width = 8 - local height = percentage / 100 * max_height - local y = 6 + max_height - height + local max_height = 14 + local x = 8 + local width = 8 + local height = percentage / 100 * max_height + local y = 6 + max_height - height - cr:set_source(gcolor(color)) - cr:rectangle(x, y, width, height) - cr:fill() + cr:set_source(gcolor(color)) + cr:rectangle(x, y, width, height) + cr:fill() - return surface + return surface end --- Update handler for the battery widget. This is the function called @@ -53,16 +53,16 @@ end -- @tparam my_battery widget The battery widget to update. -- @tparam UPowerGLib.Device device The UPower device to monitor. function my_battery.update(widget, device) - widget.image = my_battery.draw_battery(device.percentage, widget.color) + widget.image = my_battery.draw_battery(device.percentage, widget.color) end --- Give the widget template for the battery widget. -- (It's a basic `wibox.widget.imagebox` for my battery implementation) function my_battery.widget_template() - local widget = imagebox() - widget.resize = true + local widget = imagebox() + widget.resize = true - return widget + return widget end --- Constructor of my battery widget! @@ -72,31 +72,32 @@ end -- @tparam gears.color|string args.color Color to use to draw the battery. -- @treturn my_battery The instantiated widget. function my_battery.new(args) - local widget = battery_widget { - screen = args.screen, - device_path = args.device_path, - widget_template = my_battery.widget_template(), - instant_update = true, - } + local widget = battery_widget { + screen = args.screen, + -- device_path = args.device_path, + use_display_device = true, + widget_template = my_battery.widget_template(), + instant_update = true, + } - widget.color = args.color + widget.color = args.color - widget:connect_signal("upower::update", function(w, device) - my_battery.update(w, device) - end) + widget:connect_signal("upower::update", function(w, device) + my_battery.update(w, device) + end) - widget.tooltip = atooltip { - objects = { widget }, - timer_function = function() - return string.format("%3d", widget.device.percentage) .. "%" - end, - } + widget.tooltip = atooltip { + objects = { widget }, + timer_function = function() + return string.format("%3d", widget.device.percentage) .. "%" + end, + } - return widget + return widget end -function mt:__call(...) - return my_battery.new(...) +function mt:__call(...) -- luacheck: ignore unused self + return my_battery.new(...) end return setmetatable(my_battery, mt) diff --git a/src/awesomerc/ui/desktop_decoration/bar/widgets/init.lua b/src/awesomerc/ui/desktop_decoration/bar/widgets/init.lua new file mode 100644 index 0000000..ea61b44 --- /dev/null +++ b/src/awesomerc/ui/desktop_decoration/bar/widgets/init.lua @@ -0,0 +1,6 @@ +local bar_widgets = {} + +bar_widgets.battery = require "awesomerc.ui.desktop_decoration.bar.widgets.battery" +bar_widgets.prompt = require "awesomerc.ui.desktop_decoration.bar.widgets.prompt" + +return bar_widgets diff --git a/src/awesomerc/ui/desktop_decoration/bar/widgets/prompt.lua b/src/awesomerc/ui/desktop_decoration/bar/widgets/prompt.lua new file mode 100644 index 0000000..bf7b727 --- /dev/null +++ b/src/awesomerc/ui/desktop_decoration/bar/widgets/prompt.lua @@ -0,0 +1,72 @@ +local acompletion = require "awful.completion" +local aspawn = require "awful.spawn" +local gstring = require "gears.string" +local gtable = require "gears.table" +local prompt = require "awful.widget.prompt" + +local string = string + +local function completion_cb(self, command_before_comp, cur_pos_before_comp, ncomp) + return acompletion.generic(command_before_comp, cur_pos_before_comp, ncomp, self.completion_keywords) +end + +local function exe_cb(self, input) + -- Exit if the input is empty + if not input or #input == 0 then + return + end + + -- Trim + input = string.gsub(input, "^%s*(.-)%s*$", "%1") + + -- If the input is not a VI command, spawn it + if input:sub(1, 1) ~= ":" then + aspawn(input) + return + end + + -- Parse the custom command + local command, parameters = input:gmatch ":([%w-]+)%s*(.*)"() + command = command:sub(1, 1) + parameters = gstring.split(parameters, "%s") + + -- Quit if the command doesn't exist + if not self.commands[command] then + print('":' .. command .. '" not reconized as a command') + return + end + + self.commands[command].callback(parameters) +end + +local my_prompt = { mt = {} } + +function my_prompt.new(args) + local prompt_widget = nil + prompt_widget = prompt { + prompt = " ", -- needs the 3 spaces + completion_callback = function(...) + return completion_cb(prompt_widget, ...) + end, + exe_callback = function(...) + return exe_cb(prompt_widget, ...) + end, + } + + prompt_widget.commands = args.commands + prompt_widget.completion_keywords = gtable.join( + -- TODO: find applications list + gtable.find_keys(args.commands, function() + return true + end, false), + args.completion_keywords or {} + ) + + return prompt_widget +end + +function my_prompt.mt:__call(...) -- luacheck: ignore unused arg self + return my_prompt.new(...) +end + +return setmetatable(my_prompt, my_prompt.mt) diff --git a/src/awesomerc/ui/desktop_decoration/init.lua b/src/awesomerc/ui/desktop_decoration/init.lua new file mode 100644 index 0000000..f1210db --- /dev/null +++ b/src/awesomerc/ui/desktop_decoration/init.lua @@ -0,0 +1,5 @@ +local desktop_decoration = {} + +desktop_decoration.bar = require "awesomerc.ui.desktop_decoration.bar" + +return desktop_decoration diff --git a/src/awesomerc/ui/hotkeys_popup/init.lua b/src/awesomerc/ui/hotkeys_popup/init.lua new file mode 100644 index 0000000..690078d --- /dev/null +++ b/src/awesomerc/ui/hotkeys_popup/init.lua @@ -0,0 +1,12 @@ +local hotkeys_popup = require "awful.hotkeys_popup" + +-- luacheck: ignore unset variable keys +local keys = { + vim = require "awful.hotkeys_popup.keys.vim", + firefox = require "awful.hotkeys_popup.keys.firefox", + tmux = require "awful.hotkeys_popup.keys.tmux", + qutebrowser = require "awful.hotkeys_popup.keys.qutebrowser", + termite = require "awful.hotkeys_popup.keys.termite", +} + +return hotkeys_popup diff --git a/src/awesomerc/ui/menu/mymainmenu.lua b/src/awesomerc/ui/menu/mymainmenu.lua new file mode 100644 index 0000000..7daf82c --- /dev/null +++ b/src/awesomerc/ui/menu/mymainmenu.lua @@ -0,0 +1,32 @@ +local amenu = require "awful.menu" + +local configuration = { + menu = require "awesomerc.configuration.menu", +} + +local mymainmenu = { _private = {}, mt = {} } + +function mymainmenu:instance() + local instance = self._private.instance + + if not instance then + instance = mymainmenu.new() + self._private.instance = instance + end + + return instance +end + +function mymainmenu.new() + local menu = amenu { + items = configuration.menu.mymainmenu, + } + + return menu +end + +function mymainmenu.mt:__call() + return self:instance() +end + +return setmetatable(mymainmenu, mymainmenu.mt) diff --git a/src/awesomerc/ui/titlebar/init.lua b/src/awesomerc/ui/titlebar/init.lua new file mode 100644 index 0000000..2a9e672 --- /dev/null +++ b/src/awesomerc/ui/titlebar/init.lua @@ -0,0 +1,24 @@ +local gtimer = require "gears.timer" +local nice = require "awesome-wm-nice" + +local titlebar = { mt = {} } + +function titlebar.new(client) + -- We need first to manually set the base color because of how nice is designed + client._nice_base_color = nice.config.titlebar_color + + -- We need to delay the call to add_window_decoration because the client has + -- to be fully initialized first (e.g. the client's geometry has to be set) + -- for the decoration to be added correctly. + -- Note: resizing the client will not update the decoration size, so it feels + -- a bit hacky. I need to fix this. + gtimer.delayed_call(function() + nice.add_window_decoration(client) + end) +end + +function titlebar.mt:__call(client) -- luacheck: ignore unused arg self + return titlebar.new(client) +end + +return setmetatable(titlebar, titlebar.mt) diff --git a/start-xephyr.sh b/start-xephyr.sh deleted file mode 100755 index bd14829..0000000 --- a/start-xephyr.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env sh - -killXephyr() { - kill "$(pgrep Xephyr)" >/dev/null 2>&1 -} - -trap killXephyr EXIT - -if [ "$1" = "stop" ]; then - killXephyr - exit 0 -fi; - -xephyr=$1 -awesome=$2 -rc_file=$3 - -# Check for the first free display -for i in $(seq 1 10); do - if [ ! -f "/tmp/.X${i}-lock" ]; then - D=$i; - break; - fi; -done - -# Start Xephyr -$xephyr :"$D" -name xephyr_"$D" -ac -br -noreset -screen 1600x900 >/dev/null 2>&1 & -sleep 1 - -# Start Awesome -DISPLAY=:$D.0 $awesome \ - --config "$rc_file" - diff --git a/theme/assets/wallpapers/pizza.jpg b/theme/assets/wallpapers/pizza.jpg deleted file mode 100644 index 5d799a2..0000000 Binary files a/theme/assets/wallpapers/pizza.jpg and /dev/null differ diff --git a/ui/desktop_decoration/bar/init.lua b/ui/desktop_decoration/bar/init.lua deleted file mode 100644 index cb3486c..0000000 --- a/ui/desktop_decoration/bar/init.lua +++ /dev/null @@ -1,247 +0,0 @@ -local awibar = require "awful.wibar" -local awful = require "awful" -local battery_widget = require "battery-widget" -local beautiful = require "beautiful" - -local container_background = require "wibox.container.background" -local container_margin = require "wibox.container.margin" -local container_place = require "wibox.container.place" - -local gshape = require "gears.shape" - -local layout_align = require "wibox.layout.align" -local layout_fixed = require "wibox.layout.fixed" - -local systray = require "wibox.widget.systray" -local textclock = require "wibox.widget.textclock" -local widget = require "wibox.widget" - -local mycommands = require "rc.configuration.prompt_commands" -local mymainmenu = require "rc.ui.menu.mymainmenu" -local mytaglist = require "MyTagListWidget" - -local bar_widgets = require "rc.ui.desktop_decoration.bar.widgets" -local mybattery = bar_widgets.battery -local myprompt = bar_widgets.prompt - -local capi = { - screen = _G.screen, -} - -local abs = math.abs -local dpi = beautiful.xresources.apply_dpi - -local function get_screen_id(screen) - local s = capi.screen[screen or 1] - - return s.index -end - ---- Build a widget box in the bar. --- A widget box is a combinaision of "shaped" `wibox.container/background` and --- `wibox.container.margin` to create this nice round-colored-buttons I like. --- @tparam args table --- @tparam args.screen The screen where the widget will be drawn. --- @tparam args.widget The widget to draw. --- @tparam[opt] args.bg The box's background. --- @tpram[opt] args.fg The box's foreground (used mainly for textbox text color). --- @treturn wibox.container.background The builded widget. -local function build_widget(args) - local screen_id = get_screen_id(args.screen) - - -- Callback for the shape function. - -- If the widget is almost a square: draw a circle. Otherwise, draw a - -- rounded_bar. - local shape_callback = function(cr, width, height) - local shape = gshape.circle - - -- 10 is an arbitrary value I found after some tests :shrug: - if abs(width - height) > 10 then - shape = gshape.rounded_bar - end - - return shape(cr, width, height) - end - - local box_widget = widget { - { - widget = container_margin, - draw_empty = false, - top = dpi(beautiful.bar_box_padding_y, screen_id), - bottom = dpi(beautiful.bar_box_padding_y, screen_id), - right = dpi(beautiful.bar_box_padding_x, screen_id), - left = dpi(beautiful.bar_box_padding_x, screen_id), - args.widget, - }, - bg = args.bg, - fg = args.fg, - shape = shape_callback, - widget = container_background, - } - - return box_widget -end - -local bar = { _private = { instances = {} }, mt = {} } - -bar.widgets = bar_widgets - ---- Get the bar instance for a given screen. --- If no instance was found, we build a new one. --- @tparam screen screen|integer The bar's screen. --- @treturn wibox.wibar -function bar:instance(screen) - local screen_id = get_screen_id(screen) - local instance = self._private.instances[screen_id] - - if not instance then - instance = bar.new(screen) - self._private.instances[screen_id] = instance - end - - return instance -end - -function bar.new(screen) - local my_bar = {} - - my_bar.launcher = build_widget { - screen = screen, - bg = "#2196F3", - widget = awful.widget.launcher { - image = beautiful.icon_apps, - menu = mymainmenu(), - }, - } - - my_bar.textclock = build_widget { - screen = screen, - bg = "#FF5722", - fg = "#ECEFF1", - widget = textclock "%l:%M %p", - } - - my_bar.promptbox = myprompt { - commands = mycommands, - } - - -- This widget needs to be reworded and integrated inside the project. - my_bar.taglist = mytaglist.new { - screen = screen, - } - - my_bar.battery = build_widget { - screen = screen, - bg = "#673AB7", - widget = mybattery { - screen = screen, - device_path = battery_widget.get_BAT0_device_path(), - color = "#ECEFF1", - }, - } - - my_bar.systray = build_widget { - screen = screen, - bg = "#455A64", - widget = systray(), - } - - my_bar.wibar = awibar { - screen = screen, - position = "bottom", - height = dpi(beautiful.bar_height, screen), - width = beautiful.bar_width, -- Width is a percentage of the screen size - } - - my_bar.wibar:setup { - -- Bar margins - { - -- Physical bar - { - -- Bar paddings - { - -- Bar content - { - -- Left side of the bar, align on the left - { - -- box margins - { - -- Left widget boxes - my_bar.launcher, - my_bar.promptbox, - spacing = dpi( - beautiful.bar_box_spacing, - screen - ), - layout = layout_fixed.horizontal, - }, - top = dpi(beautiful.bar_box_margin_y, screen), - bottom = dpi(beautiful.bar_box_margin_y, screen), - right = dpi(beautiful.bar_box_margin_x, screen), - left = dpi(beautiful.bar_box_margin_x, screen), - widget = container_margin, - }, - halign = "left", - widget = container_place, - }, - expand = "outside", - layout = layout_align.horizontal, - - -- Middle widget is the custom taglist - -- it doesn't need box/margins/... - { - my_bar.taglist, - widget = container_place, - }, - { - -- Right side of the bar, align on the right - { - -- box margins - { - -- Right widget boxes - my_bar.systray, - my_bar.battery, - my_bar.textclock, - spacing = dpi( - beautiful.bar_box_spacing, - screen - ), - layout = layout_fixed.horizontal, - }, - top = dpi(beautiful.bar_box_margin_y, screen), - bottom = dpi(beautiful.bar_box_margin_y, screen), - right = dpi(beautiful.bar_box_margin_x, screen), - left = dpi(beautiful.bar_box_margin_x, screen), - widget = container_margin, - }, - halign = "right", - widget = container_place, - }, - }, - top = dpi(beautiful.bar_padding_y, screen), - bottom = dpi(beautiful.bar_padding_y, screen), - right = dpi(beautiful.bar_padding_x, screen), - left = dpi(beautiful.bar_padding_x, screen), - widget = container_margin, - }, - bg = beautiful.bar_bg, - shape = function(cr, width, height) - gshape.rounded_rect(cr, width, height, 10) - end, - widget = container_background, - }, - top = dpi(beautiful.bar_margin_y, screen), - bottom = dpi(beautiful.bar_margin_y, screen), - right = dpi(beautiful.bar_margin_x, screen), - left = dpi(beautiful.bar_margin_x, screen), - widget = container_margin, - } - - return my_bar -end - -function bar.mt:__call(...) - return self:instance(...) -end - -return setmetatable(bar, bar.mt) diff --git a/ui/desktop_decoration/bar/widgets/init.lua b/ui/desktop_decoration/bar/widgets/init.lua deleted file mode 100644 index e5ae9df..0000000 --- a/ui/desktop_decoration/bar/widgets/init.lua +++ /dev/null @@ -1,6 +0,0 @@ -local bar_widgets = {} - -bar_widgets.battery = require "rc.ui.desktop_decoration.bar.widgets.battery" -bar_widgets.prompt = require "rc.ui.desktop_decoration.bar.widgets.prompt" - -return bar_widgets diff --git a/ui/desktop_decoration/bar/widgets/prompt.lua b/ui/desktop_decoration/bar/widgets/prompt.lua deleted file mode 100644 index ad7ebb9..0000000 --- a/ui/desktop_decoration/bar/widgets/prompt.lua +++ /dev/null @@ -1,82 +0,0 @@ -local acompletion = require "awful.completion" -local aspawn = require "awful.spawn" -local gstring = require "gears.string" -local gtable = require "gears.table" -local prompt = require "awful.widget.prompt" - -local string = string - -local function completion_cb( - self, - command_before_comp, - cur_pos_before_comp, - ncomp -) - return acompletion.generic( - command_before_comp, - cur_pos_before_comp, - ncomp, - self.completion_keywords - ) -end - -local function exe_cb(self, input) - -- Exit if the input is empty - if not input or #input == 0 then - return - end - - -- Trim - input = string.gsub(input, "^%s*(.-)%s*$", "%1") - - -- If the input is not a VI command, spawn it - if input:sub(1, 1) ~= ":" then - aspawn(input) - return - end - - -- Parse the custom command - local command, parameters = input:gmatch ":([%w-]+)%s*(.*)"() - command = command:sub(1, 1) - parameters = gstring.split(parameters, "%s") - - -- Quit if the command doesn't exist - if not self.commands[command] then - print('":' .. command .. '" not reconized as a command') - return - end - - self.commands[command].callback(parameters) -end - -local my_prompt = { mt = {} } - -function my_prompt.new(args) - local prompt_widget = nil - prompt_widget = prompt { - prompt = " ", -- needs the 3 spaces - completion_callback = function(...) - return completion_cb(prompt_widget, ...) - end, - exe_callback = function(...) - return exe_cb(prompt_widget, ...) - end, - } - - prompt_widget.commands = args.commands - prompt_widget.completion_keywords = gtable.join( - -- TODO: find applications list - gtable.find_keys(args.commands, function() - return true - end, false), - args.completion_keywords or {} - ) - - return prompt_widget -end - -function my_prompt.mt:__call(...) - return my_prompt.new(...) -end - -return setmetatable(my_prompt, my_prompt.mt) diff --git a/ui/desktop_decoration/init.lua b/ui/desktop_decoration/init.lua deleted file mode 100644 index d603578..0000000 --- a/ui/desktop_decoration/init.lua +++ /dev/null @@ -1,5 +0,0 @@ -local desktop_decoration = {} - -desktop_decoration.bar = require "rc.ui.desktop_decoration.bar" - -return desktop_decoration diff --git a/ui/hotkeys_popup/init.lua b/ui/hotkeys_popup/init.lua deleted file mode 100644 index 6a93272..0000000 --- a/ui/hotkeys_popup/init.lua +++ /dev/null @@ -1,12 +0,0 @@ -local hotkeys_popup = require "awful.hotkeys_popup" - --- luacheck: ignore unset variable keys -local keys = { - vim = require "awful.hotkeys_popup.keys.vim", - firefox = require "awful.hotkeys_popup.keys.firefox", - tmux = require "awful.hotkeys_popup.keys.tmux", - qutebrowser = require "awful.hotkeys_popup.keys.qutebrowser", - termite = require "awful.hotkeys_popup.keys.termite", -} - -return hotkeys_popup diff --git a/ui/menu/mymainmenu.lua b/ui/menu/mymainmenu.lua deleted file mode 100644 index 78fb9aa..0000000 --- a/ui/menu/mymainmenu.lua +++ /dev/null @@ -1,32 +0,0 @@ -local amenu = require "awful.menu" - -local configuration = { - menu = require "rc.configuration.menu", -} - -local mymainmenu = { _private = {}, mt = {} } - -function mymainmenu:instance() - local instance = self._private.instance - - if not instance then - instance = mymainmenu.new() - self._private.instance = instance - end - - return instance -end - -function mymainmenu.new() - local menu = amenu { - items = configuration.menu.mymainmenu, - } - - return menu -end - -function mymainmenu.mt:__call() - return self:instance() -end - -return setmetatable(mymainmenu, mymainmenu.mt)