10
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
12
config = configparser.ConfigParser()
13
ini_path = os.path.join(os.path.dirname(__file__), 'arma_terminal_real.ini')
16
except Exception as e:
17
logging.error(f"Error reading configuration: {e}")
21
CONSOLE_LOG = config.get('Paths', 'console_log')
22
COMMANDS_FILE = config.get('Paths', 'commands_file')
23
COMMAND_PREFIX = config.get('Settings', 'command_prefix')
24
MAX_LOG_LINES = config.getint('Settings', 'max_log_lines', fallback=1000)
25
except Exception as e:
26
logging.error(f"Error retrieving config values: {e}")
29
def tail_log(lines, lock, stop_event):
30
with open(CONSOLE_LOG, 'r', encoding='utf-8', errors='ignore') as f:
31
f.seek(0, os.SEEK_END)
32
while not stop_event.is_set():
42
if len(lines) > MAX_LOG_LINES:
43
lines[:] = lines[-MAX_LOG_LINES:]
45
def draw_scrollbar(win, top_line, total_lines, height):
46
if total_lines <= height:
48
scroll_height = height - 2
49
bar_height = max(1, int(scroll_height * (height / total_lines)))
50
top_pos = int(scroll_height * (top_line / total_lines))
51
for i in range(scroll_height):
52
char = '█' if top_pos <= i < top_pos + bar_height else '│'
54
win.addch(i + 1, win.getmaxyx()[1] - 2, char)
58
def draw_screen(stdscr, lines, lock):
62
height, width = stdscr.getmaxyx()
64
log_win = curses.newwin(log_h, width, 0, 0)
65
input_win = curses.newwin(3, width, log_h, 0)
75
drag_start_offset = None
77
curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION)
81
raw_lines = lines[-MAX_LOG_LINES:]
83
for line in raw_lines:
84
wrapped.extend(textwrap.wrap(line, width - 3) or [''])
86
visible_lines = log_h - 2
87
max_offset = max(len(wrapped) - visible_lines, 0)
88
scroll_offset = min(scroll_offset, max_offset)
90
start_idx = max(0, len(wrapped) - visible_lines - scroll_offset)
91
display = wrapped[start_idx:start_idx + visible_lines]
95
for idx, disp in enumerate(display):
96
log_win.addnstr(idx + 1, 1, disp, width - 3)
97
draw_scrollbar(log_win, start_idx, len(wrapped), log_h)
102
prompt = "> " + input_str
103
input_win.addnstr(1, 1, prompt, width - 2)
104
input_win.move(1, 2 + cursor_x)
108
ch = stdscr.get_wch()
113
if ch == curses.KEY_RESIZE:
114
height, width = stdscr.getmaxyx()
116
stdscr.erase(); stdscr.refresh()
117
log_win.resize(log_h, width); log_win.mvwin(0, 0)
118
input_win.resize(3, width); input_win.mvwin(log_h, 0)
121
if ch == curses.KEY_UP:
123
history_index = max(history_index - 1, 0)
124
input_str = history[history_index]
125
cursor_x = len(input_str)
127
if ch == curses.KEY_DOWN:
129
history_index = min(history_index + 1, len(history))
130
input_str = history[history_index] if history_index < len(history) else ""
131
cursor_x = len(input_str)
134
if ch == curses.KEY_LEFT:
135
cursor_x = max(0, cursor_x - 1)
137
if ch == curses.KEY_RIGHT:
138
cursor_x = min(len(input_str), cursor_x + 1)
141
if ch == curses.KEY_PPAGE:
142
scroll_offset = min(scroll_offset + 3, max_offset)
144
if ch == curses.KEY_NPAGE:
145
scroll_offset = max(scroll_offset - 3, 0)
148
if ch == curses.KEY_END:
152
if ch == curses.KEY_MOUSE:
154
_, mx, my, _, bstate = curses.getmouse()
155
if bstate & curses.BUTTON1_PRESSED:
156
if 1 <= my < log_h - 1 and mx == width - 2:
159
drag_start_offset = scroll_offset
160
elif bstate & curses.BUTTON1_RELEASED:
162
elif dragging and drag_start_y is not None:
163
dy = my - drag_start_y
164
scroll_offset = min(max(drag_start_offset - dy, 0), max_offset)
165
elif bstate & curses.BUTTON4_PRESSED:
166
scroll_offset = min(scroll_offset + 1, max_offset)
167
elif bstate & curses.BUTTON5_PRESSED:
168
scroll_offset = max(scroll_offset - 1, 0)
173
if isinstance(ch, str) and ch.isprintable():
174
input_str = input_str[:cursor_x] + ch + input_str[cursor_x:]
176
elif ch in (curses.KEY_BACKSPACE, '\b', '\x7f'):
178
input_str = input_str[:cursor_x - 1] + input_str[cursor_x:]
180
elif ch == curses.KEY_DC:
181
if cursor_x < len(input_str):
182
input_str = input_str[:cursor_x] + input_str[cursor_x + 1:]
184
cmd = input_str.strip()
189
history_index = len(history)
190
if cmd.lower() in ("exit", "quit"):
192
full_cmd = f"{COMMAND_PREFIX} {cmd}" if COMMAND_PREFIX else cmd
194
with open(COMMANDS_FILE, 'a', encoding='utf-8') as f:
195
f.write(full_cmd + "\n")
198
except Exception as e:
200
lines.append(f"→ ERROR writing command: {e}")
203
lines.append(f"→ SENT: {full_cmd}")
204
elif ch in (curses.KEY_EXIT, '\x1b'):
209
lock = threading.Lock()
210
stop_event = threading.Event()
212
t = threading.Thread(target=tail_log, args=(lines, lock, stop_event), daemon=True)
216
draw_screen(stdscr, lines, lock)
221
if __name__ == "__main__":
b'\\ No newline at end of file'