diff options
Diffstat (limited to '')
| -rw-r--r-- | shell/src/lib.rs | 194 |
1 files changed, 158 insertions, 36 deletions
diff --git a/shell/src/lib.rs b/shell/src/lib.rs index c18d319..e1d54c8 100644 --- a/shell/src/lib.rs +++ b/shell/src/lib.rs | |||
| @@ -704,6 +704,8 @@ write `{} <path> TIL <marker>`", | |||
| 704 | "pci" => cmd_pci(&parsed), | 704 | "pci" => cmd_pci(&parsed), |
| 705 | "rescan" => cmd_rescan(&parsed), | 705 | "rescan" => cmd_rescan(&parsed), |
| 706 | "mouse" => cmd_mouse(&parsed), | 706 | "mouse" => cmd_mouse(&parsed), |
| 707 | "cursor" => cmd_cursor(&parsed), | ||
| 708 | "gfx" => cmd_gfx(&parsed), | ||
| 707 | "ls" => cmd_ls(&parsed), | 709 | "ls" => cmd_ls(&parsed), |
| 708 | "cat" => cmd_cat(&parsed), | 710 | "cat" => cmd_cat(&parsed), |
| 709 | "klog" => cmd_klog(&parsed), | 711 | "klog" => cmd_klog(&parsed), |
| @@ -741,8 +743,11 @@ fn cmd_help(_p: &Parsed) { | |||
| 741 | date — show current date/time (RTC) | 743 | date — show current date/time (RTC) |
| 742 | pci — list PCI devices | 744 | pci — list PCI devices |
| 743 | rescan — re-scan for hotplugged/removed disks (also runs automatically) | 745 | rescan — re-scan for hotplugged/removed disks (also runs automatically) |
| 744 | mouse — live mouse events (dx/dy/buttons); ^C stops | 746 | cursor — report the on-screen mouse cursor (x, y, buttons) |
| 745 | mouse <id> — post decoded mouse events to task <id>'s queue (input routing) | 747 | mouse — watch the cursor live (x/y/buttons); ^C stops |
| 748 | mouse <id> — route mouse events to task <id>'s queue (cursor still tracks) | ||
| 749 | gfx — demo: draw a status bar + RGB boxes (bottom strip) | ||
| 750 | gfx check — pixel self-test: shadow, display push, cursor overlay | ||
| 746 | bg <name> — spawn a background task (spinner, ticker, counter, boom, overflow) | 751 | bg <name> — spawn a background task (spinner, ticker, counter, boom, overflow) |
| 747 | msgrecv — spawn a task that blocks on its message queue and logs every | 752 | msgrecv — spawn a task that blocks on its message queue and logs every |
| 748 | message to serial (ids from `jobs`) | 753 | message to serial (ids from `jobs`) |
| @@ -1546,13 +1551,11 @@ fn cmd_rescan(_p: &Parsed) { | |||
| 1546 | } | 1551 | } |
| 1547 | } | 1552 | } |
| 1548 | 1553 | ||
| 1549 | /// Live mouse event stream: print each decoded packet (dx/dy/buttons) as | 1554 | /// Mouse front-end. The cursor task owns the PS/2 ring, so this |
| 1550 | /// it arrives until ^C. With an optional task id, events are **posted to | 1555 | /// command *watches the on-screen cursor* (position + buttons, printed |
| 1551 | /// that task's message queue** instead of printed — the input-routing | 1556 | /// only on change) until ^C. With a task id it also routes every event |
| 1552 | /// path the window manager will use (`mouse <id>` + `msgrecv` proves the | 1557 | /// to that task's queue — the window-manager input path — clearing the |
| 1553 | /// whole chain: IRQ12 → ring → decoder → queue → blocked task). Polls | 1558 | /// route on exit. |
| 1554 | /// the PS/2 ring every 10 ms (via `sleep_ms`, which only blocks the | ||
| 1555 | /// shell task) so background tasks keep running. | ||
| 1556 | fn cmd_mouse(p: &Parsed) { | 1559 | fn cmd_mouse(p: &Parsed) { |
| 1557 | let target: Option<usize> = p.args.first().and_then(|s| s.parse::<usize>().ok()); | 1560 | let target: Option<usize> = p.args.first().and_then(|s| s.parse::<usize>().ok()); |
| 1558 | if let Some(id) = target { | 1561 | if let Some(id) = target { |
| @@ -1560,48 +1563,167 @@ fn cmd_mouse(p: &Parsed) { | |||
| 1560 | term_err(&alloc::format!("mouse: no such task {}", id)); | 1563 | term_err(&alloc::format!("mouse: no such task {}", id)); |
| 1561 | return; | 1564 | return; |
| 1562 | } | 1565 | } |
| 1566 | rsnix::cursor::set_forward(Some(id)); | ||
| 1563 | term_println(&alloc::format!( | 1567 | term_println(&alloc::format!( |
| 1564 | "mouse: posting events to task {} — ^C to stop", | 1568 | "mouse: routing events to task {} (cursor still tracks) — ^C to stop", |
| 1565 | id | 1569 | id |
| 1566 | )); | 1570 | )); |
| 1571 | } else if !rsnix::ps2::mouse_present() { | ||
| 1572 | term_err("mouse: no PS/2 mouse at boot — cat /dev/mouse still works"); | ||
| 1573 | return; | ||
| 1567 | } else { | 1574 | } else { |
| 1568 | if !rsnix::ps2::mouse_present() { | 1575 | term_println("mouse: watching cursor — move the mouse, ^C to stop"); |
| 1569 | term_err("mouse: no PS/2 mouse at boot — cat /dev/mouse still works"); | ||
| 1570 | return; | ||
| 1571 | } | ||
| 1572 | term_println("mouse: streaming events — ^C to stop"); | ||
| 1573 | } | 1576 | } |
| 1577 | let mut last = (usize::MAX, usize::MAX, u8::MAX); | ||
| 1574 | loop { | 1578 | loop { |
| 1575 | if tty::interrupt_pressed() { | 1579 | if tty::interrupt_pressed() { |
| 1580 | rsnix::cursor::set_forward(None); | ||
| 1576 | term_println("mouse: stopped"); | 1581 | term_println("mouse: stopped"); |
| 1577 | return; | 1582 | return; |
| 1578 | } | 1583 | } |
| 1579 | while let Some(ev) = rsnix::ps2::try_poll_mouse() { | 1584 | let (x, y, b) = ( |
| 1580 | match target { | 1585 | rsnix::gop::cursor_pos().0, |
| 1581 | Some(id) => { | 1586 | rsnix::gop::cursor_pos().1, |
| 1582 | if let Err(e) = rsnix::sched::send(id, rsnix::sched::TaskMsg::Mouse(ev)) { | 1587 | rsnix::gop::cursor_buttons(), |
| 1583 | term_err(&alloc::format!("mouse: {}", e)); | 1588 | ); |
| 1584 | return; | 1589 | if (x, y, b) != last { |
| 1585 | } | 1590 | last = (x, y, b); |
| 1586 | } | 1591 | term_println(&alloc::format!( |
| 1587 | None => { | 1592 | "mouse: x={} y={} buttons={}{}{}", |
| 1588 | let mut b = String::new(); | 1593 | x, |
| 1589 | b.push(if ev.left { 'L' } else { '-' }); | 1594 | y, |
| 1590 | b.push(if ev.right { 'R' } else { '-' }); | 1595 | if b & 1 != 0 { 'L' } else { '-' }, |
| 1591 | b.push(if ev.middle { 'M' } else { '-' }); | 1596 | if b & 2 != 0 { 'R' } else { '-' }, |
| 1592 | term_println(&alloc::format!( | 1597 | if b & 4 != 0 { 'M' } else { '-' } |
| 1593 | "dx={:+} dy={:+} buttons={}", | 1598 | )); |
| 1594 | ev.dx, | ||
| 1595 | ev.dy, | ||
| 1596 | b | ||
| 1597 | )); | ||
| 1598 | } | ||
| 1599 | } | ||
| 1600 | } | 1599 | } |
| 1601 | rsnix::sched::sleep_ms(10); | 1600 | rsnix::sched::sleep_ms(10); |
| 1602 | } | 1601 | } |
| 1603 | } | 1602 | } |
| 1604 | 1603 | ||
| 1604 | /// One-shot cursor state (parseable; used by the test harness). | ||
| 1605 | fn cmd_cursor(_p: &Parsed) { | ||
| 1606 | let (x, y) = rsnix::gop::cursor_pos(); | ||
| 1607 | let b = rsnix::gop::cursor_buttons(); | ||
| 1608 | term_println(&alloc::format!( | ||
| 1609 | "cursor: x={} y={} buttons={}{}{}", | ||
| 1610 | x, | ||
| 1611 | y, | ||
| 1612 | if b & 1 != 0 { 'L' } else { '-' }, | ||
| 1613 | if b & 2 != 0 { 'R' } else { '-' }, | ||
| 1614 | if b & 4 != 0 { 'M' } else { '-' } | ||
| 1615 | )); | ||
| 1616 | } | ||
| 1617 | |||
| 1618 | /// Graphics primitives demo + pixel self-test (shadow + display + cursor | ||
| 1619 | /// overlay readbacks). `gfx` / `gfx demo` draws a status-bar scene; | ||
| 1620 | /// `gfx check` runs the readback assertions (run after `clear` for a | ||
| 1621 | /// predictable background). | ||
| 1622 | fn cmd_gfx(p: &Parsed) { | ||
| 1623 | match p.args.first().map(|s| s.as_str()) { | ||
| 1624 | Some("check") => gfx_check(), | ||
| 1625 | _ => gfx_demo(), | ||
| 1626 | } | ||
| 1627 | } | ||
| 1628 | |||
| 1629 | fn gfx_demo() { | ||
| 1630 | let (w, h) = rsnix::gop::pixel_dims(); | ||
| 1631 | if w == 0 { | ||
| 1632 | term_err("gfx: no framebuffer"); | ||
| 1633 | return; | ||
| 1634 | } | ||
| 1635 | // Status-bar strip across the bottom, with three RGB boxes. | ||
| 1636 | rsnix::gop::fill_rect(0, h - 40, w, 40, rsnix::gop::rgb(0, 0, 128)); | ||
| 1637 | rsnix::gop::fill_rect(20, h - 32, 60, 24, rsnix::gop::rgb(255, 0, 0)); | ||
| 1638 | rsnix::gop::fill_rect(90, h - 32, 60, 24, rsnix::gop::rgb(0, 255, 0)); | ||
| 1639 | rsnix::gop::fill_rect(160, h - 32, 60, 24, rsnix::gop::rgb(255, 255, 0)); | ||
| 1640 | term_println(&alloc::format!( | ||
| 1641 | "gfx: drew status bar + RGB boxes ({}x{} px bottom strip)", | ||
| 1642 | w, | ||
| 1643 | h | ||
| 1644 | )); | ||
| 1645 | } | ||
| 1646 | |||
| 1647 | fn gfx_check() { | ||
| 1648 | let (w, _h) = rsnix::gop::pixel_dims(); | ||
| 1649 | if w == 0 { | ||
| 1650 | term_err("gfx: no framebuffer"); | ||
| 1651 | return; | ||
| 1652 | } | ||
| 1653 | let mut ok = true; | ||
| 1654 | let mut report = String::new(); | ||
| 1655 | |||
| 1656 | // 1. fill_rect lands in the shadow. | ||
| 1657 | let (rx, ry, rw, rh) = (8usize, 8usize, 40usize, 16usize); | ||
| 1658 | let red = rsnix::gop::rgb(255, 0, 0); | ||
| 1659 | rsnix::gop::fill_rect(rx, ry, rw, rh, red); | ||
| 1660 | match rsnix::gop::shadow_pixel(rx + rw / 2, ry + rh / 2) { | ||
| 1661 | Some(p) if p == red => report.push_str("fill_rect/shadow: OK\n"), | ||
| 1662 | Some(p) => { | ||
| 1663 | ok = false; | ||
| 1664 | report.push_str(&alloc::format!("fill_rect/shadow: FAIL ({:#x})\n", p)); | ||
| 1665 | } | ||
| 1666 | None => { | ||
| 1667 | ok = false; | ||
| 1668 | report.push_str("fill_rect/shadow: FAIL (no shadow)\n"); | ||
| 1669 | } | ||
| 1670 | } | ||
| 1671 | // 2. the push reached the display. | ||
| 1672 | match rsnix::gop::display_pixel(rx + rw / 2, ry + rh / 2) { | ||
| 1673 | Some(p) if p == red => report.push_str("display push: OK\n"), | ||
| 1674 | Some(p) => { | ||
| 1675 | ok = false; | ||
| 1676 | report.push_str(&alloc::format!("display push: FAIL ({:#x})\n", p)); | ||
| 1677 | } | ||
| 1678 | None => { | ||
| 1679 | ok = false; | ||
| 1680 | report.push_str("display push: FAIL (readback)\n"); | ||
| 1681 | } | ||
| 1682 | } | ||
| 1683 | // 3. cursor overlay: the arrow tip is white on the display… | ||
| 1684 | let (cx, cy) = rsnix::gop::cursor_pos(); | ||
| 1685 | let (dw, dh) = rsnix::gop::pixel_dims(); | ||
| 1686 | let white = rsnix::gop::rgb(255, 255, 255); | ||
| 1687 | let tip = rsnix::gop::display_pixel(cx + 2, cy); | ||
| 1688 | let out = rsnix::gop::display_pixel(cx, cy); | ||
| 1689 | match (tip, out) { | ||
| 1690 | (Some(t), Some(o)) if t == white && o != white => report.push_str("cursor overlay: OK\n"), | ||
| 1691 | (Some(t), Some(o)) => { | ||
| 1692 | ok = false; | ||
| 1693 | report.push_str(&alloc::format!( | ||
| 1694 | "cursor overlay: FAIL (tip={:#x} outside={:#x})\n", | ||
| 1695 | t, | ||
| 1696 | o | ||
| 1697 | )); | ||
| 1698 | } | ||
| 1699 | _ => { | ||
| 1700 | ok = false; | ||
| 1701 | report.push_str(&alloc::format!( | ||
| 1702 | "cursor overlay: FAIL (readback cx={} cy={} dims={}x{})\n", | ||
| 1703 | cx, | ||
| 1704 | cy, | ||
| 1705 | dw, | ||
| 1706 | dh | ||
| 1707 | )); | ||
| 1708 | } | ||
| 1709 | } | ||
| 1710 | // …and it must NOT be in the shadow (pure overlay). | ||
| 1711 | match rsnix::gop::shadow_pixel(cx + 2, cy) { | ||
| 1712 | Some(p) if p != white => report.push_str("cursor not in shadow: OK\n"), | ||
| 1713 | p => { | ||
| 1714 | ok = false; | ||
| 1715 | report.push_str(&alloc::format!("cursor in shadow: FAIL ({:?})\n", p)); | ||
| 1716 | } | ||
| 1717 | } | ||
| 1718 | |||
| 1719 | term_println(&report); | ||
| 1720 | term_println(if ok { | ||
| 1721 | "gfx check: PASS" | ||
| 1722 | } else { | ||
| 1723 | "gfx check: FAIL" | ||
| 1724 | }); | ||
| 1725 | } | ||
| 1726 | |||
| 1605 | // --------------------------------------------------------------------------- | 1727 | // --------------------------------------------------------------------------- |
| 1606 | // Multitasking: bg / jobs / sleep / kill / demo | 1728 | // Multitasking: bg / jobs / sleep / kill / demo |
| 1607 | // | 1729 | // |
