Menus
Menus are created with the help of the menu_matic()
convenience call.
Conceptually, a Menu in Xous is a list of MenuItem
. Graphically, the menus
are rendered in the order that the MenuItem
s are added to the list. When a MenuItem
is selected, it fires a message off to another server to effect the corresponding outcome desired of the logical menu description.
Thus, each MenuItem
has the following fields:
- A
name
describing the menu item, limited to a 64-byte long unicode string - An
Option
for anaction connection
. This is a CID to a server to which a message will be sent upon selecting the item. IfNone
, the menu item does nothing and just closes the menu. - An
action opcode
. This is au32
value that corresponds to the discriminant of theenum
used to dispatch opcodes in your main loop (e.g., the parameter passed asmsg.body.id()
). - A
MenuPayload
, which is anenum
that currently can only be aScalar
payload consisting of up to 4u32
values. There's a future provision for this to be extended to a smallMemory
message but it is not yet implemented (please open an issue if you need this feature, and helpfully remind the maintainers to also update the Xous Book docs once this is done). close on select
- when set to true, the menu will automatically close when the item is selected.
So, when a MenuItem
is selected by the user, the menu implementation will fire off a Scalar
message to the server identified by action_conn
with the opcode specified by action opcode
and a payload of MenuPayload
. The receiving server can asynchronously receive this message in its main loop and act upon the menu selection.
The general idiom is to create a Vec
of MenuItem
s, and then pass them into MenuMatic
, as seen below:
pub fn create_kbd_menu(status_conn: xous::CID, kbd_mgr: xous::SID) -> MenuMatic {
let mut menu_items = Vec::<MenuItem>::new();
let code: usize = KeyMap::Qwerty.into();
menu_items.push(MenuItem {
name: xous_ipc::String::from_str("QWERTY"),
action_conn: Some(status_conn),
action_opcode: StatusOpcode::SetKeyboard.to_u32().unwrap(),
action_payload: MenuPayload::Scalar([code as u32, 0, 0, 0]),
close_on_select: true,
});
let code: usize = KeyMap::Dvorak.into();
menu_items.push(MenuItem {
name: xous_ipc::String::from_str("Dvorak"),
action_conn: Some(status_conn),
action_opcode: StatusOpcode::SetKeyboard.to_u32().unwrap(),
action_payload: MenuPayload::Scalar([code as u32, 0, 0, 0]),
close_on_select: true,
});
menu_items.push(MenuItem {
name: xous_ipc::String::from_str("Close Menu"),
action_conn: None,
action_opcode: 0,
action_payload: MenuPayload::Scalar([code as u32, 0, 0, 0]),
close_on_select: true,
});
menu_matic(menu_items, gam::KBD_MENU_NAME, Some(kbd_mgr)).expect("couldn't create MenuMatic manager")
}
This will create a menu with three items, "QWERTY", "Dvorak", and "Close Menu". When, for example, the "QWERTY" item is selected, it will send a message to the server pointed to be status_conn
, with the argument of StatusOpcode::SetKeyboard
as au32
, and an argument consisting of [code, 0, 0, 0,]
. In this case, only code
has meaning, and the other three are just placeholders.
The third menu item has None
for the connection, so when it is selected, no messages are sent and the menu is simply closed.
Raising the Menu
Once you have created your menu, you can cause the menu to pop up with the following gam
call:
gam.raise_menu(gam::KBD_MENU_NAME).expect("couldn't raise keyboard layout submenu");
The menu will automatically close if close_on_select
is true
.
Permission to Create Menus
What's the gam::KBD_MENU_NAME
field all about?
In order to prevent rogue processes from creating menus willy-nilly that resemble, for example, the main menu but firing off forged messages to undesired processes, there is an access control list for menus.
The access control list is kept in the gam
, and can be found in services/gam/src/lib.rs
. You must add a const str
that gives your menu a unique name, and insert it into the EXPECTED_BOOT_CONTEXTS
structure, otherwise, the gam
will deny the creation of your menu item. The access list is "trust on first use", and secure operations such as accessing the root keys will not be allowed to proceed until all contexts have been allocated. Therefore, if you are creating a menu, you need to call menu_matic()
early in the boot process, or else you will be unable to unlock the PDDB.
Permissions Checklist:
- Give your menu a name in
services/gam/src/lib.rs
- Add the name to
EXPECTED_BOOT_CONTEXTS
- Claim your name with
menu_matic()
early in the boot process
Modifying the Menu
menu_matic()
has a third argument, which is an Option<xous::SID>
. If you never plan to modify your menu, you can leave it as None
. However, if you want to do things such as dynamically create and remove menu items, or pre-select an index in the menu list, you will need to specify an SID. This is used to create the MenuMatic
object, which is returned to the caller.
The SID is created as follows:
let kbd_mgr = xous::create_server().unwrap();
MenuMatic
has the following methods available on it:
add_item(MenuItem)
- adds theMenuItem
specified to the end of the menu list, returningtrue
to indicate success.delete_item(&str)
- deletes an item with aname
specified as the argument. Returnstrue
to indicate success.set_index(usize)
- sets the index pointer of the menu to the specified offset. Typically ued to create a "default" position for the menu before it is raised.quit()
- exit and destory theMenuMatic
server
If you don't need the above functionality, it's recommended that you do not create the server, as it consumes memory and eats up connection and server name space.