1 -- Notion xinerama module - lua setup
3 -- by Tomas Ebenlendr <ebik@ucw.cz>
5 -- This library is free software; you can redistribute it and/or
6 -- modify it under the terms of the GNU Lesser General Public
7 -- License as published by the Free Software Foundation; either
8 -- version 2.1 of the License,or (at your option) any later version.
10 -- This library is distributed in the hope that it will be useful,
11 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
12 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 -- Lesser General Public License for more details.
15 -- You should have received a copy of the GNU Lesser General Public
16 -- License along with this library; if not,write to the Free Software
17 -- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 -- This is a slight abuse of the package.loaded variable perhaps, but
21 -- library-like packages should handle checking if they're loaded instead of
22 -- confusing the user with require/include differences.
23 if package
.loaded
["mod_xinerama"] then return end
25 if not notioncore
.load_module("mod_xinerama") then
29 local mod_xinerama
=_G
["mod_xinerama"]
34 -- Helper functions {{{
36 local function max(one
, other
)
37 if one
== nil then return other
end
38 if other
== nil then return one
end
40 return (one
> other
) and one
or other
43 -- creates new table, converts {x,y,w,h} representation to {x,y,xmax,ymax}
44 local function to_max_representation(screen
)
48 xmax
= screen
.x
+ screen
.w
,
49 ymax
= screen
.y
+ screen
.h
53 -- edits passed table, converts representation {x,y,xmax,ymax} to {x,y,w,h},
54 -- and sorts table of indices (entry screen.ids)
55 local function fix_representation(screen
)
56 screen
.w
= screen
.xmax
- screen
.x
57 screen
.h
= screen
.ymax
- screen
.y
60 table.sort(screen
.ids
)
63 local function fix_representations(screens
)
64 for _k
, screen
in pairs(screens
) do
65 fix_representation(screen
)
71 -- Contained screens {{{
73 -- true if [from1, to1] contains [from2, to2]
74 local function contains(from1
, to1
, from2
, to2
)
75 return (from1
<= from2
) and (to1
>= to2
)
78 -- true if scr1 contains scr2
79 local function screen_contains(scr1
, scr2
)
80 local x_in
= contains(scr1
.x
, scr1
.xmax
, scr2
.x
, scr2
.xmax
)
81 local y_in
= contains(scr1
.y
, scr1
.ymax
, scr2
.y
, scr2
.ymax
)
86 -- Filters out fully contained screens. I.e. it merges two screens
87 -- if one is fully contained in the other screen (this contains the
88 -- case that both screens are of the same geometry).
89 -- The output screens also contain field ids containing the numbers
90 -- of merged screens. The order of the screens is defined by the
91 -- first screen in the merged set. (I.e., having big B, and big C,
92 -- showing different parts of desktop and small A (primary) showing
93 -- part of C, then the order will be C,B and not B,C as someone
96 -- Example input format: \{\{x=0,y=0,w=1024,h=768\},\{x=0,y=0,w=1280,h=1024\}\}
97 function mod_xinerama
.merge_contained_screens(screens
)
99 for newnum
, _newscreen
in ipairs(screens
) do
100 newscreen
= to_max_representation(_newscreen
)
102 for prevnum
, prevscreen
in pairs(ret
) do
103 if screen_contains(prevscreen
, newscreen
) then
104 table.insert(prevscreen
.ids
,newnum
)
106 elseif screen_contains(newscreen
, prevscreen
) then
107 prevscreen
.x
= newscreen
.x
108 prevscreen
.y
= newscreen
.y
109 prevscreen
.xmax
= newscreen
.xmax
110 prevscreen
.ymax
= newscreen
.ymax
111 table.insert(prevscreen
.ids
,newnum
)
114 if merged
then break end
117 newscreen
.ids
= { newnum
}
118 table.insert(ret
, newscreen
)
121 fix_representations(ret
)
127 --- {{{ Overlapping screens
129 -- true if [from1, to1] overlaps [from2, to2]
130 local function overlaps (from1
, to1
, from2
, to2
)
131 return (from1
< to2
) and (from2
< to1
)
134 -- true if scr1 overlaps scr2
135 local function screen_overlaps(scr1
, scr2
)
136 local x_in
= overlaps(scr1
.x
, scr1
.xmax
, scr2
.x
, scr2
.xmax
)
137 local y_in
= overlaps(scr1
.y
, scr1
.ymax
, scr2
.y
, scr2
.ymax
)
142 -- Merges overlapping screens. I.e. it finds set of smallest rectangles,
143 -- such that these rectangles do not overlap and such that they contain
146 -- Example input format: \{\{x=0,y=0,w=1024,h=768\},\{x=0,y=0,w=1280,h=1024\}\}
147 function mod_xinerama
.merge_overlapping_screens(screens
)
149 for _newnum
, _newscreen
in ipairs(screens
) do
150 local newscreen
= to_max_representation(_newscreen
)
151 newscreen
.ids
= { _newnum
}
152 local overlaps
= true
156 for prevpos
, prevscreen
in pairs(ret
) do
157 if screen_overlaps(prevscreen
, newscreen
) then
158 -- stabilise ordering
159 if (not pos
) or (prevpos
< pos
) then pos
= prevpos
end
160 -- merge with the previous screen
161 newscreen
.x
= math
.min(newscreen
.x
, prevscreen
.x
)
162 newscreen
.y
= math
.min(newscreen
.y
, prevscreen
.y
)
163 newscreen
.xmax
= math
.max(newscreen
.xmax
, prevscreen
.xmax
)
164 newscreen
.ymax
= math
.max(newscreen
.ymax
, prevscreen
.ymax
)
166 for _k
, _v
in ipairs(prevscreen
.ids
) do
167 table.insert(newscreen
.ids
, _v
)
170 -- delete the merged previous screen
171 table.remove(ret
, prevpos
)
173 -- restart from beginning
179 if not pos
then pos
= table.maxn(ret
)+1 end
180 table.insert(ret
, pos
, newscreen
)
182 fix_representations(ret
)
187 -- Merges overlapping screens. I.e. it merges two screens
188 -- if they overlap. It merges two screens if and only if there
189 -- is a path between them using only overlapping screens.
190 -- one is fully contained in the other screen (this contains the
191 -- case that both screens are of the same geometry).
192 -- The output screens also contain field ids containing the numbers
193 -- of merged screens. The order of the screens is defined by the
194 -- first screen in the merged set. (I.e., having big B, and big C,
195 -- showing different parts of desktop and small A (primary) showing
196 -- part of C, then the order will be C,B and not B,C as someone
199 -- This function may output overlapping regions, AB and C on the example:
209 -- Notion's WScreen implementation will (partially) hide C when AB is focused.
210 -- Thus this algorithm is not what you want by default.
212 -- Example input format: \{\{x=0,y=0,w=1024,h=768\},\{x=0,y=0,w=1280,h=1024\}\}
213 -- See test_xinerama.lua for example input/output
214 function mod_xinerama
.merge_overlapping_screens_alternative(screens
)
215 -- Group overlapping screens into sets for merging.
226 -- Our algorithm merges A with B, but it does not
227 -- merge C to 'AB'. This is due to we only identify
228 -- overlapping screen sets in first phase.
232 -- | A +-+-----+-+ B |
236 -- Our algoritm merges all three screens even if
237 -- it first decides that A and B does not overlap,
238 -- and then takes screen C.
240 local screensets
= {}
241 for _newnum
, _newscreen
in ipairs(screens
) do
242 newscreen
= to_max_representation(_newscreen
)
243 newscreen
.id
= _newnum
245 --Find all screensets to merge with:
246 --if there is a screen in a screenset that overlaps
247 --with current screen, then we mark the set in 'mergekeys'
249 -- We use ipairs here, because we rely on the order.
250 for setkey
, screenset
in ipairs(screensets
) do
251 -- find any screen of 'screenset' that overlaps 'newscreen'
252 for _k
, prevscreen
in pairs(screenset
) do
253 if screen_overlaps(newscreen
, prevscreen
) then
254 -- Found. 'setkey' contains indices of overlapping
255 -- 'screenset's, sorted decreasingly
256 table.insert(mergekeys
, 1, setkey
)
262 -- Here we merge all marked screensets to one new screenset.
263 -- We also delete the merged screensets from the 'screensets' table.
264 local mergedset
= {newscreen
}
266 -- we use ipairs here, because we rely on the order.
267 for _k
, setkey
in ipairs(mergekeys
) do
268 -- copy contents of 'screensets[setkey]' to 'mergedset'
269 for _k2
, prevscreen
in pairs(screensets
[setkey
]) do
270 table.insert(mergedset
, prevscreen
)
272 -- remove 'screensets[setkey]'
273 table.remove(screensets
, setkey
)
277 -- pos keeps index of first set that we merged in this loop,
278 -- we want to insert the product of this merge to pos.
279 if not pos
then pos
= table.maxn(screensets
)+1 end
280 table.insert(screensets
, pos
, mergedset
)
283 -- Now we have the screenset that contains the screens to be merged
285 for _k
, screenset
in ipairs(screensets
) do
288 xmax
= screenset
[1].xmax
,
290 ymax
= screenset
[1].ymax
,
293 for _k2
, screen
in pairs(screenset
) do
294 newscreen
.x
= math
.min(newscreen
.x
, screen
.x
)
295 newscreen
.y
= math
.min(newscreen
.y
, screen
.y
)
296 newscreen
.xmax
= math
.max(newscreen
.xmax
, screen
.xmax
)
297 newscreen
.ymax
= math
.max(newscreen
.ymax
, screen
.ymax
)
298 table.insert(newscreen
.ids
, screen
.id
)
300 table.insert(ret
, newscreen
)
302 fix_representations(ret
)
309 --- {{{ Setup notion's screens */
311 function mod_xinerama
.close_invisible_screens(max_visible_screen_id
)
312 local invisible_screen_id
= max_visible_screen_id
+ 1
313 local invisible_screen
= notioncore
.find_screen_id(invisible_screen_id
)
314 while invisible_screen
do
315 -- note that this may not close the screen when it is still populated by
316 -- child windows that cannot be 'rescued'
317 invisible_screen
:rqclose();
319 invisible_screen_id
= invisible_screen_id
+ 1
320 invisible_screen
= notioncore
.find_screen_id(invisible_screen_id
)
325 -- find any screens with 0 workspaces and populate them with an empty one
326 function mod_xinerama
.populate_empty_screens()
328 local screen
= notioncore
.find_screen_id(screen_id
)
329 while (screen
~= nil) do
330 if screen
:mx_count() == 0 then
331 notioncore
.create_ws(screen
)
334 screen_id
= screen_id
+ 1
335 screen
= notioncore
.find_screen_id(screen_id
)
339 -- This should be made 'smarter', but at least let's make sure workspaces don't
340 -- end up on invisible screens
341 function mod_xinerama
.rearrange_workspaces(max_visible_screen_id
)
342 function move_to_first_screen(workspace
)
343 notioncore
.find_screen_id(0):attach(workspace
)
346 function rearrange_workspaces_s(screen
)
347 if screen
:id() > max_visible_screen_id
then
348 for i
= 0, screen
:mx_count() do
349 move_to_first_screen(screen
:mx_nth(i
))
355 local screen
= notioncore
.find_screen_id(screen_id
)
356 while (screen
~= nil) do
357 rearrange_workspaces_s(screen
);
359 screen_id
= screen_id
+ 1
360 screen
= notioncore
.find_screen_id(screen_id
)
362 mod_xinerama
.populate_empty_screens()
365 function mod_xinerama
.find_max_screen_id(screens
)
366 local max_screen_id
= 0
368 for screen_index
, screen
in ipairs(screens
) do
369 local screen_id
= screen_index
- 1
370 max_screen_id
= max(max_screen_id
, screen_id
)
373 return max_screen_id
;
377 -- Perform the setup of notion screens.
379 -- The first call sets up the screens of notion, subsequent calls update the
382 -- Returns true on success, false on failure
384 -- Example input: {{x=0,y=0,w=1024,h=768},{x=1024,y=0,w=1280,h=1024}}
385 function mod_xinerama
.setup_screens(screens
)
386 -- Update screen dimensions or create new screens
387 for screen_index
, screen
in ipairs(screens
) do
388 local screen_id
= screen_index
- 1
389 local existing_screen
= notioncore
.find_screen_id(screen_id
)
391 if existing_screen
~= nil then
392 mod_xinerama
.update_screen(existing_screen
, screen
)
394 mod_xinerama
.setup_new_screen(screen_id
, screen
)
395 if package
.loaded
["mod_sp"] then
396 mod_sp
.create_scratchpad(notioncore
.find_screen_id(screen_id
))
404 -- Mark ourselves loaded.
405 package
.loaded
["mod_xinerama"]=true
407 -- Load configuration file
408 dopath('cfg_xinerama', true)
411 -- Queries Xinerama for the screen dimensions and updates notion screens
413 function mod_xinerama
.refresh()
414 local screens
= mod_xinerama
.query_screens()
416 local merged_screens
= mod_xinerama
.merge_overlapping_screens(screens
)
417 mod_xinerama
.setup_screens(merged_screens
)
419 -- when the number of screens is lower than last time this function was
420 -- called, ask 'superfluous' to close
421 mod_xinerama
.close_invisible_screens(mod_xinerama
.find_max_screen_id(screens
))
423 notioncore
.screens_updated(notioncore
.rootwin());
426 -- At this point any workspaces from a saved session haven't been added yet
427 mod_xinerama
.refresh()