You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
447 lines
14 KiB
447 lines
14 KiB
|
|
-- Functions that you use more than once and in different files would |
|
-- be nice to define here. |
|
|
|
local awful = require("awful") |
|
local gears = require("gears") |
|
local beautiful = require("beautiful") |
|
local xresources = require("beautiful.xresources") |
|
local dpi = xresources.apply_dpi |
|
local wibox = require("wibox") |
|
local icons = require("icons") |
|
local notifications = require("notifications") |
|
local naughty = require("naughty") |
|
|
|
local helpers = {} |
|
|
|
-- Create rounded rectangle shape (in one line) |
|
helpers.rrect = function(radius) |
|
return function(cr, width, height) |
|
gears.shape.rounded_rect(cr, width, height, radius) |
|
end |
|
end |
|
|
|
helpers.prrect = function(radius, tl, tr, br, bl) |
|
return function(cr, width, height) |
|
gears.shape.partially_rounded_rect(cr, width, height, tl, tr, br, bl, radius) |
|
end |
|
end |
|
|
|
helpers.squircle = function(rate, delta) |
|
return function(cr, width, height) |
|
gears.shape.squircle(cr, width, height, rate, delta) |
|
end |
|
end |
|
helpers.psquircle = function(rate, delta, tl, tr, br, bl) |
|
return function(cr, width, height) |
|
gears.shape.partial_squircle(cr, width, height, tl, tr, br, bl, rate, delta) |
|
end |
|
end |
|
|
|
helpers.colorize_text = function(text, color) |
|
return "<span foreground='"..color.."'>"..text.."</span>" |
|
end |
|
|
|
function helpers.client_menu_toggle() |
|
local instance = nil |
|
|
|
return function () |
|
if instance and instance.wibox.visible then |
|
instance:hide() |
|
instance = nil |
|
else |
|
instance = awful.menu.clients({ theme = { width = dpi(250) } }) |
|
end |
|
end |
|
end |
|
|
|
-- Escapes a string so that it can be displayed inside pango markup |
|
-- tags. Modified from: |
|
-- https://github.com/kernelsauce/turbo/blob/master/turbo/escape.lua |
|
function helpers.pango_escape(s) |
|
return (string.gsub(s, "[&<>]", { |
|
["&"] = "&", |
|
["<"] = "<", |
|
[">"] = ">" |
|
})) |
|
end |
|
|
|
function helpers.vertical_pad(height) |
|
return wibox.widget{ |
|
forced_height = height, |
|
layout = wibox.layout.fixed.vertical |
|
} |
|
end |
|
|
|
function helpers.horizontal_pad(width) |
|
return wibox.widget{ |
|
forced_width = width, |
|
layout = wibox.layout.fixed.horizontal |
|
} |
|
end |
|
|
|
local direction_translate = { |
|
['up'] = 'top', |
|
['down'] = 'bottom', |
|
['left'] = 'left', |
|
['right'] = 'right' |
|
} |
|
function helpers.move_to_edge(c, direction) |
|
local old = c:geometry() |
|
local new = awful.placement[direction_translate[direction]](c, {honor_padding = true, honor_workarea = true, margins = beautiful.useless_gap * 2, pretend = true}) |
|
if direction == "up" or direction == "down" then |
|
c:geometry({ x = old.x, y = new.y }) |
|
else |
|
c:geometry({ x = new.x, y = old.y }) |
|
end |
|
end |
|
|
|
local double_tap_timer = nil |
|
function helpers.single_double_tap(single_tap_function, double_tap_function) |
|
if double_tap_timer then |
|
double_tap_timer:stop() |
|
double_tap_timer = nil |
|
double_tap_function() |
|
-- naughty.notify({text = "We got a double tap"}) |
|
return |
|
end |
|
|
|
double_tap_timer = |
|
gears.timer.start_new(0.20, function() |
|
double_tap_timer = nil |
|
-- naughty.notify({text = "We got a single tap"}) |
|
if single_tap_function then |
|
single_tap_function() |
|
end |
|
return false |
|
end) |
|
end |
|
|
|
|
|
-- Used as a custom command in rofi to move a window into the current tag |
|
-- instead of following it. |
|
-- Rofi has access to the X window id of the client. |
|
function helpers.rofi_move_client_here(window) |
|
local win = function (c) |
|
return awful.rules.match(c, {window = window}) |
|
end |
|
|
|
for c in awful.client.iterate(win) do |
|
c.minimized = false |
|
c:move_to_tag(mouse.screen.selected_tag) |
|
client.focus = c |
|
end |
|
end |
|
|
|
-- Add a hover cursor to a widget by changing the cursor on |
|
-- mouse::enter and mouse::leave |
|
-- You can find the names of the available cursors by opening any |
|
-- cursor theme and looking in the "cursors folder" |
|
-- For example: "hand1" is the cursor that appears when hovering over |
|
-- links |
|
function helpers.add_hover_cursor(w, hover_cursor) |
|
local original_cursor = "left_ptr" |
|
|
|
w:connect_signal("mouse::enter", function () |
|
local w = _G.mouse.current_wibox |
|
if w then |
|
w.cursor = hover_cursor |
|
end |
|
end) |
|
|
|
w:connect_signal("mouse::leave", function () |
|
local w = _G.mouse.current_wibox |
|
if w then |
|
w.cursor = original_cursor |
|
end |
|
end) |
|
end |
|
|
|
-- Tag back and forth: |
|
-- If you try to focus the tag you are already at, go back to the previous tag. |
|
-- Useful for quick switching after for example checking an incoming chat |
|
-- message at tag 2 and coming back to your work at tag 1 with the same |
|
-- keypress. |
|
function helpers.tag_back_and_forth(tag_index) |
|
local s = mouse.screen |
|
local tag = s.tags[tag_index] |
|
if tag then |
|
if tag == s.selected_tag then |
|
awful.tag.history.restore() |
|
else |
|
tag:view_only() |
|
end |
|
end |
|
end |
|
|
|
-- Resize DWIM (Do What I Mean) |
|
-- Resize client or factor |
|
-- Constants -- |
|
local floating_resize_amount = dpi(20) |
|
local tiling_resize_factor= 0.05 |
|
--------------- |
|
function helpers.resize_dwim(c, direction) |
|
if c and c.floating then |
|
if direction == "up" then |
|
c:relative_move( 0, 0, 0, -floating_resize_amount) |
|
elseif direction == "down" then |
|
c:relative_move( 0, 0, 0, floating_resize_amount) |
|
elseif direction == "left" then |
|
c:relative_move( 0, 0, -floating_resize_amount, 0) |
|
elseif direction == "right" then |
|
c:relative_move( 0, 0, floating_resize_amount, 0) |
|
end |
|
elseif awful.layout.get(mouse.screen) ~= awful.layout.suit.floating then |
|
if direction == "up" then |
|
awful.client.incwfact(-tiling_resize_factor) |
|
elseif direction == "down" then |
|
awful.client.incwfact( tiling_resize_factor) |
|
elseif direction == "left" then |
|
awful.tag.incmwfact(-tiling_resize_factor) |
|
elseif direction == "right" then |
|
awful.tag.incmwfact( tiling_resize_factor) |
|
end |
|
end |
|
end |
|
|
|
-- Move client DWIM (Do What I Mean) |
|
-- Move to edge if the client / layout is floating |
|
-- Swap by index if maximized |
|
-- Else swap client by direction |
|
function helpers.move_client_dwim(c, direction) |
|
if c.floating or (awful.layout.get(mouse.screen) == awful.layout.suit.floating) then |
|
helpers.move_to_edge(c, direction) |
|
elseif awful.layout.get(mouse.screen) == awful.layout.suit.max then |
|
if direction == "up" or direction == "left" then |
|
awful.client.swap.byidx(-1, c) |
|
elseif direction == "down" or direction == "right" then |
|
awful.client.swap.byidx(1, c) |
|
end |
|
else |
|
awful.client.swap.bydirection(direction, c, nil) |
|
end |
|
end |
|
|
|
-- Make client floating and snap to the desired edge |
|
local axis_translate = { |
|
['up'] = 'horizontally', |
|
['down'] = 'horizontally', |
|
['left'] = 'vertically', |
|
['right'] = 'vertically' |
|
} |
|
function helpers.float_and_edge_snap(c, direction) |
|
c.maximized = false |
|
c.maximized_vertical = false |
|
c.maximized_horizontal = false |
|
c.floating = true |
|
local f = awful.placement.scale |
|
+ awful.placement[direction_translate[direction]] |
|
+ awful.placement['maximize_'..axis_translate[direction]] |
|
f(c, {honor_padding = true, honor_workarea=true, to_percent = 0.5, margins = beautiful.useless_gap * 2 }) |
|
end |
|
|
|
-- Rounds a number to any number of decimals |
|
function helpers.round(number, decimals) |
|
local power = 10 ^ decimals |
|
return math.floor(number * power) / power |
|
end |
|
|
|
function helpers.volume_control(step) |
|
local cmd |
|
if step == 0 then |
|
cmd = "pactl set-sink-mute @DEFAULT_SINK@ toggle" |
|
else |
|
sign = step > 0 and "+" or "" |
|
cmd = "pactl set-sink-mute @DEFAULT_SINK@ 0 && pactl set-sink-volume @DEFAULT_SINK@ "..sign..tostring(step).."%" |
|
end |
|
awful.spawn.with_shell(cmd) |
|
end |
|
|
|
function helpers.send_key(c, key) |
|
awful.spawn.with_shell("xdotool key --window "..tostring(c.window).." "..key) |
|
end |
|
|
|
function helpers.send_key_sequence(c, seq) |
|
awful.spawn.with_shell("xdotool type --delay 5 --window "..tostring(c.window).." "..seq) |
|
end |
|
|
|
function helpers.fake_escape() |
|
root.fake_input('key_press', "Escape") |
|
root.fake_input('key_release', "Escape") |
|
end |
|
|
|
local prompt_font = beautiful.prompt_font or "sans bold 8" |
|
function helpers.prompt(action, textbox, prompt, callback) |
|
if action == "run" then |
|
awful.prompt.run { |
|
prompt = prompt, |
|
-- prompt = "<b>Run: </b>", |
|
textbox = textbox, |
|
font = prompt_font, |
|
done_callback = callback, |
|
exe_callback = awful.spawn, |
|
completion_callback = awful.completion.shell, |
|
history_path = awful.util.get_cache_dir() .. "/history" |
|
} |
|
elseif action == "web_search" then |
|
awful.prompt.run { |
|
prompt = prompt, |
|
-- prompt = '<b>Web search: </b>', |
|
textbox = textbox, |
|
font = prompt_font, |
|
history_path = awful.util.get_cache_dir() .. "/history_web", |
|
done_callback = callback, |
|
exe_callback = function(input) |
|
if not input or #input == 0 then return end |
|
awful.spawn.with_shell("noglob "..user.web_search_cmd.."'"..input.."'") |
|
naughty.notify { title = "Searching the web for", text = input, icon = icons.image.firefox, urgency = "low" } |
|
end |
|
} |
|
end |
|
end |
|
|
|
-- Given a `match` condition, returns an array with clients that match it, or |
|
-- just the first found client if `first_only` is true |
|
function helpers.find_clients(match, first_only) |
|
local matcher = function (c) |
|
return awful.rules.match(c, match) |
|
end |
|
|
|
if first_only then |
|
for c in awful.client.iterate(matcher) do |
|
return c |
|
end |
|
else |
|
local clients = {} |
|
for c in awful.client.iterate(matcher) do |
|
table.insert(clients, c) |
|
end |
|
return clients |
|
end |
|
return nil |
|
end |
|
|
|
-- Given a `match` condition, calls the specified function `f_do` on all the |
|
-- clients that match it |
|
function helpers.find_clients_and_do(match, f_do) |
|
local matcher = function (c) |
|
return awful.rules.match(c, match) |
|
end |
|
|
|
for c in awful.client.iterate(matcher) do |
|
f_do(c) |
|
end |
|
end |
|
|
|
function helpers.run_or_raise(match, move, spawn_cmd, spawn_args) |
|
local matcher = function (c) |
|
return awful.rules.match(c, match) |
|
end |
|
|
|
-- Find and raise |
|
local found = false |
|
for c in awful.client.iterate(matcher) do |
|
found = true |
|
c.minimized = false |
|
if move then |
|
c:move_to_tag(mouse.screen.selected_tag) |
|
client.focus = c |
|
else |
|
c:jump_to() |
|
end |
|
break |
|
end |
|
|
|
-- Spawn if not found |
|
if not found then |
|
awful.spawn(spawn_cmd, spawn_args) |
|
end |
|
end |
|
|
|
-- Run raise or minimize a client (scratchpad style) |
|
-- Depends on helpers.run_or_raise |
|
-- If it not running, spawn it |
|
-- If it is running, focus it |
|
-- If it is focused, minimize it |
|
function helpers.scratchpad(match, spawn_cmd, spawn_args) |
|
local cf = client.focus |
|
if cf and awful.rules.match(cf, match) then |
|
cf.minimized = true |
|
else |
|
helpers.run_or_raise(match, true, spawn_cmd, spawn_args) |
|
end |
|
end |
|
|
|
function helpers.float_and_resize(c, width, height) |
|
c.maximized = false |
|
c.width = width |
|
c.height = height |
|
awful.placement.centered(c,{honor_workarea=true, honor_padding = true}) |
|
awful.client.property.set(c, 'floating_geometry', c:geometry()) |
|
c.floating = true |
|
c:raise() |
|
end |
|
|
|
-- Adds a maximized mask to a screen |
|
function helpers.screen_mask(s, bg) |
|
local mask = wibox({visible = false, ontop = true, type = "splash", screen = s}) |
|
awful.placement.maximize(mask) |
|
mask.bg = bg |
|
return mask |
|
end |
|
|
|
-- Useful for periodically checking the output of a command that |
|
-- requires internet access. |
|
-- Ensures that `command` will be run EXACTLY once during the desired |
|
-- `interval`, even if awesome restarts multiple times during this time. |
|
-- Saves output in `output_file` and checks its last modification |
|
-- time to determine whether to run the command again or not. |
|
-- Passes the output of `command` to `callback` function. |
|
function helpers.remote_watch(command, interval, output_file, callback) |
|
local run_the_thing = function() |
|
-- Pass output to callback AND write it to file |
|
awful.spawn.easy_async_with_shell(command.." | tee "..output_file, function(out) callback(out) end) |
|
end |
|
|
|
local timer |
|
timer = gears.timer { |
|
timeout = interval, |
|
call_now = true, |
|
autostart = true, |
|
single_shot = false, |
|
callback = function() |
|
awful.spawn.easy_async_with_shell("date -r "..output_file.." +%s", function(last_update, _, __, exitcode) |
|
-- Probably the file does not exist yet (first time |
|
-- running after reboot) |
|
if exitcode == 1 then |
|
run_the_thing() |
|
return |
|
end |
|
|
|
local diff = os.time() - tonumber(last_update) |
|
if diff >= interval then |
|
run_the_thing() |
|
else |
|
-- Pass the date saved in the file since it is fresh enough |
|
awful.spawn.easy_async_with_shell("cat "..output_file, function(out) callback(out) end) |
|
|
|
-- Schedule an update for when the remaining time to complete the interval passes |
|
timer:stop() |
|
gears.timer.start_new(interval - diff, function() |
|
run_the_thing() |
|
timer:again() |
|
end) |
|
end |
|
end) |
|
end |
|
} |
|
end |
|
|
|
-- The directory of the currently executed lua script |
|
-- Requires the `debug` library to be available in the build of Lua that is running |
|
function helpers.this_dir() |
|
local str = debug.getinfo(2, "S").source:sub(2) |
|
return str:match("(.*/)") |
|
end |
|
|
|
return helpers
|
|
|