mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-22 12:14:05 +09:00
tsvm new api: con.setFullscreen
This commit is contained in:
@@ -98,7 +98,7 @@ function generateRandomHashStr(len) {
|
||||
|
||||
// define TVDOS
|
||||
const _TVDOS = {};
|
||||
_TVDOS.VERSION = "1.0";
|
||||
_TVDOS.VERSION = "1.4";
|
||||
_TVDOS.DRIVES = {}; // Object where key-value pair is <drive-letter> : [serial-port, drive-number]
|
||||
_TVDOS.DRIVEFS = {}; // filesystem driver for the drive letter
|
||||
_TVDOS.DRIVEINFO = {};
|
||||
@@ -1151,6 +1151,23 @@ input.withEvent = function(callback) {
|
||||
let mb = sys.peek(-37) & 0xFF; // bits 0..2 = L/R/M held, bit 6 = wheel up, bit 7 = wheel down
|
||||
let mouse = [mx, my, mb];
|
||||
|
||||
// Under vtmgr, a backgrounded console must not see the physical keyboard or
|
||||
// mouse (another VT is in the foreground). con.isActiveConsole() is true on
|
||||
// bare metal and on the foreground pane, false on a background pane. When
|
||||
// inactive, zero the key snapshot and pin the mouse to its previous state so
|
||||
// the dispatch below emits no key/mouse events (still pacing via sleep). This
|
||||
// makes every input.withEvent app auto-ignore background input — no per-app
|
||||
// active check needed; a fullscreen app only has to call con.setFullscreen().
|
||||
if (con.isActiveConsole && !con.isActiveConsole()) {
|
||||
keys = [0,0,0,0,0,0,0,0];
|
||||
if (inputwork.oldMouse && inputwork.oldMouse.length === 3) {
|
||||
mx = inputwork.oldMouse[0]; my = inputwork.oldMouse[1]; mb = inputwork.oldMouse[2] & 0x07;
|
||||
} else {
|
||||
mb = 0;
|
||||
}
|
||||
mouse = [mx, my, mb];
|
||||
}
|
||||
|
||||
// --- mouse dispatch ---
|
||||
let oldMouse = inputwork.oldMouse;
|
||||
let hasOld = oldMouse && oldMouse.length === 3;
|
||||
|
||||
@@ -396,10 +396,11 @@ function queuePop() {
|
||||
return b
|
||||
}
|
||||
con.getch = function() {
|
||||
// Reading cooked input means we are NOT a raw-keyboard grabber; drop any
|
||||
// stale grab this VT left set (e.g. a fullscreen app that crashed without
|
||||
// releasing) so the dispatcher resumes feeding our ring.
|
||||
if (sys.peek(RAW_GRAB_ADDR) === VT_NUM) sys.poke(RAW_GRAB_ADDR, 0)
|
||||
// Reading cooked input means we are NOT a fullscreen raw-keyboard owner;
|
||||
// drop any stale grab this VT left set (e.g. a fullscreen app that crashed
|
||||
// without calling setFullscreen(false)) so the dispatcher resumes feeding
|
||||
// our ring.
|
||||
if (sys.peek(RAW_GRAB_ADDR) === VT_NUM) { sys.poke(RAW_GRAB_ADDR, 0); con._fullscreen = false }
|
||||
while (true) {
|
||||
if (sys.peek(ACTIVE_VT_ADDR) === VT_NUM) {
|
||||
let k = queuePop()
|
||||
@@ -408,22 +409,46 @@ con.getch = function() {
|
||||
sys.sleep(20)
|
||||
}
|
||||
}
|
||||
// A fullscreen app that reads the raw keyboard snapshot (-41..-48) directly,
|
||||
// bypassing this ring, must grab so the dispatcher stops piling cooked chars
|
||||
// into a ring it never drains. Flush any prior type-ahead on grab; the
|
||||
// dispatcher keeps the ring empty while held. Release on exit (or the next
|
||||
// con.getch self-heals a grab leaked by a crashed app).
|
||||
con.grabRawKeyboard = function() {
|
||||
sys.poke(RAW_GRAB_ADDR, VT_NUM)
|
||||
sys.poke(QUEUE_HEAD_ADDR, sys.peek(QUEUE_TAIL_ADDR))
|
||||
}
|
||||
con.releaseRawKeyboard = function() {
|
||||
if (sys.peek(RAW_GRAB_ADDR) === VT_NUM) sys.poke(RAW_GRAB_ADDR, 0)
|
||||
// ── Fullscreen / raw-keyboard session (VT-aware overrides) ─────────────────
|
||||
// con.setFullscreen(true) declares this pane a fullscreen raw-keyboard owner:
|
||||
// a fullscreen app reads the raw key snapshot (-41..-48) directly, bypassing
|
||||
// this ring, so it grabs to stop the dispatcher piling cooked chars into a ring
|
||||
// it never drains (they'd flood the shell the instant the app exits). Flush any
|
||||
// prior type-ahead on grab; the dispatcher keeps the ring empty while held.
|
||||
// setFullscreen(false) releases (a leaked grab also self-heals on the next
|
||||
// con.getch). These override JS_INIT's bare-metal no-ops so an app's single
|
||||
// con.setFullscreen(true) call does the right thing under vtmgr too.
|
||||
con.setFullscreen = function(on) {
|
||||
con._fullscreen = !!on
|
||||
if (on) {
|
||||
// Claim the grab only while we are the foreground VT. Apps may re-assert
|
||||
// this every frame (e.g. taut/zfm, to re-establish it after launching a
|
||||
// sub-program); a BACKGROUNDED fullscreen app must not clobber the active
|
||||
// grabber's claim on the shared CTRL_RAW_GRAB_VT byte. The claim persists
|
||||
// across a switch-away (nobody else clears it) and the app re-claims on
|
||||
// return, so a single up-front claim also survives backgrounding.
|
||||
if (sys.peek(ACTIVE_VT_ADDR) === VT_NUM) {
|
||||
sys.poke(RAW_GRAB_ADDR, VT_NUM)
|
||||
sys.poke(QUEUE_HEAD_ADDR, sys.peek(QUEUE_TAIL_ADDR))
|
||||
}
|
||||
} else if (sys.peek(RAW_GRAB_ADDR) === VT_NUM) {
|
||||
sys.poke(RAW_GRAB_ADDR, 0)
|
||||
}
|
||||
}
|
||||
con.isFullscreen = function() { return con._fullscreen }
|
||||
// This pane owns the physical keyboard only while it is the foreground VT.
|
||||
// con.poll_keys() (inherited from JS_INIT) and direct-MMIO raw-input apps gate
|
||||
// their key/mouse reads on this, so a backgrounded app reads nothing.
|
||||
con.isActiveConsole = function() { return sys.peek(ACTIVE_VT_ADDR) === VT_NUM }
|
||||
// Deprecated aliases kept for older raw-keyboard apps; prefer setFullscreen().
|
||||
con.grabRawKeyboard = function() { con.setFullscreen(true) }
|
||||
con.releaseRawKeyboard = function() { con.setFullscreen(false) }
|
||||
con.hitterminate = function() { return false }
|
||||
con.hiteof = function() { return false }
|
||||
con.resetkeybuf = function() { sys.poke(QUEUE_HEAD_ADDR, sys.peek(QUEUE_TAIL_ADDR)) }
|
||||
con.poll_keys = function() { return [0,0,0,0,0,0,0,0] }
|
||||
// poll_keys is inherited from JS_INIT: it returns zeros unless isActiveConsole()
|
||||
// (overridden above), so a backgrounded pane reads no keys while a foreground
|
||||
// pane reads the physical snapshot — no pane-specific override needed.
|
||||
|
||||
// ── TVDOS.SYS init flags + BIOS stub ───────────────────────────────────────
|
||||
globalThis._TVDOS_IS_VT_PANE = true
|
||||
|
||||
@@ -195,6 +195,13 @@ let dec = null
|
||||
let stage = "open" // breadcrumb for the error log
|
||||
|
||||
try {
|
||||
// Fullscreen raw-keyboard app: one declaration. Under vtmgr it grabs the
|
||||
// cooked-input feed (so keystrokes typed at this player don't flood the
|
||||
// shell on exit), and con.poll_keys() in readInput() auto-ignores the
|
||||
// keyboard while this console is backgrounded; on bare metal it is a no-op.
|
||||
// Released in the finally below.
|
||||
con.setFullscreen(true)
|
||||
|
||||
dec = mediadec.open(fullPath, decOpts)
|
||||
const info = dec.info
|
||||
|
||||
@@ -265,8 +272,10 @@ try {
|
||||
// triggered so a held key fires once. Quit + ASCII/colour toggles work even
|
||||
// without -i; the rest of the transport is interactive-only.
|
||||
function readInput() {
|
||||
sys.poke(-40, 1)
|
||||
const key = sys.peek(-41)
|
||||
// con.poll_keys() returns the raw key snapshot, but yields all-zeros
|
||||
// while this console is backgrounded under vtmgr, so a backgrounded
|
||||
// player never reacts to the foreground console's typing.
|
||||
const key = con.poll_keys()[0]
|
||||
if (key == K.BACKSPACE) { quit = true; return }
|
||||
if (key && key !== lastKey) {
|
||||
if (key == K.A) { if (aa) toggleAscii() } // inert when aa.mjs is absent
|
||||
@@ -350,6 +359,7 @@ catch (e) {
|
||||
errorlevel = 1
|
||||
}
|
||||
finally {
|
||||
con.setFullscreen(false)
|
||||
if (dec) dec.close()
|
||||
if (aa && aaCtx) aa.close(aaCtx)
|
||||
if (errorlevel === 0) con.clear()
|
||||
|
||||
@@ -486,6 +486,11 @@ audio.setSampleBank(0) // restore the bank window to bank 0 after probing
|
||||
// ── Console setup ───────────────────────────────────────────────────────────
|
||||
con.curs_set(0)
|
||||
con.clear()
|
||||
// Fullscreen raw-keyboard app: one declaration. Under vtmgr it grabs the
|
||||
// dispatcher's cooked-input feed (so the visualiser's keystrokes don't flood the
|
||||
// shell on exit), and con.poll_keys() below auto-ignores input while this console
|
||||
// is backgrounded; a no-op on bare metal. Released in the finally.
|
||||
con.setFullscreen(true)
|
||||
|
||||
function mvprn(row, col, ch) { con.mvaddch(row, col, ch) }
|
||||
function mvtext(row, col, s) { con.move(row, col); print(s) }
|
||||
@@ -1255,8 +1260,10 @@ try {
|
||||
// Keyboard polling (mirrors playtad). Backspace exits; Up/Down switch
|
||||
// to the previous/next song (wrapping) when the file holds more than
|
||||
// one song. lastNavKey debounces so each press switches exactly once.
|
||||
sys.poke(-40, 1)
|
||||
const rawKey = sys.peek(-41)
|
||||
// con.poll_keys() returns the raw key snapshot, but all-zeros while this
|
||||
// console is backgrounded under vtmgr, so we never act on another
|
||||
// console's keys.
|
||||
const rawKey = con.poll_keys()[0]
|
||||
if (rawKey === 67) stopReq = true
|
||||
else if (rawKey !== lastNavKey && song.numSongs > 1) {
|
||||
if (rawKey === 19) // up = previous song
|
||||
@@ -1307,6 +1314,7 @@ catch (e) {
|
||||
errorlevel = 1
|
||||
}
|
||||
finally {
|
||||
con.setFullscreen(false)
|
||||
audio.stop(PLAYHEAD)
|
||||
con.move(ROW_BOT_BORDER + 1, 1)
|
||||
con.curs_set(1)
|
||||
|
||||
@@ -6837,6 +6837,12 @@ let exitFlag = false
|
||||
let pendingExternalDraw = false
|
||||
|
||||
while (!exitFlag) {
|
||||
// Fullscreen app: (re)assert the raw-keyboard grab each frame so cooked chars
|
||||
// never pile into this pane's ring (they'd flood the shell on exit), and so
|
||||
// it is re-established after a sub-editor returns. input.withEvent below is
|
||||
// auto-guarded by con.isActiveConsole(), so a backgrounded editor sees no
|
||||
// input. Both are no-ops on bare metal. Released in the teardown.
|
||||
con.setFullscreen(true)
|
||||
input.withEvent(event => {
|
||||
if (dispatchMouseEvent(event)) return
|
||||
if (event[0] !== "key_down") return
|
||||
@@ -6955,6 +6961,7 @@ while (!exitFlag) {
|
||||
}
|
||||
|
||||
audio.stop(PLAYHEAD)
|
||||
con.setFullscreen(false)
|
||||
resetAudioDevice()
|
||||
sys.free(SCRATCH_PTR)
|
||||
font.resetLowRom()
|
||||
|
||||
@@ -1115,6 +1115,12 @@ let firstRunLatch = true
|
||||
let pendingPostExecDrain = false
|
||||
|
||||
while (!exit) {
|
||||
// Fullscreen app: (re)assert the raw-keyboard grab each frame so cooked chars
|
||||
// never pile into this pane's ring (they'd flood the shell on exit), and so
|
||||
// it is re-established after a launched program returns. input.withEvent
|
||||
// below is auto-guarded by con.isActiveConsole(); both are no-ops on bare
|
||||
// metal. Released after the loop.
|
||||
con.setFullscreen(true)
|
||||
input.withEvent(event => {
|
||||
|
||||
if (dispatchMouseEvent(event)) {
|
||||
@@ -1160,6 +1166,7 @@ while (!exit) {
|
||||
}
|
||||
}
|
||||
|
||||
con.setFullscreen(false)
|
||||
con.curs_set(1)
|
||||
con.clear()
|
||||
return 0
|
||||
@@ -1,6 +1,6 @@
|
||||
HopperManifestVersion:1
|
||||
HopperPackageName:tvdos
|
||||
HopperPackageVersion:1.0.0
|
||||
HopperPackageVersion:1.4.0
|
||||
HopperPackageMaintainer:CuriousTorvald
|
||||
HopperProvides:tvdos;
|
||||
HopperRequires:
|
||||
|
||||
Reference in New Issue
Block a user