-- 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