diff mbox

Input: hyperv-keyboard - implement Type Clipboard Text

Message ID 1408095719-19117-1-git-send-email-decui@microsoft.com (mailing list archive)
State New, archived
Headers show

Commit Message

Dexuan Cui Aug. 15, 2014, 9:41 a.m. UTC
In the menu of the Hyper-V's Virtual Machine Connection, there is a feature
called "Clipboard | Type clipboard text", which can be used to copy a string
in the host's clipboard into the guest's current input focus(text console or
a GUI window).

Currently the feature doesn't work for Linux VM because the driver
hyperv-keyboard hasn't been enhanced to support it -- this patch is made
to do it.

For each char in the string, the host sends 2 events (key down/up with the
char's UNICODE value) to the guest.
The patch finds each char's scan codes of key down/up, and injects the
scan codes to the serio keyboard module.

Known issues:
1) Only printable ASCII chars are supported, and unsupported chars are
ignored. It seems unlikely to support generic UNICODE chars because there
is not a generic API to inject a UNICODE char to text mode console, KDE,
gnome, etc.

2) When we use the feature, make sure the CapsLock state of the VM's
(virtual) keyboard is OFF because this patch assumes it -- we'll try to
fix this later, probably by tracking the state of virtual CapsLock, because
it looks the keyboard module doesn't supply an API for us to query the state
of the keyboard.

Signed-off-by: Dexuan Cui <decui@microsoft.com>
Cc: K. Y. Srinivasan <kys@microsoft.com>
---
 drivers/input/serio/hyperv-keyboard.c | 213 ++++++++++++++++++++++++++++++++--
 1 file changed, 201 insertions(+), 12 deletions(-)

Comments

Dmitry Torokhov Aug. 15, 2014, 4:57 p.m. UTC | #1
Hi Dexuan,

On Fri, Aug 15, 2014 at 02:41:59AM -0700, Dexuan Cui wrote:
> In the menu of the Hyper-V's Virtual Machine Connection, there is a feature
> called "Clipboard | Type clipboard text", which can be used to copy a string
> in the host's clipboard into the guest's current input focus(text console or
> a GUI window).
> 
> Currently the feature doesn't work for Linux VM because the driver
> hyperv-keyboard hasn't been enhanced to support it -- this patch is made
> to do it.
> 
> For each char in the string, the host sends 2 events (key down/up with the
> char's UNICODE value) to the guest.
> The patch finds each char's scan codes of key down/up, and injects the
> scan codes to the serio keyboard module.
> 
> Known issues:
> 1) Only printable ASCII chars are supported, and unsupported chars are
> ignored. It seems unlikely to support generic UNICODE chars because there
> is not a generic API to inject a UNICODE char to text mode console, KDE,
> gnome, etc.
> 
> 2) When we use the feature, make sure the CapsLock state of the VM's
> (virtual) keyboard is OFF because this patch assumes it -- we'll try to
> fix this later, probably by tracking the state of virtual CapsLock, because
> it looks the keyboard module doesn't supply an API for us to query the state
> of the keyboard.
> 
> Signed-off-by: Dexuan Cui <decui@microsoft.com>

No way. If you want to do this this way, do it in hypervisor code and keep
feeding AT scan codes to hyperv-keyboard, although I am pretty sure users of
French, Czech and other keyboard layouts with numbers in upper register and
symbols in lower will have a few choice words for you.

If you want real cut-and-paste support in various DEs I'd recommend working
with VMware on open-vm-tools package to see what can be shared/reused there.

Consider this NACked with prejudice.

Thanks.
Dexuan Cui Aug. 18, 2014, 3:54 a.m. UTC | #2
> -----Original Message-----
> From: Dmitry Torokhov
> Sent: Saturday, August 16, 2014 0:58 AM
> To: Dexuan Cui
> > For each char in the string, the host sends 2 events (key down/up with the
> > char's UNICODE value) to the guest.
> > The patch finds each char's scan codes of key down/up, and injects the
> > scan codes to the serio keyboard module.
> >
> > Known issues:
> > 1) Only printable ASCII chars are supported, and unsupported chars are
> > ignored. It seems unlikely to support generic UNICODE chars because there
> > is not a generic API to inject a UNICODE char to text mode console, KDE,
> > gnome, etc.
> >
> > 2) When we use the feature, make sure the CapsLock state of the VM's
> > (virtual) keyboard is OFF because this patch assumes it -- we'll try to
> > fix this later, probably by tracking the state of virtual CapsLock, because
> > it looks the keyboard module doesn't supply an API for us to query the
> state
> > of the keyboard.
> >
> No way. If you want to do this this way, do it in hypervisor code and keep
> feeding AT scan codes to hyperv-keyboard, although I am pretty sure users
Hi Dmitry,
Yeah, I had the same wish, but later I found this seems unlikely because IMO
the feature was firstly invented for Windows VM + generic UNICODE chars,
and we know there is no "scan code" for generic UNICODE chars... :-(

> of
> French, Czech and other keyboard layouts with numbers in upper register
> and
> symbols in lower will have a few choice words for you.
Sorry, I can't understand what these are.
Can you please give more details or a link to further info?

> If you want real cut-and-paste support in various DEs I'd recommend
> working
> with VMware on open-vm-tools package to see what can be shared/reused
> there.
> Consider this NACked with prejudice.
> Dmitry
Thanks for the suggestion!
Let me study open-vm-tools and report back.

Thanks,
-- Dexuan
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Dmitry Torokhov Aug. 18, 2014, 6:51 p.m. UTC | #3
On Mon, Aug 18, 2014 at 03:54:51AM +0000, Dexuan Cui wrote:
> > -----Original Message-----
> > From: Dmitry Torokhov
> > Sent: Saturday, August 16, 2014 0:58 AM
> > To: Dexuan Cui
> > > For each char in the string, the host sends 2 events (key down/up with the
> > > char's UNICODE value) to the guest.
> > > The patch finds each char's scan codes of key down/up, and injects the
> > > scan codes to the serio keyboard module.
> > >
> > > Known issues:
> > > 1) Only printable ASCII chars are supported, and unsupported chars are
> > > ignored. It seems unlikely to support generic UNICODE chars because there
> > > is not a generic API to inject a UNICODE char to text mode console, KDE,
> > > gnome, etc.
> > >
> > > 2) When we use the feature, make sure the CapsLock state of the VM's
> > > (virtual) keyboard is OFF because this patch assumes it -- we'll try to
> > > fix this later, probably by tracking the state of virtual CapsLock, because
> > > it looks the keyboard module doesn't supply an API for us to query the
> > state
> > > of the keyboard.
> > >
> > No way. If you want to do this this way, do it in hypervisor code and keep
> > feeding AT scan codes to hyperv-keyboard, although I am pretty sure users
> Hi Dmitry,
> Yeah, I had the same wish, but later I found this seems unlikely because IMO
> the feature was firstly invented for Windows VM + generic UNICODE chars,
> and we know there is no "scan code" for generic UNICODE chars... :-(

Do you know what guest is running? You could potentially gate on that.

> 
> > of
> > French, Czech and other keyboard layouts with numbers in upper register
> > and
> > symbols in lower will have a few choice words for you.
> Sorry, I can't understand what these are.
> Can you please give more details or a link to further info?

On French and many other European layouts to produce a number one needs to use
Shift key, instead of simply using lower register, as on US or UK layouts. I.e.
if you send scancode for KEY_3 through your proposed implementation it will
produce '#' and KEY_SHIFT + KEY_3 will result in '3'.

There are also issues with QWERTY/AZERTY and other layouts...

Thanks.
Dexuan Cui Aug. 19, 2014, 8:55 a.m. UTC | #4
> -----Original Message-----
> From: Dmitry Torokhov
> Sent: Tuesday, August 19, 2014 2:52 AM
> To: Dexuan Cui
> On Mon, Aug 18, 2014 at 03:54:51AM +0000, Dexuan Cui wrote:
> > > -----Original Message-----
> > > From: Dmitry Torokhov
> > > Sent: Saturday, August 16, 2014 0:58 AM
> > > To: Dexuan Cui
> > > > For each char in the string, the host sends 2 events (key down/up with
> the
> > > > char's UNICODE value) to the guest.
> > > > The patch finds each char's scan codes of key down/up, and injects the
> > > > scan codes to the serio keyboard module.
> > > >
> > > No way. If you want to do this this way, do it in hypervisor code and keep
> > > feeding AT scan codes to hyperv-keyboard, although I am pretty sure
> users
> > Hi Dmitry,
> > Yeah, I had the same wish, but later I found this seems unlikely because
> IMO
> > the feature was firstly invented for Windows VM + generic UNICODE chars,
> > and we know there is no "scan code" for generic UNICODE chars... :-(
>
> Do you know what guest is running? You could potentially gate on that.
I'm not sure if the host knows this or not.
But even if the host knows this, IMO it seems bad for the host to discriminate
between Windows guest and Linux guest. And generally there is no scan code
for UNICODE chars.

And, here the patch tries to use a light-weight and straightforward method
to achieve the basic functionality of "Type (host) clipboard text (to guest)" --
the patch doesn't use the guest's X11 clipboard and it works for both text
mode tty and X11 window's input (for English keyboard layout. Please see my
below reply).

I studied open-vm-tools.sourceforge.net and
github.com/vmware/open-vm-tools.
My impression is: it's a pretty big collection of utilities/daemons and its code
seems bound to vmware-specific  features, maybe that's why it's called
"open-vm-tools" rather than "generic-vm-tools" :-)
1)  2517/2528 of the commits of the git repo are from vmware;
2) In Ubuntu 14.04, the utilities of open-vm-tools are just named as
vmware-XXX (I checked this by "dpkg-query -L open-vm-tools");
3) about the text copy&paste functionality between host/guest in either
direction, the implementation of open-vm-tools uses host/guest X11
clipboards and hence doesn't work in real text mode tty terminal, and,
vmware-specific implementation of backdoor() is extensively used -- I
don't think there is such an equivalent in other hypervisors'
environment, AFAIK.

So IMHO it's impractical for me to re-use open-vm-tools here?

> > > of
> > > French, Czech and other keyboard layouts with numbers in upper register
> > > and
> > > symbols in lower will have a few choice words for you.
> > Sorry, I can't understand what these are.
> > Can you please give more details or a link to further info?
>
> On French and many other European layouts to produce a number one
> needs to use
> Shift key, instead of simply using lower register, as on US or UK layouts. I.e.
> if you send scancode for KEY_3 through your proposed implementation it will
> produce '#' and KEY_SHIFT + KEY_3 will result in '3'.
>
> There are also issues with QWERTY/AZERTY and other layouts...
> Dmitry

Hi Dmitry,
Thanks for pointing this out!
This is a real issue -- my patch only works for English layout. :-(

I wrongly assumed  there is a 1:1 mapping between an ASCII char and a
scan code -- this is not true considering different keyboard layouts(keymaps).

So looks it's impossible for me to implement the functionality through the
path of the keyboard input subsystem... :-(
A feasible method would be: I can write a lightweight userland daemon,
just like open-vm-tools, which receives the string from the host and passes it
to the X11 windowing system directly using X11 APIs(this doesn't work in text
mode however).

Looking forward to your comment.

Thanks,
-- Dexuan

--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/input/serio/hyperv-keyboard.c b/drivers/input/serio/hyperv-keyboard.c
index e74e5d6..5761869 100644
--- a/drivers/input/serio/hyperv-keyboard.c
+++ b/drivers/input/serio/hyperv-keyboard.c
@@ -18,6 +18,8 @@ 
 #include <linux/hyperv.h>
 #include <linux/serio.h>
 #include <linux/slab.h>
+#include <linux/kfifo.h>
+#include <linux/delay.h>
 
 /*
  * Current version 1.0
@@ -84,7 +86,15 @@  struct synth_kbd_keystroke {
 #define HK_MAXIMUM_MESSAGE_SIZE 256
 
 #define KBD_VSC_SEND_RING_BUFFER_SIZE		(10 * PAGE_SIZE)
-#define KBD_VSC_RECV_RING_BUFFER_SIZE		(10 * PAGE_SIZE)
+
+/*
+ * For the Type Clipboard Text feature, the host can inject a string of
+ * up to 2047 chars, and each char means 2 events(key down/up) and every
+ * event means a VMBUS packet of 32 Bytes: so the max recv buf's size
+ * should be about 2K * 2 * 32 = 32 pages.
+ * Let's add 8 extra pages for safety.
+ */
+#define KBD_VSC_RECV_RING_BUFFER_SIZE		(40 * PAGE_SIZE)
 
 #define XTKBD_EMUL0     0xe0
 #define XTKBD_EMUL1     0xe1
@@ -103,8 +113,173 @@  struct hv_kbd_dev {
 	struct completion wait_event;
 	spinlock_t lock; /* protects 'started' field */
 	bool started;
+
+	DECLARE_KFIFO_PTR(fifo, u8);
+	struct work_struct work;
+};
+
+static const u8 ascii_to_scan_code[128] = {
+	['a'] = 0x1e, ['A'] = 0x1e,
+	['b'] = 0x30, ['B'] = 0x30,
+	['c'] = 0x2e, ['C'] = 0x2e,
+	['d'] = 0x20, ['D'] = 0x20,
+	['e'] = 0x12, ['E'] = 0x12,
+	['f'] = 0x21, ['F'] = 0x21,
+	['g'] = 0x22, ['G'] = 0x22,
+	['h'] = 0x23, ['H'] = 0x23,
+	['i'] = 0x17, ['I'] = 0x17,
+	['j'] = 0x24, ['J'] = 0x24,
+	['k'] = 0x25, ['K'] = 0x25,
+	['l'] = 0x26, ['L'] = 0x26,
+	['m'] = 0x32, ['M'] = 0x32,
+	['n'] = 0x31, ['N'] = 0x31,
+	['o'] = 0x18, ['O'] = 0x18,
+	['p'] = 0x19, ['P'] = 0x19,
+	['q'] = 0x10, ['Q'] = 0x10,
+	['r'] = 0x13, ['R'] = 0x13,
+	['s'] = 0x1f, ['S'] = 0x1f,
+	['t'] = 0x14, ['T'] = 0x14,
+	['u'] = 0x16, ['U'] = 0x16,
+	['v'] = 0x2f, ['V'] = 0x2f,
+	['w'] = 0x11, ['W'] = 0x11,
+	['x'] = 0x2d, ['X'] = 0x2d,
+	['y'] = 0x15, ['Y'] = 0x15,
+	['z'] = 0x2c, ['Z'] = 0x2c,
+	['`'] = 0x29, ['~'] = 0x29,
+	['1'] = 0x02, ['!'] = 0x02,
+	['2'] = 0x03, ['@'] = 0x03,
+	['3'] = 0x04, ['#'] = 0x04,
+	['4'] = 0x05, ['$'] = 0x05,
+	['5'] = 0x06, ['%'] = 0x06,
+	['6'] = 0x07, ['^'] = 0x07,
+	['7'] = 0x08, ['&'] = 0x08,
+	['8'] = 0x09, ['*'] = 0x09,
+	['9'] = 0x0a, ['('] = 0x0a,
+	['0'] = 0x0b, [')'] = 0x0b,
+	['-'] = 0x0c, ['_'] = 0x0c,
+	['='] = 0x0d, ['+'] = 0x0d,
+	['\t'] = 0x0f,
+	['['] = 0x1a, ['{'] = 0x1a,
+	[']'] = 0x1b, ['}'] = 0x1b,
+	['\\'] = 0x2b, ['|'] = 0x2b,
+	[';'] = 0x27, [':'] = 0x27,
+	['\''] = 0x28, ['\"'] = 0x28,
+	['\r'] = 0x1c,
+	['\n'] = 0x1c,
+	[','] = 0x33, ['<'] = 0x33,
+	['.'] = 0x34, ['>'] = 0x34,
+	['/'] = 0x35, ['?'] = 0x35,
+	[' '] = 0x39,
 };
 
+static const bool ascii_needs_shift[128] = {
+	['A'] = 1,
+	['B'] = 1,
+	['C'] = 1,
+	['D'] = 1,
+	['E'] = 1,
+	['F'] = 1,
+	['G'] = 1,
+	['H'] = 1,
+	['I'] = 1,
+	['J'] = 1,
+	['K'] = 1,
+	['L'] = 1,
+	['M'] = 1,
+	['N'] = 1,
+	['O'] = 1,
+	['P'] = 1,
+	['Q'] = 1,
+	['R'] = 1,
+	['S'] = 1,
+	['T'] = 1,
+	['U'] = 1,
+	['V'] = 1,
+	['W'] = 1,
+	['X'] = 1,
+	['Y'] = 1,
+	['Z'] = 1,
+	['~'] = 1,
+	['!'] = 1,
+	['@'] = 1,
+	['#'] = 1,
+	['$'] = 1,
+	['%'] = 1,
+	['^'] = 1,
+	['&'] = 1,
+	['*'] = 1,
+	['('] = 1,
+	[')'] = 1,
+	['_'] = 1,
+	['+'] = 1,
+	['{'] = 1,
+	['}'] = 1,
+	['|'] = 1,
+	[':'] = 1,
+	['\"'] = 1,
+	['<'] = 1,
+	['>'] = 1,
+	['?'] = 1,
+};
+static void handle_scancode(struct work_struct *work)
+{
+	unsigned long flags;
+	struct hv_kbd_dev *kbd_dev =
+		container_of(work, struct hv_kbd_dev, work);
+
+	u8 scan_code = -1;
+	unsigned int ret;
+
+	/*
+	 * Inject the information through the serio interrupt.
+	 */
+	do {
+		spin_lock_irqsave(&kbd_dev->lock, flags);
+
+		ret = kfifo_get(&kbd_dev->fifo, &scan_code);
+		if (ret != 0)
+			serio_interrupt(kbd_dev->hv_serio, scan_code, 0);
+
+		spin_unlock_irqrestore(&kbd_dev->lock, flags);
+
+		/*
+		 * We can't inject the scan codes too fast, otherwise, some
+		 * of the keys can be found lost in a X11 window. Tests show
+		 * 10ms is a safe empirical value.
+		 */
+		if (ret != 0)
+			msleep(10);
+	} while (ret);
+}
+
+static void handle_unicode(struct hv_kbd_dev *kbd_dev, u32 c, u32 info)
+{
+	u8 scan_code;
+	bool break_code = false;
+
+	/* We don't support generic UNICODE chars. */
+	if (c >= 0x80)
+		return;
+
+	/* We only support printable ASCII chars */
+	scan_code = ascii_to_scan_code[c];
+	if (!scan_code)
+		return;
+
+	if (info & IS_BREAK) {
+		scan_code |= XTKBD_RELEASE;
+		break_code = true;
+	}
+
+	if (ascii_needs_shift[c] && !break_code)
+		kfifo_put(&kbd_dev->fifo, 0x2a); /* L-Shift Down */
+
+	kfifo_put(&kbd_dev->fifo, scan_code);
+
+	if (ascii_needs_shift[c] && break_code)
+		kfifo_put(&kbd_dev->fifo, 0x2a | XTKBD_RELEASE); /* L-Shift Up */
+}
+
 static void hv_kbd_on_receive(struct hv_device *hv_dev,
 			      struct synth_kbd_msg *msg, u32 msg_length)
 {
@@ -152,22 +327,28 @@  static void hv_kbd_on_receive(struct hv_device *hv_dev,
 		ks_msg = (struct synth_kbd_keystroke *)msg;
 		info = __le32_to_cpu(ks_msg->info);
 
-		/*
-		 * Inject the information through the serio interrupt.
-		 */
+		/* Put the scan codes into a fifo and handle it in a work */
 		spin_lock_irqsave(&kbd_dev->lock, flags);
 		if (kbd_dev->started) {
 			if (info & IS_E0)
-				serio_interrupt(kbd_dev->hv_serio,
-						XTKBD_EMUL0, 0);
+				kfifo_put(&kbd_dev->fifo, XTKBD_EMUL0);
 			if (info & IS_E1)
-				serio_interrupt(kbd_dev->hv_serio,
-						XTKBD_EMUL1, 0);
+				kfifo_put(&kbd_dev->fifo, XTKBD_EMUL1);
+
 			scan_code = __le16_to_cpu(ks_msg->make_code);
-			if (info & IS_BREAK)
-				scan_code |= XTKBD_RELEASE;
 
-			serio_interrupt(kbd_dev->hv_serio, scan_code, 0);
+			if (!(info & IS_UNICODE)) {
+				if (info & IS_BREAK)
+					scan_code |= XTKBD_RELEASE;
+
+				kfifo_put(&kbd_dev->fifo, scan_code);
+			} else {
+				/* the scan_code is actually a UNICODE char */
+				handle_unicode(kbd_dev, scan_code, info);
+			}
+
+			schedule_work(&kbd_dev->work);
+
 		}
 		spin_unlock_irqrestore(&kbd_dev->lock, flags);
 
@@ -354,6 +535,11 @@  static int hv_kbd_probe(struct hv_device *hv_dev,
 		goto err_free_mem;
 	}
 
+	error = kfifo_alloc(&kbd_dev->fifo, PAGE_SIZE*4, GFP_KERNEL);
+	if (error)
+		goto err_free_mem;
+	INIT_WORK(&kbd_dev->work, handle_scancode);
+
 	kbd_dev->hv_dev = hv_dev;
 	kbd_dev->hv_serio = hv_serio;
 	spin_lock_init(&kbd_dev->lock);
@@ -378,7 +564,7 @@  static int hv_kbd_probe(struct hv_device *hv_dev,
 			   hv_kbd_on_channel_callback,
 			   hv_dev);
 	if (error)
-		goto err_free_mem;
+		goto err_free_fifo;
 
 	error = hv_kbd_connect_to_vsp(hv_dev);
 	if (error)
@@ -392,6 +578,8 @@  static int hv_kbd_probe(struct hv_device *hv_dev,
 
 err_close_vmbus:
 	vmbus_close(hv_dev->channel);
+err_free_fifo:
+	kfifo_free(&kbd_dev->fifo);
 err_free_mem:
 	kfree(hv_serio);
 	kfree(kbd_dev);
@@ -405,6 +593,7 @@  static int hv_kbd_remove(struct hv_device *hv_dev)
 	device_init_wakeup(&hv_dev->device, false);
 	serio_unregister_port(kbd_dev->hv_serio);
 	vmbus_close(hv_dev->channel);
+	kfifo_free(&kbd_dev->fifo);
 	kfree(kbd_dev);
 
 	hv_set_drvdata(hv_dev, NULL);