diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/edit.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/edit.lua index bb988aa84..9a5becea9 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/edit.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/edit.lua @@ -430,7 +430,7 @@ local function find() setStatus("Find: " .. findText) local _, address, char, code = term.pull("key_down") - if address == term.keyboard().address then + if address == term.keyboard() then local handler, name = getKeyBindHandler(code) highlight(cbx, cby, unicode.wlen(findText), false) if name == "newline" then @@ -557,7 +557,7 @@ getKeyBindHandler = function(code) elseif value == "shift" then shift = true else key = value end end - local keyboardAddress = term.keyboard().address + local keyboardAddress = term.keyboard() if (not alt or keyboard.isAltDown(keyboardAddress)) and (not control or keyboard.isControlDown(keyboardAddress)) and (not shift or keyboard.isShiftDown(keyboardAddress)) and @@ -656,7 +656,7 @@ end while running do local event, address, arg1, arg2, arg3 = term.pull() - if address == term.keyboard().address or address == term.screen().address then + if address == term.keyboard() or address == term.screen() then local blink = true if event == "key_down" then onKeyDown(arg1, arg2) diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/04_component.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/04_component.lua index a961a9aa5..8d620e79f 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/04_component.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/04_component.lua @@ -101,6 +101,7 @@ function component.setPrimary(componentType, address) if wasAvailable or wasAdding then adding[componentType] = { address=address, + proxy = primary, timer=event.timer(0.1, function() adding[componentType] = nil primaries[componentType] = primary @@ -116,14 +117,55 @@ end ------------------------------------------------------------------------------- -for address in component.list('screen', true) do - if #component.invoke(address,'getKeyboards') > 0 then - component.setPrimary('screen',address) - end -end - local function onComponentAdded(_, address, componentType) - if not (primaries[componentType] or adding[componentType]) then + local prev = primaries[componentType] or (adding[componentType] and adding[componentType].proxy) + + if prev then + -- special handlers -- some components are just better at being primary + if componentType == "screen" then + --the primary has no keyboards but we do + if #prev.getKeyboards() == 0 then + local first_kb = component.invoke(address, 'getKeyboards')[1] + if first_kb then + -- just in case our kb failed to achieve primary + -- possible if existing primary keyboard became primary first without a screen + -- then prev (a screen) was added without a keyboard + -- and then we attached this screen+kb pair, and our kb fired first - failing to achieve primary + -- also, our kb may fire right after this, which is fine + component.setPrimary("keyboard", first_kb) + prev = nil -- nil meaning we should take this new one over the previous + end + end + elseif componentType == "keyboard" then + -- to reduce signal noise, if this kb is also the prev, we do not need to reset primary + if address ~= prev.address then + --keyboards never replace primary keyboards unless the are the only keyboard on the primary screen + local current_screen = primaries.screen or (adding.screen and adding.screen.proxy) + --if there is not yet a screen, do not use this keyboard, it's not any better + if current_screen then + -- the next phase is complicated + -- there is already a screen and there is already a keyboard + -- this keyboard is only better if this is a keyboard of the primary screen AND the current keyboard is not + -- i don't think we can trust kb order (1st vs 2nd), 2nd could fire first + -- but if there are two kbs on a screen, we can give preferred treatment to the first + -- thus, assume 2nd is not attached for the purposes of primary kb + -- and THUS, whichever (if either) is the 1st kb of the current screen + -- this is only possible if + -- 1. the only kb on the system (current) has no screen + -- 2. a screen is added without a kb + -- 3. this kb is added later manually + + -- prev is true when addr is not equal to the primary keyboard of the current screen -- meaning + -- when addr is different, and thus it is not the primary keyboard, then we ignore this + -- keyboard, and keep the previous + -- prev is false means we should take this new keyboard + prev = address ~= current_screen.getKeyboards()[1] + end + end + end + end + + if not prev then component.setPrimary(componentType, address) end end @@ -132,9 +174,31 @@ local function onComponentRemoved(_, address, componentType) if primaries[componentType] and primaries[componentType].address == address or adding[componentType] and adding[componentType].address == address then - component.setPrimary(componentType, component.list(componentType, true)()) + local next = component.list(componentType, true)() + component.setPrimary(componentType, next) + + if componentType == "screen" and next then + -- setPrimary already set the proxy (if successful) + local proxy = (primaries.screen or (adding.screen and adding.screen.proxy)) + if proxy then + -- if a screen is removed, and the primary keyboard is actually attached to another, non-primary, screen + -- then the `next` screen, if it has a keyboard, should TAKE priority + local next_kb = proxy.getKeyboards()[1] -- costly, don't call this method often + local old_kb = primaries.keyboard or adding.keyboard + -- if the next screen doesn't have a kb, this operation is without purpose, leave things as they are + -- if there was no previous kb, use the new one + if next_kb and (not old_kb or old_kb.address ~= next_kb) then + component.setPrimary("keyboard", next_kb) + end + end + end end end event.listen("component_added", onComponentAdded) event.listen("component_removed", onComponentRemoved) + +if _G.boot_screen then + component.setPrimary("screen", _G.boot_screen) +end +_G.boot_screen = nil diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/93_term.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/93_term.lua index 47a2be383..f39495e53 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/93_term.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/93_term.lua @@ -7,30 +7,59 @@ local process = require("process") -- this should be the init level process process.info().data.window = term.internal.open() -event.listen("gpu_bound", function(ename, gpu, screen) +event.listen("gpu_bound", function(ename, gpu) gpu=component.proxy(gpu) - screen=component.proxy(screen) - term.bind(gpu, screen) + term.bind(gpu) computer.pushSignal("term_available") end) -event.listen("component_unavailable", function(_,type) - if type == "screen" or type == "gpu" then - if term.isAvailable() then - local window = term.internal.window() - if window[type] and not component.proxy(window[type].address) then - window[type] = nil - end - end - if not term.isAvailable() then - computer.pushSignal("term_unavailable") - end +local function components_changed(ename, address, type) + local window = term.internal.window() + if not window then + return end -end) + + if ename == "component_available" or ename == "component_unavailable" then + type = address + end + + if ename == "component_removed" or ename == "component_unavailable" then + -- address can be type, when ename is *_unavailable, but *_removed works here and that's all we need + if type == "gpu" and window.gpu.address == address then + window.gpu = nil + window.keyboard = nil + elseif type == "keyboard" then + -- we could check if this was our keyboard + -- i.e. if address == window.keyboard + -- but it is also simple for the terminal to + -- recheck what kb to use + window.keyboard = nil + end + elseif (ename == "component_added" or ename == "component_available") and type == "keyboard" then + -- we need to clear the current terminals cached keyboard (if any) when + -- a new keyboard becomes available. This is in case the new keyboard was + -- attached to the terminal's window. The terminal library has the code to + -- determine what the best keyboard to use is, but here we'll just set the + -- cache to nil to force term library to reload it. An alternative to this + -- method would be to make sure the terminal library doesn't cache the + -- wrong keybaord to begin with but, users may actually expect that any + -- primary keyboard is a valid keyboard (weird, in my opinion) + window.keyboard = nil + end + + if (type == "screen" or type == "gpu") and not term.isAvailable() then + computer.pushSignal("term_unavailable") + end +end + +event.listen("component_removed", components_changed) +event.listen("component_added", components_changed) +event.listen("component_available", components_changed) +event.listen("component_unavailable", components_changed) event.listen("screen_resized", function(_,addr,w,h) local window = term.internal.window() - if term.isAvailable(window) and window.screen.address == addr and window.fullscreen then + if term.isAvailable(window) and term.screen(window) == addr and window.fullscreen then window.w,window.h = w,h end end) diff --git a/src/main/resources/assets/opencomputers/loot/openos/init.lua b/src/main/resources/assets/opencomputers/loot/openos/init.lua index b5e39025a..a0da27d97 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/init.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/init.lua @@ -32,9 +32,12 @@ do for address in component.list('screen', true) do if #component.invoke(address, 'getKeyboards') > 0 then screen = address + break end end + _G.boot_screen = screen + -- Report boot progress if possible. local gpu = component.list("gpu", true)() local w, h @@ -150,17 +153,9 @@ do status("Initializing components...") - local primaries = {} for c, t in component.list() do - local s = component.slot(c) - if not primaries[t] or (s >= 0 and s < primaries[t].slot) then - primaries[t] = {address=c, slot=s} - end computer.pushSignal("component_added", c, t) end - for t, c in pairs(primaries) do - component.setPrimary(t, c.address) - end os.sleep(0.5) -- Allow signal processing by libraries. computer.pushSignal("init") -- so libs know components are initialized. diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua index 7b8c90e33..6b9b4e6f0 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/keyboard.lua @@ -42,14 +42,7 @@ setmetatable(keyboard.keys, ------------------------------------------------------------------------------- local function getKeyboardAddress(address) - if address then - return address - else - local primary = component.isAvailable("keyboard") and component.getPrimary("keyboard") - if primary then - return primary.address - end - end + return address or require("term").keyboard() end local function getPressedCodes(address) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua index 2bef57a06..fed3a783c 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua @@ -2,6 +2,7 @@ local unicode = require("unicode") local event = require("event") local process = require("process") local kb = require("keyboard") +local component = require("component") local keys = kb.keys local term = {} @@ -51,7 +52,7 @@ end function term.isAvailable(w) w = w or W() - return w and not not (w.gpu and w.screen) + return w and not not (w.gpu and w.gpu.getScreen()) end function term.internal.pull(input, c, off, t, ...) @@ -74,6 +75,11 @@ function term.internal.pull(input, c, off, t, ...) c=c or {{gpu.getBackground()},{gpu.getForeground()},gpu.get(x,y)} local c11,c12 = unpack(c[1]) local c21,c22 = unpack(c[2]) + -- c can fail if gpu does not have a screen + -- if can happen during a type of race condition when a screen is removed + if not c11 then + return nil, "interrupted" + end if not off then sf(c11,c12) sb(c21,c22) @@ -227,54 +233,64 @@ function term.readKeyboard(ops) local draw=io.stdin.tty and term.drawText or term.internal.nop local input={w=w,promptx=w.x,prompty=w.y,index=0,data="",mask=pwchar} input.blink = ops.blink - if input.blink == nil then input.blink = w.blink end - if ops.nowrap then - term.internal.build_horizontal_reader(input) - else - term.internal.build_vertical_reader(input) + if input.blink == nil then + input.blink = w.blink end + + -- two wrap types currently supported, vertical and hortizontal + if ops.nowrap then term.internal.build_horizontal_reader(input) + else term.internal.build_vertical_reader(input) + end + while true do local killed, name, address, char, code = term.internal.pull(input) + -- we have to keep checking what kb is active in case it is switching during use + -- we could have multiple screens, each with keyboards active + local main_kb = term.keyboard(w) + local main_sc = term.screen(w) local c = nil local backup_cache = hints.cache - if name =="interrupted" then draw("^C\n",true) return "" - elseif name=="touch" or name=="drag" then term.internal.onTouch(input,char,code) - elseif name=="clipboard" then c=char hints.cache = nil - elseif name=="key_down" then - hints.cache = nil - local ctrl = kb.isControlDown(address) - if ctrl and code == keys.d then return - elseif char==9 then hints.cache = backup_cache term.internal.tab(input,hints) - elseif char==13 and filter(input) then - input:move(math.huge) - if db ~= false then draw("\n") end - term.internal.read_history(history,input) - return input.data.."\n" - elseif code==keys.back then - input:update(-1) - elseif code==keys.left then - input:move(ctrl and term.internal.ctrl_movement(input, -1) or -1) - elseif code==keys.right then - input:move(ctrl and term.internal.ctrl_movement(input, 1) or 1) - elseif ctrl and char=="w" then - -- cut word - elseif code==keys.up then - term.internal.read_history(history,input,1) - elseif code==keys.down then - term.internal.read_history(history,input,-1) - elseif code==keys.home then - input:move(-math.huge) - elseif code==keys["end"] then - input:move(math.huge) - elseif code==keys.delete then - input:update(0) - elseif char>=32 then - c=unicode.char(char) - else - hints.cache = backup_cache + if name == "interrupted" or name == "term_unavailable" then + draw("^C\n",true) + return "" + elseif address == main_kb or address == main_sc then + if name == "touch" or name == "drag" then + term.internal.onTouch(input,char,code) + elseif name == "clipboard" then + c = char + hints.cache = nil + elseif name == "key_down" then + hints.cache = nil + local ctrl = kb.isControlDown(address) + if ctrl and code == keys.d then return --nil + elseif char == 9 then + hints.cache = backup_cache + term.internal.tab(input,hints) + elseif char == 13 and filter(input) then + input:move(math.huge) + if db ~= false then + draw("\n") + end + term.internal.read_history(history,input) + return input.data .. "\n" + elseif code == keys.up then term.internal.read_history(history, input, 1) + elseif code == keys.down then term.internal.read_history(history, input, -1) + elseif code == keys.left then input:move(ctrl and term.internal.ctrl_movement(input, -1) or -1) + elseif code == keys.right then input:move(ctrl and term.internal.ctrl_movement(input, 1) or 1) + elseif code == keys.home then input:move(-math.huge) + elseif code == keys["end"] then input:move( math.huge) + elseif code == keys.back then c = -1 + elseif code == keys.delete then c = 0 + elseif char >= 32 then c = unicode.char(char) + elseif ctrl and char == "w"then -- TODO: cut word + else hints.cache = backup_cache -- ignored chars shouldn't clear hint cache + end + end + -- if we obtained something (c) to handle + if c then + input:update(c) end end - if c then input:update(c) end end end @@ -380,16 +396,56 @@ function term.getCursorBlink() return W().blink end -function term.bind(gpu, screen, kb, window) +function term.bind(gpu, window) window = window or W() window.gpu = gpu or window.gpu - window.screen = screen or window.screen - window.keyboard = kb or window.keyboard + window.keyboard = nil -- without a keyboard bound, always use the screen's main keyboard (1st) if window.fullscreen then term.setViewport(nil,nil,nil,nil,window.x,window.y,window) end end +function term.keyboard(window) + window = window or W() or {} -- this method needs to be safe even if there is no terminal window (e.g. no gpu) + + if window.keyboard then + return window.keyboard + end + + local system_keyboard = component.isAvailable("keyboard") and component.keyboard + system_keyboard = system_keyboard and system_keyboard.address or "no_system_keyboard" + + local screen = term.screen(window) + + if not screen then + -- no screen, no known keyboard, use system primary keyboard if any + return system_keyboard + end + + -- if we are using a gpu bound to the primary scren, then use the primary keyboard + if component.isAvailable("screen") and component.screen.address == screen then + window.keyboard = system_keyboard + else + -- calling getKeyboards() on the screen is costly (time) + -- custom terminals should avoid designs that require + -- this on every key hit + + -- this is expensive (slow!) + window.keyboard = component.invoke(screen, "getKeyboards")[1] or system_keyboard + end + + return window.keyboard +end + +function term.screen(window) + window = window or W() + local gpu = window.gpu + if not gpu then + return nil + end + return gpu.getScreen() +end + function --[[@delayloaded-start@]] term.scroll(number, window) -- if zero scroll length is requested, do nothing if number == 0 then return end @@ -552,7 +608,13 @@ function --[[@delayloaded-start@]] term.internal.tab(input,hints) hints.cache.i=-1 end local c=hints.cache - local change = kb.isShiftDown(term.keyboard().address) and -1 or 1 + local main_kb = term.keyboard() + -- term may not have a keyboard + -- in which case, we shouldn't be handling tab events + if not main_kb then + return + end + local change = kb.isShiftDown(main_kb) and -1 or 1 c.i=(c.i+change)%math.max(#c,1) local next=c[c.i+1] if next then @@ -568,21 +630,4 @@ function --[[@delayloaded-start@]] term.getGlobalArea(window) return dx+1,dy+1,w,h end --[[@delayloaded-end@]] -function --[[@delayloaded-start@]] term.screen(window) - return (window or W()).screen -end --[[@delayloaded-end@]] - -function --[[@delayloaded-start@]] term.keyboard(window) - window = window or W() - local kba = window.keyboard and window.keyboard.address - if kba and kb.pressedCodes[kba] then return window.keyboard end - window.keyboard=nil - local component = require("component") - if not window.screen or not component.proxy(window.screen.address) then window.screen = nil return end - local kba = window.screen.getKeyboards()[1] - if not kba then return end - window.keyboard = component.proxy(kba) - return window.keyboard -end --[[@delayloaded-end@]] - return term, local_env