ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
keyboard_handler.c
Go to the documentation of this file.
1
7#include <ascii-chat/session/keyboard_handler.h>
8#include <ascii-chat/session/capture.h>
9#include <ascii-chat/session/display.h>
10#include <ascii-chat/ui/help_screen.h>
11#include <ascii-chat/media/source.h>
12#include <ascii-chat/options/options.h>
13#include <ascii-chat/log/logging.h>
14#include <ascii-chat/audio/audio.h>
15#include <string.h>
16#include <signal.h>
17
18#ifndef NDEBUG
19#include <ascii-chat/debug/lock.h>
20#endif
21
22/* ============================================================================
23 * Internal Helpers
24 * ============================================================================ */
25
27static double g_mute_saved_volume = 1.0;
28
40static double clamp_volume(double volume) {
41 if (volume < 0.0) {
42 return 0.0;
43 }
44 if (volume > 1.0) {
45 return 1.0;
46 }
47 return volume;
48}
49
53static int next_color_mode(int current) {
54 // Cycle: TERM_COLOR_NONE → TERM_COLOR_16 → TERM_COLOR_256 → TERM_COLOR_TRUECOLOR → TERM_COLOR_NONE
55 switch (current) {
56 case 0: // TERM_COLOR_NONE
57 return 1; // TERM_COLOR_16
58 case 1: // TERM_COLOR_16
59 return 2; // TERM_COLOR_256
60 case 2: // TERM_COLOR_256
61 return 3; // TERM_COLOR_TRUECOLOR
62 default:
63 return 0; // Back to TERM_COLOR_NONE
64 }
65}
66
67/* ============================================================================
68 * Keyboard Handler
69 * ============================================================================ */
70
71void session_handle_keyboard_input(session_capture_ctx_t *capture, session_display_ctx_t *display, keyboard_key_t key) {
72 switch (key) {
73 // ===== HELP SCREEN TOGGLE =====
74 case KEY_QUESTION: {
75 if (display) {
77 // Render help screen immediately so user sees it
79 }
80 break;
81 }
82
83 // ===== HELP SCREEN CLOSE / QUIT =====
84 case KEY_ESCAPE: {
85 if (display && session_display_is_help_active(display)) {
86 // Close help screen if it's active
88 } else {
89 // If help screen is not active, quit the app (like Ctrl-C)
90 // The signal handler will gracefully shutdown all modes (client, server, mirror, etc.)
91 raise(SIGINT);
92 }
93 break;
94 }
95
96 // ===== SEEK CONTROLS (file sources only) =====
97 case KEY_LEFT: { // Seek backward 30 seconds
98 if (capture) {
100 if (source && media_source_get_type(source) == MEDIA_SOURCE_FILE) {
101 double current_pos = media_source_get_position(source);
102 if (current_pos >= 0.0) {
103 double new_pos = current_pos - 30.0;
104 if (new_pos < 0.0) {
105 new_pos = 0.0;
106 }
107 asciichat_error_t err = media_source_seek(source, new_pos);
108 if (err == ASCIICHAT_OK) {
109 log_info("Seeked backward to %.1f seconds", new_pos);
110 }
111 void *audio_ctx = session_capture_get_audio_context(capture);
112 if (audio_ctx) {
113 audio_flush_playback_buffers((audio_context_t *)audio_ctx);
114 }
115 }
116 }
117 }
118 break;
119 }
120
121 case KEY_RIGHT: { // Seek forward 30 seconds
122 if (capture) {
124 if (source && media_source_get_type(source) == MEDIA_SOURCE_FILE) {
125 double current_pos = media_source_get_position(source);
126 double duration = media_source_get_duration(source);
127 if (current_pos >= 0.0) {
128 double new_pos = current_pos + 30.0;
129 // Clamp to duration if known
130 if (duration > 0.0 && new_pos > duration) {
131 new_pos = duration;
132 }
133 asciichat_error_t err = media_source_seek(source, new_pos);
134 if (err == ASCIICHAT_OK) {
135 log_info("Seeked forward to %.1f seconds", new_pos);
136 }
137 void *audio_ctx = session_capture_get_audio_context(capture);
138 if (audio_ctx) {
139 audio_flush_playback_buffers((audio_context_t *)audio_ctx);
140 }
141 }
142 }
143 }
144 break;
145 }
146
147 // ===== VOLUME CONTROLS =====
148 case KEY_DOWN: { // Decrease volume 10%
149 double current_volume = GET_OPTION(speakers_volume);
150 double new_volume = clamp_volume(current_volume - 0.1);
151 options_set_double("speakers_volume", new_volume);
152 double verify_volume = GET_OPTION(speakers_volume);
153 log_info("Volume DOWN: %.0f%% → %.0f%% (verified: %.0f%%)", current_volume * 100.0, new_volume * 100.0,
154 verify_volume * 100.0);
155 break;
156 }
157
158 case KEY_UP: { // Increase volume 10%
159 double current_volume = GET_OPTION(speakers_volume);
160 double new_volume = clamp_volume(current_volume + 0.1);
161 options_set_double("speakers_volume", new_volume);
162 double verify_volume = GET_OPTION(speakers_volume);
163 log_info("Volume UP: %.0f%% → %.0f%% (verified: %.0f%%)", current_volume * 100.0, new_volume * 100.0,
164 verify_volume * 100.0);
165 break;
166 }
167
168 // ===== PLAY/PAUSE CONTROL =====
169 case KEY_SPACE: { // Toggle play/pause
170 if (capture) {
172 if (source && media_source_get_type(source) == MEDIA_SOURCE_FILE) {
174 if (media_source_is_paused(source)) {
175 log_info("Paused");
176 } else {
177 log_info("Playing");
178 }
179 }
180 }
181 break;
182 }
183
184 // ===== COLOR MODE CONTROL =====
185 case KEY_C: {
186 int current_mode = (int)GET_OPTION(color_mode);
187 int next_mode = next_color_mode(current_mode);
188 options_set_int("color_mode", next_mode);
189
190 const char *mode_names[] = {"Mono", "16-color", "256-color", "Truecolor"};
191 if (next_mode >= 0 && next_mode <= 3) {
192 log_info("Color mode: %s", mode_names[next_mode]);
193 }
194 break;
195 }
196
197 // ===== MUTE CONTROL =====
198 case KEY_M: {
199 double current_volume = GET_OPTION(speakers_volume);
200 log_debug("Mute toggle: current_volume=%.2f, g_mute_saved_volume=%.2f, threshold=0.01", current_volume,
201 g_mute_saved_volume);
202
203 if (current_volume > 0.01) { // If not already muted
204 // Save current volume and mute
205 g_mute_saved_volume = current_volume;
206 options_set_double("speakers_volume", 0.0);
207 double verify = GET_OPTION(speakers_volume);
208 log_info("Muted: saved %.0f%%, set to 0%% (verified: %.2f)", g_mute_saved_volume * 100.0, verify);
209 } else {
210 // Restore previous volume
211 double restore_volume = g_mute_saved_volume > 0.0 ? g_mute_saved_volume : 0.5;
212 options_set_double("speakers_volume", restore_volume);
213 double verify = GET_OPTION(speakers_volume);
214 log_info("Unmuted: restored %.0f%% (verified: %.2f)", restore_volume * 100.0, verify);
215 }
216 break;
217 }
218
219 // ===== HORIZONTAL FLIP CONTROL =====
220 case KEY_F: {
221 bool current_flip_x = (bool)GET_OPTION(flip_x);
222 options_set_bool("flip_x", !current_flip_x);
223 log_info("Horizontal flip: %s", !current_flip_x ? "enabled" : "disabled");
224 break;
225 }
226
227 // ===== LOCK DEBUG (debug builds only) =====
228#ifndef NDEBUG
229 case KEY_CTRL_L: {
231 log_debug("Lock state dump triggered via Ctrl+L");
232 break;
233 }
234#endif
235
236 default:
237 // Unknown key - silently ignore
238 break;
239 }
240}
void session_display_render_help(session_display_ctx_t *ctx)
Render help screen centered on terminal.
void session_handle_keyboard_input(session_capture_ctx_t *capture, session_display_ctx_t *display, keyboard_key_t key)
void audio_flush_playback_buffers(audio_context_t *ctx)
void * session_capture_get_audio_context(session_capture_ctx_t *ctx)
void * session_capture_get_media_source(session_capture_ctx_t *ctx)
bool session_display_is_help_active(session_display_ctx_t *ctx)
Check if help screen is currently active (implemented in display.c for struct access)
void session_display_toggle_help(session_display_ctx_t *ctx)
Toggle help screen on/off (implemented in display.c for struct access)
void lock_debug_trigger_print(void)
Definition lock.c:1384
asciichat_error_t options_set_int(const char *field_name, int value)
Definition rcu.c:449
asciichat_error_t options_set_double(const char *field_name, double value)
Definition rcu.c:695
asciichat_error_t options_set_bool(const char *field_name, bool value)
Definition rcu.c:562
bool media_source_is_paused(media_source_t *source)
Definition source.c:926
double media_source_get_position(media_source_t *source)
Definition source.c:860
double media_source_get_duration(media_source_t *source)
Definition source.c:838
asciichat_error_t media_source_seek(media_source_t *source, double timestamp_sec)
Definition source.c:757
media_source_type_t media_source_get_type(media_source_t *source)
Definition source.c:834
void media_source_toggle_pause(media_source_t *source)
Definition source.c:936
#define bool
Definition stdbool.h:22
Media source for video and audio capture.
Definition source.c:31
Internal session display context structure.