From b57d8f960dbe486895914035453d385cc6f847d7 Mon Sep 17 00:00:00 2001 From: Rudi Date: Fri, 21 Aug 2026 21:00:10 -0400 Subject: [PATCH] How had I not tested on Linux before --- bugs.txt | 3 + .../org/pubvm/j3270/ui/TerminalPanel$1.class | Bin 2347 -> 2389 bytes .../org/pubvm/j3270/ui/TerminalPanel$2.class | Bin 689 -> 689 bytes .../org/pubvm/j3270/ui/TerminalPanel.class | Bin 25789 -> 25849 bytes .../org/lib3270j/input/InputProcessor.class | Bin 9896 -> 10152 bytes .../org/lib3270j/screen/ScreenBuffer.class | Bin 8178 -> 8257 bytes .../org/pubvm/j3270/ui/TerminalPanel.java | 5 ++ .../org/lib3270j/input/InputProcessor.java | 20 +++++ .../org/lib3270j/screen/ScreenBuffer.java | 22 +++-- .../lib3270j/input/InputProcessorTest.java | 81 ++++++++++++++++++ 10 files changed, 125 insertions(+), 6 deletions(-) diff --git a/bugs.txt b/bugs.txt index f1c3a0d..13339b6 100644 --- a/bugs.txt +++ b/bugs.txt @@ -8,3 +8,6 @@ - IND$FILE CMS and TSO: Fixed command formatting options handling (empty parenthesis removal for CMS binary/default modes and option spacing) and added Query Reply filtering for DFT/DDM mode. +- Local keyboard input and cursor updates not rendering in terminal: + Fixed in ScreenBuffer, InputProcessor, and TerminalPanel by ensuring display snapshot and cursor address are updated synchronously on user input operations (typing, backspace, delete, cursor movement, erase) so the presentation layer immediately renders user keystrokes. + diff --git a/build/j3270/org/pubvm/j3270/ui/TerminalPanel$1.class b/build/j3270/org/pubvm/j3270/ui/TerminalPanel$1.class index bb643f8516c669a7f5116a1bc83b94a68b222784..fad118437a66e4e0efcfb68d2a70dc9cd4469313 100644 GIT binary patch delta 349 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 UDi*=lNbvP^f)cLzKL*tQ0>Q4(#Q*>R literal 8178 zcma)B3t&{`mHz(BBr%gq9^_%dOhiy183+l8D3B17kdQ!#LI4phWioR^1|~CcW+G54 zbgS0Z%2w;60*ZjPY}cru3_e;Zwn}%~efPDyb=z*+t#s4~?>q^+0{=bt z|L?iyKj-}4dCcV-=UzDpV3|T8@)Tx7qFb6m!NIHMU(>v`X*3#%NBogc)3O0s=!}G8 zabGwdD}odGg(xui(j^L)XT22jM@=)_G@wbx*wBy}Rd7YkaKMcAMf}?oCiiXi-R5fw z`NCV8t{>cL`r}JnaG|&mhJi^aQ7Fr0P$*~#hJ*2?3Xb}Q^`bzTfpSb%D2ke+p`EKD z0aHPB$dgI~Rj5`lhJw*pymOfQm;pZQm4`J3YUN>Z$d`I1>uv)cEiPaV`Nl%=?k%!< znSrU9s~viEnt|z=tIMOpd!>OHnX8@JPqPfvXRh}8h1Y9fHs;VT@u)8x3;E&^I%!s4 zmLvVczG%#hH+2pA13~}V6yp*RFxNnnm{CY|*F?7Kt>zh+pIqyVgkml%z}1CVXy6)L zt1u-?`dTwO5)Au7;$K%o9;8@&rp&!Mf%^Cv(SWBEF54QaRPhU(1J>8G>25yuC%p3B_^dr zQTibHW5bcSxF}*^6t^l| z;*X5(?2bl86uk8xP)jQgV+P`~Z?P>sdAhe5*q*tbJmH-NJ|gRtHh*WPA>z`H8n^?y z2yH^L(+m-u-um8#52%y)=wk-{0w1U6EdLVL4E~KS+=aUf@d*R>;FAg^=@hfd7au0v ziA;t3`rh7#UQy#-1D}?Y&G&~)UsQZ`uL%E31E0~tha%AtUp#IGWc2|9pOw`~)|Ph8 zk(VAa@Gu@>qy%H#K{J#Fs17h{rSPbMz1YW>hkU)U8<=yM5hY9c^W=JX-4K0uQMu&rb z%ZyGJUcr|N@nr+A3Qu;OxmcznWX(aH{37eZLBCG%B7IFpF{!L?xUigX(@xD@+7}F) z{bM78W^}D@FvJqkorCVIJh&tqWf#+HNyB=YIS}{xx2^Jx+JcLym=&i2*oF@p>Izk3 zqX8z4W!b?=gi@frXoW$>f&^JM2eYHXr5UeehLsge)=N69G`EY~tE_a)N@PtWO0CH< ztFWXd=_d>*OO(9BiS?v77#q+vU&mWDD;IC9da^7yVuoWuwhwlI(S1@mvO=Frd?#l~ zLV(OhikTZ{PPw%u%JRIO^ej<1OCn=Ux>aBs(LqVvGGozM-yz%On^9kkSaH^GY*=3? zsk1__w1+}jkx@aB*>TX$R3kc5bg6Z$-N`h;49OIbp2^l3)3RWg-lL!5X(Iw=?vmlN z68_qV<%rZ&JEAli+0L7_k(7wi{iUR>42=E6NnYvGvy?>2F7Mq({a&)M}W%V>ufI?$lZ;>_;BX{%cp1{+ZWjny4y93 z^ifIF33FGcm3ZX7yS-y)oX6)Hh>S)3W_M8HzT5(*1&Xu(Yotm=K)Res?>1639HDyDt*IDp2Wog!wrC3vzUeblEIw$cJ zUQcmWN2*Bky~p7=${U5(crDP2vw59_ukw0|4<^CEzrbGaizxCs5-9cNB~T%kOJzDm zE_HIbTrOA0Wv2H_aNR`y1RUNu2{d@iY7%Jlmfes*vv>9hT$R8gp)Cotd8xyq1UiJa zCD0|bF@YYT%?YfetbWSktE;)Py6m+C*2#@CfeqdhxcOL0{Xx|5elE{=3W|8fCG6xY z*tgf<3R-+6?`B{YX3}rvQn1S0CkNlY731ur%b3 zykA3n4QLIV$SZeJ$I~efearUHttnY8yxVpfhLDqV%?#U9+bmC2%X+qR-m#w4=qK}e z-Q<~n0%7gAp9@HO5{L?2nZTG(Gl3mxH!dWW!DDqXJf>ifAn>DsLBEi+g`IULk9!U2 z2KLgMv5m734}U}(REhkvn4-?2h+e()EX29BH1O*wk6mwjYzFtGS(Qo0ML8d%Fd56EjwewpkGh`1REeh!_e;3bPS9N*eczQ#yjCKZu1nBo_;u!fCW8my z#)B+R4>5&4%=mhQ3F}cC7mo5;H`V#>gr;gVWuD-EvVIT$YU?jWTM|xzsB5Ovz7+wF zk?Y@5VyBZ+LPui8qKi_%q!;oXdKUtZz=Nw$AUFCLSX1 zf8Ynao}#*$VoP(yK1rq@;zu@XmN!asz3~*e{u4j8dH2y9PKsZ}WBZhQulC9!D}oza z?RlFuZ(b@8*nJR{Bt0IYM7&q0Q9^+9cM|JDE|7m0W8j!SOe@t>-c7nNLw`xaX$6*(X~aOY1y`Q8Bx2 z{$gjX^91g@%fWK_%XGPvyFFgE0{6?^#Jva~X#ZGPUCiBpm#OG0%;hh$Mffrmew8)h zBund8*ap1DQ~oNI{~DEloyxyKhBTP ziQ9%-9AW~?Tv#khTJhjscx#=+1&Xod>;M#|b>ThSYJyJZzMQ36P`mvUWnL>5>-3+?k@a{A64FBYxK7E zN)__Y@zaUonJz3uzw-|Dw`Xob^zWq(YCB=jU5JkD-_ftfv1(DK;2#m)cKPYsH@>DvzH~99lM$ z&RO}MqU1Y@Ep@LA-@Z)4+|JFO(S^~+1rxwsmoE%`CN5{t%2ix zlq*$jLE-HQi`Jf;G^b`z*`KFUS5}(alauDuO!EGB%KtNYrcxkE=yO_OgPMruinwa_R^?0EQB@#6 z0=e9ymsciR3rnnp`sKu050T$Xysbc$S_!x6!!)%DGgUvM;CeKx8@TE3Qfi)NHIkCI zEp?jA#e(l!xIldOH|kjHX*`bo`%v6S7aTZ(MVuaXKEXL@;Xzb4dWAvq^a&(13zv?g zA-(!CS9O4VJ{=%)QOs6!fC{fA{@0;Kt;bY#BbD92;J67()kdsVw;;ghTh(R_swx!N z;W~{8v~=*6r5A%Kr>D?SZar++9TGGc! zJ*0;@zR&Rt=M|*Sl2TDtYp5nt{<32fgE$2v7_8n3X z5Lh&!t|5J$;~kDSIDW!$n&Y<|U+4G}$2Z9*Q2sW@uQZ%QbYKu+Jd*$Fn*ze__f-K->GeQPla$+ z@xMz{SUFWhU817OP%%}e;;K@OsY}&uYKq#f>eLQ(x!S3&R3B0G>UK3peN;88JJbTT zOI@pWs}}V!b)EXSKJ}?^{|Rq8vi{`pPjZ1L|9e)p3%kSBIlttU{4DqbU23uVnKQ)I z^O8SEt?v_uq}eC_-Sa(yX?7a>t*O9270_0dt(IO!J;WmSFeB{|%vPUclv$0u{L<$` z*4sbY8{Wq4B;Kz3fE(7?`yamO{!{ol3hD{&e~|kh;@*d`SUt&F_mp;BQin6dmSyyo z|JP{i(-F;Q{@I|5TaMAzrcZOzaehmAh7tQL4S$ZCAI;kIY$};A=cWuwOv^Pq-=TX6 zH+u=4K+<5--%Zqutl%%Dgi1MiKh>XH@F6C7d>KMB8Wc<5DGGcoOW--`m51|x2IBR$ A`2YX_ diff --git a/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java b/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java index f5a77bc..95f861b 100644 --- a/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java +++ b/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java @@ -190,6 +190,8 @@ public class TerminalPanel extends JPanel { ScreenBuffer sb = client.getScreenBuffer(); sb.setCursorAddress(selectionStartRow * sb.getCols() + selectionStartCol); clearSelection(); + refreshScreen(); + return; } } repaint(); @@ -723,6 +725,9 @@ public class TerminalPanel extends JPanel { } private void refreshScreen() { + if (client != null) { + client.getScreenBuffer().updateDisplaySnapshot(); + } repaint(); // Notify parent to update status bar too Container parent = getParent(); diff --git a/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java b/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java index f72b973..63362be 100644 --- a/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java +++ b/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java @@ -152,6 +152,7 @@ public class InputProcessor { } screen.setCursorAddress(baddr); screen.markAllChanged(); + screen.updateDisplaySnapshot(); } /** @@ -206,6 +207,7 @@ public class InputProcessor { int nextRowAddr = ((row + 1) % screen.getRows()) * cols; screen.setCursorAddress(nextRowAddr); screen.markAllChanged(); + screen.updateDisplaySnapshot(); setKeyboardLocked(false); return; } @@ -309,6 +311,7 @@ public class InputProcessor { addr -= screen.getCols(); if (addr < 0) addr += screen.getRows() * screen.getCols(); screen.setCursorAddress(addr); + screen.updateDisplaySnapshot(); } public void cursorDown() { @@ -316,18 +319,21 @@ public class InputProcessor { addr += screen.getCols(); if (addr >= screen.getRows() * screen.getCols()) addr -= screen.getRows() * screen.getCols(); screen.setCursorAddress(addr); + screen.updateDisplaySnapshot(); } public void cursorLeft() { int addr = screen.getCursorAddress(); addr = screen.decrementAddress(addr); screen.setCursorAddress(addr); + screen.updateDisplaySnapshot(); } public void cursorRight() { int addr = screen.getCursorAddress(); addr = screen.incrementAddress(addr); screen.setCursorAddress(addr); + screen.updateDisplaySnapshot(); } public void cursorHome() { @@ -336,11 +342,13 @@ public class InputProcessor { } else { screen.setCursorAddress(0); } + screen.updateDisplaySnapshot(); } public void tab() { int addr = screen.findNextUnprotected(screen.getCursorAddress()); screen.setCursorAddress(addr); + screen.updateDisplaySnapshot(); } public int getLastAid() { return lastAid; } @@ -348,6 +356,7 @@ public class InputProcessor { public void setCursorAddress(int baddr) { screen.setCursorAddress(baddr); + screen.updateDisplaySnapshot(); } public void backTab() { @@ -361,6 +370,7 @@ public class InputProcessor { if (screen.getCell(addr).isFieldAttribute()) { if (!faIsProtected(screen.getCell(addr).fa & 0xFF)) { screen.setCursorAddress(screen.incrementAddress(addr)); + screen.updateDisplaySnapshot(); return; } } @@ -377,6 +387,7 @@ public class InputProcessor { ea.ucs4 = 0; } screen.markAllChanged(); + screen.updateDisplaySnapshot(); return; } int addr = screen.getCursorAddress(); @@ -398,6 +409,7 @@ public class InputProcessor { screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY); } screen.markAllChanged(); + screen.updateDisplaySnapshot(); } public void deleteChar() { @@ -426,6 +438,7 @@ public class InputProcessor { screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY); } screen.markAllChanged(); + screen.updateDisplaySnapshot(); } public void backspace() { @@ -439,6 +452,7 @@ public class InputProcessor { if (keyboardLocked) return; screen.eraseAllUnprotected(); screen.markAllChanged(); + screen.updateDisplaySnapshot(); } /** Move cursor to first unprotected field on next line (Newline key in 3270). */ @@ -455,6 +469,7 @@ public class InputProcessor { } screen.setCursorAddress(target); screen.markAllChanged(); + screen.updateDisplaySnapshot(); } /** Insert Duplicate (DUP) code and advance to next field. */ @@ -472,6 +487,8 @@ public class InputProcessor { screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY); } tab(); + screen.markAllChanged(); + screen.updateDisplaySnapshot(); } /** Insert Field Mark (FM) code. */ @@ -495,6 +512,7 @@ public class InputProcessor { } screen.setCursorAddress(baddr); screen.markAllChanged(); + screen.updateDisplaySnapshot(); } /** Attention key (sends Telnet IP). */ @@ -571,6 +589,7 @@ public class InputProcessor { screen.getCell(i).ucs4 = 0; } screen.markAllChanged(); + screen.updateDisplaySnapshot(); return size; } @@ -608,6 +627,7 @@ public class InputProcessor { screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY); } screen.markAllChanged(); + screen.updateDisplaySnapshot(); return fieldLen; } diff --git a/lib3270j/src/main/java/org/lib3270j/screen/ScreenBuffer.java b/lib3270j/src/main/java/org/lib3270j/screen/ScreenBuffer.java index c217566..c7150c4 100644 --- a/lib3270j/src/main/java/org/lib3270j/screen/ScreenBuffer.java +++ b/lib3270j/src/main/java/org/lib3270j/screen/ScreenBuffer.java @@ -60,6 +60,7 @@ public class ScreenBuffer { defaultFA.ic = 1; allocateBuffers(); + updateDisplaySnapshot(); } private void allocateBuffers() { @@ -139,7 +140,7 @@ public class ScreenBuffer { public boolean isScreenAlt() { return screenAlt; } /** Update alternate dimensions from BIND image. Re-allocates buffers if needed. */ - public void setAlternateDimensions(int newAltRows, int newAltCols) { + public synchronized void setAlternateDimensions(int newAltRows, int newAltCols) { if (newAltRows == altRows && newAltCols == altCols) return; this.altRows = newAltRows; this.altCols = newAltCols; @@ -149,11 +150,15 @@ public class ScreenBuffer { this.maxCols = Math.max(maxCols, newAltCols); allocateBuffers(); } + updateDisplaySnapshot(); } // ========== Cursor ========== public int getCursorAddress() { return cursorAddress; } - public void setCursorAddress(int addr) { this.cursorAddress = addr; } + public synchronized void setCursorAddress(int addr) { + this.cursorAddress = addr; + this.displayCursorAddress = addr; + } public int getCursorRow() { return cursorAddress / cols; } public int getCursorCol() { return cursorAddress % cols; } @@ -168,20 +173,22 @@ public class ScreenBuffer { /** * Perform an erase, optionally using the alternate screen size. */ - public void erase(boolean alt) { + public synchronized void erase(boolean alt) { clear(); int newRows = alt ? altRows : defRows; int newCols = alt ? altCols : defCols; if (alt == screenAlt && rows == newRows && cols == newCols) { + updateDisplaySnapshot(); return; } rows = newRows; cols = newCols; screenAlt = alt; + updateDisplaySnapshot(); } /** Clear the entire buffer. */ - public void clear() { + public synchronized void clear() { for (ExtendedAttribute ea : buffer) { ea.clear(); } @@ -196,12 +203,13 @@ public class ScreenBuffer { defaultCs = 0x00; defaultIc = 0x00; replyMode = SF_SRM_FIELD; + updateDisplaySnapshot(); } /** * Erase all unprotected fields. */ - public void eraseAllUnprotected() { + public synchronized void eraseAllUnprotected() { int size = rows * cols; boolean inUnprotected = false; @@ -229,6 +237,7 @@ public class ScreenBuffer { // Move cursor to first unprotected field cursorAddress = findNextUnprotected(0); screenChanged = true; + updateDisplaySnapshot(); } // ========== Field attribute navigation ========== @@ -322,7 +331,7 @@ public class ScreenBuffer { // ========== Setters for model reconfiguration ========== - public void setDimensions(int maxRows, int maxCols, int defRows, int defCols, + public synchronized void setDimensions(int maxRows, int maxCols, int defRows, int defCols, int altRows, int altCols) { this.maxRows = maxRows; this.maxCols = maxCols; @@ -333,6 +342,7 @@ public class ScreenBuffer { this.rows = defRows; this.cols = defCols; allocateBuffers(); + updateDisplaySnapshot(); } public void translateToUnicode() { diff --git a/lib3270j/src/test/java/org/lib3270j/input/InputProcessorTest.java b/lib3270j/src/test/java/org/lib3270j/input/InputProcessorTest.java index 55d4dd4..dd15919 100644 --- a/lib3270j/src/test/java/org/lib3270j/input/InputProcessorTest.java +++ b/lib3270j/src/test/java/org/lib3270j/input/InputProcessorTest.java @@ -44,10 +44,91 @@ public class InputProcessorTest { assertEquals('A', screen.getCell(1).ucs4); assertEquals('B', screen.getCell(2).ucs4); + assertEquals('A', screen.getDisplayCell(1).ucs4); + assertEquals('B', screen.getDisplayCell(2).ucs4); input.eraseInput(); assertEquals(0, screen.getCell(1).ec); assertEquals(0, screen.getCell(2).ec); + assertEquals(0, screen.getDisplayCell(1).ec); + assertEquals(0, screen.getDisplayCell(2).ec); + } + + @Test + public void testTypeCharacterUpdatesDisplaySnapshot() { + InputProcessor input = new InputProcessor(screen, translator, null); + screen.erase(false); + screen.setCellFA(0, (byte) FA_PRINTABLE); // Unprotected field starting at 1 + screen.setCursorAddress(1); + screen.updateDisplaySnapshot(); // Initial snapshot + + // Type "logon" + input.typeCharacter('l'); + input.typeCharacter('o'); + input.typeCharacter('g'); + input.typeCharacter('o'); + input.typeCharacter('n'); + + // Verify underlying buffer + assertEquals('l', screen.getCell(1).ucs4); + assertEquals('o', screen.getCell(2).ucs4); + assertEquals('g', screen.getCell(3).ucs4); + assertEquals('o', screen.getCell(4).ucs4); + assertEquals('n', screen.getCell(5).ucs4); + + // Verify display snapshot (what TerminalPanel renders) + assertEquals('l', screen.getDisplayCell(1).ucs4); + assertEquals('o', screen.getDisplayCell(2).ucs4); + assertEquals('g', screen.getDisplayCell(3).ucs4); + assertEquals('o', screen.getDisplayCell(4).ucs4); + assertEquals('n', screen.getDisplayCell(5).ucs4); + assertEquals(6, screen.getDisplayCursorAddress()); + assertEquals(6, screen.getCursorAddress()); + } + + @Test + public void testCursorMovementUpdatesDisplayCursorAddress() { + InputProcessor input = new InputProcessor(screen, translator, null); + screen.erase(false); + screen.setCursorAddress(10); + assertEquals(10, screen.getDisplayCursorAddress()); + + input.cursorRight(); + assertEquals(11, screen.getDisplayCursorAddress()); + + input.cursorLeft(); + assertEquals(10, screen.getDisplayCursorAddress()); + + screen.setCursorAddress(45); + assertEquals(45, screen.getDisplayCursorAddress()); + } + + @Test + public void testDeleteAndEraseEofUpdateDisplaySnapshot() { + InputProcessor input = new InputProcessor(screen, translator, null); + screen.erase(false); + screen.setCellFA(0, (byte) FA_PRINTABLE); + screen.setCellFA(10, (byte) (FA_PRINTABLE | FA_PROTECT)); + screen.setCursorAddress(1); + + input.typeCharacter('A'); + input.typeCharacter('B'); + input.typeCharacter('C'); + assertEquals('A', screen.getDisplayCell(1).ucs4); + assertEquals('B', screen.getDisplayCell(2).ucs4); + assertEquals('C', screen.getDisplayCell(3).ucs4); + + // Backspace over 'C' + input.backspace(); + assertEquals(3, screen.getCursorAddress()); + assertEquals(3, screen.getDisplayCursorAddress()); + assertEquals(0, screen.getDisplayCell(3).ucs4); + + // Erase EOF from position 2 ('B') + screen.setCursorAddress(2); + input.eraseEof(); + assertEquals(0, screen.getDisplayCell(2).ucs4); + assertEquals('A', screen.getDisplayCell(1).ucs4); } }