From 1ec95df464d667f10d33994e284cf713b3f41b23 Mon Sep 17 00:00:00 2001
From: ~byakuren
Date: Wed, 24 Sep 2025 03:35:46 +0100
Subject: Implement 1!:111 character input and output control function

Add comprehensive character-level I/O control with 4 modes:
- (1!:111) 1: Echo mode - read character with display
- (1!:111) 0: Silent mode - global no-echo with stdin buffering
- (1!:111) _1: Restore mode - return terminal to normal behavior
- (1!:111) 2: Flush mode - flush stdout for immediate display

Features:
* Cross-platform support (Windows _getch/_getche, Unix termios)
* Global terminal state management for silent mode
* Essential for character-by-character interactive applications

This patch is standalone and can be applied independently.
---
diff --git a/jsrc/je.h b/jsrc/je.h
index 1c01e4b8..b97e4bc6 100644
--- a/jsrc/je.h
+++ b/jsrc/je.h
@@ -187,6 +187,7 @@ extern DF1(jtjferase);
 extern F1(jtjfiles);
 extern DF1(jtjfperm1);
 extern DF1(jtjfread);
+extern DF1(jtjfreadchar);
 extern DF1(jtjfsize);
 extern DF1(jtjgetenv);
 extern F1(jtjgetx);
diff --git a/jsrc/x.c b/jsrc/x.c
index f4b3e5e5..57816e2b 100644
--- a/jsrc/x.c
+++ b/jsrc/x.c
@@ -125,6 +125,8 @@ void jtforeigninit(J jt){UI i;
  MN(1,11)  XPRIM(VERB, jtjiread,     0,            VASGSAFE,VF2NONE,1,   RMAX,RMAX);
  MN(1,12)  XPRIM(VERB, 0,            jtjiwrite,    VASGSAFE,VF2NONE,RMAX,RMAX,1   );
 
+ MN(1,111) XPRIM(VERB, jtjfreadchar, 0,            VASGSAFE,VF2NONE,0,   RMAX,RMAX);
+
  MN(3,0)   XPRIM(VERB, jtstype,      0,            VASGSAFE,VF2NONE,RMAX,RMAX,RMAX);
  MN(3,10)  XPRIM(VERB, jttobase64,   0,            VASGSAFE,VF2NONE,RMAX,RMAX,RMAX);
  MN(3,11)  XPRIM(VERB, jtfrombase64, 0,            VASGSAFE,VF2NONE,RMAX,RMAX,RMAX);
diff --git a/jsrc/xf.c b/jsrc/xf.c
index 334d6643..42c157f2 100644
--- a/jsrc/xf.c
+++ b/jsrc/xf.c
@@ -55,6 +55,54 @@ static int rmdir2(const char *dir);
 static int rmdir2(J jt, const wchar_t *dir);
 #endif
 
+// For 1!:111 character input
+#if SY_WIN32
+#include <conio.h>
+#else
+#include <termios.h>
+#endif
+
+// Global state for 1!:111 character input function
+static I global_echo_disabled = 0;  // 0=normal echo, 1=echo disabled globally
+
+// Global terminal state management
+#if !SY_WIN32
+static struct termios original_tio;
+static I terminal_settings_saved = 0;
+static I terminal_in_raw_mode = 0;
+#endif
+
+// Terminal mode helper functions for 1!:111
+#if !SY_WIN32
+static void set_terminal_raw_mode(I enable_echo) {
+    if (!terminal_settings_saved) {
+        if (tcgetattr(STDIN_FILENO, &original_tio) == 0) {
+            terminal_settings_saved = 1;
+        } else {
+            return;
+        }
+    }
+    struct termios new_tio = original_tio;
+    new_tio.c_lflag &= ~ICANON;
+    if (enable_echo) {
+        new_tio.c_lflag |= ECHO;
+    } else {
+        new_tio.c_lflag &= ~ECHO;
+    }
+    new_tio.c_cc[VMIN] = 1;
+    new_tio.c_cc[VTIME] = 0;
+    if (tcsetattr(STDIN_FILENO, TCSANOW, &new_tio) == 0) {
+        terminal_in_raw_mode = 1;
+    }
+}
+
+static void restore_terminal_mode(void) {
+    if (terminal_settings_saved && terminal_in_raw_mode) {
+        tcsetattr(STDIN_FILENO, TCSANOW, &original_tio);
+        terminal_in_raw_mode = 0;
+    }
+}
+#endif
 
 #if SY_64
 static I fsize(F f){
@@ -205,6 +253,96 @@ DF1(jtjfsize){F12IP;B b;F f;I m;
  RE(0); EPILOG(sc(m));   // check error, return mtm if none
 }
 
+/* 1!:111 - Character input and output control function
+ *
+ * MODES:
+ *   (1!:111) 1   - Echo mode: read character and display it as typed
+ *   (1!:111) 0   - Silent mode: read character without echo (global setting)
+ *   (1!:111) _1  - Restore mode: return terminal to normal echo behavior
+ *   (1!:111) 2   - Flush mode: flush stdout buffer for immediate display
+ *
+ * USAGE EXAMPLES:
+ *   c =: (1!:111) 1     NB. Read character with echo (normal input)
+ *   c =: (1!:111) 0     NB. First call enters global silent mode
+ *   (1!:111) _1         NB. Restore normal terminal echo
+ *   (1!:111) 2          NB. Flush stdout - text appears immediately
+ */
+DF1(jtjfreadchar){F12IP;A z;C c;I mode;
+ ASSERT(!JT(jt,seclev),EVSECURE)
+ F1RANK(0,jtjfreadchar,self);
+ PROLOG(000);
+
+ if(!AN(w)) {
+  mode = 0;
+ } else {
+  RE(mode = i0(w));
+  ASSERT(mode==1||mode==0||mode==-1||mode==2,EVDOMAIN);
+ }
+
+ if(mode == 2) {
+  fflush(stdout);
+  EPILOG(sc(2));
+ }
+
+ if(mode == -1) {
+  global_echo_disabled = 0;
+#if !SY_WIN32
+  restore_terminal_mode();
+#endif
+  EPILOG(sc(-1));
+ }
+
+ if(mode == 0) {
+  if(!global_echo_disabled) {
+   global_echo_disabled = 1;
+#if !SY_WIN32
+   set_terminal_raw_mode(0);
+#endif
+  }
+ }
+
+ I echo_mode = (mode == 1) ? 1 : 0;
+
+#if SY_WIN32
+ if(echo_mode) {
+  c = _getche();
+  if(c == 0 || c == (C)224) { c = _getche(); }
+ } else {
+  c = _getch();
+  if(c == 0 || c == (C)224) { c = _getch(); }
+ }
+#else
+ if(terminal_in_raw_mode) {
+  if(read(STDIN_FILENO, &c, 1) != 1) { c = 0; }
+ } else {
+  struct termios old_tio, new_tio;
+  if(tcgetattr(STDIN_FILENO, &old_tio) != 0) {
+   jsignal(EVFACE);
+   R 0;
+  }
+  new_tio = old_tio;
+  new_tio.c_lflag &= ~ICANON;
+  if(echo_mode) {
+   new_tio.c_lflag |= ECHO;
+  } else {
+   new_tio.c_lflag &= ~ECHO;
+  }
+  new_tio.c_cc[VMIN] = 1;
+  new_tio.c_cc[VTIME] = 0;
+  if(tcsetattr(STDIN_FILENO, TCSANOW, &new_tio) != 0) {
+   jsignal(EVFACE);
+   R 0;
+  }
+  if(read(STDIN_FILENO, &c, 1) != 1) { c = 0; }
+  tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
+ }
+#endif
+
+ GATV0(z,LIT,1,1);
+ CAV1(z)[0] = c;
+ EPILOG(z);
+}
+
 // process index file arg for file number; return 0 if error or file name
 // if the result is non0, vfn has been called to lock the file#
 static F jtixf(J jt,A w){F f;

-- 
2.51.2

