516 lines
18 KiB
Lua
516 lines
18 KiB
Lua
--[[
|
|
███╗ ██╗██╗ ██████╗███████╗
|
|
████╗ ██║██║██╔════╝██╔════╝
|
|
██╔██╗ ██║██║██║ █████╗
|
|
██║╚██╗██║██║██║ ██╔══╝
|
|
██║ ╚████║██║╚██████╗███████╗
|
|
╚═╝ ╚═══╝╚═╝ ╚═════╝╚══════╝
|
|
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,
|
|
})
|