1local degenerates = {} -- set of known visible regions 2local root = {id = 0} -- tree of current stacking order 3local idt = {} -- mapping between xids stacking tree nodes 4local native = {} -- VIDs for arcan native clients 5local pending = {} -- set of pending attributes to apply 6local paired = {} -- lookup from XID to native 7local dirty = true 8root.children = {} 9 10local input_focus 11local xarcan_client 12local native_handler 13local input_grab 14 15local function set_txcos(vid, tbl) 16 local props = image_storage_properties(vid) 17 18-- convert to surface local coordinates, windows can lie in negative 19-- in coordinate space, so shrink the rectangle and clamp the coordinates 20 local ss = 1.0 / props.width 21 local st = 1.0 / props.height 22 local bx = tbl.rel_x * ss 23 if bx < 0.0 then 24 tbl.anchor_w = tbl.anchor_w + tbl.rel_x 25 tbl.rel_x = 0 26 bx = 0.0 27 end 28 29 local by = tbl.rel_y * st 30 if by < 0.0 then 31 tbl.anchor_h = tbl.anchor_h + tbl.rel_y 32 tbl.rel_y = 0 33 by = 0.0 34 end 35 36 local bw = tbl.anchor_w * ss 37 local bh = tbl.anchor_h * st 38 39 image_set_txcos(vid, 40 { 41 bx, by, bx+bw, by, 42 bx+bw, by+bh, bx, by+bh 43 }) 44end 45 46-- return the (sub)tree in processing order (DFS) 47local function flatten(tree) 48 local tmp = {} 49 local add 50 51 add = function(node) 52 table.insert(tmp, node) 53 for _,v in ipairs(node.children) do 54 add(v) 55 end 56 end 57 58 for _,v in ipairs(tree.children) do 59 add(v) 60 end 61 62 return tmp 63end 64 65-- we reparent on viewport and restack calls 66local function add_to_stack(xid) 67 if idt[xid] then 68 return 69 end 70 71 local new = { 72 parent = root, 73 id = xid, 74 children = {} 75 } 76 77 table.insert(root.children, new) 78 idt[xid] = new 79 dirty = true 80end 81 82local function drop_from_stack(xid) 83 local src = idt[xid] 84 if not src then 85 warning("attempt to destroy unknown: " .. tostring(xid)) 86 return 87 end 88 89 idt[xid] = nil 90 91-- get rid of the visible scene graph node 92 if degenerates[xid] then 93 delete_image(degenerates[xid]) 94 degenerates[xid] = nil 95 end 96 97-- it is a native window, get rid off it 98 if paired[xid] then 99 delete_image(paired[xid]) 100 native[paired[xid]] = nil 101 paired[xid] = nil 102 end 103 104-- unlink from tree 105 local parent = src.parent 106 local pre = #parent.children 107 print("drop", src.id) 108 table.remove_match(parent.children, src) 109 local post = #parent.children 110 assert(pre ~= post, "could not remove") 111 112-- let parent adopt children, this might need to change to unlink 113 for _,v in ipairs(src.children) do 114 v.parent = parent 115 table.insert(parent.children, v) 116 end 117 dirty = true 118end 119 120local function restack(xid, parent, nextsib) 121 local src = idt[xid] 122 dirty = true 123 124 if not src then 125 warning("attempt to unknown source: " .. tostring(xid)) 126 return 127 end 128 129-- become new root 130 local ptable 131 if not parent or parent == -1 then 132 ptable = root 133 elseif not idt[parent] then 134 warning("attempt to restack to unknown parent: " .. tostring(parent)) 135 return 136 else 137 ptable = idt[parent] 138 end 139 140 local res = table.remove_match(src.parent.children, src) 141 if not res then 142 print("no match in parent") 143 for _,v in ipairs(src.parent.children) do 144 print(v.xid) 145 end 146 end 147 148 local sibindex = 1 149 150 if not nextsib or nextsib <= 0 then 151 sibindex = #ptable.children 152 153 elseif not idt[nextsib] then 154 warning("invalid next sibling sent: " .. tostring(nextsib)) 155 else 156 for k,v in ipairs(ptable.children) do 157 if v.id == nextsib then 158 sibindex = k 159 break 160 end 161 end 162 end 163 164 src.parent = ptable 165 table.insert(ptable.children, sibindex, src) 166-- local out = {} 167-- for _,v in ipairs(ptable.children) do 168-- table.insert(out, v.id) 169-- end 170-- print(table.concat(out, " -> ")) 171end 172 173local function apply_stack() 174 dirty = false 175 local lst = flatten(root) 176 177-- go through the current stack and match against pending updates 178 for i, v in ipairs(lst) do 179 local id = v.id 180 181 if not degenerates[id] and pending[id] and not pending[id].invisible then 182 local new = null_surface(32, 32) 183 if paired[id] then 184 image_sharestorage(paired[id], new) 185-- could / should apply some cropping if there is a disagreement on size 186 else 187 image_sharestorage(xarcan_client, new) 188 end 189 degenerates[id] = new 190 end 191 192-- synch changes 193 if degenerates[id] and pending[id] then 194 order_image(degenerates[id], i) --1 + pending[id].rel_order) 195 if paired[id] then 196 image_set_txcos_default(degenerates[id], false) 197 else 198 set_txcos(degenerates[id], pending[id]) 199 end 200 resize_image(degenerates[id], pending[id].anchor_w, pending[id].anchor_h) 201 move_image(degenerates[id], pending[id].rel_x, pending[id].rel_y) 202 show_image(degenerates[id]) 203 pending[id] = nil 204 end 205 end 206end 207 208local function cursor_handler(source, status) 209 print("cursor", status.kind) 210end 211 212local function clipboard_handler(source, status) 213 print("clipboard", status.kind, status.message) 214end 215 216local handler 217handler = 218function(source, status) 219 if status.kind == "terminated" then 220 delete_image(source) 221 222 xarcan_client = target_alloc("xarcan", handler) 223 for _,v in pairs(degenerates) do 224 if valid_vid(v.vid) then 225 delete_image(v.vid) 226 end 227 mouse_droplistener(v.vid) 228 end 229 degenerates = {} 230 stack = {} 231 232-- requesting initial screen properties 233 elseif status.kind == "preroll" then 234 target_displayhint(source, VRESW, VRESH) 235 236 elseif status.kind == "segment_request" then 237 if status.segkind == "cursor" then 238 accept_target(32, 32, cursor_handler) 239 240 elseif status.segkind == "clipboard" then 241 CLIPBOARD = accept_target(32, 32, clipboard_handler) 242 end 243 244 elseif status.kind == "frame" then 245 if dirty then 246 apply_stack() 247 end 248 249-- randr changed resolution 250 elseif status.kind == "resized" then 251 resize_image(source, status.width, status.height) 252 253-- remove immediately, scene-graph only contains currently visible 254 elseif status.kind == "viewport" then 255 local node = idt[status.ext_id] 256 if not node then 257 warning("viewport on unknown node: " .. tostring(status.ext_id)) 258 return 259 end 260 261 if status.invisible then 262 if degenerates[status.ext_id] then 263 print("dropping degenerate") 264 delete_image(degenerates[status.ext_id]) 265 degenerates[status.ext_id] = nil 266 end 267 else 268 dirty = true 269 pending[status.ext_id] = status 270 end 271 272 if idt[status.parent] and node.parent ~= idt[status.parent] then 273-- print("reparent", node.parent.id, status.parent) 274-- table.insert(idt[status.parent].children, node) 275-- table.remove_match(node.parent.children, node) 276-- node.parent = idt[status.parent] 277 end 278 279-- this is in the format used with ARCAN_ARG and so on as an env- packed argv 280 elseif status.kind == "message" then 281 local args = string.unpack_shmif_argstr(status.message) 282 283 if args.kind == "pair" then 284 local xid = tonumber(args.xid) 285 local vid = tonumber(args.vid) 286 if not xid or not vid then 287 warning("pair argument error, missing xid/vid") 288 return 289 end 290 if not native[vid] then 291 warning("pair error, no such vid") 292 return 293 end 294 if not idt[xid] then 295 add_to_stack(xid) 296 end 297 print("paired", xid, vid) 298 paired[xid] = vid 299 300 elseif args.kind == "restack" then 301 local xid = tonumber(args.xid) 302 local parent = tonumber(args.parent) 303 local sibling = tonumber(args.next) 304 305 print("restack", xid, parent, sibling) 306 restack(xid, parent, sibling) 307 308 elseif args.kind == "create" then 309 local id = tonumber(args.xid) 310 add_to_stack(id) 311 312 elseif args.kind == "destroy" then 313 local id = tonumber(args.xid) 314 drop_from_stack(id) 315 316 if not valid_vid(input_focus) then 317 input_focus = xarcan_client 318 end 319 end 320 end 321end 322 323function xwm(arguments) 324 symtable = system_load("builtin/keyboard.lua")() 325 system_load("builtin/string.lua")() 326 system_load("builtin/table.lua")() 327 system_load("builtin/mouse.lua")() 328 mouse_setup(fill_surface(8, 8, 0, 255, 0), 65535, 1, true, false) 329 330 if arguments[1] then 331 xarcan_client = launch_target(arguments[1], handler) 332 else 333 xarcan_client = target_alloc("xarcan", handler) 334 end 335 336 input_focus = xarcan_client 337 target_flags(xarcan_client, TARGET_VERBOSE) -- enable 'frame update' events 338 target_flags(xarcan_client, TARGET_DRAINQUEUE) 339 assert(TARGET_DRAINQUEUE > 0) 340end 341 342local function wnd_meta(wnd) 343 res = "" 344 345 if wnd.mark then 346 res = res .. " color=\"deepskyblue\"" 347 end 348 349 if degenerates[wnd.id] then 350 res = res .. " shape=\"triangle\"" 351 end 352 353 return res 354end 355 356local function dump_nodes(io, tree) 357 local id = tree.id 358 local shape = "square" 359 if degenerates[id] then 360 if paired[id] then 361 shape = "triangle" 362 else 363 shape = "circle" 364 end 365 end 366 367 io:write( 368 string.format( 369 "%.0f[label=\"%.0f\" shape=\"%s\"]\n", 370 id, id, shape 371 ) 372 ) 373 374 for _,v in ipairs(tree.children) do 375 dump_nodes(io, v) 376 end 377end 378 379local function dump_relations(io, tree) 380 local lst = {} 381 local visit 382 383 for _,v in ipairs(tree.children) do 384 io:write(string.format("%d->%d;\n", tree.id, v.id)) 385 io:write(string.format("%d->%d;\n", v.id, v.parent.id)) 386 end 387 388 for _,v in ipairs(tree.children) do 389 dump_relations(io, v) 390 end 391end 392 393local bindings = { 394 F1 = 395 function() 396 local new = target_alloc("demo", native_handler) 397 native[new] = {} 398 target_input(xarcan_client, "kind=new:x=100:y=100:w=640:h=480:id=" .. new) 399 end, 400 F2 = 401 function() 402 if valid_vid(CLIPBOARD) then 403 local io = open_nonblock(CLIPBOARD, false, "primary:utf-8") 404 xwm_clock_pulse = function() 405 local msg, ok = io:read() 406 if not ok then 407 xwn_clock_pulse = nil 408 io:close() 409 elseif msg and #msg > 0 then 410 print("read: ", msg) 411 end 412 end 413 end 414 end, 415 F3 = 416 function() 417 local new = launch_avfeed("", "terminal", native_handler) 418 native[new] = {} 419 target_input(xarcan_client, "kind=new:x=100:y=100:w=640:h=480:id=" .. new) 420 end, 421 F4 = 422 function() 423 print("creating dump.dot") 424 zap_resource("dump.dot") 425 local io = open_nonblock("dump.dot", true) 426 io:write("digraph g{\n") 427 dump_nodes(io, root) 428 dump_relations(io, root) 429 io:write("subgraph order {") 430 local lst = {} 431 for _,v in ipairs(flatten(root)) do 432 if degenerates[v.id] then 433 local ch = "o" 434 if paired[v.id] then 435 ch = "P" 436 end 437 local name = ch .. tostring(v.id) 438 io:write(string.format("%s[label=\"%s\" %s]\n", name, name, wnd_meta(v))) 439 table.insert(lst, name) 440 end 441 end 442 io:write(table.concat(lst, "->")) 443 io:write(";\n}}\n") 444 io:close() 445 end, 446 F5 = 447 function() 448 snapshot_target(xarcan_client, "xorg.dot", APPL_TEMP_RESOURCE, "dot") 449 end, 450 F6 = 451 function() 452 local x, y = mouse_xy() 453 local items = pick_items(x, y, 1, true) 454 if items[1] then 455 for k,v in pairs(degenerates) do 456 if v == items[1] then 457 print("matched xid:", k) 458 idt[k].mark = true 459 break 460 end 461 end 462 end 463 end, 464 F10 = shutdown, 465 F12 = 466 function() 467 input_grab = not input_grab 468 end 469} 470 471native_handler = 472function(source, status) 473 if status.kind == "resized" then 474-- resizes are driven by the x11 side here, so forward the information 475 local wnd = native[source] 476 if wnd and wnd.xid then 477 image_set_txcos_default(wnd.vid, status.origo_ll) 478 target_input(xarcan_client, string.format( 479 "kind=configure:w=%.0f:h=%.0f:id=%d", status.width, status.height, wnd.xid)) 480 end 481 482 elseif status.kind == "connected" then 483 484 elseif status.kind == "preroll" then 485 target_displayhint(source, 640, 480) 486 487 elseif status.kind == "terminated" then 488 end 489end 490 491function xwm_input(iotbl) 492 local sym, lutsym 493 if iotbl.translated then 494 sym, lutsym = symtable:patch(iotbl) 495 end 496 497 if iotbl.mouse then 498 mouse_iotbl_input(iotbl) 499 end 500 501 if bindings[sym] then 502 if iotbl.active then 503 bindings[sym]() 504 end 505 return 506 end 507 508-- routing, keyboard always goes to focus target, mouse will just use helper routing 509 if not valid_vid(input_focus, TYPE_FRAMESERVER) then 510 return 511 end 512 513-- If the WM is completely window controlled, this will conflict with modifiers 514-- when a native arcan client is selected as keyboard focus won't be able to 515-- 'jump' without some kind of toggle on our level. While basic keys will have 516-- the mod mask set, the 'release' event won't have the modifiers, causing ghost 517-- releases being sent to X. For this demo we use F12 as an 'arcan client' 518-- toggle. 519 if iotbl.translated then 520 if input_focus ~= xarcan_client and input_grab then 521 target_input(input_focus, iotbl) 522 else 523 target_input(xarcan_client, iotbl) 524 end 525 else 526 target_input(xarcan_client, iotbl) 527 end 528end 529