263 lines
7.7 KiB
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
|