summaryrefslogtreecommitdiffstats
path: root/shell/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--shell/src/lib.rs211
1 files changed, 209 insertions, 2 deletions
diff --git a/shell/src/lib.rs b/shell/src/lib.rs
index 4640697..c18d319 100644
--- a/shell/src/lib.rs
+++ b/shell/src/lib.rs
@@ -703,6 +703,7 @@ write `{} <path> TIL <marker>`",
703 "date" => cmd_date(&parsed), 703 "date" => cmd_date(&parsed),
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 "ls" => cmd_ls(&parsed), 707 "ls" => cmd_ls(&parsed),
707 "cat" => cmd_cat(&parsed), 708 "cat" => cmd_cat(&parsed),
708 "klog" => cmd_klog(&parsed), 709 "klog" => cmd_klog(&parsed),
@@ -713,6 +714,8 @@ write `{} <path> TIL <marker>`",
713 "edit" => cmd_edit(&parsed), 714 "edit" => cmd_edit(&parsed),
714 "bg" => cmd_bg(&parsed), 715 "bg" => cmd_bg(&parsed),
715 "jobs" => cmd_jobs(&parsed), 716 "jobs" => cmd_jobs(&parsed),
717 "msgrecv" => cmd_msgrecv(&parsed),
718 "send" => cmd_send(&parsed),
716 "sleep" => cmd_sleep(&parsed), 719 "sleep" => cmd_sleep(&parsed),
717 "kill" => cmd_kill(&parsed), 720 "kill" => cmd_kill(&parsed),
718 "demo" => cmd_demo(&parsed), 721 "demo" => cmd_demo(&parsed),
@@ -738,8 +741,16 @@ fn cmd_help(_p: &Parsed) {
738 date — show current date/time (RTC) 741 date — show current date/time (RTC)
739 pci — list PCI devices 742 pci — list PCI devices
740 rescan — re-scan for hotplugged/removed disks (also runs automatically) 743 rescan — re-scan for hotplugged/removed disks (also runs automatically)
744 mouse — live mouse events (dx/dy/buttons); ^C stops
745 mouse <id> — post decoded mouse events to task <id>'s queue (input routing)
741 bg <name> — spawn a background task (spinner, ticker, counter, boom, overflow) 746 bg <name> — spawn a background task (spinner, ticker, counter, boom, overflow)
742 jobs — list tasks (id, name, state, ticks) 747 msgrecv — spawn a task that blocks on its message queue and logs every
748 message to serial (ids from `jobs`)
749 send <id> <type> [args] — post a message to a task's queue:
750 user <n> · timer · mouse <dx> <dy> <btn> (1=L 2=R 4=M)
751 key <name> (enter esc tab space bksp up down left right home
752 end pgup pgdn del ctrl-c f1..f12, or a single char)
753 jobs — list tasks (id, name, state, ticks, q=pending messages)
743 sleep <ms> — block the shell for ms; background tasks keep running 754 sleep <ms> — block the shell for ms; background tasks keep running
744 kill <id> — stop a background task (id from jobs) 755 kill <id> — stop a background task (id from jobs)
745 demo — spawn spinner + ticker + counter 756 demo — spawn spinner + ticker + counter
@@ -1517,7 +1528,12 @@ fn rescan_all() -> (usize, usize, usize, usize, usize, usize) {
1517fn report_rescan((da, dr, ma, mu, na, nr): (usize, usize, usize, usize, usize, usize)) -> bool { 1528fn report_rescan((da, dr, ma, mu, na, nr): (usize, usize, usize, usize, usize, usize)) -> bool {
1518 term_println(&alloc::format!( 1529 term_println(&alloc::format!(
1519 "rescan: {} disk(s) added, {} removed · {} volume(s) mounted, {} unmounted · {} node(s) added, {} removed", 1530 "rescan: {} disk(s) added, {} removed · {} volume(s) mounted, {} unmounted · {} node(s) added, {} removed",
1520 da, dr, ma, mu, na, nr 1531 da,
1532 dr,
1533 ma,
1534 mu,
1535 na,
1536 nr
1521 )); 1537 ));
1522 da + dr + ma + mu + na + nr > 0 1538 da + dr + ma + mu + na + nr > 0
1523} 1539}
@@ -1530,6 +1546,62 @@ fn cmd_rescan(_p: &Parsed) {
1530 } 1546 }
1531} 1547}
1532 1548
1549/// Live mouse event stream: print each decoded packet (dx/dy/buttons) as
1550/// it arrives until ^C. With an optional task id, events are **posted to
1551/// that task's message queue** instead of printed — the input-routing
1552/// path the window manager will use (`mouse <id>` + `msgrecv` proves the
1553/// whole chain: IRQ12 → ring → decoder → queue → blocked task). Polls
1554/// the PS/2 ring every 10 ms (via `sleep_ms`, which only blocks the
1555/// shell task) so background tasks keep running.
1556fn cmd_mouse(p: &Parsed) {
1557 let target: Option<usize> = p.args.first().and_then(|s| s.parse::<usize>().ok());
1558 if let Some(id) = target {
1559 if !rsnix::sched::is_alive(id) {
1560 term_err(&alloc::format!("mouse: no such task {}", id));
1561 return;
1562 }
1563 term_println(&alloc::format!(
1564 "mouse: posting events to task {} — ^C to stop",
1565 id
1566 ));
1567 } else {
1568 if !rsnix::ps2::mouse_present() {
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 }
1574 loop {
1575 if tty::interrupt_pressed() {
1576 term_println("mouse: stopped");
1577 return;
1578 }
1579 while let Some(ev) = rsnix::ps2::try_poll_mouse() {
1580 match target {
1581 Some(id) => {
1582 if let Err(e) = rsnix::sched::send(id, rsnix::sched::TaskMsg::Mouse(ev)) {
1583 term_err(&alloc::format!("mouse: {}", e));
1584 return;
1585 }
1586 }
1587 None => {
1588 let mut b = String::new();
1589 b.push(if ev.left { 'L' } else { '-' });
1590 b.push(if ev.right { 'R' } else { '-' });
1591 b.push(if ev.middle { 'M' } else { '-' });
1592 term_println(&alloc::format!(
1593 "dx={:+} dy={:+} buttons={}",
1594 ev.dx,
1595 ev.dy,
1596 b
1597 ));
1598 }
1599 }
1600 }
1601 rsnix::sched::sleep_ms(10);
1602 }
1603}
1604
1533// --------------------------------------------------------------------------- 1605// ---------------------------------------------------------------------------
1534// Multitasking: bg / jobs / sleep / kill / demo 1606// Multitasking: bg / jobs / sleep / kill / demo
1535// 1607//
@@ -1577,6 +1649,141 @@ fn cmd_jobs(p: &Parsed) {
1577 pipe.end(); 1649 pipe.end();
1578} 1650}
1579 1651
1652/// Demo of the scheduler message queues: spawn a task that blocks on
1653/// `recv_blocking` and logs every message it receives to serial. The
1654/// shell then posts to it with `send <id> …` (id from `jobs`).
1655fn cmd_msgrecv(_p: &Parsed) {
1656 match rsnix::sched::spawn(task_msgrecv, "msgrecv") {
1657 Some(id) => term_println(&alloc::format!(
1658 "msgrecv: task {} listening — post with: send {} <type> …",
1659 id,
1660 id
1661 )),
1662 None => term_err("msgrecv: task table full"),
1663 }
1664}
1665
1666/// The recv demo body: block forever, log each message. Headless by
1667/// design (serial only — it never touches tty/vt/fs, per ADR 0003).
1668fn task_msgrecv() {
1669 loop {
1670 let msg = rsnix::sched::recv_blocking();
1671 rsnix::serial_println!(
1672 "msgrecv[{}]: {}",
1673 rsnix::sched::current(),
1674 rsnix::sched::msg_describe(&msg)
1675 );
1676 }
1677}
1678
1679/// `send <id> <type> [args]` — post a TaskMsg to a task's queue. This
1680/// is the shell-side knob for the primitive the window manager will use
1681/// to route input; types: user <n>, timer, mouse <dx> <dy> <btn>
1682/// (1=L 2=R 4=M), key <name> (enter esc tab space bksp up down left
1683/// right home end pgup pgdn del ctrl-c f1..f12, or a single char).
1684fn cmd_send(p: &Parsed) {
1685 let mut args = p.args.iter();
1686 let id: usize = match args.next().and_then(|s| s.parse().ok()) {
1687 Some(v) => v,
1688 None => {
1689 term_err("send: needs a task id — jobs lists them, msgrecv spawns a listener");
1690 return;
1691 }
1692 };
1693 let kind = match args.next() {
1694 Some(k) => k.as_str(),
1695 None => {
1696 term_err("send: needs a type — user <n> | timer | mouse <dx> <dy> <btn> | key <name>");
1697 return;
1698 }
1699 };
1700 let msg = match kind {
1701 "user" => {
1702 let Some(n) = args.next().and_then(|s| s.parse().ok()) else {
1703 term_err("send: user needs a number");
1704 return;
1705 };
1706 rsnix::sched::TaskMsg::User(n)
1707 }
1708 "timer" => rsnix::sched::TaskMsg::Timer,
1709 "mouse" => {
1710 let dx: i16 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
1711 let dy: i16 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
1712 let btn: u8 = args.next().and_then(|s| s.parse().ok()).unwrap_or(0);
1713 rsnix::sched::TaskMsg::Mouse(rsnix::ps2::MouseEvent {
1714 dx,
1715 dy,
1716 left: btn & 1 != 0,
1717 right: btn & 2 != 0,
1718 middle: btn & 4 != 0,
1719 })
1720 }
1721 "key" => match args.next() {
1722 Some(k) => match key_from_name(k.as_str()) {
1723 Some(ev) => rsnix::sched::TaskMsg::Key(ev),
1724 None => {
1725 term_err("send: unknown key name");
1726 return;
1727 }
1728 },
1729 None => {
1730 term_err("send: key needs a name");
1731 return;
1732 }
1733 },
1734 _ => {
1735 term_err("send: unknown type — user <n> | timer | mouse <dx> <dy> <btn> | key <name>");
1736 return;
1737 }
1738 };
1739 match rsnix::sched::send(id, msg) {
1740 Ok(()) => {}
1741 Err(e) => term_err(&alloc::format!("send: {}", e)),
1742 }
1743}
1744
1745/// Map a small key-name vocabulary to a KeyEvent for `send key …`.
1746fn key_from_name(name: &str) -> Option<rsnix::ps2::KeyEvent> {
1747 use rsnix::ps2::KeyEvent;
1748 Some(match name {
1749 "enter" => KeyEvent::Enter,
1750 "esc" | "escape" => KeyEvent::Escape,
1751 "tab" => KeyEvent::Tab,
1752 "space" => KeyEvent::Char(' '),
1753 "bksp" | "backspace" => KeyEvent::Backspace,
1754 "up" => KeyEvent::ArrowUp,
1755 "down" => KeyEvent::ArrowDown,
1756 "left" => KeyEvent::ArrowLeft,
1757 "right" => KeyEvent::ArrowRight,
1758 "home" => KeyEvent::Home,
1759 "end" => KeyEvent::End,
1760 "pgup" => KeyEvent::PageUp,
1761 "pgdn" => KeyEvent::PageDown,
1762 "del" | "delete" => KeyEvent::Delete,
1763 "ctrl-c" => KeyEvent::Ctrl('c'),
1764 "ctrl-d" => KeyEvent::Ctrl('d'),
1765 "ctrl-s" => KeyEvent::Ctrl('s'),
1766 "ctrl-x" => KeyEvent::Ctrl('x'),
1767 _ => {
1768 if let Some(n) = name.strip_prefix('f') {
1769 if let Ok(n) = n.parse::<u8>() {
1770 if (1..=12).contains(&n) {
1771 KeyEvent::F(n)
1772 } else {
1773 return None;
1774 }
1775 } else {
1776 return None;
1777 }
1778 } else if name.chars().count() == 1 {
1779 KeyEvent::Char(name.chars().next().unwrap())
1780 } else {
1781 return None;
1782 }
1783 }
1784 })
1785}
1786
1580fn cmd_sleep(p: &Parsed) { 1787fn cmd_sleep(p: &Parsed) {
1581 let ms: u64 = match p.args.first().and_then(|s| s.parse().ok()) { 1788 let ms: u64 = match p.args.first().and_then(|s| s.parse().ok()) {
1582 Some(m) if m > 0 => m, 1789 Some(m) if m > 0 => m,