ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
keyboard_handler.c
Go to the documentation of this file.
1
8#include "session/capture.h"
9#include "session/display.h"
13#include <ascii-chat/log/log.h>
16#include <string.h>
17#include <signal.h>
18
19#ifndef NDEBUG
21#endif
22
23/* ============================================================================
24 * Internal Helpers
25 * ============================================================================ */
26
28static double g_mute_saved_volume = 1.0;
29
31static const char *COLOR_FILTER_NAMES[COLOR_FILTER_COUNT] = {
32 "None", // COLOR_FILTER_NONE = 0
33 "Black", // COLOR_FILTER_BLACK = 1
34 "White", // COLOR_FILTER_WHITE = 2
35 "Green", // COLOR_FILTER_GREEN = 3
36 "Magenta", // COLOR_FILTER_MAGENTA = 4
37 "Fuchsia", // COLOR_FILTER_FUCHSIA = 5
38 "Orange", // COLOR_FILTER_ORANGE = 6
39 "Teal", // COLOR_FILTER_TEAL = 7
40 "Cyan", // COLOR_FILTER_CYAN = 8
41 "Pink", // COLOR_FILTER_PINK = 9
42 "Red", // COLOR_FILTER_RED = 10
43 "Yellow", // COLOR_FILTER_YELLOW = 11
44 "Rainbow", // COLOR_FILTER_RAINBOW = 12
45};
46
58static double clamp_volume(double volume) {
59 if (volume < 0.0) {
60 return 0.0;
61 }
62 if (volume > 1.0) {
63 return 1.0;
64 }
65 return volume;
66}
67
71static int next_color_mode(int current) {
72 // Cycle: TERM_COLOR_NONE → TERM_COLOR_16 → TERM_COLOR_256 → TERM_COLOR_TRUECOLOR → TERM_COLOR_NONE
73 switch (current) {
74 case 0: // TERM_COLOR_NONE
75 return 1; // TERM_COLOR_16
76 case 1: // TERM_COLOR_16
77 return 2; // TERM_COLOR_256
78 case 2: // TERM_COLOR_256
79 return 3; // TERM_COLOR_TRUECOLOR
80 default:
81 return 0; // Back to TERM_COLOR_NONE
82 }
83}
84
88static int next_render_mode(int current) {
89 // 3 render modes: FOREGROUND (0), BACKGROUND (1), HALF_BLOCK (2)
90 return (current + 1) % 3;
91}
92
96static int next_color_filter(int current) {
97 return (current + 1) % COLOR_FILTER_COUNT;
98}
99
100/* ============================================================================
101 * Keyboard Handler
102 * ============================================================================ */
103
105 // Debug: log all key codes to help identify unknown keys
106 if (key != KEY_NONE) {
107 log_info("KEYBOARD INPUT: code=%d (0x%02x) char='%c'", key, key, (key >= 32 && key < 127) ? key : '?');
108 }
109
110 switch ((int)key) {
111 // ===== HELP SCREEN TOGGLE =====
112 case KEY_QUESTION: {
113 log_info("✓✓✓ USER PRESSED ? KEY - HELP SCREEN TOGGLE ✓✓✓");
114 if (display) {
115 log_info("Toggling help screen (display=%p)", (void *)display);
116 keyboard_help_toggle(display);
117 // Render help screen immediately so user sees it
118 keyboard_help_render(display);
119 log_info("✓ Help screen toggle complete");
120 } else {
121 log_error("ERROR: Cannot toggle help - display context is NULL");
122 }
123 break;
124 }
125
126 // ===== HELP SCREEN CLOSE / QUIT =====
127 case KEY_ESCAPE: {
128 if (display && keyboard_help_is_active(display)) {
129 // Close help screen if it's active
130 keyboard_help_toggle(display);
132 } else {
133 // If help screen is not active, quit the app (like Ctrl-C)
134 // The signal handler will gracefully shutdown all modes (client, server, mirror, etc.)
135 raise(SIGINT);
136 }
137 break;
138 }
139
140 // ===== SEEK CONTROLS (file sources only) =====
141 case KEY_LEFT: { // Seek backward 30 seconds
142 if (capture) {
144 if (source && media_source_get_type(source) == MEDIA_SOURCE_FILE) {
145 double current_pos = media_source_get_position(source);
146 if (current_pos >= 0.0) {
147 double new_pos = current_pos - 30.0;
148 if (new_pos < 0.0) {
149 new_pos = 0.0;
150 }
151 asciichat_error_t err = media_source_seek(source, new_pos);
152 if (err == ASCIICHAT_OK) {
153 log_info("Seeked backward to %.1f seconds", new_pos);
154 }
155 void *audio_ctx = session_capture_get_audio_context(capture);
156 if (audio_ctx) {
158 }
159 }
160 }
161 }
162 break;
163 }
164
165 case KEY_RIGHT: { // Seek forward 30 seconds
166 if (capture) {
168 if (source && media_source_get_type(source) == MEDIA_SOURCE_FILE) {
169 double current_pos = media_source_get_position(source);
170 double duration = media_source_get_duration(source);
171 if (current_pos >= 0.0) {
172 double new_pos = current_pos + 30.0;
173 // Clamp to duration if known
174 if (duration > 0.0 && new_pos > duration) {
175 new_pos = duration;
176 }
177 asciichat_error_t err = media_source_seek(source, new_pos);
178 if (err == ASCIICHAT_OK) {
179 log_info("Seeked forward to %.1f seconds", new_pos);
180 }
181 void *audio_ctx = session_capture_get_audio_context(capture);
182 if (audio_ctx) {
184 }
185 }
186 }
187 }
188 break;
189 }
190
191 // ===== VOLUME CONTROLS =====
192 case KEY_DOWN: { // Decrease volume 10%
193 double current_volume = GET_OPTION(speakers_volume);
194 double new_volume = clamp_volume(current_volume - 0.1);
195 options_set_double("speakers_volume", new_volume);
196 double verify_volume = GET_OPTION(speakers_volume);
197 log_info("Volume DOWN: %.0f%% → %.0f%% (verified: %.0f%%)", current_volume * 100.0, new_volume * 100.0,
198 verify_volume * 100.0);
199 break;
200 }
201
202 case KEY_UP: { // Increase volume 10%
203 double current_volume = GET_OPTION(speakers_volume);
204 double new_volume = clamp_volume(current_volume + 0.1);
205 options_set_double("speakers_volume", new_volume);
206 double verify_volume = GET_OPTION(speakers_volume);
207 log_info("Volume UP: %.0f%% → %.0f%% (verified: %.0f%%)", current_volume * 100.0, new_volume * 100.0,
208 verify_volume * 100.0);
209 break;
210 }
211
212 // ===== PLAY/PAUSE CONTROL =====
213 case KEY_SPACE: { // Toggle play/pause
214 if (capture) {
216 if (source && media_source_get_type(source) == MEDIA_SOURCE_FILE) {
218 if (media_source_is_paused(source)) {
219 log_info("Paused");
220 } else {
221 log_info("Playing");
222 }
223 void *audio_ctx = session_capture_get_audio_context(capture);
224 if (audio_ctx) {
226 }
227 }
228 }
229 break;
230 }
231
232 // ===== COLOR MODE CONTROL =====
233 case KEY_C:
234 case 'C': {
235 int current_mode = (int)GET_OPTION(color_mode);
236 int next_mode = next_color_mode(current_mode);
237 options_set_int("color_mode", next_mode);
238
239 const char *mode_names[] = {"Mono", "16-color", "256-color", "Truecolor"};
240 if (next_mode >= 0 && next_mode <= 3) {
241 log_info("Color mode: %s", mode_names[next_mode]);
242 }
243 break;
244 }
245
246 // ===== MUTE CONTROL =====
247 case KEY_M:
248 case 'M': {
249 double current_volume = GET_OPTION(speakers_volume);
250 log_debug("Mute toggle: current_volume=%.2f, g_mute_saved_volume=%.2f, threshold=0.01", current_volume,
251 g_mute_saved_volume);
252
253 if (current_volume > 0.01) { // If not already muted
254 // Save current volume and mute
255 g_mute_saved_volume = current_volume;
256 options_set_double("speakers_volume", 0.0);
257 double verify = GET_OPTION(speakers_volume);
258 log_info("Muted: saved %.0f%%, set to 0%% (verified: %.2f)", g_mute_saved_volume * 100.0, verify);
259 } else {
260 // Restore previous volume
261 double restore_volume = g_mute_saved_volume > 0.0 ? g_mute_saved_volume : 0.5;
262 options_set_double("speakers_volume", restore_volume);
263 double verify = GET_OPTION(speakers_volume);
264 log_info("Unmuted: restored %.0f%% (verified: %.2f)", restore_volume * 100.0, verify);
265 }
266 break;
267 }
268
269 // ===== RENDER MODE CONTROL =====
270 case KEY_R:
271 case 'R': {
272 int current_mode = (int)GET_OPTION(render_mode);
273 int next_mode = next_render_mode(current_mode);
274 options_set_int("render_mode", next_mode);
275
276 const char *mode_names[] = {"Foreground", "Background", "Half-block"};
277 if (next_mode >= 0 && next_mode <= 2) {
278 log_info("Render mode: %s", mode_names[next_mode]);
279 }
280 break;
281 }
282
283 // ===== COLOR FILTER CONTROL =====
284 case KEY_F:
285 case 'F': {
286 int current_filter = (int)GET_OPTION(color_filter);
287 int next_filter = next_color_filter(current_filter);
288 options_set_int("color_filter", next_filter);
289
290 if (next_filter >= 0 && next_filter < (int)COLOR_FILTER_COUNT) {
291 log_info("Color filter: %s", COLOR_FILTER_NAMES[next_filter]);
292 }
293 break;
294 }
295
296 // ===== HORIZONTAL FLIP CONTROL =====
297 case 'X':
298 case 'x': {
299 log_info("✓ X KEY PRESSED - handling horizontal flip");
300 bool current_flip_x = (bool)GET_OPTION(flip_x);
301 options_set_bool("flip_x", !current_flip_x);
302 log_info("Horizontal flip: %s (was: %s)", !current_flip_x ? "enabled" : "disabled",
303 current_flip_x ? "enabled" : "disabled");
304 break;
305 }
306
307 // ===== VERTICAL FLIP CONTROL =====
308 case 'Y':
309 case 'y': {
310 bool current_flip_y = (bool)GET_OPTION(flip_y);
311 options_set_bool("flip_y", !current_flip_y);
312 log_info("Vertical flip: %s", !current_flip_y ? "enabled" : "disabled");
313 break;
314 }
315
316 // ===== MATRIX RAIN EFFECT CONTROL =====
317 case KEY_0: {
318 bool current_matrix = (bool)GET_OPTION(matrix_rain);
319 options_set_bool("matrix_rain", !current_matrix);
320 log_info("Matrix rain effect: %s", !current_matrix ? "enabled" : "disabled");
321 break;
322 }
323
324 // ===== FPS COUNTER TOGGLE =====
325 case KEY_MINUS: {
326 bool current = (bool)GET_OPTION(fps_counter);
327 options_set_bool("fps_counter", !current);
328 log_info("FPS counter: %s", !current ? "enabled" : "disabled");
329 break;
330 }
331
332 // ===== LOCK DEBUG (debug builds only) =====
333#ifndef NDEBUG
334 case KEY_BACKTICK: {
336 log_debug("Lock state dump triggered via backtick key");
337 break;
338 }
339#endif
340
341 default:
342 // Unknown key - log it for debugging
343 if (key != KEY_NONE) {
344 log_info("UNKNOWN KEY: code=%d (0x%02x) char='%c'", key, key, (key >= 32 && key < 127) ? key : '?');
345 }
346 break;
347 }
348
349 // Re-render help screen if it's active to show updated settings
350 if (display && keyboard_help_is_active(display)) {
351 keyboard_help_render(display);
352 }
353}
void * session_capture_get_audio_context(session_capture_ctx_t *ctx)
Get the audio context for playback control.
📹 Unified media capture abstraction for session-based video sources
void audio_flush_playback_buffers(audio_context_t *ctx)
Flush all audio buffers to synchronize with seek operations.
void debug_sync_trigger_print(void)
Trigger sync state print immediately (synchronous)
Definition sync.c:710
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
bool media_source_is_paused(media_source_t *source)
Check if media source is paused.
Definition source.c:1022
double media_source_get_position(media_source_t *source)
Get current playback position in seconds.
Definition source.c:956
double media_source_get_duration(media_source_t *source)
Get media duration in seconds.
Definition source.c:934
asciichat_error_t media_source_seek(media_source_t *source, double timestamp_sec)
Seek media source to timestamp.
Definition source.c:851
media_source_type_t media_source_get_type(media_source_t *source)
Get media source type.
Definition source.c:930
void media_source_toggle_pause(media_source_t *source)
Toggle pause state of media source.
Definition source.c:1032
@ MEDIA_SOURCE_FILE
Media file (video/audio)
Definition source.h:83
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
asciichat_error_t options_set_int(const char *field_name, int value)
Check if an option was explicitly set via command-line.
Definition rcu.c:761
asciichat_error_t options_set_double(const char *field_name, double value)
Definition rcu.c:1283
asciichat_error_t options_set_bool(const char *field_name, bool value)
Definition rcu.c:969
asciichat_error_t terminal_clear_screen(void)
Clear the terminal screen.
keyboard_key_t
Unified keyboard key code enumeration.
Definition keyboard.h:54
@ KEY_SPACE
Space bar.
Definition keyboard.h:57
@ KEY_UP
Up arrow key.
Definition keyboard.h:58
@ KEY_M
'm' key - toggle mute
Definition keyboard.h:70
@ KEY_BACKTICK
'‘’ key - print lock state (debug builds)
Definition keyboard.h:73
@ KEY_ESCAPE
Escape key (ESC)
Definition keyboard.h:56
@ KEY_F
'f' key - flip webcam
Definition keyboard.h:71
@ KEY_MINUS
'-' key - toggle FPS counter
Definition keyboard.h:66
@ KEY_LEFT
Left arrow key.
Definition keyboard.h:60
@ KEY_RIGHT
Right arrow key.
Definition keyboard.h:61
@ KEY_0
'0' key - toggle matrix rain effect
Definition keyboard.h:67
@ KEY_NONE
No key pressed or no input available.
Definition keyboard.h:55
@ KEY_C
'c' key - cycle color modes
Definition keyboard.h:68
@ KEY_R
'r' key - cycle render modes
Definition keyboard.h:69
@ KEY_DOWN
Down arrow key.
Definition keyboard.h:59
@ KEY_QUESTION
'?' key - show help screen
Definition keyboard.h:72
void keyboard_help_toggle(session_display_ctx_t *ctx)
Toggle keyboard help on/off (implemented in display.c for struct access)
bool keyboard_help_is_active(session_display_ctx_t *ctx)
Check if keyboard help is currently active.
void keyboard_help_render(session_display_ctx_t *ctx)
Render keyboard help centered on terminal.
void session_handle_keyboard_input(session_capture_ctx_t *capture, session_display_ctx_t *display, keyboard_key_t key)
Handle keyboard input in a session.
void * session_capture_get_media_source(session_capture_ctx_t *ctx)
Get the underlying media source from capture context.
🔊 Audio Capture and Playback Interface for ascii-chat
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
🎮 Keyboard input handler for interactive session controls
🆘 Interactive keyboard help overlay for session keyboard shortcuts
📝 Logging API with multiple log levels and terminal output control
Cross-platform crash signal and exception handling.
🎬 Unified media source abstraction for webcam, files, and stdin
🖥️ Unified terminal display abstraction for session-based rendering
#define bool
Definition stdbool.h:61
Audio context for full-duplex capture and playback.
Media source for video and audio capture.
Definition source.c:34
Internal session capture context structure.
Internal session display context structure.
🔒 Synchronization primitive debugging (mutexes, rwlocks, condition variables)
🖥️ Cross-platform terminal interface for ascii-chat
@ COLOR_FILTER_COUNT
Total count of filters (not a valid filter)
Definition terminal.h:627