-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathTermReadKey.java
More file actions
328 lines (275 loc) · 11.2 KB
/
TermReadKey.java
File metadata and controls
328 lines (275 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
package org.perlonjava.runtime.perlmodule;
import org.perlonjava.runtime.runtimetypes.*;
import org.perlonjava.runtime.terminal.LinuxTerminalHandler;
import org.perlonjava.runtime.terminal.MacOSTerminalHandler;
import org.perlonjava.runtime.terminal.TerminalHandler;
import org.perlonjava.runtime.terminal.WindowsTerminalHandler;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarCache.scalarUndef;
import static org.perlonjava.runtime.runtimetypes.RuntimeScalarType.INTEGER;
/**
* The TermReadKey class provides functionalities similar to the Perl Term::ReadKey module.
* This is a wrapper that delegates to platform-specific implementations.
*/
public class TermReadKey extends PerlModuleBase {
private static final Map<String, Integer> terminalModes = new HashMap<>();
private static final TerminalHandler handler;
static {
// Initialize terminal mode mappings
terminalModes.put("restore", TerminalHandler.RESTORE_MODE);
terminalModes.put("normal", TerminalHandler.NORMAL_MODE);
terminalModes.put("noecho", TerminalHandler.NOECHO_MODE);
terminalModes.put("cbreak", TerminalHandler.CBREAK_MODE);
terminalModes.put("raw", TerminalHandler.RAW_MODE);
terminalModes.put("ultra-raw", TerminalHandler.ULTRA_RAW_MODE);
// Detect platform and create appropriate handler
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("win")) {
handler = new WindowsTerminalHandler();
} else if (osName.contains("mac")) {
handler = new MacOSTerminalHandler();
} else {
handler = new LinuxTerminalHandler();
}
// Register shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (handler != null) {
handler.cleanup();
}
}));
}
/**
* Constructor initializes the module.
*/
public TermReadKey() {
super("Term::ReadKey");
}
/**
* Static initializer to set up the module.
*/
public static void initialize() {
TermReadKey readkey = new TermReadKey();
// Initialize as an Exporter module
readkey.initializeExporter();
// Define EXPORT array with commonly exported functions
readkey.defineExport("EXPORT",
"ReadMode", "ReadKey", "ReadLine", "GetTerminalSize",
"SetTerminalSize", "GetSpeed", "GetControlChars", "SetControlChars");
// Define EXPORT_OK array with all exportable functions
readkey.defineExport("EXPORT_OK");
try {
readkey.registerMethod("ReadMode", "readMode", "$;$");
readkey.registerMethod("ReadKey", "readKey", ";$$"); // ReadKey([timeout [, $fh]])
readkey.registerMethod("ReadLine", "readLine", ";$$"); // ReadLine([timeout [, $fh]])
readkey.registerMethod("GetTerminalSize", "getTerminalSize", ";$");
readkey.registerMethod("SetTerminalSize", "setTerminalSize", "$;$");
readkey.registerMethod("GetSpeed", "getSpeed", ";$");
readkey.registerMethod("GetControlChars", "getControlChars", ";$");
readkey.registerMethod("SetControlChars", "setControlChars", "\\@;$");
} catch (NoSuchMethodException e) {
System.err.println("Warning: Missing Term::ReadKey method: " + e.getMessage());
}
}
/**
* Sets the terminal input mode.
* ReadMode(mode, [filehandle])
*/
public static RuntimeList readMode(RuntimeArray args, int ctx) {
if (args.isEmpty()) {
return new RuntimeList(scalarUndef);
}
RuntimeScalar modeArg = args.get(0);
int mode;
if (modeArg.type == INTEGER) {
mode = modeArg.getInt();
} else {
String modeStr = modeArg.toString().toLowerCase();
mode = terminalModes.getOrDefault(modeStr, TerminalHandler.NORMAL_MODE);
}
// Get filehandle (defaults to STDIN)
RuntimeIO fh = RuntimeIO.stdin;
if (args.size() > 1) {
RuntimeScalar fileHandle = args.get(1);
fh = RuntimeIO.getRuntimeIO(fileHandle);
}
handler.setTerminalMode(mode, fh);
return new RuntimeList();
}
/**
* Reads a single character from the keyboard.
* ReadKey([timeout], [filehandle])
*/
public static RuntimeList readKey(RuntimeArray args, int ctx) {
double timeout = 0; // Default is blocking
RuntimeIO fh = RuntimeIO.stdin;
if (!args.isEmpty() && args.get(0).getDefinedBoolean()) {
timeout = args.get(0).getDouble();
}
if (args.size() > 1) {
RuntimeScalar fileHandle = args.get(1);
fh = RuntimeIO.getRuntimeIO(fileHandle);
}
// Flush output handles before blocking on input
RuntimeIO.flushFileHandles();
try {
char ch = handler.readSingleChar(timeout, fh);
if (ch == 0) {
return new RuntimeList(scalarUndef);
}
return new RuntimeList(new RuntimeScalar(String.valueOf(ch)));
} catch (IOException e) {
return new RuntimeList(scalarUndef);
}
}
/**
* Reads a line of input with timeout.
* ReadLine([timeout], [filehandle])
*/
public static RuntimeList readLine(RuntimeArray args, int ctx) {
double timeout = 0; // Default is blocking
RuntimeIO fh = RuntimeIO.stdin;
if (!args.isEmpty() && args.get(0).getDefinedBoolean()) {
timeout = args.get(0).getDouble();
}
if (args.size() > 1) {
RuntimeScalar fileHandle = args.get(1);
fh = RuntimeIO.getRuntimeIO(fileHandle);
}
// Flush output handles before blocking on input
RuntimeIO.flushFileHandles();
try {
String line = handler.readLineWithTimeout(timeout, fh);
if (line == null) {
return new RuntimeList(scalarUndef);
}
return new RuntimeList(new RuntimeScalar(line));
} catch (IOException e) {
return new RuntimeList(scalarUndef);
}
}
/**
* Gets the terminal size.
* GetTerminalSize([filehandle])
* Returns (width, height, xpixels, ypixels)
*/
public static RuntimeList getTerminalSize(RuntimeArray args, int ctx) {
RuntimeIO fh = RuntimeIO.stdout;
if (!args.isEmpty()) {
RuntimeScalar fileHandle = args.get(0);
fh = RuntimeIO.getRuntimeIO(fileHandle);
}
int[] size = handler.getTerminalSize(fh);
if (size == null) {
return new RuntimeList(); // Empty array for unsupported
}
RuntimeList result = new RuntimeList();
result.add(new RuntimeScalar(size[0])); // width (columns)
result.add(new RuntimeScalar(size[1])); // height (rows)
result.add(new RuntimeScalar(size[2])); // xpixels
result.add(new RuntimeScalar(size[3])); // ypixels
return result;
}
/**
* Sets the terminal size.
* SetTerminalSize(width, height, xpixels, ypixels, [filehandle])
*/
public static RuntimeList setTerminalSize(RuntimeArray args, int ctx) {
if (args.size() < 4) {
return new RuntimeList(new RuntimeScalar(-1));
}
int width = args.get(0).getInt();
int height = args.get(1).getInt();
int xpixels = args.get(2).getInt();
int ypixels = args.get(3).getInt();
RuntimeIO fh = RuntimeIO.stdout;
if (args.size() > 4) {
RuntimeScalar fileHandle = args.get(4);
fh = RuntimeIO.getRuntimeIO(fileHandle);
}
boolean success = handler.setTerminalSize(width, height, xpixels, ypixels, fh);
return new RuntimeList(new RuntimeScalar(success ? 0 : -1));
}
/**
* Gets the terminal speed.
* GetSpeed([filehandle])
* Returns (input_speed, output_speed)
*/
public static RuntimeList getSpeed(RuntimeArray args, int ctx) {
RuntimeIO fh = RuntimeIO.stdin;
if (!args.isEmpty()) {
RuntimeScalar fileHandle = args.get(0);
fh = RuntimeIO.getRuntimeIO(fileHandle);
}
int[] speeds = handler.getTerminalSpeed(fh);
if (speeds == null) {
return new RuntimeList(); // Empty array for unsupported
}
RuntimeList result = new RuntimeList();
result.add(new RuntimeScalar(speeds[0])); // input speed
result.add(new RuntimeScalar(speeds[1])); // output speed
return result;
}
/**
* Gets the terminal control characters.
* GetControlChars([filehandle])
* Returns an array containing key/value pairs suitable for a hash
*/
public static RuntimeList getControlChars(RuntimeArray args, int ctx) {
RuntimeIO fh = RuntimeIO.stdin;
if (!args.isEmpty()) {
RuntimeScalar fileHandle = args.get(0);
fh = RuntimeIO.getRuntimeIO(fileHandle);
}
Map<String, String> controlChars = handler.getControlChars(fh);
RuntimeList result = new RuntimeList();
// Return as array of key/value pairs suitable for hash
for (Map.Entry<String, String> entry : controlChars.entrySet()) {
result.add(new RuntimeScalar(entry.getKey()));
result.add(new RuntimeScalar(entry.getValue()));
}
return result;
}
/**
* Sets control characters.
* SetControlChars(array_ref, [filehandle])
*/
public static RuntimeList setControlChars(RuntimeArray args, int ctx) {
if (args.isEmpty()) {
throw new PerlCompilerException("SetControlChars requires array reference");
}
RuntimeScalar arrayRef = args.get(0);
if (arrayRef.type != RuntimeScalarType.ARRAYREFERENCE) {
throw new PerlCompilerException("First argument to SetControlChars must be array reference");
}
RuntimeArray controlArray = (RuntimeArray) arrayRef.value;
RuntimeIO fh = RuntimeIO.stdin;
if (args.size() > 1) {
RuntimeScalar fileHandle = args.get(1);
fh = RuntimeIO.getRuntimeIO(fileHandle);
}
// Convert array to map
Map<String, String> controlChars = new HashMap<>();
for (int i = 0; i < controlArray.size() - 1; i += 2) {
String key = controlArray.get(i).toString();
RuntimeScalar valueScalar = controlArray.get(i + 1);
String value;
if (valueScalar.type == INTEGER) {
int charCode = valueScalar.getInt();
if (charCode < 0 || charCode > 255) {
throw new PerlCompilerException("Control character value must be 0-255");
}
value = String.valueOf((char) charCode);
} else {
value = valueScalar.toString();
if (value.length() != 1) {
throw new PerlCompilerException("Control character must be single character");
}
}
controlChars.put(key, value);
}
handler.setControlChars(controlChars, fh);
return new RuntimeList();
}
}