vbox6: support capslock="rom" mode

In ROM mode the global CapsLock state is controlled by the capslock ROM
by virtual KEY_CAPSLOCK events.

Guests are easily confused by spurious KEY_CAPSLOCK input events in
caps="rom" mode. These spurious events may reach the VMM if KEY_CAPSLOCK
is not pressed as first key in a combination and, therefore, is not
filtered as global key. We filter KEY_CAPSLOCK in ROM mode in the VMM
explicitly, but let it pass in non-ROM mode.

Per default RAW mode is used and CapsLock key events are sent unfiltered
to the guest.
This commit is contained in:
Christian Helmuth 2021-06-10 14:26:25 +02:00
parent 434d007dc1
commit 331844c979
3 changed files with 122 additions and 14 deletions

View File

@ -44,7 +44,7 @@
<binary name="virtualbox6" />
<resource name="RAM" quantum="8G"/>
<exit propagate="yes"/>
<config vbox_file="machine.vbox" xhci="yes" vm_name="linux" capslock="ROM">
<config vbox_file="machine.vbox" xhci="yes" vm_name="linux" capslock="rom">
<vfs>
<dir name="dev">
<log/> <rtc/> <null/> <zero/>

View File

@ -0,0 +1,32 @@
VirtualBox configuration options
================================
The configuration requires an attribute named vbox_file with the name of the
vbox configuration to be used (.vbox).
<config vbox_file="file.vbox">
XHCI controller
~~~~~~~~~~~~~~~
The virtual XHCI controller can be enabled with the following
configuration option:
<config xhci="yes">
CapsLock
~~~~~~~~
Per default CapsLock key events are send unmodified to guest
operating systems. As guest and host state may diverge, the event
filter optionally reports the capslock state as ROM, which can be
monitored by VirtualBox if configured like follows.
<config capslock="rom">
VirtualBox requests a "capslock" ROM, which is interpreted as XML. The
top-level node attribute 'enabled' is expected boolean and represents
the CapsLock state. If ROM and VM-internal CapsLock state differ, the
VMM sends aritifical CapsLock key events to the VM. In this mode,
incoming KEY_CAPSLOCK input events a dropped and not sent to the guest
to prevent the state divergence.

View File

@ -207,19 +207,63 @@ struct Main : Event_handler
}
} _idisplay { _iconsole };
Signal_handler<Main> _capslock_handler { _env.ep(), *this, &Main::_handle_capslock };
void _handle_capslock();
struct Capslock
{
enum class Mode { RAW, ROM } const mode;
bool _host { false };
bool _guest { false };
Constructible<Attached_rom_dataspace> _rom;
Capslock(Env &env, Xml_node config, Signal_context_capability sigh)
:
mode(config.attribute_value("capslock", String<4>()) == "rom"
? Mode::ROM : Mode::RAW)
{
if (mode == Mode::ROM) {
_rom.construct(env, "capslock");
_rom->sigh(sigh);
}
}
void update_guest(bool enabled) { _guest = enabled; }
bool update_from_rom()
{
_rom->update();
bool const rom = _rom->xml().attribute_value("enabled", _guest);
bool trigger = false;
/*
* If guest didn't respond with led change last time, we have to
* trigger CapsLock change - mainly assuming that guest don't use the
* led to externalize its internal state.
*/
if (rom != _host && _host != _guest)
trigger = true;
if (rom != _guest)
trigger = true;
/* remember last seen host capslock state */
_host = rom;
return trigger;
}
} _capslock { _env, _config.xml(), _capslock_handler };
Registry<Registered<Gui::Connection>> _gui_connections { };
Signal_handler<Main> _input_handler { _env.ep(), *this, &Main::_handle_input };
void _handle_input_event(Input::Event const &);
void _handle_input()
{
Libc::with_libc([&] {
_gui_connections.for_each([&] (Gui::Connection &gui) {
gui.input()->for_each_event([&] (Input::Event const &ev) {
_handle_input_event(ev); }); }); });
}
void _handle_input();
Input_adapter _input_adapter { _iconsole };
@ -296,9 +340,34 @@ struct Main : Event_handler
};
void Main::_handle_input_event(Input::Event const &ev)
void Main::_handle_capslock()
{
_input_adapter.handle_input_event(ev);
if (_capslock.update_from_rom()) {
Libc::with_libc([&] {
_input_adapter.handle_input_event(Input::Event { Input::Press { Input::KEY_CAPSLOCK } });
_input_adapter.handle_input_event(Input::Event { Input::Release { Input::KEY_CAPSLOCK } });
});
}
}
void Main::_handle_input()
{
auto handle_one_event = [&] (Input::Event const &ev) {
/* don't confuse guests and drop CapsLock events in ROM mode */
if (_capslock.mode == Capslock::Mode::ROM) {
if (ev.key_press(Input::KEY_CAPSLOCK)
|| ev.key_release(Input::KEY_CAPSLOCK))
return;
}
_input_adapter.handle_input_event(ev);
};
Libc::with_libc([&] {
_gui_connections.for_each([&] (Gui::Connection &gui) {
gui.input()->for_each_event([&] (Input::Event const &ev) {
handle_one_event(ev); }); }); });
}
@ -314,7 +383,14 @@ void Main::handle_vbox_event(VBoxEventType_T ev_type, IEvent &ev)
} break;
case VBoxEventType_OnMousePointerShapeChanged: break;
case VBoxEventType_OnKeyboardLedsChanged: break;
case VBoxEventType_OnKeyboardLedsChanged:
{
ComPtr<IKeyboardLedsChangedEvent> led_ev = &ev;
BOOL capslock;
led_ev->COMGETTER(CapsLock)(&capslock);
_capslock.update_guest(!!capslock);
} break;
default: /* ignore other events */ break;
}
@ -335,7 +411,7 @@ void Libc::Component::construct(Libc::Env &env)
char **envp = nullptr;
populate_args_and_env(env, argc, argv, envp);
environ = envp;
Pthread::init(env);