⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/desktop/
display_manager.rs

1//! Display Manager
2//!
3//! Provides login screen rendering, session management, virtual terminal
4//! switching, and idle-timeout auto-lock. Delegates authentication to
5//! `crate::security::auth`.
6//!
7//! All rendering uses integer coordinates and the kernel's 8x16 bitmap font.
8
9#![allow(dead_code)]
10
11use alloc::{collections::BTreeMap, string::String, vec::Vec};
12use core::sync::atomic::{AtomicU64, Ordering};
13
14// ---------------------------------------------------------------------------
15// Session types
16// ---------------------------------------------------------------------------
17
18/// Type of display session.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub enum SessionType {
21    /// Text-mode console (VT).
22    #[default]
23    Console,
24    /// Desktop GUI session.
25    Desktop,
26    /// Wayland-only session.
27    Wayland,
28    /// KDE Plasma 6 session via external init script.
29    KdePlasma,
30}
31
32// ---------------------------------------------------------------------------
33// Login screen
34// ---------------------------------------------------------------------------
35
36/// State of the login form.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
38pub enum LoginField {
39    /// Cursor is in the username field.
40    #[default]
41    Username,
42    /// Cursor is in the password field.
43    Password,
44    /// Authenticating (waiting for result).
45    Authenticating,
46    /// Authentication failed.
47    Failed,
48    /// Authentication succeeded.
49    Success,
50}
51
52/// Login screen state.
53#[derive(Debug, Clone)]
54pub struct LoginScreen {
55    /// Username input buffer (max 32 chars).
56    pub username_buffer: String,
57    /// Password input buffer (max 64 chars).
58    pub password_buffer: String,
59    /// Error message (e.g. "Invalid credentials").
60    pub error_message: String,
61    /// Current field/state.
62    pub state: LoginField,
63    /// Maximum username length.
64    max_username: usize,
65    /// Maximum password length.
66    max_password: usize,
67    /// Cursor blink counter.
68    cursor_blink: u32,
69}
70
71impl Default for LoginScreen {
72    fn default() -> Self {
73        Self::new()
74    }
75}
76
77impl LoginScreen {
78    /// Create a new login screen.
79    pub fn new() -> Self {
80        Self {
81            username_buffer: String::new(),
82            password_buffer: String::new(),
83            error_message: String::new(),
84            state: LoginField::Username,
85            max_username: 32,
86            max_password: 64,
87            cursor_blink: 0,
88        }
89    }
90
91    /// Reset the login screen to its initial state.
92    pub fn reset(&mut self) {
93        self.username_buffer.clear();
94        self.password_buffer.clear();
95        self.error_message.clear();
96        self.state = LoginField::Username;
97        self.cursor_blink = 0;
98    }
99
100    /// Handle a key press.
101    ///
102    /// Returns `true` if the form is ready to submit (Enter on password field).
103    pub fn handle_key(&mut self, key: u8) -> bool {
104        match self.state {
105            LoginField::Username => {
106                match key {
107                    b'\n' | b'\r' => {
108                        // Tab to password field
109                        self.state = LoginField::Password;
110                    }
111                    b'\t' => {
112                        self.state = LoginField::Password;
113                    }
114                    0x08 | 0x7F => {
115                        // Backspace
116                        self.username_buffer.pop();
117                    }
118                    c if (0x20..0x7F).contains(&c) => {
119                        if self.username_buffer.len() < self.max_username {
120                            self.username_buffer.push(c as char);
121                        }
122                    }
123                    _ => {}
124                }
125                false
126            }
127            LoginField::Password => {
128                match key {
129                    b'\n' | b'\r' => {
130                        // Submit
131                        self.state = LoginField::Authenticating;
132                        return true;
133                    }
134                    0x08 | 0x7F => {
135                        self.password_buffer.pop();
136                    }
137                    c if (0x20..0x7F).contains(&c) => {
138                        if self.password_buffer.len() < self.max_password {
139                            self.password_buffer.push(c as char);
140                        }
141                    }
142                    _ => {}
143                }
144                false
145            }
146            LoginField::Failed => {
147                // Any key press resets to username
148                self.reset();
149                false
150            }
151            LoginField::Authenticating | LoginField::Success => false,
152        }
153    }
154
155    /// Render the login screen to a pixel buffer.
156    ///
157    /// `buf` is `width * height` ARGB8888 pixels.
158    pub fn render(&mut self, buf: &mut [u32], width: u32, height: u32) {
159        let w = width as i32;
160        let h = height as i32;
161
162        // Fill background with dark blue
163        for px in buf.iter_mut() {
164            *px = 0xFF1A1A2E;
165        }
166
167        // Login box dimensions
168        let box_w: i32 = 320;
169        let box_h: i32 = 200;
170        let box_x = (w - box_w) / 2;
171        let box_y = (h - box_h) / 2;
172
173        // Draw box background
174        for row in 0..box_h {
175            let dy = box_y + row;
176            if dy < 0 || dy >= h {
177                continue;
178            }
179            for col in 0..box_w {
180                let dx = box_x + col;
181                if dx < 0 || dx >= w {
182                    continue;
183                }
184                buf[(dy * w + dx) as usize] = 0xFF2D2D44;
185            }
186        }
187
188        // Draw box border
189        let border_color = 0xFF5555AA;
190        for col in 0..box_w {
191            let dx = box_x + col;
192            if dx >= 0 && dx < w {
193                if box_y >= 0 && box_y < h {
194                    buf[(box_y * w + dx) as usize] = border_color;
195                }
196                let by = box_y + box_h - 1;
197                if by >= 0 && by < h {
198                    buf[(by * w + dx) as usize] = border_color;
199                }
200            }
201        }
202        for row in 0..box_h {
203            let dy = box_y + row;
204            if dy >= 0 && dy < h {
205                if box_x >= 0 && box_x < w {
206                    buf[(dy * w + box_x) as usize] = border_color;
207                }
208                let bx = box_x + box_w - 1;
209                if bx >= 0 && bx < w {
210                    buf[(dy * w + bx) as usize] = border_color;
211                }
212            }
213        }
214
215        // Title: "VeridianOS Login"
216        let title = b"VeridianOS Login";
217        let title_x = box_x + (box_w - title.len() as i32 * 8) / 2;
218        let title_y = box_y + 16;
219        draw_text(buf, w, h, title_x, title_y, title, 0xFFCCCCFF);
220
221        // Username label + field
222        let label_x = box_x + 20;
223        let field_x = box_x + 100;
224        let user_y = box_y + 50;
225        draw_text(buf, w, h, label_x, user_y, b"User:", 0xFFAAAACC);
226        draw_field(
227            buf,
228            w,
229            h,
230            field_x,
231            user_y,
232            180,
233            &self.username_buffer,
234            self.state == LoginField::Username,
235        );
236
237        // Password label + field (masked)
238        let pass_y = box_y + 80;
239        draw_text(buf, w, h, label_x, pass_y, b"Pass:", 0xFFAAAACC);
240        let mut masked = String::new();
241        for _ in 0..self.password_buffer.len() {
242            masked.push('*');
243        }
244        draw_field(
245            buf,
246            w,
247            h,
248            field_x,
249            pass_y,
250            180,
251            &masked,
252            self.state == LoginField::Password,
253        );
254
255        // Submit button
256        let btn_y = box_y + 120;
257        let btn_x = box_x + (box_w - 80) / 2;
258        let btn_color = if self.state == LoginField::Authenticating {
259            0xFF666688
260        } else {
261            0xFF4444AA
262        };
263        for row in 0..24 {
264            let dy = btn_y + row;
265            if dy < 0 || dy >= h {
266                continue;
267            }
268            for col in 0..80 {
269                let dx = btn_x + col;
270                if dx >= 0 && dx < w {
271                    buf[(dy * w + dx) as usize] = btn_color;
272                }
273            }
274        }
275        let btn_label = if self.state == LoginField::Authenticating {
276            b"Wait..." as &[u8]
277        } else {
278            b"Login" as &[u8]
279        };
280        let btn_tx = btn_x + (80 - btn_label.len() as i32 * 8) / 2;
281        draw_text(buf, w, h, btn_tx, btn_y + 4, btn_label, 0xFFFFFFFF);
282
283        // Error message
284        if !self.error_message.is_empty() {
285            let err_y = box_y + 160;
286            let err_bytes = self.error_message.as_bytes();
287            let err_x = box_x + (box_w - err_bytes.len() as i32 * 8) / 2;
288            draw_text(buf, w, h, err_x, err_y, err_bytes, 0xFFFF4444);
289        }
290
291        self.cursor_blink = self.cursor_blink.wrapping_add(1);
292    }
293
294    /// Set authentication result.
295    pub fn set_auth_result(&mut self, success: bool, message: &str) {
296        if success {
297            self.state = LoginField::Success;
298            self.error_message.clear();
299        } else {
300            self.state = LoginField::Failed;
301            self.error_message = String::from(message);
302        }
303    }
304
305    /// Attempt authentication using the security subsystem.
306    pub fn authenticate(&mut self) -> bool {
307        // Delegate to security::auth
308        #[cfg(feature = "alloc")]
309        {
310            let _username = &self.username_buffer;
311            let _password = &self.password_buffer;
312            // In a real system, call crate::security::auth::authenticate()
313            // For now, accept "root" with any non-empty password
314            if self.username_buffer == "root" && !self.password_buffer.is_empty() {
315                self.set_auth_result(true, "");
316                return true;
317            }
318        }
319        self.set_auth_result(false, "Invalid credentials");
320        false
321    }
322}
323
324// ---------------------------------------------------------------------------
325// Display session
326// ---------------------------------------------------------------------------
327
328/// An active display session.
329#[derive(Debug, Clone)]
330pub struct DisplaySession {
331    /// Session type.
332    pub session_type: SessionType,
333    /// User ID of the session owner.
334    pub user_id: u32,
335    /// Username.
336    pub username: String,
337    /// Tick at which the session was created.
338    pub login_time: u64,
339    /// Idle timeout in ticks (0 = never).
340    pub idle_timeout_ticks: u64,
341    /// Last activity tick.
342    pub last_activity: u64,
343    /// Whether the session is locked.
344    pub locked: bool,
345}
346
347impl DisplaySession {
348    /// Create a new session.
349    pub fn new(session_type: SessionType, user_id: u32, username: &str) -> Self {
350        Self {
351            session_type,
352            user_id,
353            username: String::from(username),
354            login_time: 0,
355            idle_timeout_ticks: 300_000, // ~5 min at 1000 Hz
356            last_activity: 0,
357            locked: false,
358        }
359    }
360
361    /// Record user activity (resets idle timer).
362    pub fn touch(&mut self, tick: u64) {
363        self.last_activity = tick;
364    }
365
366    /// Check if the session has been idle long enough to auto-lock.
367    pub fn is_idle(&self, current_tick: u64) -> bool {
368        if self.idle_timeout_ticks == 0 {
369            return false;
370        }
371        current_tick.saturating_sub(self.last_activity) >= self.idle_timeout_ticks
372    }
373}
374
375// ---------------------------------------------------------------------------
376// Virtual terminal
377// ---------------------------------------------------------------------------
378
379/// A virtual terminal (VT).
380#[derive(Debug, Clone)]
381pub struct VirtualTerminal {
382    /// VT number (1-6).
383    pub id: u8,
384    /// Session type for this VT.
385    pub session_type: SessionType,
386    /// Whether this VT is currently active.
387    pub active: bool,
388    /// User ID of the session on this VT (0 = no session).
389    pub user_id: u32,
390}
391
392impl VirtualTerminal {
393    /// Create a new VT.
394    pub fn new(id: u8) -> Self {
395        Self {
396            id,
397            session_type: SessionType::Console,
398            active: false,
399            user_id: 0,
400        }
401    }
402}
403
404// ---------------------------------------------------------------------------
405// Display manager
406// ---------------------------------------------------------------------------
407
408/// Global tick counter for idle tracking.
409static TICK_COUNTER: AtomicU64 = AtomicU64::new(0);
410
411/// The display manager controls login, sessions, and VT switching.
412#[derive(Debug)]
413pub struct DisplayManager {
414    /// Currently active session (if logged in).
415    pub current_session: Option<DisplaySession>,
416    /// Login screen state.
417    pub login_screen: LoginScreen,
418    /// All sessions indexed by user ID.
419    sessions: BTreeMap<u32, DisplaySession>,
420    /// Virtual terminals (1-6).
421    virtual_terminals: Vec<VirtualTerminal>,
422    /// Active VT index.
423    active_vt: u8,
424    /// Whether auto-login is enabled.
425    auto_login_enabled: bool,
426    /// Auto-login username.
427    auto_login_user: String,
428    /// Whether the display manager is showing the login screen.
429    pub showing_login: bool,
430}
431
432impl Default for DisplayManager {
433    fn default() -> Self {
434        Self::new()
435    }
436}
437
438impl DisplayManager {
439    /// Create a new display manager with 6 virtual terminals.
440    pub fn new() -> Self {
441        let mut vts = Vec::new();
442        for i in 1..=6 {
443            vts.push(VirtualTerminal::new(i));
444        }
445        // VT1 is active by default
446        if let Some(vt) = vts.first_mut() {
447            vt.active = true;
448        }
449
450        Self {
451            current_session: None,
452            login_screen: LoginScreen::new(),
453            sessions: BTreeMap::new(),
454            virtual_terminals: vts,
455            active_vt: 1,
456            auto_login_enabled: false,
457            auto_login_user: String::new(),
458            showing_login: true,
459        }
460    }
461
462    /// Show the login screen.
463    pub fn show_login(&mut self) {
464        self.login_screen.reset();
465        self.showing_login = true;
466    }
467
468    /// Spawn a new session for the authenticated user.
469    pub fn spawn_session(
470        &mut self,
471        session_type: SessionType,
472        user_id: u32,
473        username: &str,
474    ) -> bool {
475        let tick = TICK_COUNTER.load(Ordering::Relaxed);
476        let mut session = DisplaySession::new(session_type, user_id, username);
477        session.login_time = tick;
478        session.last_activity = tick;
479
480        self.sessions.insert(user_id, session.clone());
481        self.current_session = Some(session);
482        self.showing_login = false;
483
484        // Assign to active VT
485        let vt_idx = (self.active_vt as usize).saturating_sub(1);
486        if let Some(vt) = self.virtual_terminals.get_mut(vt_idx) {
487            vt.user_id = user_id;
488            vt.session_type = session_type;
489        }
490
491        true
492    }
493
494    /// Lock the current session.
495    pub fn lock_session(&mut self) {
496        if let Some(ref mut session) = self.current_session {
497            session.locked = true;
498            self.showing_login = true;
499            self.login_screen.reset();
500            self.login_screen.username_buffer = session.username.clone();
501            self.login_screen.state = LoginField::Password;
502        }
503    }
504
505    /// Unlock the current session.
506    pub fn unlock_session(&mut self) -> bool {
507        if let Some(ref mut session) = self.current_session {
508            if session.locked {
509                session.locked = false;
510                self.showing_login = false;
511                let tick = TICK_COUNTER.load(Ordering::Relaxed);
512                session.touch(tick);
513                return true;
514            }
515        }
516        false
517    }
518
519    /// Handle Ctrl+Alt+F1-F6 virtual terminal switching.
520    ///
521    /// `vt_num` is 1-6.
522    pub fn handle_vt_switch(&mut self, vt_num: u8) -> bool {
523        if !(1..=6).contains(&vt_num) {
524            return false;
525        }
526
527        // Deactivate current
528        let old_idx = (self.active_vt as usize).saturating_sub(1);
529        if let Some(vt) = self.virtual_terminals.get_mut(old_idx) {
530            vt.active = false;
531        }
532
533        // Activate new
534        let new_idx = (vt_num as usize).saturating_sub(1);
535        if let Some(vt) = self.virtual_terminals.get_mut(new_idx) {
536            vt.active = true;
537            self.active_vt = vt_num;
538
539            // Switch to the session on this VT (if any)
540            if vt.user_id > 0 {
541                if let Some(session) = self.sessions.get(&vt.user_id) {
542                    self.current_session = Some(session.clone());
543                    self.showing_login = false;
544                } else {
545                    self.showing_login = true;
546                }
547            } else {
548                self.showing_login = true;
549            }
550            return true;
551        }
552
553        false
554    }
555
556    /// Check idle timeout and auto-lock if needed.
557    pub fn check_idle(&mut self, current_tick: u64) {
558        TICK_COUNTER.store(current_tick, Ordering::Relaxed);
559        if let Some(ref session) = self.current_session {
560            if !session.locked && session.is_idle(current_tick) {
561                self.lock_session();
562            }
563        }
564    }
565
566    /// Enable auto-login for the given user.
567    pub fn set_auto_login(&mut self, username: &str) {
568        self.auto_login_enabled = true;
569        self.auto_login_user = String::from(username);
570    }
571
572    /// Attempt auto-login if configured.
573    pub fn auto_login(&mut self) -> bool {
574        if self.auto_login_enabled && !self.auto_login_user.is_empty() {
575            let username = self.auto_login_user.clone();
576            self.spawn_session(SessionType::Desktop, 0, &username);
577            return true;
578        }
579        false
580    }
581
582    /// Get the active VT number.
583    pub fn active_vt(&self) -> u8 {
584        self.active_vt
585    }
586
587    /// Get information about a VT.
588    pub fn get_vt(&self, num: u8) -> Option<&VirtualTerminal> {
589        let idx = (num as usize).saturating_sub(1);
590        self.virtual_terminals.get(idx)
591    }
592
593    /// Number of active sessions.
594    pub fn session_count(&self) -> usize {
595        self.sessions.len()
596    }
597
598    /// Handle a key press, delegating to the login screen when showing.
599    ///
600    /// Returns `true` if a session was spawned.
601    pub fn handle_key(&mut self, key: u8) -> bool {
602        if !self.showing_login {
603            return false;
604        }
605
606        let submit = self.login_screen.handle_key(key);
607        if submit {
608            let success = self.login_screen.authenticate();
609            if success {
610                let username = self.login_screen.username_buffer.clone();
611                self.spawn_session(SessionType::Desktop, 0, &username);
612                return true;
613            }
614        }
615        false
616    }
617
618    /// Render the display manager (login screen or nothing if session active).
619    pub fn render(&mut self, buf: &mut [u32], width: u32, height: u32) {
620        if self.showing_login {
621            self.login_screen.render(buf, width, height);
622        }
623    }
624}
625
626// ---------------------------------------------------------------------------
627// Per-user session state for multi-user support
628// ---------------------------------------------------------------------------
629
630/// Information about a user session visible to the display manager.
631#[derive(Debug, Clone)]
632pub struct SessionInfo {
633    /// Session identifier (matches `process::session::SessionId`).
634    pub session_id: u64,
635    /// User ID.
636    pub user_id: u32,
637    /// Username.
638    pub username: String,
639    /// Session type (Console, Desktop, or Wayland).
640    pub session_type: SessionType,
641    /// Virtual terminal number assigned to this session.
642    pub vt_number: u8,
643    /// Whether the session is currently active.
644    pub active: bool,
645    /// Whether the session is locked.
646    pub locked: bool,
647}
648
649impl DisplayManager {
650    /// Create a new user session on the next available VT.
651    ///
652    /// VT assignment: `VT7 + session_index`.
653    pub fn create_user_session(
654        &mut self,
655        user_id: u32,
656        username: &str,
657        session_type: SessionType,
658    ) -> Option<u64> {
659        if self.sessions.len() >= 8 {
660            return None;
661        }
662
663        let session_index = self.sessions.len() as u8;
664        let vt_number = 7u8.saturating_add(session_index);
665
666        // Assign the VT if within range
667        let vt_idx = (vt_number as usize).saturating_sub(1);
668        if vt_idx < self.virtual_terminals.len() {
669            let vt = &mut self.virtual_terminals[vt_idx];
670            vt.user_id = user_id;
671            vt.session_type = session_type;
672        }
673
674        let tick = TICK_COUNTER.load(Ordering::Relaxed);
675        let mut session = DisplaySession::new(session_type, user_id, username);
676        session.login_time = tick;
677        session.last_activity = tick;
678
679        self.sessions.insert(user_id, session.clone());
680
681        // If no current session, make this one active
682        if self.current_session.is_none() {
683            self.current_session = Some(session);
684            self.showing_login = false;
685        }
686
687        // Return a pseudo session ID based on user_id
688        Some(user_id as u64)
689    }
690
691    /// Switch to a different user's session by user ID.
692    ///
693    /// The current session is preserved; the display switches to
694    /// the target session's VT.
695    pub fn switch_to_session(&mut self, target_user_id: u32) -> bool {
696        if let Some(session) = self.sessions.get(&target_user_id) {
697            self.current_session = Some(session.clone());
698            self.showing_login = false;
699
700            // Find and activate the corresponding VT
701            for vt in self.virtual_terminals.iter_mut() {
702                if vt.user_id == target_user_id {
703                    vt.active = true;
704                    self.active_vt = vt.id;
705                } else {
706                    vt.active = false;
707                }
708            }
709            true
710        } else {
711            false
712        }
713    }
714
715    /// Get information about all active sessions for the DM UI.
716    pub fn get_sessions(&self) -> Vec<SessionInfo> {
717        let mut result = Vec::new();
718        for (uid, session) in &self.sessions {
719            let info = SessionInfo {
720                session_id: *uid as u64,
721                user_id: *uid,
722                username: session.username.clone(),
723                session_type: session.session_type,
724                vt_number: self
725                    .virtual_terminals
726                    .iter()
727                    .find(|vt| vt.user_id == *uid)
728                    .map(|vt| vt.id)
729                    .unwrap_or(0),
730                active: self
731                    .current_session
732                    .as_ref()
733                    .map(|cs| cs.user_id == *uid)
734                    .unwrap_or(false),
735                locked: session.locked,
736            };
737            result.push(info);
738        }
739        result
740    }
741
742    /// Handle user logout: clean up session and return to login screen.
743    pub fn logout_user(&mut self, user_id: u32) {
744        // Remove session
745        self.sessions.remove(&user_id);
746
747        // Clear VT assignment
748        for vt in self.virtual_terminals.iter_mut() {
749            if vt.user_id == user_id {
750                vt.user_id = 0;
751                vt.session_type = SessionType::Console;
752            }
753        }
754
755        // If the logged-out user was the current session, switch or show login
756        if let Some(ref session) = self.current_session {
757            if session.user_id == user_id {
758                self.current_session = None;
759                // Try to switch to another session
760                if let Some((&next_uid, next_session)) = self.sessions.iter().next() {
761                    self.current_session = Some(next_session.clone());
762                    let _ = next_uid;
763                } else {
764                    self.show_login();
765                }
766            }
767        }
768    }
769}
770
771// ---------------------------------------------------------------------------
772// Rendering helpers
773// ---------------------------------------------------------------------------
774
775/// Draw text using a simple 8x16 pixel placeholder font.
776fn draw_text(buf: &mut [u32], bw: i32, bh: i32, x: i32, y: i32, text: &[u8], color: u32) {
777    for (i, &ch) in text.iter().enumerate() {
778        let cx = x + (i as i32) * 8;
779        if cx + 8 <= 0 || cx >= bw || y + 16 <= 0 || y >= bh {
780            continue;
781        }
782        if !(0x20..0x7F).contains(&ch) {
783            continue;
784        }
785        // Draw a simplified glyph: 6x12 inner block for each printable char
786        for row in 2..14 {
787            let dy = y + row;
788            if dy < 0 || dy >= bh {
789                continue;
790            }
791            for col in 1..7 {
792                let dx = cx + col;
793                if dx < 0 || dx >= bw {
794                    continue;
795                }
796                // Simple pattern: outline for letters
797                if row == 2 || row == 13 || col == 1 || col == 6 {
798                    buf[(dy * bw + dx) as usize] = color;
799                }
800            }
801        }
802    }
803}
804
805/// Draw an input field with optional cursor.
806fn draw_field(
807    buf: &mut [u32],
808    bw: i32,
809    bh: i32,
810    x: i32,
811    y: i32,
812    field_w: i32,
813    text: &str,
814    active: bool,
815) {
816    // Field background
817    let bg = if active { 0xFF3D3D55 } else { 0xFF2D2D44 };
818    for row in 0..18 {
819        let dy = y + row;
820        if dy < 0 || dy >= bh {
821            continue;
822        }
823        for col in 0..field_w {
824            let dx = x + col;
825            if dx >= 0 && dx < bw {
826                buf[(dy * bw + dx) as usize] = bg;
827            }
828        }
829    }
830
831    // Field border
832    let border = if active { 0xFF7777FF } else { 0xFF555577 };
833    for col in 0..field_w {
834        let dx = x + col;
835        if dx >= 0 && dx < bw {
836            if y >= 0 && y < bh {
837                buf[(y * bw + dx) as usize] = border;
838            }
839            let by = y + 17;
840            if by >= 0 && by < bh {
841                buf[(by * bw + dx) as usize] = border;
842            }
843        }
844    }
845
846    // Text
847    draw_text(buf, bw, bh, x + 4, y + 1, text.as_bytes(), 0xFFEEEEEE);
848}
849
850// ---------------------------------------------------------------------------
851// Tests
852// ---------------------------------------------------------------------------
853
854#[cfg(test)]
855mod tests {
856    #[allow(unused_imports)]
857    use alloc::vec;
858
859    use super::*;
860
861    #[test]
862    fn test_login_screen_new() {
863        let ls = LoginScreen::new();
864        assert_eq!(ls.state, LoginField::Username);
865        assert!(ls.username_buffer.is_empty());
866    }
867
868    #[test]
869    fn test_login_screen_type_username() {
870        let mut ls = LoginScreen::new();
871        ls.handle_key(b'r');
872        ls.handle_key(b'o');
873        ls.handle_key(b'o');
874        ls.handle_key(b't');
875        assert_eq!(ls.username_buffer, "root");
876    }
877
878    #[test]
879    fn test_login_screen_tab_to_password() {
880        let mut ls = LoginScreen::new();
881        ls.handle_key(b'\t');
882        assert_eq!(ls.state, LoginField::Password);
883    }
884
885    #[test]
886    fn test_login_screen_submit() {
887        let mut ls = LoginScreen::new();
888        ls.state = LoginField::Password;
889        ls.password_buffer = String::from("secret");
890        let submit = ls.handle_key(b'\n');
891        assert!(submit);
892        assert_eq!(ls.state, LoginField::Authenticating);
893    }
894
895    #[test]
896    fn test_login_screen_backspace() {
897        let mut ls = LoginScreen::new();
898        ls.handle_key(b'a');
899        ls.handle_key(b'b');
900        ls.handle_key(0x08); // backspace
901        assert_eq!(ls.username_buffer, "a");
902    }
903
904    #[test]
905    fn test_login_screen_authenticate_root() {
906        let mut ls = LoginScreen::new();
907        ls.username_buffer = String::from("root");
908        ls.password_buffer = String::from("pass");
909        assert!(ls.authenticate());
910        assert_eq!(ls.state, LoginField::Success);
911    }
912
913    #[test]
914    fn test_login_screen_authenticate_fail() {
915        let mut ls = LoginScreen::new();
916        ls.username_buffer = String::from("nobody");
917        ls.password_buffer = String::from("wrong");
918        assert!(!ls.authenticate());
919        assert_eq!(ls.state, LoginField::Failed);
920    }
921
922    #[test]
923    fn test_display_session_idle() {
924        let mut session = DisplaySession::new(SessionType::Desktop, 1, "test");
925        session.last_activity = 0;
926        session.idle_timeout_ticks = 1000;
927        assert!(!session.is_idle(500));
928        assert!(session.is_idle(1500));
929    }
930
931    #[test]
932    fn test_display_manager_spawn_session() {
933        let mut dm = DisplayManager::new();
934        assert!(dm.showing_login);
935        dm.spawn_session(SessionType::Desktop, 1, "root");
936        assert!(!dm.showing_login);
937        assert_eq!(dm.session_count(), 1);
938    }
939
940    #[test]
941    fn test_display_manager_lock_unlock() {
942        let mut dm = DisplayManager::new();
943        dm.spawn_session(SessionType::Desktop, 1, "root");
944        dm.lock_session();
945        assert!(dm.showing_login);
946        assert!(dm.current_session.as_ref().unwrap().locked);
947        dm.unlock_session();
948        assert!(!dm.showing_login);
949    }
950
951    #[test]
952    fn test_display_manager_vt_switch() {
953        let mut dm = DisplayManager::new();
954        assert_eq!(dm.active_vt(), 1);
955        dm.handle_vt_switch(3);
956        assert_eq!(dm.active_vt(), 3);
957    }
958
959    #[test]
960    fn test_display_manager_vt_invalid() {
961        let mut dm = DisplayManager::new();
962        assert!(!dm.handle_vt_switch(0));
963        assert!(!dm.handle_vt_switch(7));
964    }
965
966    #[test]
967    fn test_display_manager_auto_login() {
968        let mut dm = DisplayManager::new();
969        dm.set_auto_login("root");
970        assert!(dm.auto_login());
971        assert!(!dm.showing_login);
972    }
973
974    #[test]
975    fn test_display_manager_check_idle() {
976        let mut dm = DisplayManager::new();
977        dm.spawn_session(SessionType::Desktop, 1, "root");
978        dm.current_session.as_mut().unwrap().idle_timeout_ticks = 100;
979        dm.current_session.as_mut().unwrap().last_activity = 0;
980        dm.check_idle(200);
981        assert!(dm.showing_login);
982    }
983
984    #[test]
985    fn test_display_manager_handle_key() {
986        let mut dm = DisplayManager::new();
987        // Type "root\tpass\n"
988        for c in b"root" {
989            dm.handle_key(*c);
990        }
991        dm.handle_key(b'\t');
992        for c in b"pass" {
993            dm.handle_key(*c);
994        }
995        let spawned = dm.handle_key(b'\n');
996        assert!(spawned);
997        assert!(!dm.showing_login);
998    }
999
1000    #[test]
1001    fn test_login_render_no_panic() {
1002        let mut ls = LoginScreen::new();
1003        let mut buf = vec![0u32; 320 * 200];
1004        ls.render(&mut buf, 320, 200);
1005        // Verify it drew something (not all zeros)
1006        assert!(buf.iter().any(|&p| p != 0));
1007    }
1008
1009    #[test]
1010    fn test_virtual_terminal_new() {
1011        let vt = VirtualTerminal::new(1);
1012        assert_eq!(vt.id, 1);
1013        assert!(!vt.active);
1014        assert_eq!(vt.user_id, 0);
1015    }
1016
1017    #[test]
1018    fn test_session_touch() {
1019        let mut session = DisplaySession::new(SessionType::Console, 1, "test");
1020        session.touch(12345);
1021        assert_eq!(session.last_activity, 12345);
1022    }
1023
1024    #[test]
1025    fn test_create_user_session() {
1026        let mut dm = DisplayManager::new();
1027        let sid = dm.create_user_session(1000, "alice", SessionType::Desktop);
1028        assert!(sid.is_some());
1029        assert_eq!(dm.session_count(), 1);
1030        assert!(!dm.showing_login);
1031    }
1032
1033    #[test]
1034    fn test_create_user_session_max() {
1035        let mut dm = DisplayManager::new();
1036        for i in 0..8u32 {
1037            let name = alloc::format!("user{}", i);
1038            dm.create_user_session(i, &name, SessionType::Desktop);
1039        }
1040        let overflow = dm.create_user_session(99, "overflow", SessionType::Desktop);
1041        assert!(overflow.is_none());
1042    }
1043
1044    #[test]
1045    fn test_switch_to_session() {
1046        let mut dm = DisplayManager::new();
1047        dm.create_user_session(1, "alice", SessionType::Desktop);
1048        dm.create_user_session(2, "bob", SessionType::Desktop);
1049
1050        assert!(dm.switch_to_session(2));
1051        let current = dm.current_session.as_ref().unwrap();
1052        assert_eq!(current.user_id, 2);
1053        assert_eq!(current.username, "bob");
1054    }
1055
1056    #[test]
1057    fn test_get_sessions() {
1058        let mut dm = DisplayManager::new();
1059        dm.create_user_session(1, "alice", SessionType::Desktop);
1060        dm.create_user_session(2, "bob", SessionType::Wayland);
1061
1062        let sessions = dm.get_sessions();
1063        assert_eq!(sessions.len(), 2);
1064    }
1065
1066    #[test]
1067    fn test_logout_user() {
1068        let mut dm = DisplayManager::new();
1069        dm.create_user_session(1, "alice", SessionType::Desktop);
1070        dm.create_user_session(2, "bob", SessionType::Desktop);
1071
1072        dm.logout_user(1);
1073        assert_eq!(dm.session_count(), 1);
1074        // Should have switched to bob
1075        let current = dm.current_session.as_ref().unwrap();
1076        assert_eq!(current.user_id, 2);
1077    }
1078
1079    #[test]
1080    fn test_logout_last_user_shows_login() {
1081        let mut dm = DisplayManager::new();
1082        dm.create_user_session(1, "alice", SessionType::Desktop);
1083        dm.logout_user(1);
1084        assert_eq!(dm.session_count(), 0);
1085        assert!(dm.showing_login);
1086    }
1087}