Files
crylia-theme/awesome/src/modules/crylia_bar/dock.lua
2023-04-20 01:04:06 +02:00

597 lines
16 KiB
Lua

local setmetatable = setmetatable
local table = table
local ipairs = ipairs
local pairs = pairs
-- Awesome Libs
local abutton = require('awful.button')
local aplacement = require('awful.placement')
local apopup = require('awful.popup')
local awidget = require('awful.widget')
local beautiful = require('beautiful')
local dpi = require('beautiful').xresources.apply_dpi
local gcolor = require('gears.color')
local gfilesystem = require('gears.filesystem')
local gtable = require('gears.table')
local gtimer = require('gears.timer')
local lgi = require('lgi')
local Gio = lgi.Gio
local wibox = require('wibox')
-- Local libs
local config = require('src.tools.config')
local context_menu = require('src.modules.context_menu')
local hover = require('src.tools.hover')
local icon_lookup = require('src.tools.gio_icon_lookup')()
local capi = {
awesome = awesome,
client = client,
mouse = mouse,
screen = screen,
}
local icondir = gfilesystem.get_configuration_dir() .. 'src/assets/icons/'
local elements = {
['pinned'] = {},
['applauncher_starter'] = {},
['running'] = {},
}
local instances = {}
local dock = { mt = {} }
--[[ Json format of dock.json, running apps won't be saved
[
{
"applauncher_starter": {
{
"name": "Application Launcher",
"icon": "/usr/share/icons/Papirus/48x48/apps/xfce4-appfinder.svg",
}
},
"pinned": [
{
"name": "firefox",
"icon": "/usr/share/icons/Papirus/48x48/apps/firefox.svg",
"exec": "firefox",
"desktop_file": "/usr/share/applications/firefox.desktop"
},
{
"name": "discord",
"icon": "/usr/share/icons/Papirus/48x48/apps/discord.svg",
"exec": "discord",
"desktop_file": "/usr/share/applications/discord.desktop"
}
],
}
]
]]
function dock:toggle()
self.popup.visible = not self.popup.visible
end
function dock:write_elements_to_file_async(callback)
--create a local copy of the elements["pinned"] table and only set the desktop_file key from its children
local elements_copy = { pinned = {} }
for _, element in ipairs(elements['pinned']) do
table.insert(elements_copy['pinned'], { desktop_file = element.desktop_file })
end
config.write_json(gfilesystem.get_configuration_dir() .. 'src/config/dock_' .. self.screen.index .. '.json', elements_copy['pinned'], callback)
end
---Read the content of dock.json and get the content as a table
---@param callback function Called after the elements have been set, no values are passed
function dock:read_elements_from_file_async(callback)
local data = config.read_json(gfilesystem.get_configuration_dir() .. 'src/config/dock_' .. self.screen.index .. '.json')
-- Make sure to not set the running key to nil on accident
for _, v in ipairs(data) do
local w = self:get_element_widget(v.desktop_file)
table.insert(elements['pinned'], w)
end
if callback then
callback()
end
end
---Creates a pinned widget for the dock and adds it into the elements table
---@param desktop_file string .desktop file path
---@return nil
function dock:get_element_widget(desktop_file)
if not desktop_file then return end
local GDesktopAppInfo = Gio.DesktopAppInfo.new_from_filename(desktop_file)
local icon = icon_lookup:get_gicon_path(nil, GDesktopAppInfo.get_string(GDesktopAppInfo, 'Icon')) or
icon_lookup:get_gicon_path(nil, Gio.DesktopAppInfo.get_string(GDesktopAppInfo, 'X-AppImage-Old-Icon')) or ''
local widget = wibox.widget {
{
{
{
{
widget = wibox.widget.imagebox,
image = icon,
valign = 'center',
halign = 'center',
resize = true,
id = 'icon_role',
},
widget = wibox.container.constraint,
width = beautiful.user_config.dock_icon_size,
height = beautiful.user_config.dock_icon_size,
},
widget = wibox.container.margin,
margins = dpi(5),
},
widget = wibox.container.constraint,
width = beautiful.user_config.dock_icon_size + dpi(10), -- + margins
height = beautiful.user_config.dock_icon_size + dpi(10),
strategy = 'exact',
},
bg = beautiful.colorscheme.bg1,
shape = beautiful.shape[8],
widget = wibox.container.background,
}
local action_entries = {}
for _, action in ipairs(Gio.DesktopAppInfo.list_actions(GDesktopAppInfo)) do
table.insert(action_entries, {
name = Gio.DesktopAppInfo.get_action_name(GDesktopAppInfo, action) or '',
icon = icon_lookup:get_gicon_path(nil, GDesktopAppInfo.get_string(GDesktopAppInfo, 'Icon')) or
icon_lookup:get_gicon_path(nil, Gio.DesktopAppInfo.get_string(GDesktopAppInfo, 'X-AppImage-Old-Icon')) or
gcolor.recolor_image(icondir .. 'entry.svg', beautiful.colorscheme.bg_yellow),
callback = function()
Gio.DesktopAppInfo.launch_action(GDesktopAppInfo, action)
end,
})
end
table.insert(action_entries, {
name = 'Remove from Dock',
icon = gcolor.recolor_image(icondir .. 'context_menu/entry.svg', beautiful.colorscheme.bg_yellow),
callback = function()
local data = config.read_json(gfilesystem.get_configuration_dir() .. 'src/config/dock_' .. self.screen.index .. '.json')
for i, v in ipairs(data) do
if v.desktop_file == desktop_file then
if type(data) == 'table' then
table.remove(data, i)
end
break
end
end
config.write_json(gfilesystem.get_configuration_dir() .. 'src/config/dock_' .. self.screen.index .. '.json', data)
self:remove_element_widget(widget)
end,
})
widget.cm = context_menu {
widget_template = wibox.widget {
{
{
{
{
widget = wibox.widget.imagebox,
resize = true,
valign = 'center',
halign = 'center',
id = 'icon_role',
},
widget = wibox.container.constraint,
stragety = 'exact',
width = dpi(24),
height = dpi(24),
id = 'const',
},
{
widget = wibox.widget.textbox,
valign = 'center',
halign = 'left',
id = 'text_role',
},
layout = wibox.layout.fixed.horizontal,
},
widget = wibox.container.margin,
},
widget = wibox.container.background,
}, spacing = dpi(10),
entries = action_entries,
}
widget.cm:connect_signal('mouse::leave', function()
widget.cm.visible = false
self.cm_open = widget.cm.visible
end)
hover.bg_hover {
widget = widget,
overlay = 12,
press_overlay = 24,
}
local exec = Gio.DesktopAppInfo.get_string(GDesktopAppInfo, 'Exec')
widget:buttons(gtable.join {
abutton({}, 1, function()
Gio.AppInfo.launch_uris_async(Gio.AppInfo.create_from_commandline(exec, nil, 0))
end),
abutton({}, 3, function()
widget.cm:toggle()
self.cm_open = widget.cm.visible
end),
})
table.insert(elements['pinned'], widget)
self:emit_signal('dock::element_added', widget)
end
---Removes a given widget from the dock
---@param widget wibox.widget The widget to remove
function dock:remove_element_widget(widget)
if not widget then return end
for k, v in pairs(elements['pinned']) do
if v == widget then
table.remove(elements['pinned'], k)
self:emit_signal('dock::element_removed', widget)
return
end
end
end
---Pins an element to the dock by adding it to the pinned table, then writes the table to the file
---emits the signal `dock::pin_element` then successfully added to the table
---@param args {desktop_file: string} The path to the .desktop file
function dock:pin_element(args)
if not args then return end
local e = args.desktop_file
self:emit_signal('dock::pin_element', e)
self:write_elements_to_file_async()
end
function dock:add_start_element()
local widget = wibox.widget {
{
{
{
{
widget = wibox.widget.imagebox,
image = gfilesystem.get_configuration_dir() .. 'src/assets/CT.svg',
valign = 'center',
halign = 'center',
resize = true,
id = 'icon_role',
},
widget = wibox.container.constraint,
width = beautiful.user_config.dock_icon_size,
height = beautiful.user_config.dock_icon_size,
},
widget = wibox.container.margin,
margins = dpi(5),
},
widget = wibox.container.constraint,
width = beautiful.user_config.dock_icon_size + dpi(10), -- + margins
height = beautiful.user_config.dock_icon_size + dpi(10),
strategy = 'exact',
},
bg = beautiful.colorscheme.bg1,
shape = beautiful.shape[8],
widget = wibox.container.background,
}
hover.bg_hover {
widget = widget,
overlay = 12,
press_overlay = 24,
}
widget:buttons(gtable.join {
abutton({}, 1, function()
capi.awesome.emit_signal('application_launcher::show')
end),
})
return widget
end
function dock:unpin_element(args)
if not args then return end
for index, value in ipairs(elements['pinned']) do
if value == args.desktop_file then
table.remove(elements['pinned'], index)
break;
end
end
self:emit_signal('dock::unpin_element', args.desktop_file)
self:write_elements_to_file_async()
end
function dock:get_all_elements()
return elements
end
function dock:get_applauncher_starter_element()
return elements['applauncher_starter']
end
function dock:get_pinned_elements()
return elements['pinned']
end
function dock:get_running_elements()
return elements['running']
end
function dock:get_dock_for_screen(screen)
return instances[screen]
end
local function check_for_dock_hide(self, a_popup)
if self.cm_open then return end
local clients_on_tag = self.screen.selected_tag:clients()
-- If there is no client on the current tag show the dock
if #clients_on_tag < 1 then
self.visible = true
return
end
-- If there is a maximized client hide the dock and if fullscreened hide the activation area
for _, client in ipairs(clients_on_tag) do
if client.maximized or client.fullscreen then
dock.visible = false
if client.fullscreen then
a_popup.visible = false
capi.awesome.emit_signal('notification_center_activation::toggle', self.screen, false)
end
elseif not client.fullscreen then
a_popup.visible = true
capi.awesome.emit_signal('notification_center_activation::toggle', self.screen, true)
end
end
if self.screen == capi.mouse.screen then
local minimized = false
for _, c in ipairs(clients_on_tag) do
if c.minimized then
minimized = true
else
minimized = false
local y = c:geometry().y
local h = c.height
if (y + h) >= self.screen.geometry.height - beautiful.user_config.dock_icon_size - 35 then
self.visible = false
return
else
self.visible = true
end
end
end
if minimized then
self.visible = true
end
else
self.visible = false
end
end
function dock:activation_area()
local activation = apopup {
widget = {
width = self.screen.geometry.width / 4,
height = 1,
strategy = 'exact',
widget = wibox.container.constraint,
},
ontop = true,
bg = gcolor.transparent,
visible = false,
screen = self.screen,
type = 'dock',
placement = function(c) aplacement.bottom(c) end,
}
-- Call the function every second to check if the dock needs to be hidden
local dock_hide = gtimer {
timeout = 1,
autostart = true,
call_now = true,
callback = function()
check_for_dock_hide(self, activation)
end,
}
activation:connect_signal('mouse::enter', function()
if #self.screen.clients < 1 then
self.visible = true
dock_hide:stop()
return
end
for _, c in ipairs(self.screen.clients) do
if not c.fullscreen then
self.visible = true
dock_hide:stop()
end
end
end)
self:connect_signal('mouse::enter', function()
dock_hide:stop()
end)
self:connect_signal('mouse::leave', function()
--[[ if cm_open then
return
end ]]
check_for_dock_hide(self, activation)
dock_hide:again()
end)
end
function dock.new(args)
args = args or {}
local w = apopup {
widget = {
{
{
spacing = dpi(5),
id = 'applauncher_starter',
layout = wibox.layout.fixed.horizontal,
},
wibox.widget.separator {
forced_width = dpi(2),
forced_height = dpi(20),
thickness = dpi(2),
color = beautiful.colorscheme.border_color,
},
{
spacing = dpi(5),
id = 'pinned',
layout = wibox.layout.fixed.horizontal,
},
{
id = 'running',
spacing = dpi(5),
layout = wibox.layout.fixed.horizontal,
},
spacing = dpi(10),
id = 'elements',
layout = wibox.layout.fixed.horizontal,
},
widget = wibox.container.margin,
margins = dpi(5),
},
ontop = true,
visible = true,
placement = function(c) aplacement.bottom(c, { margins = dpi(10) }) end,
bg = beautiful.colorscheme.bg,
screen = args.screen,
}
gtable.crush(w, dock)
instances[args.screen] = w
w:activation_area()
w.task_list = awidget.tasklist {
screen = args.screen,
layout = wibox.layout.fixed.horizontal,
filter = awidget.tasklist.filter.alltags,
update_function = function(widget, _, _, _, clients)
widget:reset()
if #clients == 0 then
return widget
end
widget:add {
{
widget = wibox.widget.separator,
forced_height = dpi(20),
forced_width = dpi(2),
thickness = dpi(2),
color = beautiful.colorscheme.border_color,
},
widget = wibox.container.margin,
right = dpi(5),
}
for _, client in ipairs(clients) do
local element = wibox.widget {
{
{
{
{
widget = wibox.widget.imagebox,
image = client.icon,
valign = 'center',
halign = 'center',
resize = true,
id = 'icon_role',
},
widget = wibox.container.constraint,
width = beautiful.user_config.dock_icon_size,
height = beautiful.user_config.dock_icon_size,
},
widget = wibox.container.margin,
margins = dpi(5),
},
widget = wibox.container.constraint,
width = beautiful.user_config.dock_icon_size + dpi(10), -- + margins
height = beautiful.user_config.dock_icon_size + dpi(10),
strategy = 'exact',
},
bg = beautiful.colorscheme.bg1,
shape = beautiful.shape[8],
widget = wibox.container.background,
}
hover.bg_hover {
widget = element,
overlay = 12,
press_overlay = 24,
}
element:buttons(gtable.join(
abutton({}, 1, function()
if client == client.focus then
client.minimized = true
else
if client.first_tag then
client.first_tag:view_only()
end
client:emit_signal('request::activate')
client:raise()
end
end),
abutton({}, 3, function()
--TODO: Add context menu with options
end)
))
widget:add(element)
widget:set_spacing(dpi(5))
end
return widget
end,
}
w.widget.elements.applauncher_starter:add(w:add_start_element())
w.widget.elements.running:add(w.task_list)
w:connect_signal('dock::element_added', function(_, widget)
w.widget.elements.pinned:add(widget)
end)
w:connect_signal('dock::element_removed', function(_, widget)
w.widget.elements.pinned:remove_widgets(widget)
end)
w:connect_signal('dock::pin_element', function(_, element)
w:get_element_widget(element)
end)
capi.awesome.connect_signal('dock::pin_element', function(args)
w:pin_element(args)
end)
w:connect_signal('dock::unpin_element', function(_, widget)
w:remove_element_widget(widget)
end)
w:read_elements_from_file_async()
return w
end
return setmetatable(dock, { __call = function(_, ...) return dock.new(...) end })