1 package com.guinetik.hexafun.examples.tui;
2
3 import java.io.BufferedReader;
4 import java.io.InputStreamReader;
5
6
7
8
9
10
11 public final class HexaTerminal {
12
13 private HexaTerminal() {}
14
15
16 public static final int MIN_WIDTH = 60;
17
18
19 public static final int DEFAULT_WIDTH = 120;
20
21
22 public static final int MIN_HEIGHT = 20;
23
24
25 public static final int DEFAULT_HEIGHT = 40;
26
27
28 private static final boolean IS_WINDOWS = System.getProperty("os.name", "")
29 .toLowerCase().contains("win");
30
31
32
33
34
35
36 public static int detectWidth() {
37 return detectWidth(MIN_WIDTH, DEFAULT_WIDTH);
38 }
39
40
41
42
43
44
45
46
47 public static int detectWidth(int minWidth, int defaultWidth) {
48 int[] size = detectSize();
49 if (size != null && size[1] > 0) {
50 return Math.max(minWidth, size[1]);
51 }
52 return defaultWidth;
53 }
54
55
56
57
58
59
60 public static int detectHeight() {
61 return detectHeight(MIN_HEIGHT, DEFAULT_HEIGHT);
62 }
63
64
65
66
67
68
69
70
71 public static int detectHeight(int minHeight, int defaultHeight) {
72 int[] size = detectSize();
73 if (size != null && size[0] > 0) {
74 return Math.max(minHeight, size[0]);
75 }
76 return defaultHeight;
77 }
78
79
80
81
82
83
84 public static int[] detectSize() {
85 return IS_WINDOWS ? detectSizeWindows() : detectSizeUnix();
86 }
87
88
89
90
91 private static int[] detectSizeUnix() {
92
93 try {
94 ProcessBuilder pb = new ProcessBuilder("stty", "size");
95 pb.inheritIO();
96 pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
97 pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
98 pb.redirectError(ProcessBuilder.Redirect.PIPE);
99
100 Process p = pb.start();
101 String line = new BufferedReader(
102 new InputStreamReader(p.getInputStream())
103 ).readLine();
104
105 int exitCode = p.waitFor();
106 if (exitCode == 0 && line != null && !line.isBlank()) {
107 String[] parts = line.trim().split("\\s+");
108 if (parts.length >= 2) {
109 int rows = Integer.parseInt(parts[0]);
110 int cols = Integer.parseInt(parts[1]);
111 if (rows > 0 && cols > 0) {
112 return new int[]{rows, cols};
113 }
114 }
115 }
116 } catch (Exception ignored) {}
117
118
119 try {
120 int cols = runCommandInt("tput", "cols");
121 int rows = runCommandInt("tput", "lines");
122 if (cols > 0 && rows > 0) {
123 return new int[]{rows, cols};
124 }
125 } catch (Exception ignored) {}
126
127
128 return detectSizeFromEnv();
129 }
130
131
132
133
134 private static int[] detectSizeWindows() {
135
136 try {
137 ProcessBuilder pb = new ProcessBuilder(
138 "powershell", "-NoProfile", "-Command",
139 "[Console]::WindowWidth; [Console]::WindowHeight"
140 );
141 pb.redirectErrorStream(true);
142 Process p = pb.start();
143
144 BufferedReader reader = new BufferedReader(
145 new InputStreamReader(p.getInputStream())
146 );
147 String widthLine = reader.readLine();
148 String heightLine = reader.readLine();
149 p.waitFor();
150
151 if (widthLine != null && heightLine != null) {
152 int cols = Integer.parseInt(widthLine.trim());
153 int rows = Integer.parseInt(heightLine.trim());
154 if (rows > 0 && cols > 0) {
155 return new int[]{rows, cols};
156 }
157 }
158 } catch (Exception ignored) {}
159
160
161 try {
162 ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "mode", "con");
163 pb.redirectErrorStream(true);
164 Process p = pb.start();
165
166 BufferedReader reader = new BufferedReader(
167 new InputStreamReader(p.getInputStream())
168 );
169
170 int cols = -1, rows = -1;
171 String line;
172 while ((line = reader.readLine()) != null) {
173 line = line.toLowerCase();
174 if (line.contains("columns") || line.contains("cols")) {
175 cols = extractNumber(line);
176 } else if (line.contains("lines")) {
177 rows = extractNumber(line);
178 }
179 }
180 p.waitFor();
181
182 if (cols > 0 && rows > 0) {
183 return new int[]{rows, cols};
184 }
185 } catch (Exception ignored) {}
186
187
188 return detectSizeFromEnv();
189 }
190
191
192
193
194 private static int[] detectSizeFromEnv() {
195 String colsEnv = System.getenv("COLUMNS");
196 String rowsEnv = System.getenv("LINES");
197 if (colsEnv != null && rowsEnv != null) {
198 try {
199 int cols = Integer.parseInt(colsEnv.trim());
200 int rows = Integer.parseInt(rowsEnv.trim());
201 if (rows > 0 && cols > 0) {
202 return new int[]{rows, cols};
203 }
204 } catch (NumberFormatException ignored) {}
205 }
206 return null;
207 }
208
209
210
211
212 private static int runCommandInt(String... cmd) throws Exception {
213 ProcessBuilder pb = new ProcessBuilder(cmd);
214 pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
215 pb.redirectErrorStream(true);
216 Process p = pb.start();
217 String line = new BufferedReader(
218 new InputStreamReader(p.getInputStream())
219 ).readLine();
220 p.waitFor();
221 return line != null ? Integer.parseInt(line.trim()) : -1;
222 }
223
224
225
226
227 private static int extractNumber(String line) {
228 StringBuilder sb = new StringBuilder();
229 for (char c : line.toCharArray()) {
230 if (Character.isDigit(c)) {
231 sb.append(c);
232 } else if (sb.length() > 0) {
233 break;
234 }
235 }
236 try {
237 return sb.length() > 0 ? Integer.parseInt(sb.toString()) : -1;
238 } catch (NumberFormatException e) {
239 return -1;
240 }
241 }
242
243
244
245
246
247
248 public static String getSizeString() {
249 int[] size = detectSize();
250 if (size != null) {
251 return size[0] + "x" + size[1];
252 }
253 return "unknown";
254 }
255
256
257
258
259 public static boolean isWindows() {
260 return IS_WINDOWS;
261 }
262 }