Fortran · 31827 bytes Raw Blame History
1 module input_handler_module
2 use iso_fortran_env, only: input_unit, int8, error_unit
3 use terminal_io_module, only: terminal_read_char
4 implicit none
5 private
6
7 public :: get_key_input, key_type, mouse_event_t
8
9 ! Key type constants
10 enum, bind(C)
11 enumerator :: KEY_NORMAL = 0
12 enumerator :: KEY_CTRL
13 enumerator :: KEY_ALT
14 enumerator :: KEY_SPECIAL
15 enumerator :: KEY_MOUSE
16 end enum
17
18 type :: key_type
19 integer :: type = KEY_NORMAL
20 character(len=32) :: value = ''
21 end type key_type
22
23 type :: mouse_event_t
24 integer :: button ! 0=left, 1=middle, 2=right
25 integer :: row ! Terminal row (1-based)
26 integer :: col ! Terminal column (1-based)
27 logical :: pressed ! True=press, False=release
28 logical :: shift ! Shift modifier
29 logical :: alt ! Alt modifier
30 logical :: ctrl ! Ctrl modifier
31 end type mouse_event_t
32
33 character(len=*), parameter :: ESC = char(27)
34
35 contains
36
37 subroutine get_key_input(key_str, status)
38 character(len=*), intent(out) :: key_str
39 integer, intent(out) :: status
40 character :: ch
41 integer :: char_code
42
43 key_str = ''
44 status = -1
45
46 ! Read single character using raw mode function
47 char_code = terminal_read_char()
48
49 if (char_code < 0) then
50 return
51 end if
52
53 ch = achar(char_code)
54 status = 0
55
56 ! Check for special keys
57 select case(iachar(ch))
58 case(0) ! Ctrl-Space (NULL character)
59 key_str = 'ctrl-space'
60 case(27) ! ESC
61 call handle_escape_sequence(key_str)
62 case(9) ! Tab
63 key_str = 'tab'
64 case(10, 13) ! Enter
65 key_str = 'enter'
66 case(8) ! Ctrl-H
67 key_str = 'ctrl-h'
68 case(26) ! Ctrl-Z
69 key_str = 'ctrl-z'
70 case(31) ! Ctrl-/ and Ctrl-? (both send ASCII 31)
71 key_str = 'ctrl-/'
72 case(7) ! Ctrl-G (goto)
73 key_str = 'ctrl-g'
74 case(29) ! Ctrl-] (for redo)
75 key_str = 'ctrl-]'
76 case(1:6, 11:12, 14:25, 28) ! Ctrl keys (excluding Ctrl-G, Ctrl-H, Ctrl-Z, Tab, Enter, ESC, and Ctrl-])
77 write(key_str, '(a,a)') 'ctrl-', achar(iachar('a') + iachar(ch) - 1)
78 case(127) ! Backspace
79 key_str = 'backspace'
80 case default
81 key_str = ch
82 end select
83
84 end subroutine get_key_input
85
86 subroutine handle_escape_sequence(key_str)
87 character(len=*), intent(out) :: key_str
88 character :: ch, ch1, ch2, ch3, modifier_ch
89 integer :: char_code, ios
90
91 key_str = 'esc'
92
93 ! Try to read next character (with timeout)
94 char_code = terminal_read_char()
95 if (char_code < 0) return
96 ch1 = achar(char_code)
97
98 if (ch1 == '[') then
99 ! CSI sequence (or Alt+[ if no valid sequence follows)
100 char_code = terminal_read_char()
101 if (char_code < 0) then
102 ! Timeout - no character follows, this is Alt+[
103 key_str = 'alt-['
104 return
105 end if
106 ch2 = achar(char_code)
107
108 select case(ch2)
109 case('A')
110 key_str = 'up'
111 case('B')
112 key_str = 'down'
113 case('C')
114 key_str = 'right'
115 case('D')
116 key_str = 'left'
117 case('H')
118 key_str = 'home'
119 case('F')
120 key_str = 'end'
121 case('Z')
122 ! Shift+Tab sends ESC[Z
123 key_str = 'shift-tab'
124 case('3')
125 ! Could be delete or Alt+Delete
126 char_code = terminal_read_char()
127 if (char_code >= 0) then
128 ch3 = achar(char_code)
129 if (ch3 == '~') then
130 key_str = 'delete'
131 else if (ch3 == ';') then
132 ! Modified delete: ESC [ 3 ; modifier ~
133 ! Read the modifier
134 char_code = terminal_read_char()
135 if (char_code >= 0) then
136 modifier_ch = achar(char_code)
137 ! Read the terminating ~
138 char_code = terminal_read_char()
139 if (char_code >= 0 .and. achar(char_code) == '~') then
140 ! Check modifier: 3 = Alt
141 if (modifier_ch == '3') then
142 key_str = 'alt-delete'
143 else
144 key_str = 'delete' ! Other modified deletes default to delete
145 end if
146 end if
147 end if
148 end if
149 end if
150 case('5')
151 ! Could be page up
152 char_code = terminal_read_char()
153 if (char_code >= 0) then
154 ch3 = achar(char_code)
155 ios = 0
156 else
157 ios = -1
158 end if
159 if (ios == 0 .and. ch3 == '~') then
160 key_str = 'pageup'
161 else if (ios == 0 .and. ch3 == ';') then
162 ! Modified page up (e.g., shift+pageup)
163 call handle_modified_special_key(key_str, 5)
164 end if
165 case('6')
166 ! Could be page down
167 char_code = terminal_read_char()
168 if (char_code >= 0) then
169 ch3 = achar(char_code)
170 ios = 0
171 else
172 ios = -1
173 end if
174 if (ios == 0 .and. ch3 == '~') then
175 key_str = 'pagedown'
176 else if (ios == 0 .and. ch3 == ';') then
177 ! Modified page down (e.g., shift+pagedown)
178 call handle_modified_special_key(key_str, 6)
179 end if
180 case('1')
181 ! Could be function key (F1-F9) or modified arrow/home/end
182 ! Check next character
183 char_code = terminal_read_char()
184 if (char_code >= 0) then
185 ch3 = achar(char_code)
186 if (ch3 == '~') then
187 ! F1: ESC [ 1 1 ~ (alternate format)
188 key_str = 'f1'
189 else if (ch3 == '0') then
190 ! F10 might be ESC [ 2 1 ~, check for tilde
191 char_code = terminal_read_char()
192 if (char_code >= 0 .and. achar(char_code) == '~') then
193 key_str = 'f10'
194 end if
195 else if (ch3 == '1' .or. ch3 == '2' .or. ch3 == '3' .or. ch3 == '4' .or. &
196 ch3 == '5' .or. ch3 == '7' .or. ch3 == '8' .or. ch3 == '9') then
197 ! Function keys F1-F8: ESC [ 1 X ~ or ESC [ 1 X ; modifier ~
198 char_code = terminal_read_char()
199 if (char_code >= 0) then
200 ch = achar(char_code)
201 if (ch == '~') then
202 ! Unmodified F1-F8
203 select case(ch3)
204 case('1')
205 key_str = 'f1'
206 case('2')
207 key_str = 'f2'
208 case('3')
209 key_str = 'f3'
210 case('4')
211 key_str = 'f4'
212 case('5')
213 key_str = 'f5'
214 case('7')
215 key_str = 'f6'
216 case('8')
217 key_str = 'f7'
218 case('9')
219 key_str = 'f8'
220 end select
221 else if (ch == ';') then
222 ! Modified F1-F8: ESC [ 1 X ; modifier ~
223 call handle_modified_function_key(key_str, '1', ch3)
224 end if
225 end if
226 else if (ch3 == ';') then
227 ! Modified arrow key or home/end: ESC [ 1 ; 2 A format
228 call handle_modified_key(key_str)
229 end if
230 end if
231 case('2')
232 ! Could be F9-F12 or alternate modified keys
233 char_code = terminal_read_char()
234 if (char_code >= 0) then
235 ch3 = achar(char_code)
236 if (ch3 == '0' .or. ch3 == '1' .or. ch3 == '3' .or. ch3 == '4') then
237 ! Function keys F9-F12: ESC [ 2 X ~ or ESC [ 2 X ; modifier ~
238 char_code = terminal_read_char()
239 if (char_code >= 0) then
240 ch = achar(char_code)
241 if (ch == '~') then
242 ! Unmodified F9-F12
243 select case(ch3)
244 case('0')
245 key_str = 'f9'
246 case('1')
247 key_str = 'f10'
248 case('3')
249 key_str = 'f11'
250 case('4')
251 key_str = 'f12'
252 end select
253 else if (ch == ';') then
254 ! Modified F9-F12: ESC [ 2 X ; modifier ~
255 call handle_modified_function_key(key_str, '2', ch3)
256 end if
257 end if
258 else if (ch3 == ';') then
259 ! ESC [ 2 ; A format (shift+arrow)
260 char_code = terminal_read_char()
261 if (char_code >= 0) then
262 ch = achar(char_code)
263 key_str = 'shift-'
264 select case(ch)
265 case('A')
266 key_str = trim(key_str) // 'up'
267 case('B')
268 key_str = trim(key_str) // 'down'
269 case('C')
270 key_str = trim(key_str) // 'right'
271 case('D')
272 key_str = trim(key_str) // 'left'
273 end select
274 end if
275 else
276 ! Direct format ESC [ 2 A (we already read the 'A' in ch3)
277 key_str = 'shift-'
278 select case(ch3)
279 case('A')
280 key_str = trim(key_str) // 'up'
281 case('B')
282 key_str = trim(key_str) // 'down'
283 case('C')
284 key_str = trim(key_str) // 'right'
285 case('D')
286 key_str = trim(key_str) // 'left'
287 end select
288 end if
289 end if
290 case('4', '7', '8')
291 ! Alternate format: ESC [ 2 A (modifier directly, no '1')
292 ! This is sent by some terminals for shift+arrows
293 call handle_alternate_modified_key(key_str, ch2)
294 case('<')
295 ! Mouse event in SGR mode
296 call handle_mouse_event(key_str)
297 end select
298 else if (ch1 == 'O') then
299 ! SS3 sequence (e.g., function keys F1-F4)
300 char_code = terminal_read_char()
301 if (char_code < 0) then
302 ! Timeout - this is just Alt+O
303 key_str = 'alt-o'
304 return
305 end if
306 ch2 = achar(char_code)
307 select case(ch2)
308 case('P')
309 key_str = 'f1'
310 case('Q')
311 key_str = 'f2'
312 case('R')
313 key_str = 'f3'
314 case('S')
315 key_str = 'f4'
316 case default
317 ! Unknown SS3 sequence - return as Alt+ch2
318 write(key_str, '(a,a)') 'alt-', ch2
319 end select
320 else if (ch1 == achar(27)) then
321 ! ESC ESC - likely Alt+something
322 char_code = terminal_read_char()
323 if (char_code >= 0) then
324 ch2 = achar(char_code)
325 if (ch2 == '[') then
326 ! ESC ESC [ - Alt+arrow keys or Alt+modified keys
327 char_code = terminal_read_char()
328 if (char_code >= 0) then
329 ch3 = achar(char_code)
330 select case(ch3)
331 case('A')
332 key_str = 'alt-up'
333 case('B')
334 key_str = 'alt-down'
335 case('C')
336 key_str = 'alt-right'
337 case('D')
338 key_str = 'alt-left'
339 case('3')
340 ! Could be Alt+Delete (ESC ESC [ 3 ~)
341 char_code = terminal_read_char()
342 if (char_code >= 0 .and. achar(char_code) == '~') then
343 key_str = 'alt-delete'
344 end if
345 case('1', '2', '4', '7', '8')
346 ! ESC ESC [ 1 ; modifier format (Alt+Shift+arrow, etc)
347 call handle_alt_modified_key(key_str)
348 end select
349 end if
350 end if
351 end if
352 else if (ch1 == 'A') then
353 ! Could be Alt-Shift-Up
354 key_str = 'alt-shift-up'
355 else if (ch1 == 'B') then
356 ! Could be Alt-Shift-Down
357 key_str = 'alt-shift-down'
358 else if (ch1 == achar(127)) then
359 ! Alt+Backspace (ESC followed by DEL/127)
360 key_str = 'alt-backspace'
361 else if (ch1 == achar(8)) then
362 ! Alt+Backspace (ESC followed by Ctrl-H)
363 key_str = 'alt-backspace'
364 else if (ch1 >= 'a' .and. ch1 <= 'z') then
365 ! Alt+letter
366 write(key_str, '(a,a)') 'alt-', ch1
367 else if (ch1 >= 'A' .and. ch1 <= 'Z') then
368 ! Alt+Shift+letter
369 write(key_str, '(a,a)') 'alt-shift-', achar(iachar(ch1) - iachar('A') + iachar('a'))
370 else if (ch1 >= '0' .and. ch1 <= '9') then
371 ! Alt+number (for tab switching)
372 write(key_str, '(a,a)') 'alt-', ch1
373 else if (ch1 == "'") then
374 ! Alt+apostrophe for cycle quotes
375 key_str = "alt-'"
376 else if (ch1 == '"') then
377 ! Alt+Shift+apostrophe (double quote) for remove brackets
378 key_str = "alt-shift-apostrophe"
379 else if (ch1 == '[') then
380 ! Alt+[ for jump to matching bracket
381 key_str = "alt-["
382 else if (ch1 == ']') then
383 ! Alt+] for jump to matching bracket
384 key_str = "alt-]"
385 end if
386
387 end subroutine handle_escape_sequence
388
389 subroutine handle_modified_key(key_str)
390 character(len=*), intent(out) :: key_str
391 character :: ch, terminator
392 character(len=10) :: modifier_seq
393 integer :: ios, modifier, char_code, read_count
394
395 key_str = '' ! Initialize to empty
396 modifier_seq = ''
397 ch = '' ! Initialize
398 terminator = '' ! To store the final character
399 read_count = 0
400
401 ! Read modifier sequence (e.g., ";2" for Shift)
402 do
403 read_count = read_count + 1
404 if (read_count > 20) exit ! Safety limit
405
406 char_code = terminal_read_char()
407 if (char_code >= 0) then
408 ch = achar(char_code)
409 ios = 0
410 else
411 ios = -1
412 exit
413 end if
414 if ((ch >= 'A' .and. ch <= 'D') .or. ch == 'H' .or. ch == 'F' .or. ch == '~' .or. ch == 'Z') then
415 ! End of sequence - save the terminator
416 terminator = ch
417 exit
418 end if
419 modifier_seq = trim(modifier_seq) // ch
420 end do
421
422 ! If we didn't get a terminator, return
423 if (terminator == '') return
424
425 ! Parse modifier
426 if (len_trim(modifier_seq) > 1 .and. modifier_seq(1:1) == ';') then
427 ! Standard format: ";2" where 2 is the modifier
428 if (len_trim(modifier_seq) >= 2) then
429 read(modifier_seq(2:len_trim(modifier_seq)), '(i10)', iostat=ios) modifier
430 else
431 ios = -1
432 end if
433 if (ios == 0) then
434 select case(modifier)
435 case(2) ! Shift
436 key_str = 'shift-'
437 case(3) ! Alt
438 key_str = 'alt-'
439 case(4) ! Alt+Shift
440 key_str = 'alt-shift-'
441 case(5) ! Ctrl
442 key_str = 'ctrl-'
443 case(6) ! Ctrl+Shift
444 key_str = 'ctrl-shift-'
445 case(7) ! Alt+Ctrl
446 key_str = 'alt-ctrl-'
447 case(8) ! Alt+Shift (or Option+Shift)
448 key_str = 'alt-shift-'
449 case(9) ! Alt+Cmd (or Option+Cmd on macOS)
450 key_str = 'opt-meta-'
451 case default
452 key_str = ''
453 end select
454
455 ! Append the key type using the terminator character
456 select case(terminator)
457 case('A')
458 key_str = trim(key_str) // 'up'
459 case('B')
460 key_str = trim(key_str) // 'down'
461 case('C')
462 key_str = trim(key_str) // 'right'
463 case('D')
464 key_str = trim(key_str) // 'left'
465 case('H')
466 key_str = trim(key_str) // 'home'
467 case('F')
468 key_str = trim(key_str) // 'end'
469 case('Z')
470 ! Shift+Z could be ctrl-shift-z for redo
471 if (index(key_str, 'ctrl-shift') == 1) then
472 key_str = 'ctrl-shift-z'
473 else
474 key_str = trim(key_str) // 'Z'
475 end if
476 case('~')
477 ! Check what special key it is based on the beginning of modifier_seq
478 if (index(modifier_seq, ';') == 1 .and. len_trim(modifier_seq) > 1) then
479 ! Already read the ;2 or ;5 etc, the key type should be before
480 key_str = trim(key_str) // 'unknown'
481 end if
482 end select
483 end if
484 end if
485 end subroutine handle_modified_key
486
487 subroutine handle_alternate_modified_key(key_str, modifier_char)
488 character(len=*), intent(out) :: key_str
489 character, intent(in) :: modifier_char
490 character :: ch
491 integer :: char_code, modifier
492
493 key_str = ''
494
495 ! The modifier_char ('2', '4', '7', '8') indicates the modifier
496 read(modifier_char, '(i1)') modifier
497
498 ! Read the next character - might be the key or a semicolon
499 char_code = terminal_read_char()
500 if (char_code < 0) return
501 ch = achar(char_code)
502
503 ! Check if there's a semicolon (ESC [ 2 ; A format) or direct key (ESC [ 2 A)
504 if (ch == ';') then
505 ! Read the actual key
506 char_code = terminal_read_char()
507 if (char_code < 0) return
508 ch = achar(char_code)
509 end if
510
511 ! Map modifier to key prefix
512 select case(modifier)
513 case(2) ! Shift
514 key_str = 'shift-'
515 case(3) ! Alt
516 key_str = 'alt-'
517 case(4) ! Alt+Shift
518 key_str = 'alt-shift-'
519 case(5) ! Ctrl
520 key_str = 'ctrl-'
521 case(6) ! Ctrl+Shift
522 key_str = 'ctrl-shift-'
523 case(7) ! Alt+Ctrl
524 key_str = 'alt-ctrl-'
525 case(8) ! Alt+Shift (alternate)
526 key_str = 'alt-shift-'
527 case default
528 return
529 end select
530
531 ! Append the key type
532 select case(ch)
533 case('A')
534 key_str = trim(key_str) // 'up'
535 case('B')
536 key_str = trim(key_str) // 'down'
537 case('C')
538 key_str = trim(key_str) // 'right'
539 case('D')
540 key_str = trim(key_str) // 'left'
541 case('H')
542 key_str = trim(key_str) // 'home'
543 case('F')
544 key_str = trim(key_str) // 'end'
545 case default
546 key_str = ''
547 end select
548 end subroutine handle_alternate_modified_key
549
550 subroutine handle_alt_modified_key(key_str)
551 character(len=*), intent(out) :: key_str
552 character :: ch, terminator
553 character(len=10) :: modifier_seq
554 integer :: ios, modifier, char_code, read_count
555
556 key_str = ''
557 modifier_seq = ''
558 terminator = ''
559 read_count = 0
560
561 ! For ESC ESC [ 1 ; modifier format, we already read the '1'
562 ! Read the rest of the sequence (should be ";modifier" then key)
563 do
564 read_count = read_count + 1
565 if (read_count > 20) exit
566
567 char_code = terminal_read_char()
568 if (char_code < 0) exit
569
570 ch = achar(char_code)
571 if ((ch >= 'A' .and. ch <= 'D') .or. ch == 'H' .or. ch == 'F') then
572 terminator = ch
573 exit
574 end if
575 modifier_seq = trim(modifier_seq) // ch
576 end do
577
578 if (terminator == '') return
579
580 ! Parse modifier from sequence like ";4" (Alt+Shift)
581 if (len_trim(modifier_seq) > 1 .and. modifier_seq(1:1) == ';') then
582 if (len_trim(modifier_seq) >= 2) then
583 read(modifier_seq(2:len_trim(modifier_seq)), '(i10)', iostat=ios) modifier
584 else
585 return
586 end if
587
588 ! Build the key string with alt- prefix
589 select case(modifier)
590 case(2) ! Alt+Shift (ESC ESC [ 1 ; 2 is Alt+Shift)
591 key_str = 'alt-shift-'
592 case(3) ! Alt+Alt? (unusual)
593 key_str = 'alt-'
594 case(4) ! Alt+Shift (alternate)
595 key_str = 'alt-shift-'
596 case(5) ! Alt+Ctrl
597 key_str = 'alt-ctrl-'
598 case(6) ! Alt+Ctrl+Shift
599 key_str = 'alt-ctrl-shift-'
600 case default
601 ! Unknown modifier with Alt
602 key_str = 'alt-'
603 end select
604
605 ! Append the key
606 select case(terminator)
607 case('A')
608 key_str = trim(key_str) // 'up'
609 case('B')
610 key_str = trim(key_str) // 'down'
611 case('C')
612 key_str = trim(key_str) // 'right'
613 case('D')
614 key_str = trim(key_str) // 'left'
615 case('H')
616 key_str = trim(key_str) // 'home'
617 case('F')
618 key_str = trim(key_str) // 'end'
619 end select
620 end if
621 end subroutine handle_alt_modified_key
622
623 subroutine handle_modified_special_key(key_str, key_code)
624 character(len=*), intent(out) :: key_str
625 integer, intent(in) :: key_code
626 character :: ch
627 character(len=10) :: modifier_seq
628 integer :: ios, modifier, char_code
629
630 modifier_seq = ''
631
632 ! Read modifier sequence (already past the semicolon)
633 do
634 char_code = terminal_read_char()
635 if (char_code >= 0) then
636 ch = achar(char_code)
637 ios = 0
638 else
639 ios = -1
640 end if
641 if (ios /= 0) exit
642 if (ch == '~') then
643 ! End of sequence
644 exit
645 end if
646 modifier_seq = trim(modifier_seq) // ch
647 end do
648
649 ! Parse modifier
650 if (len_trim(modifier_seq) > 0) then
651 read(modifier_seq, '(i10)', iostat=ios) modifier
652 if (ios == 0) then
653 select case(modifier)
654 case(2) ! Shift
655 key_str = 'shift-'
656 case(3) ! Alt
657 key_str = 'alt-'
658 case(4) ! Alt+Shift
659 key_str = 'alt-shift-'
660 case(5) ! Ctrl
661 key_str = 'ctrl-'
662 case(6) ! Ctrl+Shift
663 key_str = 'ctrl-shift-'
664 case default
665 key_str = ''
666 end select
667
668 ! Append the key type based on key_code
669 select case(key_code)
670 case(5)
671 key_str = trim(key_str) // 'pageup'
672 case(6)
673 key_str = trim(key_str) // 'pagedown'
674 end select
675 end if
676 end if
677 end subroutine handle_modified_special_key
678
679 subroutine handle_modified_function_key(key_str, series, fkey_code)
680 character(len=*), intent(out) :: key_str
681 character, intent(in) :: series ! '1' for ESC[1X~, '2' for ESC[2X~
682 character, intent(in) :: fkey_code ! The X in ESC[1X~ or ESC[2X~
683 character :: ch, modifier_ch
684 integer :: char_code, modifier
685 character(len=10) :: base_key
686
687 key_str = ''
688
689 ! Determine base function key from series and code
690 if (series == '1') then
691 ! ESC[1X~ format: 1=F1, 2=F2, 3=F3, 4=F4, 5=F5, 7=F6, 8=F7, 9=F8
692 select case(fkey_code)
693 case('1')
694 base_key = 'f1'
695 case('2')
696 base_key = 'f2'
697 case('3')
698 base_key = 'f3'
699 case('4')
700 base_key = 'f4'
701 case('5')
702 base_key = 'f5'
703 case('7')
704 base_key = 'f6'
705 case('8')
706 base_key = 'f7'
707 case('9')
708 base_key = 'f8'
709 case default
710 return
711 end select
712 else if (series == '2') then
713 ! ESC[2X~ format: 0=F9, 1=F10, 3=F11, 4=F12
714 select case(fkey_code)
715 case('0')
716 base_key = 'f9'
717 case('1')
718 base_key = 'f10'
719 case('3')
720 base_key = 'f11'
721 case('4')
722 base_key = 'f12'
723 case default
724 return
725 end select
726 else
727 return
728 end if
729
730 ! Read modifier (should be a digit 2-8)
731 char_code = terminal_read_char()
732 if (char_code < 0) return
733 modifier_ch = achar(char_code)
734
735 ! Read terminating ~
736 char_code = terminal_read_char()
737 if (char_code < 0 .or. achar(char_code) /= '~') return
738
739 ! Parse modifier: 2=Shift, 3=Alt, 4=Alt+Shift, 5=Ctrl, 6=Ctrl+Shift, 7=Alt+Ctrl, 8=Alt+Shift
740 read(modifier_ch, '(i1)') modifier
741
742 select case(modifier)
743 case(2) ! Shift
744 key_str = 'shift-' // trim(base_key)
745 case(3) ! Alt
746 key_str = 'alt-' // trim(base_key)
747 case(4) ! Alt+Shift
748 key_str = 'alt-shift-' // trim(base_key)
749 case(5) ! Ctrl
750 key_str = 'ctrl-' // trim(base_key)
751 case(6) ! Ctrl+Shift
752 key_str = 'ctrl-shift-' // trim(base_key)
753 case(7) ! Alt+Ctrl
754 key_str = 'alt-ctrl-' // trim(base_key)
755 case(8) ! Alt+Ctrl+Shift
756 key_str = 'alt-ctrl-shift-' // trim(base_key)
757 case default
758 ! Unknown modifier, return unmodified key
759 key_str = trim(base_key)
760 end select
761 end subroutine handle_modified_function_key
762
763 subroutine handle_mouse_event(key_str)
764 character(len=*), intent(out) :: key_str
765 character :: ch
766 character(len=100) :: buffer
767 integer :: i, ios, button, col, row, char_code
768 integer :: semicolon1, semicolon2
769 logical :: is_release
770
771 buffer = ''
772 i = 1
773 is_release = .false.
774
775 ! Read until 'M' (press) or 'm' (release)
776 do
777 char_code = terminal_read_char()
778 if (char_code >= 0) then
779 ch = achar(char_code)
780 ios = 0
781 else
782 ios = -1
783 end if
784 if (ios /= 0) exit
785 if (ch == 'M' .or. ch == 'm') then
786 is_release = (ch == 'm')
787 exit
788 end if
789 if (i <= 100) then
790 buffer(i:i) = ch
791 i = i + 1
792 end if
793 end do
794
795 ! Parse the mouse event format: button;col;row
796 semicolon1 = index(buffer, ';')
797 if (semicolon1 > 0) then
798 semicolon2 = index(buffer(semicolon1+1:), ';') + semicolon1
799 if (semicolon2 > semicolon1) then
800 read(buffer(1:semicolon1-1), '(i10)', iostat=ios) button
801 if (ios == 0) then
802 read(buffer(semicolon1+1:semicolon2-1), '(i10)', iostat=ios) col
803 if (ios == 0) then
804 read(buffer(semicolon2+1:i-1), '(i10)', iostat=ios) row
805 if (ios == 0) then
806 ! Format mouse event as key string
807 if (is_release) then
808 write(key_str, '(a,i0,a,i0,a,i0)') 'mouse-release:', button, ':', row, ':', col
809 else
810 ! Check for scroll wheel events (button 64 = scroll up, 65 = scroll down)
811 if (button == 64) then
812 key_str = 'mouse-scroll-up'
813 else if (button == 65) then
814 key_str = 'mouse-scroll-down'
815 ! Check modifiers in button code
816 else if (iand(button, 4) /= 0) then ! Shift
817 write(key_str, '(a,i0,a,i0,a,i0)') 'mouse-shift:', button, ':', row, ':', col
818 else if (iand(button, 8) /= 0) then ! Alt
819 write(key_str, '(a,i0,a,i0,a,i0)') 'mouse-alt:', button, ':', row, ':', col
820 else if (iand(button, 16) /= 0) then ! Ctrl
821 write(key_str, '(a,i0,a,i0,a,i0)') 'mouse-ctrl:', button, ':', row, ':', col
822 else if (iand(button, 32) /= 0) then ! Mouse motion (drag)
823 write(key_str, '(a,i0,a,i0,a,i0)') 'mouse-drag:', button, ':', row, ':', col
824 else
825 write(key_str, '(a,i0,a,i0,a,i0)') 'mouse-click:', button, ':', row, ':', col
826 end if
827 end if
828 return
829 end if
830 end if
831 end if
832 end if
833 end if
834
835 key_str = ''
836 end subroutine handle_mouse_event
837
838 end module input_handler_module