1 package com.guinetik.hexafun.examples.sysmon;
2
3 import static com.guinetik.hexafun.examples.tui.Ansi.*;
4
5 import com.guinetik.hexafun.HexaApp;
6 import com.guinetik.hexafun.examples.tui.View;
7 import java.io.FileInputStream;
8 import java.io.IOException;
9 import java.io.InputStream;
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 public class SysmonTUI {
30
31
32
33
34
35 private static final int REFRESH_INTERVAL_MS = 2000;
36
37 private final View<SysmonState> screen;
38 private final HexaApp app;
39 private InputStream ttyInput;
40
41 public SysmonTUI() {
42 this.app = SysmonApp.createApp(new OshiMetricsProvider());
43 this.screen = SysmonView.screen();
44 }
45
46 private void render(SysmonState state) {
47 System.out.print(screen.apply(state));
48 System.out.flush();
49 }
50
51
52
53
54
55 private int readKeyWithTimeout(int timeoutMs) {
56 try {
57 long deadline = System.currentTimeMillis() + timeoutMs;
58 while (System.currentTimeMillis() < deadline) {
59 if (ttyInput.available() > 0) {
60 return ttyInput.read();
61 }
62 Thread.sleep(50);
63 }
64 } catch (IOException | InterruptedException ignored) {}
65 return -1;
66 }
67
68
69
70
71 private void setRawMode() {
72
73 System.out.print(ALT_SCREEN_ON + HIDE_CURSOR);
74 System.out.flush();
75 try {
76 new ProcessBuilder("stty", "-icanon", "-echo")
77 .inheritIO()
78 .start()
79 .waitFor();
80 } catch (Exception ignored) {}
81 }
82
83
84
85
86 private void restoreTerminal() {
87
88 System.out.print(SHOW_CURSOR + ALT_SCREEN_OFF);
89 System.out.flush();
90 try {
91 new ProcessBuilder("stty", "sane").inheritIO().start().waitFor();
92 } catch (Exception ignored) {}
93 try {
94 if (ttyInput != null) ttyInput.close();
95 } catch (Exception ignored) {}
96 }
97
98
99
100
101 public void run() {
102 SysmonState state = SysmonState.initial(app);
103
104
105 try {
106 ttyInput = new FileInputStream("/dev/tty");
107 } catch (Exception e) {
108
109 ttyInput = System.in;
110 }
111
112
113 setRawMode();
114 Runtime.getRuntime().addShutdownHook(new Thread(this::restoreTerminal));
115
116 try {
117 while (state.running()) {
118 render(state);
119 int key = readKeyWithTimeout(REFRESH_INTERVAL_MS);
120
121 if (key == -1) {
122
123 state = state.refresh();
124 } else {
125 state = processKey(state, key);
126 }
127 }
128 render(state);
129 } finally {
130 restoreTerminal();
131 }
132 }
133
134
135
136
137 private SysmonState processKey(SysmonState state, int key) {
138 char c = Character.toLowerCase((char) key);
139
140 return switch (c) {
141 case 'q', 3 -> state.stop();
142 case '1' -> state.withFormat(SysmonFormat.TUI);
143 case '2' -> state.withFormat(SysmonFormat.CLI);
144 case '3' -> state.withFormat(SysmonFormat.JSON);
145 case '4' -> state.withFormat(SysmonFormat.PROMETHEUS);
146 case 'r', ' ', '\n' -> state.refresh();
147 default -> state;
148 };
149 }
150
151
152
153
154
155 public static void main(String[] args) {
156 new SysmonTUI().run();
157 }
158 }