Skip to main content

atmos/kernel/
keyboard.rs

1#[derive(Debug, Clone, Copy)]
2pub struct KeyEvent {
3    pub keycode: u8,
4    pub pressed: bool,
5}
6
7use core::sync::atomic::{AtomicU8, Ordering};
8
9/// グローバル修飾キー状態。ビット 0=Shift, 1=Ctrl, 2=Alt, 3=Meta。
10/// キーボードデコード時に更新され、JS の DOM イベント(MouseEvent/KeyboardEvent)が
11/// shiftKey/ctrlKey/altKey/metaKey を読むために参照する。割り込み文脈からも触れるよう
12/// ロックフリーの Atomic にしている。
13static MODIFIER_STATE: AtomicU8 = AtomicU8::new(0);
14
15pub const MOD_SHIFT: u8 = 1 << 0;
16pub const MOD_CTRL: u8 = 1 << 1;
17pub const MOD_ALT: u8 = 1 << 2;
18pub const MOD_META: u8 = 1 << 3;
19
20/// 現在の修飾キー状態ビットマスクを取得する。
21pub fn modifier_state() -> u8 {
22    MODIFIER_STATE.load(Ordering::Relaxed)
23}
24
25/// 個々の修飾キーが押されているか。
26pub fn shift_down() -> bool {
27    modifier_state() & MOD_SHIFT != 0
28}
29pub fn ctrl_down() -> bool {
30    modifier_state() & MOD_CTRL != 0
31}
32pub fn alt_down() -> bool {
33    modifier_state() & MOD_ALT != 0
34}
35pub fn meta_down() -> bool {
36    modifier_state() & MOD_META != 0
37}
38
39/// 1つの修飾キーの押下状態を設定する(内部用)。
40fn set_modifier(bit: u8, pressed: bool) {
41    let cur = MODIFIER_STATE.load(Ordering::Relaxed);
42    let next = if pressed { cur | bit } else { cur & !bit };
43    MODIFIER_STATE.store(next, Ordering::Relaxed);
44}
45
46/// Ctrl+K (KillLine) 用の論理キーコード(センチネル)。
47/// 実キーには割り当てられていない 0x5A を用い、Ctrl+K を OS 共通の
48/// 「カーソル位置から行末まで削除」キーとして全テキスト入力へ届ける。
49pub const KC_KILL_LINE: u8 = 0x5A;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum KeyboardLayout {
53    Us,
54    Jis,
55}
56
57impl KeyboardLayout {
58    pub const fn name(self) -> &'static str {
59        match self {
60            Self::Us => "US",
61            Self::Jis => "JIS",
62        }
63    }
64
65    pub fn parse(name: &str) -> Option<Self> {
66        if name.eq_ignore_ascii_case("us") {
67            Some(Self::Us)
68        } else if name.eq_ignore_ascii_case("js")
69            || name.eq_ignore_ascii_case("jis")
70            || name.eq_ignore_ascii_case("jp")
71        {
72            Some(Self::Jis)
73        } else {
74            None
75        }
76    }
77}
78
79pub struct Keyboard {
80    pub shift_pressed: bool,
81    pub ctrl_pressed: bool,
82    pub alt_pressed: bool,
83    layout: KeyboardLayout,
84    logical_map: [u8; 128],
85}
86
87impl Default for Keyboard {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93impl Keyboard {
94    pub const fn new() -> Self {
95        Self {
96            shift_pressed: false,
97            ctrl_pressed: false,
98            alt_pressed: false,
99            layout: KeyboardLayout::Jis,
100            logical_map: [0; 128],
101        }
102    }
103
104    pub fn set_layout(&mut self, layout: KeyboardLayout) {
105        self.layout = layout;
106    }
107
108    pub const fn layout(&self) -> KeyboardLayout {
109        self.layout
110    }
111
112    /// スキャンコード(Make/Break)をデコードしてキーイベントを生成します
113    pub fn decode(&mut self, code: u8) -> Option<KeyEvent> {
114        // 最上位ビットが0ならMake (押下)、1ならBreak (離鍵)
115        let pressed = (code & 0x80) == 0;
116        let phys_keycode = code & 0x7F;
117
118        if phys_keycode >= 128 {
119            return None;
120        }
121
122        let mut keycode = phys_keycode;
123
124        if pressed {
125            // CapsLock ➔ Ctrl 置換の適用 (CapsLock=0x3A ➔ LCtrl=0x1D)
126            if keycode == 0x3A && crate::kernel::config::get_config().swap_caps_ctrl {
127                keycode = 0x1D;
128            }
129
130            // Ctrl + 各種キーの Emacs ライクな変換 (Ctrl + H ➔ Backspace などのショートカットを網羅)
131            if self.ctrl_pressed {
132                if let Some(c) = alpha_char(keycode, self.shift_pressed) {
133                    match c.to_ascii_lowercase() {
134                        'p' => keycode = 0x48,         // Up
135                        'n' => keycode = 0x50,         // Down
136                        'b' => keycode = 0x4B,         // Left
137                        'f' => keycode = 0x4D,         // Right
138                        'a' => keycode = 0x47,         // Home
139                        'e' => keycode = 0x4F,         // End
140                        'h' => keycode = 0x0E,         // Backspace
141                        'd' => keycode = 0x53,         // Delete
142                        'k' => keycode = KC_KILL_LINE, // KillLine (行末まで削除)
143                        _ => {}
144                    }
145                }
146            }
147
148            // Make 時の論理キーコードを保存
149            self.logical_map[phys_keycode as usize] = keycode;
150        } else {
151            // Break 時は Make 時と同一の論理キーコードを復元
152            let mapped = self.logical_map[phys_keycode as usize];
153            if mapped != 0 {
154                keycode = mapped;
155                self.logical_map[phys_keycode as usize] = 0;
156            } else {
157                if keycode == 0x3A && crate::kernel::config::get_config().swap_caps_ctrl {
158                    keycode = 0x1D;
159                }
160            }
161        }
162
163        // 特殊キー(Shift, Ctrl, Alt)の状態管理
164        match keycode {
165            0x2A | 0x36 => {
166                // LShift, RShiftのスキャンコード
167                self.shift_pressed = pressed;
168                set_modifier(MOD_SHIFT, pressed);
169            }
170            0x1D => {
171                // LCtrlのスキャンコード
172                self.ctrl_pressed = pressed;
173                set_modifier(MOD_CTRL, pressed);
174            }
175            0x38 => {
176                // LAltのスキャンコード
177                self.alt_pressed = pressed;
178                set_modifier(MOD_ALT, pressed);
179            }
180            _ => {}
181        }
182
183        Some(KeyEvent { keycode, pressed })
184    }
185
186    /// キーコードに対応する文字(ASCII)を取得します(Shift対応)
187    pub fn get_ascii(&self, keycode: u8) -> Option<char> {
188        if let Some(letter) = alpha_char(keycode, self.shift_pressed) {
189            return Some(letter);
190        }
191
192        let layout = crate::kernel::config::get_config().keyboard_layout;
193        match layout {
194            KeyboardLayout::Us => us_ascii(keycode, self.shift_pressed),
195            KeyboardLayout::Jis => jis_ascii(keycode, self.shift_pressed),
196        }
197    }
198}
199
200/// シリアルコンソール用: ASCII 文字を物理スキャンコードと Shift 要否へ逆変換する。
201///
202/// `alpha_char` とレイアウトの ascii テーブルを総当たりで照合するため、
203/// `get_ascii` の定義と**常に一致**する (テーブルを二重管理しない)。
204/// 戻り値 `(make_scancode, needs_shift)`。対応キーが無ければ `None`。
205pub fn ascii_to_scancode(c: char, layout: KeyboardLayout) -> Option<(u8, bool)> {
206    // 制御文字は直接対応キーへ
207    match c {
208        '\n' | '\r' => return Some((0x1C, false)),     // Enter
209        '\x08' | '\x7f' => return Some((0x0E, false)), // Backspace
210        '\t' => return Some((0x0F, false)),
211        // 【2026-07-31】ESC が変換表から漏れていた。
212        // シリアルコンソールから ESC を送っても捨てられるため、
213        // ブラウザ起動後に抜ける手段(`main.rs` の keycode 0x01 判定)が
214        // 使えず、ターミナルへコマンドを送れなくなっていた。
215        '' => return Some((0x01, false)),
216        _ => {}
217    }
218    for kc in 0u8..0x80 {
219        for &shift in &[false, true] {
220            if alpha_char(kc, shift) == Some(c) {
221                return Some((kc, shift));
222            }
223            let mapped = match layout {
224                KeyboardLayout::Us => us_ascii(kc, shift),
225                KeyboardLayout::Jis => jis_ascii(kc, shift),
226            };
227            if mapped == Some(c) {
228                return Some((kc, shift));
229            }
230        }
231    }
232    None
233}
234
235fn alpha_char(keycode: u8, shift_pressed: bool) -> Option<char> {
236    let c = match keycode {
237        0x1E => {
238            if shift_pressed {
239                'A'
240            } else {
241                'a'
242            }
243        }
244        0x30 => {
245            if shift_pressed {
246                'B'
247            } else {
248                'b'
249            }
250        }
251        0x2E => {
252            if shift_pressed {
253                'C'
254            } else {
255                'c'
256            }
257        }
258        0x20 => {
259            if shift_pressed {
260                'D'
261            } else {
262                'd'
263            }
264        }
265        0x12 => {
266            if shift_pressed {
267                'E'
268            } else {
269                'e'
270            }
271        }
272        0x21 => {
273            if shift_pressed {
274                'F'
275            } else {
276                'f'
277            }
278        }
279        0x22 => {
280            if shift_pressed {
281                'G'
282            } else {
283                'g'
284            }
285        }
286        0x23 => {
287            if shift_pressed {
288                'H'
289            } else {
290                'h'
291            }
292        }
293        0x17 => {
294            if shift_pressed {
295                'I'
296            } else {
297                'i'
298            }
299        }
300        0x24 => {
301            if shift_pressed {
302                'J'
303            } else {
304                'j'
305            }
306        }
307        0x25 => {
308            if shift_pressed {
309                'K'
310            } else {
311                'k'
312            }
313        }
314        0x26 => {
315            if shift_pressed {
316                'L'
317            } else {
318                'l'
319            }
320        }
321        0x32 => {
322            if shift_pressed {
323                'M'
324            } else {
325                'm'
326            }
327        }
328        0x31 => {
329            if shift_pressed {
330                'N'
331            } else {
332                'n'
333            }
334        }
335        0x18 => {
336            if shift_pressed {
337                'O'
338            } else {
339                'o'
340            }
341        }
342        0x19 => {
343            if shift_pressed {
344                'P'
345            } else {
346                'p'
347            }
348        }
349        0x10 => {
350            if shift_pressed {
351                'Q'
352            } else {
353                'q'
354            }
355        }
356        0x13 => {
357            if shift_pressed {
358                'R'
359            } else {
360                'r'
361            }
362        }
363        0x1F => {
364            if shift_pressed {
365                'S'
366            } else {
367                's'
368            }
369        }
370        0x14 => {
371            if shift_pressed {
372                'T'
373            } else {
374                't'
375            }
376        }
377        0x16 => {
378            if shift_pressed {
379                'U'
380            } else {
381                'u'
382            }
383        }
384        0x2F => {
385            if shift_pressed {
386                'V'
387            } else {
388                'v'
389            }
390        }
391        0x11 => {
392            if shift_pressed {
393                'W'
394            } else {
395                'w'
396            }
397        }
398        0x2D => {
399            if shift_pressed {
400                'X'
401            } else {
402                'x'
403            }
404        }
405        0x15 => {
406            if shift_pressed {
407                'Y'
408            } else {
409                'y'
410            }
411        }
412        0x2C => {
413            if shift_pressed {
414                'Z'
415            } else {
416                'z'
417            }
418        }
419        _ => return None,
420    };
421    Some(c)
422}
423
424fn us_ascii(keycode: u8, shift_pressed: bool) -> Option<char> {
425    let c = match keycode {
426        0x02 => {
427            if shift_pressed {
428                '!'
429            } else {
430                '1'
431            }
432        }
433        0x03 => {
434            if shift_pressed {
435                '@'
436            } else {
437                '2'
438            }
439        }
440        0x04 => {
441            if shift_pressed {
442                '#'
443            } else {
444                '3'
445            }
446        }
447        0x05 => {
448            if shift_pressed {
449                '$'
450            } else {
451                '4'
452            }
453        }
454        0x06 => {
455            if shift_pressed {
456                '%'
457            } else {
458                '5'
459            }
460        }
461        0x07 => {
462            if shift_pressed {
463                '^'
464            } else {
465                '6'
466            }
467        }
468        0x08 => {
469            if shift_pressed {
470                '&'
471            } else {
472                '7'
473            }
474        }
475        0x09 => {
476            if shift_pressed {
477                '*'
478            } else {
479                '8'
480            }
481        }
482        0x0A => {
483            if shift_pressed {
484                '('
485            } else {
486                '9'
487            }
488        }
489        0x0B => {
490            if shift_pressed {
491                ')'
492            } else {
493                '0'
494            }
495        }
496        0x0C => {
497            if shift_pressed {
498                '_'
499            } else {
500                '-'
501            }
502        }
503        0x0D => {
504            if shift_pressed {
505                '+'
506            } else {
507                '='
508            }
509        }
510        0x0E => '\x08',
511        0x0F => '\t',
512        0x1A => {
513            if shift_pressed {
514                '{'
515            } else {
516                '['
517            }
518        }
519        0x1B => {
520            if shift_pressed {
521                '}'
522            } else {
523                ']'
524            }
525        }
526        0x27 => {
527            if shift_pressed {
528                ':'
529            } else {
530                ';'
531            }
532        }
533        0x28 => {
534            if shift_pressed {
535                '"'
536            } else {
537                '\''
538            }
539        }
540        0x29 => {
541            if shift_pressed {
542                '~'
543            } else {
544                '`'
545            }
546        }
547        0x2B => {
548            if shift_pressed {
549                '|'
550            } else {
551                '\\'
552            }
553        }
554        0x33 => {
555            if shift_pressed {
556                '<'
557            } else {
558                ','
559            }
560        }
561        0x34 => {
562            if shift_pressed {
563                '>'
564            } else {
565                '.'
566            }
567        }
568        0x35 => {
569            if shift_pressed {
570                '?'
571            } else {
572                '/'
573            }
574        }
575        0x39 => ' ',
576        0x1C => '\n',
577        _ => return None,
578    };
579    Some(c)
580}
581
582fn jis_ascii(keycode: u8, shift_pressed: bool) -> Option<char> {
583    let c = match keycode {
584        0x02 => {
585            if shift_pressed {
586                '!'
587            } else {
588                '1'
589            }
590        }
591        0x03 => {
592            if shift_pressed {
593                '"'
594            } else {
595                '2'
596            }
597        }
598        0x04 => {
599            if shift_pressed {
600                '#'
601            } else {
602                '3'
603            }
604        }
605        0x05 => {
606            if shift_pressed {
607                '$'
608            } else {
609                '4'
610            }
611        }
612        0x06 => {
613            if shift_pressed {
614                '%'
615            } else {
616                '5'
617            }
618        }
619        0x07 => {
620            if shift_pressed {
621                '&'
622            } else {
623                '6'
624            }
625        }
626        0x08 => {
627            if shift_pressed {
628                '\''
629            } else {
630                '7'
631            }
632        }
633        0x09 => {
634            if shift_pressed {
635                '('
636            } else {
637                '8'
638            }
639        }
640        0x0A => {
641            if shift_pressed {
642                ')'
643            } else {
644                '9'
645            }
646        }
647        // JIS配列の0キー。ここがNoneだと0が入力できない。
648        0x0B => {
649            if shift_pressed {
650                ')'
651            } else {
652                '0'
653            }
654        }
655        0x0C => {
656            if shift_pressed {
657                '='
658            } else {
659                '-'
660            }
661        }
662        0x0D => {
663            if shift_pressed {
664                '~'
665            } else {
666                '^'
667            }
668        }
669        0x0E => '\x08',
670        0x0F => '\t',
671        0x1A => {
672            if shift_pressed {
673                '`'
674            } else {
675                '@'
676            }
677        }
678        0x1B => {
679            if shift_pressed {
680                '{'
681            } else {
682                '['
683            }
684        }
685        0x27 => {
686            if shift_pressed {
687                '+'
688            } else {
689                ';'
690            }
691        }
692        0x28 => {
693            if shift_pressed {
694                '*'
695            } else {
696                ':'
697            }
698        }
699        0x29 => return None,
700        0x2B => {
701            if shift_pressed {
702                '}'
703            } else {
704                ']'
705            }
706        }
707        0x33 => {
708            if shift_pressed {
709                '<'
710            } else {
711                ','
712            }
713        }
714        0x34 => {
715            if shift_pressed {
716                '>'
717            } else {
718                '.'
719            }
720        }
721        0x35 => {
722            if shift_pressed {
723                '?'
724            } else {
725                '/'
726            }
727        }
728        0x39 => ' ',
729        0x1C => '\n',
730        0x73 => {
731            if shift_pressed {
732                '_'
733            } else {
734                '\\'
735            }
736        }
737        0x7D => {
738            if shift_pressed {
739                '|'
740            } else {
741                '\\'
742            }
743        }
744        _ => return None,
745    };
746    Some(c)
747}