feat: extend kmain with injectKeyPress and injectShiftedKeyPress

This commit is contained in:
dualfroz committed 2026-09-04 20:44:06 +02:00
1 parent b4f8d5215f
commit 750354f099
1 file changed
+173
+173
View File
@@ -504,6 +504,179 @@ static void pgroupInjectorThread(void)
#define scancodeMakeP 0x19
#define scancodeMakeI 0x17
#define scancodeMakeE 0x12
#define scancodeMakeS 0x1F
#define scancodeMakeR 0x13
#define scancodeMakeN 0x31
#define scancodeMakeK 0x25
#define scancodeMakeBackslash 0x2B
static void injectKeyPress(uint8 makeCode)
{
keyboardHandleScancode(makeCode);
keyboardHandleScancode(makeCode | scancodeBreakBit);
}
/* '&' is shift+7 on a real US keyboard -- keyboard.c's scancodeUpper table
maps index scancodeMakeSeven to '&' only while shiftHeld is true. */
static void injectShiftedKeyPress(uint8 makeCode)
{
keyboardHandleScancode(scancodeLeftShiftMake);
keyboardHandleScancode(makeCode);
keyboardHandleScancode(makeCode | scancodeBreakBit);
keyboardHandleScancode(scancodeLeftShiftBreak);
}
/* A single bare Enter, unconditionally, before typing anything this thread
actually cares about: every keystroke keyboardHandleScancode decodes is
broadcast to EVERY open console reader (keyboard.c), the shell's own
included, so consoleInjectorThread's single synthetic 'k' (well before
this thread ever runs) is still sitting unflushed in the shell's own
line buffer -- there is no way for this thread to tell "a stray
character from an earlier, unrelated demo" apart from "something a real
user typed". One Enter here executes that "k" as a harmless unknown
command (shell.c's execute()) and guarantees an empty line buffer before
the real commands below, exactly like pressing Enter at a real prompt to
clear a garbled line. */
static void jobctlTypeFlushLine(void)
{
injectKeyPress(scancodeMakeEnter);
}
static void jobctlTypeBackgroundCommand(void)
{
injectKeyPress(scancodeMakeJ);
injectKeyPress(scancodeMakeO);
injectKeyPress(scancodeMakeB);
injectKeyPress(scancodeCPressMake);
injectKeyPress(scancodeMakeT);
injectKeyPress(scancodeMakeL);
injectKeyPress(scancodeMakeB);
injectKeyPress(scancodeMakeG);
injectKeyPress(scancodeMakeSpace);
injectShiftedKeyPress(scancodeMakeSeven);
injectKeyPress(scancodeMakeEnter);
}
static void jobctlTypeForegroundCommand(void)
{
injectKeyPress(scancodeMakeJ);
injectKeyPress(scancodeMakeO);
injectKeyPress(scancodeMakeB);
injectKeyPress(scancodeCPressMake);
injectKeyPress(scancodeMakeT);
injectKeyPress(scancodeMakeL);
injectKeyPress(scancodeMakeF);
injectKeyPress(scancodeMakeG);
injectKeyPress(scancodeMakeEnter);
}
#define jobctlDriverInitialDelayMs 900
#define jobctlDriverTypeSettleMs 150
#define jobctlDriverForegroundSettleMs 350
#define jobctlDriverSignalSettleMs 150
static void jobctlDriverThread(void)
{
threadSleep(jobctlDriverInitialDelayMs);
while (currentThread()->state == threadSleeping)
halt();
kprintf("[kbd] typing shell commands for the on-demand job-control demo\n");
jobctlTypeFlushLine();
/* "jobctlbg &" -- backgrounded, so shell.c's runProgram never blocks
on it: jobctlSpawnForeground/jobctlRunBackground's own load+spawn
work is synchronous kernel code, so by the time the shell has
actually consumed all of these buffered bytes (needs its own
timeslice; keyboardHandleScancode above only enqueues them) its
shellLastSpawnedPid() is already this job's real pid, not stale. */
jobctlTypeBackgroundCommand();
threadSleep(jobctlDriverTypeSettleMs);
while (currentThread()->state == threadSleeping)
halt();
uint32 jobctlbgPid = shellLastSpawnedPid();
/* "jobctlfg" -- foreground, so the shell thread itself parks inside
jobctlWaitForeground (threadJoin) the moment it processes this
line's own trailing Enter. shellLastSpawnedPid() is still safe to
read afterward: shell.c's runProgram sets it right after
jobctlSpawnForeground returns, BEFORE ever calling
jobctlWaitForeground, precisely so a live demo like this one is
never stuck waiting for a foreground job to exit before it can even
learn that job's own pid. The longer settle here (vs. the
background command above) gives jobctlfg.c itself real scheduler
time to reach its own blocking pipe read before Ctrl-C below --
the same margin ratio pgroupInjectDelayMs/pgroupSettleDelayMs above
already established for an equivalent single-process case. */
jobctlTypeForegroundCommand();
threadSleep(jobctlDriverForegroundSettleMs);
while (currentThread()->state == threadSleeping)
halt();
uint32 jobctlfgPid = shellLastSpawnedPid();
if (jobctlbgPid == threadInvalidId || jobctlfgPid == threadInvalidId ||
jobctlbgPid == jobctlfgPid) {
kprintf("[kbd] on-demand job-control demo skipped: jobctlbg/jobctlfg never started\n");
for (;;)
halt();
}
kprintf("[kbd] injecting synthetic ctrl-c for the on-demand job-control demo\n");
keyboardHandleScancode(scancodeLeftCtrlMake);
keyboardHandleScancode(scancodeCPressMake);
keyboardHandleScancode(scancodeCPressBreak);
keyboardHandleScancode(scancodeLeftCtrlBreak);
/* Give the scheduler a real timeslice to actually deliver the
resulting sigInt to jobctlfg (early-wake + return-to-ring3), let
the now-parked shell thread's own threadJoin notice it Dead, and let
jobctlWaitForeground's reclaim (jobctl.c) run -- all real scheduled
work, not instantaneous. */
threadSleep(jobctlDriverSignalSettleMs);
while (currentThread()->state == threadSleeping)
halt();
/* The direct, causal proof that shell.c's runProgram/jobctl.c's
jobctlWaitForeground actually reclaimed the terminal for the shell's
OWN group the instant jobctlfg died, rather than leaving it
permanently pointed at a now-dead pgid the way nothing in this
kernel ever un-claimed it before this subsystem existed: if the
reclaim never ran (or ran against the wrong pgid), the foreground
would still equal jobctlfg's own now-meaningless pid forever. */
uint32 fgAfterReclaim = keyboardForegroundPgid();
bool reclaimed = (fgAfterReclaim != jobctlfgPid);
kprintf("[user] jobctl demo: jobctlfg pid=%u foreground pgid now=%u -> %s\n",
jobctlfgPid, fgAfterReclaim,
reclaimed ? "shell-foreground-reclaimed-ok" : "FAIL");
/* Proves jobctlbg (never granted the terminal at all, per shell.c's
`&` handling) is still genuinely alive and running real code after
the Ctrl-C above -- if the shell's background path ever put it in
jobctlfg's group (or granted it the foreground itself), it would
already be Dead by now and this sigRaise would silently do nothing,
so jobctlbg.c's own "still-alive-after-ctrlc-ok" needle would simply
never appear in the boot log, failing the gate. */
sigRaise(jobctlbgPid, sigUsr1);
for (;;)
halt();
}
#define pipelineDriverInitialDelayMs 2500
#define pipelineDriverSettleMs 500
static void pipelineTypeCommand(void)
{
injectKeyPress(scancodeMakeP);
injectKeyPress(scancodeMakeI);
injectKeyPress(scancodeMakeP);
injectKeyPress(scancodeMakeE);
injectKeyPress(scancodeMakeS);
injectKeyPress(scancodeMakeR);
injectKeyPress(scancodeCPressMake);
injectKeyPress(scancodeMakeSpace);
injectShiftedKeyPress(scancodeMakeBackslash);
injectKeyPress(scancodeMakeSpace);
}
void kmain(uint32 magic, uint64 multibootInfo)