dotfiles/.notion/cfg_xrandr.lua

263 lines
7.7 KiB
Lua

-- For honest workspaces, the initial outputs information, which determines the
-- physical screen that a workspace wants to be on, is part of the C class
-- WGroupWS. For "full screen workspaces" and scratchpads, we only keep this
-- information in a temporary list.
InitialOutputs={}
function getInitialOutputs(ws)
if obj_is(ws, "WGroupCW") or is_scratchpad(ws) then
return InitialOutputs[ws:name()]
elseif obj_is(ws, "WGroupWS") then
return WGroupWS.get_initial_outputs(ws)
else
return nil
end
end
function setInitialOutputs(ws, outputs)
if obj_is(ws, "WGroupCW") or is_scratchpad(ws) then
InitialOutputs[ws:name()] = outputs
elseif obj_is(ws, "WGroupWS") then
WGroupWS.set_initial_outputs(ws, outputs)
end
end
function nilOrEmpty(t)
return not t or empty(t)
end
function mod_xrandr.workspace_added(ws)
if nilOrEmpty(getInitialOutputs(ws)) then
outputs = mod_xrandr.get_outputs(ws:screen_of(ws))
outputKeys = {}
for k,v in pairs(outputs) do
table.insert(outputKeys, k)
end
setInitialOutputs(ws, outputKeys)
end
return true
end
function for_all_workspaces_do(fn)
local workspaces={}
notioncore.region_i(function(scr)
scr:managed_i(function(ws)
table.insert(workspaces, ws)
return true
end)
return true
end, "WScreen")
for _,ws in ipairs(workspaces) do
fn(ws)
end
end
function mod_xrandr.workspaces_added()
for_all_workspaces_do(mod_xrandr.workspace_added)
end
function mod_xrandr.screenmanagedchanged(tab)
if tab.mode == 'add' then
mod_xrandr.workspace_added(tab.sub);
end
end
screen_managed_changed_hook = notioncore.get_hook('screen_managed_changed_hook')
if screen_managed_changed_hook then
screen_managed_changed_hook:add(mod_xrandr.screenmanagedchanged)
end
post_layout_setup_hook = notioncore.get_hook('ioncore_post_layout_setup_hook')
post_layout_setup_hook:add(mod_xrandr.workspaces_added)
function add_safe(t, key, value)
if t[key] == nil then
t[key] = {}
end
table.insert(t[key], value)
end
-- parameter: list of output names
-- returns: map from screen name to screen
function candidate_screens_for_output(max_screen_id, all_outputs, outputname)
local retval = {}
function addIfContainsOutput(screen)
local outputs_within_screen = mod_xrandr.get_outputs_within(all_outputs, screen)
if screen:id() <= max_screen_id and outputs_within_screen[outputname] ~= nil then
retval[screen:name()] = screen
end
return true
end
notioncore.region_i(addIfContainsOutput, "WScreen")
return retval
end
-- parameter: maximum screen id, list of all output names, list of output names for which we want the screens
-- returns: map from screen name to screen
function candidate_screens_for_outputs(max_screen_id, all_outputs, outputnames)
local result = {}
if outputnames == nil then return result end
for i,outputname in pairs(outputnames) do
local screens = candidate_screens_for_output(max_screen_id, all_outputs, outputname)
for k,screen in pairs(screens) do
result[k] = screen;
end
end
return result;
end
function firstValue(t)
local key, value = next(t)
return value
end
function firstKey(t)
local key, value = next(t)
return key
end
function empty(t)
return not next(t)
end
function singleton(t)
local first = next(t)
return first and not next(t, first)
end
function is_scratchpad(ws)
return package.loaded["mod_sp"] and mod_sp.is_scratchpad(ws)
end
function find_scratchpad(screen)
local sp
screen:managed_i(function(ws)
if is_scratchpad(ws) then
sp=ws
return false
else
return true
end
end)
return sp
end
function move_if_needed(workspace, screen_id)
local screen = notioncore.find_screen_id(screen_id)
if workspace:screen_of() ~= screen then
if is_scratchpad(workspace) then
-- Moving a scratchpad to another screen is not meaningful, so instead we move
-- its content
local content={}
workspace:bottom():managed_i(function(reg)
table.insert(content, reg)
return true
end)
local sp=find_scratchpad(screen)
for _,reg in ipairs(content) do
sp:bottom():attach(reg)
end
return
end
screen:attach(workspace)
end
end
-- Arrange the workspaces over the first number_of_screens screens
function mod_xrandr.rearrangeworkspaces(max_screen_id)
-- for each screen id, which workspaces should be on that screen
new_mapping = {}
-- workspaces that want to be on an output that's currently not on any screen
orphans = {}
-- workspaces that want to be on multiple available outputs
wanderers = {}
local all_outputs = mod_xrandr.get_all_outputs()
-- When moving a "full screen workspace" to another screen, we seem to lose
-- its placeholder and thereby the possibility to return it from full
-- screen later. Let's therefore try to close any full screen workspace
-- before rearranging.
full_screen_workspaces={}
for_all_workspaces_do(function(ws)
if obj_is(ws, "WGroupCW") then table.insert(full_screen_workspaces, ws)
end
return true
end)
for _,ws in ipairs(full_screen_workspaces) do
ws:set_fullscreen("false")
end
-- round one: divide workspaces in directly assignable,
-- orphans and wanderers
function roundone(workspace)
local screens = candidate_screens_for_outputs(max_screen_id, all_outputs, getInitialOutputs(workspace))
if nilOrEmpty(screens) then
table.insert(orphans, workspace)
elseif singleton(screens) then
add_safe(new_mapping, firstValue(screens):id(), workspace)
else
wanderers[workspace] = screens
end
return true
end
for_all_workspaces_do(roundone)
for workspace,screens in pairs(wanderers) do
-- TODO add to screen with least # of workspaces instead of just the
-- first one that applies
if screens[workspace:screen_of():name()] then
add_safe(new_mapping, workspace:screen_of():id(), workspace)
else
add_safe(new_mapping, firstValue(screens):id(), workspace)
end
end
for i,workspace in pairs(orphans) do
-- TODO add to screen with least # of workspaces instead of just the first one
add_safe(new_mapping, 0, workspace)
end
for screen_id,workspaces in pairs(new_mapping) do
-- move workspace to that
for i,workspace in pairs(workspaces) do
move_if_needed(workspace, screen_id)
end
end
end
-- refresh xinerama and rearrange workspaces on screen layout updates
function mod_xrandr.screenlayoutupdated()
notioncore.profiling_start('notion_xrandrrefresh.prof')
local screens = mod_xinerama.query_screens()
if screens then
local merged_screens = mod_xinerama.merge_overlapping_screens(screens)
mod_xinerama.setup_screens(merged_screens)
end
local max_screen_id = mod_xinerama.find_max_screen_id(screens);
mod_xrandr.rearrangeworkspaces(max_screen_id)
if screens then
mod_xinerama.close_invisible_screens(max_screen_id)
end
mod_xinerama.populate_empty_screens()
notioncore.screens_updated(notioncore.rootwin())
notioncore.profiling_stop()
end
randr_screen_change_notify_hook = notioncore.get_hook('randr_screen_change_notify')
if randr_screen_change_notify_hook then
randr_screen_change_notify_hook:add(mod_xrandr.screenlayoutupdated)
end