mirror of
https://github.com/Relz/awesome-wm-theme.git
synced 2026-09-10 07:26:41 -04:00
1127 lines
35 KiB
Lua
1127 lines
35 KiB
Lua
local awful = require("awful")
|
|
local gears = require("gears")
|
|
local hotkeys_popup = require("awful.hotkeys_popup")
|
|
|
|
-- Common
|
|
|
|
round_number = function(a)
|
|
return math.floor(a + 0.5)
|
|
end
|
|
|
|
math_clamp = function(value, min, max)
|
|
return math.max(min, math.min(value, max))
|
|
end
|
|
|
|
clamp_percent = function(value)
|
|
return math.max(0, math.min(value, 100))
|
|
end
|
|
|
|
lpad_string = function(str, len, char)
|
|
if char == nil then
|
|
char = ' '
|
|
end
|
|
|
|
return string.rep(char, len - #str) .. str
|
|
end
|
|
|
|
rpad_string = function(str, len, char)
|
|
if char == nil then
|
|
char = ' '
|
|
end
|
|
|
|
return str .. string.rep(char, len - #str)
|
|
end
|
|
|
|
create_today = function()
|
|
local now = os.date("*t")
|
|
now.hour = 0
|
|
now.min = 0
|
|
now.sec = 0
|
|
|
|
return os.time(now)
|
|
end
|
|
|
|
create_local_datetime = function(year, month, day, hour, min, sec)
|
|
local now = os.date("*t")
|
|
if year ~= nil then
|
|
now.year = year
|
|
end
|
|
if month ~= nil then
|
|
now.month = month
|
|
end
|
|
if day ~= nil then
|
|
now.day = day
|
|
end
|
|
if hour ~= nil then
|
|
now.hour = hour
|
|
end
|
|
if min ~= nil then
|
|
now.min = min
|
|
end
|
|
if sec ~= nil then
|
|
now.sec = sec
|
|
end
|
|
now.isdst = false
|
|
|
|
return os.time(now) + get_timezone_offset()
|
|
end
|
|
|
|
get_timezone_offset = function()
|
|
local date_utc = os.date("!*t")
|
|
local date_local = os.date("*t")
|
|
date_local.isdst = false
|
|
|
|
return os.difftime(os.time(date_local), os.time(date_utc))
|
|
end
|
|
|
|
get_color_between = function(color1Hex, color2Hex, weight)
|
|
if color1Hex:len() ~= 7 or color2Hex:len() ~= 7 then
|
|
return nil
|
|
end
|
|
|
|
local weight1 = 1 - weight
|
|
local weight2 = weight
|
|
local color1Rgb = hex_to_rgb(color1Hex)
|
|
local color2Rgb = hex_to_rgb(color2Hex)
|
|
|
|
return rgb_to_hex({
|
|
round_number(color1Rgb[1] * weight1 + color2Rgb[1] * weight2),
|
|
round_number(color1Rgb[2] * weight1 + color2Rgb[2] * weight2),
|
|
round_number(color1Rgb[3] * weight1 + color2Rgb[3] * weight2),
|
|
})
|
|
end
|
|
|
|
hex_to_rgb = function(hexColor)
|
|
if hexColor:len() ~= 7 or string.sub(hexColor, 1, 1) ~= "#" then
|
|
return nil
|
|
end
|
|
|
|
local redHex = string.sub(hexColor, 2, 3)
|
|
local greenHex = string.sub(hexColor, 4, 5)
|
|
local blueHex = string.sub(hexColor, 6, 7)
|
|
local red = tonumber(redHex, 16)
|
|
local green = tonumber(greenHex, 16)
|
|
local blue = tonumber(blueHex, 16)
|
|
|
|
return {red, green, blue}
|
|
end
|
|
|
|
rgb_to_hex = function(rgbColor)
|
|
local result = "#"
|
|
|
|
for _,value in pairs(rgbColor) do
|
|
local hex = ''
|
|
|
|
while (value > 0) do
|
|
local index = math.fmod(value, 16) + 1
|
|
value = math.floor(value / 16)
|
|
hex = string.sub('0123456789ABCDEF', index, index) .. hex
|
|
end
|
|
|
|
if (string.len(hex) == 0) then
|
|
hex = '00'
|
|
elseif (string.len(hex) == 1) then
|
|
hex = '0' .. hex
|
|
end
|
|
|
|
result = result .. hex
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
run_command_sync = function(command)
|
|
local result = ""
|
|
|
|
local command_subprocess = io.popen(command)
|
|
if command_subprocess ~= nil then
|
|
result = command_subprocess:read('*all')
|
|
command_subprocess:close()
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
get_percent_number_digits_count = function(number)
|
|
return number < 10 and 1 or (number < 100 and 2 or 3)
|
|
end
|
|
|
|
read_file_content_async = function(file_path, callback)
|
|
awful.spawn.easy_async("cat " .. file_path, callback)
|
|
end
|
|
|
|
read_file_content = function(file_path)
|
|
local file_content
|
|
|
|
local file = io.open(file_path, "r")
|
|
if file ~= nil then
|
|
file_content = file:read("*all")
|
|
file:close()
|
|
end
|
|
|
|
return file_content
|
|
end
|
|
|
|
write_file_content = function(file_path, content)
|
|
local file = io.open(file_path, "w")
|
|
if file ~= nil then
|
|
file:write(content)
|
|
file:close()
|
|
end
|
|
end
|
|
|
|
table_map = function(tbl, f)
|
|
local t = {}
|
|
|
|
for k,v in pairs(tbl) do
|
|
t[k] = f(v)
|
|
end
|
|
|
|
return t
|
|
end
|
|
|
|
get_screen = function(index)
|
|
for s in screen do
|
|
if s.index == index then
|
|
return s
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
get_clients_names = function()
|
|
local client_names = {}
|
|
|
|
for s in screen do
|
|
for _,t in pairs(s.tags) do
|
|
for _,c in pairs(t:clients()) do
|
|
table.insert(client_names, c.name)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
return client_names
|
|
end
|
|
|
|
is_client_in_tag = function(c, tag)
|
|
local ctags = c:tags()
|
|
for _, v in ipairs(ctags) do
|
|
if v == tag then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
get_program_installed = function(program_name, callback)
|
|
awful.spawn.easy_async("which " .. program_name, function(stdout, stderr, exit_reason, exit_code)
|
|
callback(exit_code == 0)
|
|
end)
|
|
end
|
|
|
|
set_gtk_theme_mode = function(theme_mode)
|
|
local is_dark_theme = theme_mode == "dark"
|
|
awful.spawn("gsettings set org.gnome.desktop.interface color-scheme prefer-" .. theme_mode)
|
|
|
|
local gtk2_settings_file_path = gears.filesystem.get_xdg_config_home() .. "../.gtkrc-2.0"
|
|
local gtk2_settings_file_content = read_file_content(gtk2_settings_file_path)
|
|
local new_gtk2_settings_file_content = gtk2_settings_file_content
|
|
|
|
local gtk3_settings_file_path = gears.filesystem.get_xdg_config_home() .. "gtk-3.0/settings.ini"
|
|
local gtk3_settings_file_content = read_file_content(gtk3_settings_file_path)
|
|
|
|
if gtk3_settings_file_content ~= nil then
|
|
local new_gtk3_settings_file_content = gtk3_settings_file_content
|
|
|
|
local pattern = "gtk[-]application[-]prefer[-]dark[-]theme=(.-)%c"
|
|
local new_setting = "gtk-application-prefer-dark-theme=" .. tostring(is_dark_theme) .. "\n"
|
|
if new_gtk3_settings_file_content:match(pattern) ~= nil then
|
|
new_gtk3_settings_file_content = new_gtk3_settings_file_content:gsub(pattern, new_setting)
|
|
else
|
|
new_gtk3_settings_file_content = new_gtk3_settings_file_content .. "\n" .. new_setting
|
|
end
|
|
|
|
local pattern = "gtk[-]theme[-]name=(.-)%c"
|
|
local gtk_theme_name = new_gtk3_settings_file_content:match(pattern)
|
|
|
|
if gears.filesystem.is_dir("/usr/share/themes/" .. gtk_theme_name) then
|
|
local gtk_theme_base_name = gtk_theme_name:gsub("-dark", "")
|
|
local gtk_theme_dark_name = gtk_theme_base_name .. "-dark"
|
|
local gtk_theme_new_name = is_dark_theme and gtk_theme_dark_name or gtk_theme_base_name
|
|
if gears.filesystem.is_dir("/usr/share/themes/" .. gtk_theme_new_name) then
|
|
local new_setting = "gtk-theme-name=" .. tostring(gtk_theme_new_name) .. "\n"
|
|
new_gtk3_settings_file_content = new_gtk3_settings_file_content:gsub(pattern, new_setting)
|
|
awful.spawn("gsettings set org.gnome.desktop.interface gtk-theme " .. gtk_theme_new_name)
|
|
|
|
|
|
local pattern = "include \"/usr/share/themes/.-/gtk[-]2.0/gtkrc\"%c"
|
|
local new_setting = "include \"/usr/share/themes/" .. gtk_theme_new_name .. "/gtk-2.0/gtkrc\"" .. "\n"
|
|
new_gtk2_settings_file_content = new_gtk2_settings_file_content:gsub(pattern, new_setting)
|
|
end
|
|
end
|
|
|
|
local pattern = "gtk[-]icon[-]theme[-]name=(.-)%c"
|
|
local gtk_icon_theme_name = new_gtk3_settings_file_content:match(pattern)
|
|
|
|
if gears.filesystem.is_dir("/usr/share/icons/" .. gtk_icon_theme_name) then
|
|
local gtk_icon_theme_base_name = gtk_icon_theme_name:gsub("-dark", "")
|
|
local gtk_icon_theme_dark_name = gtk_icon_theme_base_name .. "-dark"
|
|
local gtk_icon_theme_new_name = is_dark_theme and gtk_icon_theme_dark_name or gtk_icon_theme_base_name
|
|
if gears.filesystem.is_dir("/usr/share/icons/" .. gtk_icon_theme_new_name) then
|
|
local new_setting = "gtk-icon-theme-name=" .. tostring(gtk_icon_theme_new_name) .. "\n"
|
|
new_gtk3_settings_file_content = new_gtk3_settings_file_content:gsub(pattern, new_setting)
|
|
awful.spawn("gsettings set org.gnome.desktop.interface icon-theme " .. gtk_icon_theme_new_name)
|
|
end
|
|
end
|
|
|
|
write_file_content(gtk3_settings_file_path, new_gtk3_settings_file_content)
|
|
write_file_content(gtk2_settings_file_path, new_gtk2_settings_file_content)
|
|
end
|
|
end
|
|
|
|
set_ulauncher_theme_mode = function(theme_mode)
|
|
local is_dark_theme = theme_mode == "dark"
|
|
local ulauncher_settings_file_path = gears.filesystem.get_xdg_config_home() .. "ulauncher/settings.json"
|
|
local ulauncher_settings_file_content = read_file_content(ulauncher_settings_file_path)
|
|
|
|
if ulauncher_settings_file_content == nil then
|
|
return
|
|
end
|
|
|
|
local pattern = "\"theme[-]name\": \"(.-)\"%c"
|
|
local ulauncher_theme_name = ulauncher_settings_file_content:match(pattern)
|
|
local new_theme_name = ulauncher_theme_name:gsub(is_dark_theme and "light" or "dark", theme_mode)
|
|
local new_setting = "\"theme-name\": \"" .. new_theme_name .. "\"" .. "\n"
|
|
local new_ulauncher_settings_file_content = ulauncher_settings_file_content:gsub(pattern, new_setting)
|
|
write_file_content(ulauncher_settings_file_path, new_ulauncher_settings_file_content)
|
|
if new_theme_name ~= ulauncher_theme_name then
|
|
awful.spawn("pkill ulauncher")
|
|
awful.spawn("ulauncher --hide-window")
|
|
end
|
|
end
|
|
|
|
set_obs_theme_mode = function(theme_mode)
|
|
local is_dark_theme = theme_mode == "dark"
|
|
local obs_settings_file_path = gears.filesystem.get_xdg_config_home() .. "obs-studio/global.ini"
|
|
local obs_settings_file_content = read_file_content(obs_settings_file_path)
|
|
|
|
if obs_settings_file_content == nil then
|
|
return
|
|
end
|
|
|
|
local pattern = "CurrentTheme2=(.-)%c"
|
|
local new_theme_name = is_dark_theme and "Dark" or "System"
|
|
local new_setting = "CurrentTheme2=" .. new_theme_name .. "\n"
|
|
local new_obs_settings_file_content = obs_settings_file_content:gsub(pattern, new_setting)
|
|
write_file_content(obs_settings_file_path, new_obs_settings_file_content)
|
|
end
|
|
|
|
set_remmina_theme_mode = function(theme_mode)
|
|
local is_dark_theme = theme_mode == "dark"
|
|
local remmina_settings_file_path = gears.filesystem.get_xdg_config_home() .. "remmina/remmina.pref"
|
|
local remmina_settings_file_content = read_file_content(remmina_settings_file_path)
|
|
|
|
if remmina_settings_file_content == nil then
|
|
return
|
|
end
|
|
|
|
local pattern = "dark_theme=(.-)%c"
|
|
local new_setting = "dark_theme=" .. tostring(is_dark_theme) .. "\n"
|
|
local new_remmina_settings_file_content = remmina_settings_file_content:gsub(pattern, new_setting)
|
|
write_file_content(remmina_settings_file_path, new_remmina_settings_file_content)
|
|
end
|
|
|
|
-- Brightness
|
|
|
|
get_system_brightness = function(callback)
|
|
awful.spawn.easy_async("xfpm-power-backlight-helper --get-brightness", function(current_brightness_string)
|
|
awful.spawn.easy_async("xfpm-power-backlight-helper --get-max-brightness", function(max_brightness_string)
|
|
local current_brightness = tonumber(current_brightness_string)
|
|
local max_brightness = tonumber(max_brightness_string)
|
|
|
|
callback(math.floor(current_brightness / max_brightness * 100))
|
|
end)
|
|
end)
|
|
end
|
|
|
|
set_system_brightness = function(step_percent, increase, callback)
|
|
awful.spawn.easy_async("xfpm-power-backlight-helper --get-brightness", function(current_brightness_string)
|
|
awful.spawn.easy_async("xfpm-power-backlight-helper --get-max-brightness", function(max_brightness_string)
|
|
local current_brightness = tonumber(current_brightness_string)
|
|
local max_brightness = tonumber(max_brightness_string)
|
|
|
|
local step = math.floor(step_percent * max_brightness / 100)
|
|
|
|
local new_brightness = 0
|
|
if increase then
|
|
new_brightness = current_brightness + step;
|
|
else
|
|
new_brightness = current_brightness - step;
|
|
end
|
|
local new_brightness = math.min(math.max(new_brightness, 0), max_brightness)
|
|
|
|
awful.spawn.easy_async("pkexec xfpm-power-backlight-helper --set-brightness=" .. new_brightness, function()
|
|
callback(math.floor(new_brightness / max_brightness * 100))
|
|
end)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
-- Audio
|
|
|
|
local cached_audio_server = nil
|
|
|
|
get_audio_server = function(callback)
|
|
if cached_audio_server ~= nil then
|
|
callback(cached_audio_server)
|
|
else
|
|
get_program_installed("pactl", function(is_pactl_installed)
|
|
if is_pactl_installed then
|
|
cached_audio_server = "pactl"
|
|
callback("pactl")
|
|
else
|
|
get_program_installed("pacmd", function(is_pacmd_installed)
|
|
if is_pacmd_installed then
|
|
cached_audio_server = "pacmd"
|
|
callback("pacmd")
|
|
else
|
|
callback(nil)
|
|
end
|
|
end)
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
get_audio_server_installed = function(callback)
|
|
get_audio_server(function(audio_server)
|
|
callback(audio_server ~= nil)
|
|
end)
|
|
end
|
|
|
|
get_default_sink = function(callback)
|
|
awful.spawn.easy_async("pactl get-default-sink", function(default_sink_stdout)
|
|
local default_sink = default_sink_stdout:gsub("\n", "")
|
|
callback(default_sink)
|
|
end)
|
|
end
|
|
|
|
get_default_source = function(callback)
|
|
awful.spawn.easy_async("pactl get-default-source", function(default_source_stdout)
|
|
local default_source = default_source_stdout:gsub("\n", "")
|
|
callback(default_source)
|
|
end)
|
|
end
|
|
|
|
get_sink_volume = function(callback)
|
|
get_default_sink(function(default_sink)
|
|
awful.spawn.easy_async("pactl get-sink-volume " .. default_sink, function(current_default_sink_volume_output)
|
|
local current_default_sink_volume = tonumber(current_default_sink_volume_output:match("(%d+)%%"))
|
|
callback(current_default_sink_volume)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
get_source_volume = function(callback)
|
|
get_default_source(function(default_source)
|
|
awful.spawn.easy_async("pactl get-source-volume " .. default_source, function(current_default_source_volume_output)
|
|
local current_default_source_volume = tonumber(current_default_source_volume_output:match("(%d+)%%"))
|
|
callback(current_default_source_volume)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
set_sink_volume = function(step, increase, callback)
|
|
get_default_sink(function(default_sink)
|
|
awful.spawn.easy_async("pactl get-sink-volume " .. default_sink, function(current_default_sink_volume_output)
|
|
local current_default_sink_volume = tonumber(current_default_sink_volume_output:match("(%d+)%%"))
|
|
local command = "pactl set-sink-volume " .. default_sink;
|
|
local new_volume_value = 0
|
|
if increase == nil then
|
|
new_volume_value = clamp_percent(step)
|
|
elseif increase then
|
|
new_volume_value = clamp_percent(current_default_sink_volume + step)
|
|
else
|
|
new_volume_value = clamp_percent(current_default_sink_volume - step)
|
|
end
|
|
command = command .. " " .. new_volume_value .. "%"
|
|
awful.spawn.easy_async(command, callback)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
set_source_volume = function(step, increase, callback)
|
|
get_default_source(function(default_source)
|
|
awful.spawn.easy_async("pactl get-source-volume " .. default_source, function(current_default_source_volume_output)
|
|
local current_default_source_volume = tonumber(current_default_source_volume_output:match("(%d+)%%"))
|
|
local command = "pactl set-source-volume " .. default_source;
|
|
local new_volume_value = 0
|
|
if increase == nil then
|
|
new_volume_value = clamp_percent(step)
|
|
elseif increase then
|
|
new_volume_value = clamp_percent(current_default_source_volume + step)
|
|
else
|
|
new_volume_value = clamp_percent(current_default_source_volume - step)
|
|
end
|
|
command = command .. " " .. new_volume_value .. "%"
|
|
awful.spawn.easy_async(command, callback)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
parse_audio_server_devices = function(audio_server_output, default_device)
|
|
local device_index_pattern = "^%a+ #(%d+)"
|
|
local devices = {}
|
|
|
|
local device
|
|
local properties
|
|
local profiles
|
|
local ports
|
|
local port
|
|
local port_properties
|
|
|
|
local in_device = false
|
|
local in_properties = false
|
|
local in_ports = false
|
|
local in_profiles = false
|
|
local in_port_properties = false
|
|
|
|
local matches = audio_server_output:gmatch("[^\r\n]+")
|
|
|
|
local lines = {}
|
|
|
|
for match in matches do
|
|
table.insert(lines, match)
|
|
end
|
|
|
|
for i = #lines - 1, 1, -1 do
|
|
local line = lines[i]
|
|
local next_line = lines[i + 1]
|
|
if not string.match(next_line, ":") and not string.match(next_line, "=") and not string.match(next_line, device_index_pattern) then
|
|
lines[i] = line .. next_line
|
|
table.remove(lines, i + 1)
|
|
end
|
|
end
|
|
|
|
for _,line in ipairs(lines) do
|
|
if string.match(line, device_index_pattern) then
|
|
in_device = true
|
|
in_properties = false
|
|
in_ports = false
|
|
in_profiles = false
|
|
in_port_properties = false
|
|
device = {
|
|
id = line:match(device_index_pattern)
|
|
}
|
|
table.insert(devices, device)
|
|
goto continue
|
|
end
|
|
|
|
if string.match(line, "%s+Properties:") then
|
|
in_device = false
|
|
in_properties = not in_ports
|
|
in_profiles = false
|
|
in_port_properties = in_ports
|
|
|
|
if in_ports then
|
|
port_properties = {}
|
|
ports[port].properties = port_properties
|
|
else
|
|
properties = {}
|
|
device.properties = properties
|
|
end
|
|
|
|
in_ports = false
|
|
|
|
goto continue
|
|
end
|
|
|
|
if string.match(line, "%s+Profiles:") then
|
|
in_device = false
|
|
in_properties = false
|
|
in_ports = false
|
|
in_profiles = true
|
|
in_port_properties = false
|
|
profiles = {}
|
|
device.profiles = profiles
|
|
goto continue
|
|
end
|
|
|
|
if string.match(line, "%s+Active Profile:") then
|
|
in_device = false
|
|
in_properties = false
|
|
in_ports = false
|
|
in_profiles = false
|
|
in_port_properties = false
|
|
device.active_profile = line:match(": (.+)"):gsub("<",""):gsub(">","")
|
|
goto continue
|
|
end
|
|
|
|
if string.match(line, "%s+Ports:") then
|
|
|
|
in_device = false
|
|
in_properties = false
|
|
in_ports = true
|
|
in_profiles = false
|
|
in_port_properties = false
|
|
ports = {}
|
|
device.ports = ports
|
|
goto continue
|
|
end
|
|
|
|
if string.match(line, "%s+Active Port: ") then
|
|
in_device = false
|
|
in_properties = false
|
|
in_ports = false
|
|
in_profiles = false
|
|
in_port_properties = false
|
|
device.active_port = line:match(": (.+)"):gsub("<",""):gsub(">","")
|
|
goto continue
|
|
end
|
|
|
|
if in_device then
|
|
local key = line:match("%s+(.+):")
|
|
local value = line:match(": (.+)"):gsub("<",""):gsub(">","")
|
|
device[key] = value
|
|
end
|
|
|
|
if in_properties then
|
|
local matches = string.gmatch(line, "([^=]+)")
|
|
|
|
local parts = {}
|
|
|
|
for match in matches do
|
|
table.insert(parts, match)
|
|
end
|
|
|
|
local key = parts[1]:gsub("%s", ""):gsub("%.", "_"):gsub("-", "_"):gsub(":", ""):gsub("%s+$", "")
|
|
local value = parts[2]
|
|
if value ~= nil then
|
|
value = value:gsub("\"", ""):gsub("^%s+", ""):gsub(" Analog Stereo", "")
|
|
end
|
|
|
|
properties[key] = value
|
|
end
|
|
|
|
if in_profiles then
|
|
local key = line:match("%s+(%S+):")
|
|
local value = line:match(": (.+) %(")
|
|
|
|
profiles[key] = value
|
|
end
|
|
|
|
if in_port_properties and string.match(line, ":") then
|
|
in_port_properties = false
|
|
in_ports = true
|
|
end
|
|
|
|
if in_port_properties then
|
|
local matches = string.gmatch(line, "([^=]+)")
|
|
|
|
local parts = {}
|
|
|
|
for match in matches do
|
|
table.insert(parts, match)
|
|
end
|
|
|
|
|
|
local key = parts[1]:gsub(" ", ""):gsub("%.", "_"):gsub("-", "_"):gsub(":", ""):gsub("%s+$", "")
|
|
local value = parts[2]
|
|
if value ~= nil then
|
|
value = value:gsub("\"", ""):gsub("^%s+", "")
|
|
end
|
|
|
|
port_properties[key] = value
|
|
end
|
|
|
|
if in_ports then
|
|
local key = line:match("(%S+):")
|
|
local value = line:match(": (.+) %(")
|
|
|
|
port = key
|
|
|
|
ports[port] = {
|
|
name = value
|
|
}
|
|
end
|
|
::continue::
|
|
end
|
|
|
|
for _,device in ipairs(devices) do
|
|
device.is_default = device["Name"] == default_device
|
|
end
|
|
|
|
return devices
|
|
end
|
|
|
|
audio_previous = function()
|
|
awful.spawn("dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Previous", false)
|
|
end
|
|
|
|
audio_next = function()
|
|
awful.spawn("dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Next", false)
|
|
end
|
|
|
|
audio_toggle_play_pause = function()
|
|
awful.spawn("dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause", false)
|
|
end
|
|
|
|
audio_stop = function()
|
|
awful.spawn("dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Stop", false)
|
|
end
|
|
|
|
-- Bluetooth
|
|
|
|
local get_bluetooth_adapter_index = function(callback)
|
|
awful.spawn.easy_async("rfkill list bluetooth", function(rfkill_list_bluetooth_output)
|
|
callback(string.match(rfkill_list_bluetooth_output, "^(%d+): "))
|
|
end)
|
|
end
|
|
|
|
local bluetooth_status_subscribers = {}
|
|
|
|
subscribe_bluetooth_status = function(callback)
|
|
table.insert(bluetooth_status_subscribers, callback)
|
|
|
|
if #bluetooth_status_subscribers == 1 then
|
|
get_bluetooth_adapter_index(function(index)
|
|
awful.spawn.with_line_callback("rfkill event", {
|
|
stdout = function(line)
|
|
if string.match(line, "idx " .. index .. " ") then
|
|
for _,bluetooth_status_subscriber in ipairs(bluetooth_status_subscribers) do
|
|
bluetooth_status_subscriber(string.match(line, "soft 0 hard 0") ~= nil)
|
|
end
|
|
end
|
|
end
|
|
})
|
|
end)
|
|
end
|
|
end
|
|
|
|
is_bluetooth_device_connected = function(callback)
|
|
awful.spawn.easy_async("bluetoothctl info", function(bluetoothctl_info_output)
|
|
callback(string.match(bluetoothctl_info_output, "Missing device address argument\n") == nil)
|
|
end)
|
|
end
|
|
|
|
get_bluetooth_device = function(callback)
|
|
is_bluetooth_device_connected(function(is_connected)
|
|
if not is_connected then
|
|
callback({
|
|
connected = false
|
|
})
|
|
end
|
|
|
|
awful.spawn.easy_async("bluetoothctl info", function(bluetoothctl_info_output)
|
|
local bluetooth_device = {
|
|
mac_address = bluetoothctl_info_output:match("^Device (.-) "),
|
|
name = bluetoothctl_info_output:match("Name: (.-)\n"),
|
|
alias = bluetoothctl_info_output:match("Alias: (.-)\n"),
|
|
paired = bluetoothctl_info_output:match("Paired: yes") ~= nil,
|
|
bonded = bluetoothctl_info_output:match("Bonded: yes") ~= nil,
|
|
trusted = bluetoothctl_info_output:match("Trusted: yes") ~= nil,
|
|
blocked = bluetoothctl_info_output:match("Blocked: yes") ~= nil,
|
|
connected = bluetoothctl_info_output:match("Connected: yes") ~= nil,
|
|
battery_percentage = tonumber(bluetoothctl_info_output:match("Battery Percentage: %w+ %((%d-)%)"))
|
|
}
|
|
callback(bluetooth_device)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
-- Show help
|
|
|
|
show_help = function()
|
|
hotkeys_popup.show_help(nil, awful.screen.focused())
|
|
end
|
|
|
|
-- Change focused client
|
|
|
|
change_focused_client = function(direction)
|
|
restore_clients()
|
|
awful.client.focus.byidx(direction)
|
|
if client.focus then client.focus:raise() end
|
|
end
|
|
|
|
-- Move client to next screen
|
|
|
|
move_client_to_next_screen = function(c)
|
|
c:move_to_screen()
|
|
end
|
|
|
|
-- Minimize client
|
|
|
|
minimize_client = function(c)
|
|
c.minimized = true
|
|
end
|
|
|
|
-- Maximize client
|
|
|
|
maximize_client = function(c)
|
|
c.maximized_horizontal = not c.maximized_horizontal
|
|
c.maximized_vertical = not c.maximized_vertical
|
|
end
|
|
|
|
maximize_client_to_multiple_monitor = function(c)
|
|
c.floating = true
|
|
local focused_screen_geometry = awful.screen.focused().geometry
|
|
focused_screen_geometry.x2 = focused_screen_geometry.x + focused_screen_geometry.width
|
|
focused_screen_geometry.y2 = focused_screen_geometry.y + focused_screen_geometry.height
|
|
for s in screen do
|
|
local screen_geometry = s.geometry
|
|
focused_screen_geometry.x = math.min(focused_screen_geometry.x, screen_geometry.x)
|
|
focused_screen_geometry.y = math.min(focused_screen_geometry.y, screen_geometry.y)
|
|
focused_screen_geometry.x2 = math.max(focused_screen_geometry.x2, screen_geometry.x + screen_geometry.width)
|
|
focused_screen_geometry.y2 = math.max(focused_screen_geometry.y2, screen_geometry.y + screen_geometry.height)
|
|
end
|
|
c:geometry{
|
|
x = focused_screen_geometry.x,
|
|
y = focused_screen_geometry.y,
|
|
width = focused_screen_geometry.x2 - focused_screen_geometry.x,
|
|
height = focused_screen_geometry.y2 - focused_screen_geometry.y
|
|
}
|
|
end
|
|
|
|
-- Toggle client fullscreen
|
|
|
|
toggle_fullscreen = function(c)
|
|
c.fullscreen = not c.fullscreen
|
|
end
|
|
|
|
-- Do action for tag by index if tag is defined
|
|
|
|
do_for_tag = function(tag_index, action)
|
|
local screen = awful.screen.focused()
|
|
local tag = screen.tags[tag_index]
|
|
if tag then
|
|
action(tag)
|
|
end
|
|
end
|
|
|
|
-- Minimize all clients on focused tag
|
|
|
|
minimize_clients = function()
|
|
local tag = awful.tag.selected()
|
|
for i = 1, #tag:clients() do
|
|
minimize_client(tag:clients()[i])
|
|
end
|
|
end
|
|
|
|
-- Restore all clients on focused tag
|
|
|
|
restore_clients = function()
|
|
local tag = awful.tag.selected()
|
|
for i = 1, #tag:clients() do
|
|
tag:clients()[i].minimized = false
|
|
end
|
|
end
|
|
|
|
-- Toggle minimize/restore all clients on focused tag
|
|
|
|
toogle_minimize_restore_clients = function()
|
|
if client.focus then
|
|
minimize_clients()
|
|
else
|
|
restore_clients()
|
|
end
|
|
end
|
|
|
|
-- Apply snap edge feature to client
|
|
|
|
snap_edge = function(client, where)
|
|
local screen_workarea = screen[client.screen].workarea
|
|
local workarea = {
|
|
x_min = screen_workarea.x,
|
|
x_max = screen_workarea.x + screen_workarea.width,
|
|
y_min = screen_workarea.y,
|
|
y_max = screen_workarea.y + screen_workarea.height,
|
|
width = screen_workarea.width,
|
|
height = screen_workarea.height,
|
|
half_width = screen_workarea.width * 0.5,
|
|
half_height = screen_workarea.height * 0.5
|
|
}
|
|
local client_geometry = client:geometry()
|
|
local axis_border_width = client.border_width * 2
|
|
|
|
if where == 'top' then
|
|
client_geometry.width = workarea.width
|
|
client_geometry.height = workarea.half_height - axis_border_width
|
|
client_geometry.x = workarea.x_min
|
|
client_geometry.y = workarea.y_min
|
|
awful.placement.center_horizontal(client)
|
|
elseif where == 'top_right' then
|
|
client_geometry.width = workarea.half_width - axis_border_width
|
|
client_geometry.height = workarea.half_height - axis_border_width
|
|
client_geometry.x = workarea.x_max - client_geometry.width
|
|
client_geometry.y = workarea.y_min
|
|
elseif where == 'right' then
|
|
client_geometry.width = workarea.half_width - axis_border_width
|
|
client_geometry.height = workarea.height
|
|
client_geometry.x = workarea.x_max - client_geometry.width
|
|
client_geometry.y = workarea.y_min
|
|
elseif where == 'bottom_right' then
|
|
client_geometry.width = workarea.half_width - axis_border_width
|
|
client_geometry.height = workarea.half_height - axis_border_width
|
|
client_geometry.x = workarea.x_max - client_geometry.width
|
|
client_geometry.y = workarea.y_max - client_geometry.height
|
|
elseif where == 'bottom' then
|
|
client_geometry.width = workarea.width
|
|
client_geometry.height = workarea.half_height - axis_border_width
|
|
client_geometry.x = workarea.x_min
|
|
client_geometry.y = workarea.y_max - client_geometry.height
|
|
awful.placement.center_horizontal(client)
|
|
elseif where == 'bottom_left' then
|
|
client_geometry.width = workarea.half_width - axis_border_width
|
|
client_geometry.height = workarea.half_height - axis_border_width
|
|
client_geometry.x = workarea.x_min
|
|
client_geometry.y = workarea.y_max - client_geometry.height
|
|
elseif where == 'left' then
|
|
client_geometry.width = workarea.half_width - axis_border_width
|
|
client_geometry.height = workarea.height
|
|
client_geometry.x = workarea.x_min
|
|
client_geometry.y = workarea.y_min
|
|
elseif where == 'top_left' then
|
|
client_geometry.width = workarea.half_width - axis_border_width
|
|
client_geometry.height = workarea.half_height - axis_border_width
|
|
client_geometry.x = workarea.x_min
|
|
client_geometry.y = workarea.y_min
|
|
elseif where == 'centered' then
|
|
client_geometry.width = workarea.half_width - axis_border_width
|
|
client_geometry.height = workarea.half_height - axis_border_width
|
|
client_geometry.x = workarea.x_min + (workarea.x_max - workarea.x_min - client_geometry.width) * 0.5
|
|
client_geometry.y = workarea.y_min + (workarea.y_max - workarea.y_min - client_geometry.height) * 0.5
|
|
elseif where == nil then
|
|
client:geometry(client_geometry)
|
|
return
|
|
end
|
|
|
|
client.floating = true
|
|
if client.maximized then
|
|
client.maximized = false
|
|
end
|
|
client:geometry(client_geometry)
|
|
awful.placement.no_offscreen(client)
|
|
end
|
|
|
|
-- Move client
|
|
|
|
move_client = function(c)
|
|
c.maximized = false
|
|
client.focus = c
|
|
c:raise()
|
|
awful.mouse.client.move(c)
|
|
end
|
|
|
|
-- Resize client
|
|
|
|
resize_client = function(c)
|
|
c.maximized = false
|
|
client.focus = c
|
|
c:raise()
|
|
awful.mouse.client.resize(c)
|
|
end
|
|
|
|
get_geolocation = function(callback)
|
|
awful.spawn.easy_async("/usr/lib/geoclue-2.0/demos/where-am-i", function(geoclue_stdout)
|
|
local latitude_string = geoclue_stdout:match("Latitude:%s+(%S+)"):gsub(",", "."):gsub("°", "")
|
|
local longitude_string = geoclue_stdout:match("Longitude:%s+(%S+)"):gsub(",", "."):gsub("°", "")
|
|
|
|
local latitude = tonumber(latitude_string)
|
|
local longitude = tonumber(longitude_string)
|
|
|
|
callback(latitude, longitude)
|
|
end)
|
|
end
|
|
|
|
local get_days_count = function(year)
|
|
return os.date("%j", os.time({
|
|
year = year,
|
|
month = 12,
|
|
day = 31
|
|
}))
|
|
end
|
|
|
|
local get_fractional_year = function(hour)
|
|
local day_of_year = os.date("%j")
|
|
local current_year_days_count = get_days_count(os.date("%Y"))
|
|
|
|
return 2 * math.pi / current_year_days_count * (day_of_year - 1 + (hour - 12) / 24)
|
|
end
|
|
|
|
local get_time_equation_minutes = function(fractional_year)
|
|
return 229.18 * (0.000075 + 0.001868 * math.cos(fractional_year) - 0.032077 * math.sin(fractional_year) - 0.014615 * math.cos(2 * fractional_year) - 0.040849 * math.sin(2 * fractional_year))
|
|
end
|
|
|
|
local get_declination_angle = function(fractional_year)
|
|
return 0.006918 - 0.399912 * math.cos(fractional_year) + 0.070257 * math.sin(fractional_year) - 0.006758 * math.cos(2 * fractional_year) + 0.000907 * math.sin(2 * fractional_year) - 0.002697 * math.cos(3 * fractional_year) + 0.00148 * math.sin(3 * fractional_year)
|
|
end
|
|
|
|
local get_hour_angle = function(hour, latitude, to_sunrise)
|
|
local fractional_year = get_fractional_year(hour)
|
|
local declination_angle = get_declination_angle(fractional_year)
|
|
local hour_angle_sign = to_sunrise and 1 or -1
|
|
|
|
return hour_angle_sign * math.acos(math.cos(math.rad(90.833)) / (math.cos(math.rad(latitude)) * math.cos(declination_angle)) - math.tan(math.rad(latitude)) * math.tan(declination_angle))
|
|
end
|
|
|
|
local get_sunrise_sunset_time_minutes = function(latitude, longitude, is_sunrise)
|
|
local hour = os.date("%H")
|
|
local hour_angle = get_hour_angle(hour, latitude, is_sunrise)
|
|
local fractional_year = get_fractional_year(hour)
|
|
local time_equation_minutes = get_time_equation_minutes(fractional_year)
|
|
|
|
return 720 - 4 * (longitude + math.deg(hour_angle)) - time_equation_minutes
|
|
end
|
|
|
|
get_sunrise_time_minutes = function(latitude, longitude)
|
|
return get_sunrise_sunset_time_minutes(latitude, longitude, true)
|
|
end
|
|
|
|
get_sunset_time_minutes = function(latitude, longitude)
|
|
return get_sunrise_sunset_time_minutes(latitude, longitude, false)
|
|
end
|
|
|
|
get_redshift_installed = function(callback)
|
|
get_program_installed("redshift", callback)
|
|
end
|
|
|
|
local cached_redshift_dawn_time = nil
|
|
|
|
get_redshift_dawn_time = function()
|
|
if cached_redshift_dawn_time ~= nil then
|
|
return cached_redshift_dawn_time
|
|
end
|
|
local redshift_settings_file_path = gears.filesystem.get_xdg_config_home() .. "redshift/redshift.conf"
|
|
local redshift_settings_file_content = read_file_content(redshift_settings_file_path)
|
|
|
|
if redshift_settings_file_content == nil then
|
|
return nil
|
|
end
|
|
|
|
local matches = redshift_settings_file_content:gmatch("dawn[-]time=(.-)%c")
|
|
|
|
for match in matches do
|
|
cached_redshift_dawn_time = match
|
|
return match
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
set_redshift_dawn_time = function(value)
|
|
local redshift_settings_file_path = gears.filesystem.get_xdg_config_home() .. "redshift/redshift.conf"
|
|
local redshift_settings_file_content = read_file_content(redshift_settings_file_path)
|
|
|
|
if redshift_settings_file_content == nil then
|
|
return
|
|
end
|
|
|
|
local new_redshift_settings_file_content = redshift_settings_file_content
|
|
local pattern = "dawn[-]time=(.-)%c"
|
|
|
|
if redshift_settings_file_content:match(pattern) then
|
|
local new_setting = value == nil and "" or ("dawn-time=" .. os.date("%H:%M", value) .. "\n")
|
|
new_redshift_settings_file_content = redshift_settings_file_content:gsub(pattern, new_setting)
|
|
else
|
|
local pattern = "dusk[-]time=(.-)%c"
|
|
if redshift_settings_file_content:match(pattern) then
|
|
local new_setting = (value == nil and "" or ("dawn-time=" .. os.date("%H:%M", value) .. "\n")) .. "dusk-time=%1" .. "\n"
|
|
new_redshift_settings_file_content = redshift_settings_file_content:gsub(pattern, new_setting)
|
|
else
|
|
if value ~= nil then
|
|
local pattern = "%[redshift%]%c"
|
|
local new_setting = "[redshift]" .. "\n" .. "dawn-time=" .. os.date("%H:%M", value) .. "\n"
|
|
new_redshift_settings_file_content = redshift_settings_file_content:gsub(pattern, new_setting)
|
|
end
|
|
end
|
|
end
|
|
|
|
write_file_content(redshift_settings_file_path, new_redshift_settings_file_content)
|
|
end
|
|
|
|
local cached_redshift_dusk_time = nil
|
|
|
|
get_redshift_dusk_time = function()
|
|
if cached_redshift_dusk_time ~= nil then
|
|
return cached_redshift_dusk_time
|
|
end
|
|
local redshift_settings_file_path = gears.filesystem.get_xdg_config_home() .. "redshift/redshift.conf"
|
|
local redshift_settings_file_content = read_file_content(redshift_settings_file_path)
|
|
|
|
if redshift_settings_file_content == nil then
|
|
return nil
|
|
end
|
|
|
|
local matches = redshift_settings_file_content:gmatch("dusk[-]time=(.-)%c")
|
|
|
|
for match in matches do
|
|
cached_redshift_dusk_time = match
|
|
return match
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
set_redshift_dusk_time = function(value)
|
|
local redshift_settings_file_path = gears.filesystem.get_xdg_config_home() .. "redshift/redshift.conf"
|
|
local redshift_settings_file_content = read_file_content(redshift_settings_file_path)
|
|
|
|
if redshift_settings_file_content == nil then
|
|
return
|
|
end
|
|
|
|
local new_redshift_settings_file_content = redshift_settings_file_content
|
|
local pattern = "dusk[-]time=(.-)%c"
|
|
|
|
if redshift_settings_file_content:match(pattern) then
|
|
local new_setting = value == nil and "" or ("dusk-time=" .. os.date("%H:%M", value) .. "\n")
|
|
new_redshift_settings_file_content = redshift_settings_file_content:gsub(pattern, new_setting)
|
|
else
|
|
local pattern = "dawn[-]time=(.-)%c"
|
|
if redshift_settings_file_content:match(pattern) then
|
|
local new_setting = "dawn-time=%1" .. "\n" .. (value == nil and "" or ("dusk-time=" .. os.date("%H:%M", value) .. "\n"))
|
|
new_redshift_settings_file_content = redshift_settings_file_content:gsub(pattern, new_setting)
|
|
else
|
|
if value ~= nil then
|
|
local pattern = "%[redshift%]%c"
|
|
local new_setting = "[redshift]" .. "\n" .. "dusk-time=" .. os.date("%H:%M", value)
|
|
new_redshift_settings_file_content = redshift_settings_file_content:gsub(pattern, new_setting)
|
|
end
|
|
end
|
|
end
|
|
|
|
write_file_content(redshift_settings_file_path, new_redshift_settings_file_content)
|
|
end
|
|
|
|
reset_redshift = function()
|
|
awful.spawn.easy_async("redshift -x", function() end)
|
|
end
|
|
|
|
restart_redshift = function()
|
|
awful.spawn.easy_async("systemctl restart redshift --user", function() end)
|
|
end
|
|
|
|
stop_redshift = function()
|
|
awful.spawn.easy_async("systemctl stop redshift --user", function() end)
|
|
end
|
|
|
|
set_color_temperature = function(color_temperature)
|
|
awful.spawn("redshift -P -O " .. color_temperature)
|
|
end
|
|
|
|
get_wired_interface = function(callback)
|
|
awful.spawn.easy_async("ip addr", function(ip_addr_stdout)
|
|
local wired_interface = ip_addr_stdout:match("%d+:%s(e%S+):")
|
|
callback(wired_interface)
|
|
end)
|
|
end
|
|
|
|
get_wireless_interface = function(callback)
|
|
awful.spawn.easy_async("ip addr", function(ip_addr_stdout)
|
|
local wireless_interface = ip_addr_stdout:match("%d+:%s(w%S+):")
|
|
callback(wireless_interface)
|
|
end)
|
|
end
|