Skip to content

Commit f82bcad

Browse files
committedMay 21, 2024
Fix ncurses rendering issues
1 parent 8afc51d commit f82bcad

File tree

1 file changed

+49
-41
lines changed

1 file changed

+49
-41
lines changed
 

‎wfb_ng/cli.py

+49-41
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,33 @@
3737

3838
_orig_stdout = sys.stdout
3939

40+
4041
def set_window_title(s):
4142
print("\033]2;%s\007" % (s,), file=_orig_stdout)
4243

43-
# Workarond for ncurses bug that show error on output to the last position on the screen
4444

45-
def ignore_curses_err(f, *args, **kwargs):
46-
try:
47-
return f(*args, **kwargs)
48-
except curses.error:
49-
pass
45+
def ignore_curses_err(f):
46+
def _f(*args, **kwargs):
47+
try:
48+
return f(*args, **kwargs)
49+
except curses.error:
50+
pass
51+
return _f
52+
53+
54+
@ignore_curses_err
55+
def addstr_noerr(window, y, x, s, *attrs):
56+
for i, c in enumerate(s, x):
57+
window.addch(y, i, c, *attrs)
5058

51-
def addcstr(window, s, attrs=0):
59+
60+
def addstr_centered(window, s, attrs=0):
5261
h, w = window.getmaxyx()
53-
addstr(window, h // 2, max((w - len(s)) // 2, 0), s, attrs)
62+
addstr_noerr(window, h // 2, max((w - len(s)) // 2, 0), s, attrs)
63+
5464

55-
def addmcstr(window, y, x, s, attrs=0):
65+
@ignore_curses_err
66+
def addstr_markup(window, y, x, s, attrs=0):
5667
for c in s:
5768
if c == '{':
5869
attrs |= curses.A_BOLD
@@ -86,17 +97,14 @@ def rectangle(win, uly, ulx, lry, lrx):
8697
win.vline(uly+1, lrx, curses.ACS_VLINE, lry - uly - 1)
8798
win.addch(uly, ulx, curses.ACS_ULCORNER)
8899
win.addch(uly, lrx, curses.ACS_URCORNER)
89-
ignore_curses_err(win.addch, lry, lrx, curses.ACS_LRCORNER)
90-
win.addch(lry, ulx, curses.ACS_LLCORNER)
91100

92-
93-
def addstr(window, y, x, s, *attrs):
101+
# Workarond for ncurses bug that show error on output to the last position on the screen
94102
try:
95-
for i, c in enumerate(s, x):
96-
window.addch(y, i, c, *attrs)
103+
win.addch(lry, lrx, curses.ACS_LRCORNER)
97104
except curses.error:
98105
pass
99106

107+
win.addch(lry, ulx, curses.ACS_LLCORNER)
100108

101109

102110
def human_rate(r):
@@ -140,7 +148,7 @@ def draw_rx(self, attrs):
140148
return
141149

142150
window.erase()
143-
addstr(window, 0, 0, ' pkt/s pkt', curses.A_BOLD)
151+
addstr_markup(window, 0, 0, ' {pkt/s pkt}')
144152

145153
msg_l = (('{recv} %4d$ (%d)' % tuple(p['all']), 0),
146154
('{udp} %4d$ (%d)' % tuple(p['out']), 0),
@@ -152,32 +160,32 @@ def draw_rx(self, attrs):
152160
ymax = window.getmaxyx()[0]
153161
for y, (msg, attr) in enumerate(msg_l, 1):
154162
if y < ymax:
155-
addmcstr(window, y, 0, msg, attr)
163+
addstr_markup(window, y, 0, msg, attr)
164+
156165

157-
window.addstr(0, 20, 'Flow: ', curses.A_BOLD)
158-
window.addstr('%s -> %s ' % \
159-
(human_rate(p['all_bytes'][0]),
160-
human_rate(p['out_bytes'][0])))
166+
flow_str = '{Flow:} %s -> %s ' % \
167+
(human_rate(p['all_bytes'][0]),
168+
human_rate(p['out_bytes'][0]))
161169

162170
if session_d:
163-
window.addstr('FEC: ', curses.A_BOLD)
164-
window.addstr('%(fec_k)d/%(fec_n)d' % (session_d))
171+
flow_str += 'FEC: %(fec_k)d/%(fec_n)d' % (session_d)
165172

173+
addstr_markup(window, 0, 20, flow_str)
166174

167175
if stats_d:
168-
addmcstr(window, 2, 20, '{Freq MCS BW [ANT] pkt/s} {RSSI} [dBm] {SNR} [dB]')
176+
addstr_markup(window, 2, 20, '{Freq MCS BW [ANT] pkt/s} {RSSI} [dBm] {SNR} [dB]')
169177
for y, (((freq, mcs_index, bandwith), ant_id), v) in enumerate(sorted(stats_d.items()), 3):
170178
pkt_s, rssi_min, rssi_avg, rssi_max, snr_min, snr_avg, snr_max = v
171179
if y < ymax:
172180
active_tx = (ant_id >> 8) == tx_ant
173-
addmcstr(window, y, 20, '%04d %3d %2d %s%04x%s %4d %3d < {%3d} < %3d %3d < {%3d} < %3d' % \
181+
addstr_markup(window, y, 20, '%04d %3d %2d %s%04x%s %4d %3d < {%3d} < %3d %3d < {%3d} < %3d' % \
174182
(freq, mcs_index, bandwith,
175183
'{' if active_tx else '', ant_id, '}' if active_tx else '',
176184
pkt_s,
177185
rssi_min, rssi_avg, rssi_max,
178186
snr_min, snr_avg, snr_max), 0 if active_tx else curses.A_DIM)
179187
else:
180-
addstr(window, 2, 20, '[No data]', curses.A_REVERSE)
188+
addstr_noerr(window, 2, 20, '[No data]', curses.A_REVERSE)
181189

182190
window.refresh()
183191

@@ -191,7 +199,7 @@ def draw_tx(self, attrs):
191199
return
192200

193201
window.erase()
194-
addstr(window, 0, 0, ' pkt/s pkt', curses.A_BOLD)
202+
addstr_noerr(window, 0, 0, ' pkt/s pkt', curses.A_BOLD)
195203

196204
msg_l = (('{sent} %4d$ (%d)' % tuple(p['injected']), 0),
197205
('{udp} %4d$ (%d)' % tuple(p['incoming']), 0),
@@ -202,22 +210,22 @@ def draw_tx(self, attrs):
202210
ymax = window.getmaxyx()[0]
203211
for y, (msg, attr) in enumerate(msg_l, 1):
204212
if y < ymax:
205-
addmcstr(window, y, 0, msg, attr)
213+
addstr_markup(window, y, 0, msg, attr)
206214

207-
window.addstr(0, 20, 'Flow: ', curses.A_BOLD)
208-
window.addstr('%s -> %s' % \
209-
(human_rate(p['incoming_bytes'][0]),
210-
human_rate(p['injected_bytes'][0])))
215+
addstr_markup(window, 0, 20,
216+
'{Flow:} %s -> %s' % \
217+
(human_rate(p['incoming_bytes'][0]),
218+
human_rate(p['injected_bytes'][0])))
211219

212220
if latency_d:
213-
addmcstr(window, 2, 20, '{[ANT] pkt/s} {Injection} [us]')
221+
addstr_markup(window, 2, 20, '{[ANT] pkt/s} {Injection} [us]')
214222
for y, (k, v) in enumerate(sorted(latency_d.items()), 3):
215223
k = int(k) # json doesn't support int keys
216224
injected, dropped, lat_min, lat_avg, lat_max = v
217225
if y < ymax:
218-
addmcstr(window, y, 21, '{%02x}(xx) %4d %4d < {%4d} < %4d' % (k >> 8, injected, lat_min, lat_avg, lat_max))
226+
addstr_markup(window, y, 21, '{%02x}(XX) %4d %4d < {%4d} < %4d' % (k >> 8, injected, lat_min, lat_avg, lat_max))
219227
else:
220-
addstr(window, 2, 20, '[No data]', curses.A_REVERSE)
228+
addstr_noerr(window, 2, 20, '[No data]', curses.A_REVERSE)
221229

222230

223231
window.refresh()
@@ -244,7 +252,7 @@ def init_windows(self):
244252

245253
if not service_list:
246254
rectangle(self.stdscr, 0, 0, height - 1, width - 1)
247-
addstr(self.stdscr, 0, 3, '[%s not configured]' % (self.profile,), curses.A_REVERSE)
255+
addstr_noerr(self.stdscr, 0, 3, '[%s not configured]' % (self.profile,), curses.A_REVERSE)
248256
self.stdscr.refresh()
249257
return
250258

@@ -287,7 +295,7 @@ def init_windows(self):
287295
window.scrollok(1)
288296

289297
rectangle(self.stdscr, hoff_int, xoff, hoff_int + wh - 1, xoff + ww)
290-
addstr(self.stdscr, hoff_int, 3 + xoff, '[%s: %s %s]' % (txrx.upper(), self.profile, name), curses.A_BOLD)
298+
addstr_noerr(self.stdscr, hoff_int, 3 + xoff, '[%s: %s %s]' % (txrx.upper(), self.profile, name), curses.A_BOLD)
291299

292300
self.windows['%s %s' % (name, txrx)] = window
293301
whl.append(wh)
@@ -300,15 +308,15 @@ def startedConnecting(self, connector):
300308

301309
for window in self.windows.values():
302310
window.erase()
303-
addcstr(window, 'Connecting...', curses.A_DIM)
311+
addstr_centered(window, 'Connecting...', curses.A_DIM)
304312
window.refresh()
305313

306314
def buildProtocol(self, addr):
307315
set_window_title('Connected to %s' % (addr,))
308316

309317
for window in self.windows.values():
310318
window.erase()
311-
addcstr(window, 'Waiting for data...', curses.A_DIM)
319+
addstr_centered(window, 'Waiting for data...', curses.A_DIM)
312320
window.refresh()
313321

314322
self.resetDelay()
@@ -321,7 +329,7 @@ def clientConnectionLost(self, connector, reason):
321329

322330
for window in self.windows.values():
323331
window.erase()
324-
addcstr(window, '[Connection lost]', curses.A_REVERSE)
332+
addstr_centered(window, '[Connection lost]', curses.A_REVERSE)
325333
window.refresh()
326334

327335
ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
@@ -331,7 +339,7 @@ def clientConnectionFailed(self, connector, reason):
331339

332340
for window in self.windows.values():
333341
window.erase()
334-
addcstr(window, '[Connection failed]', curses.A_REVERSE)
342+
addstr_centered(window, '[Connection failed]', curses.A_REVERSE)
335343
window.refresh()
336344

337345
ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)

0 commit comments

Comments
 (0)