1 //
2 // Copyright (c) 2010 - 2019 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using Antmicro.Renode.Peripherals.Input;
8 using Antmicro.Renode.Core.USB;
9 using Antmicro.Renode.Extensions.Utilities.USBIP;
10 using System.Linq;
11 using Antmicro.Renode.Exceptions;
12 using Antmicro.Renode.Utilities;
13 using Antmicro.Renode.Logging;
14 
15 namespace Antmicro.Renode.Peripherals.USB
16 {
17     public static class USBKeyboardExtensions
18     {
19         //
20         // DISCLAIMER:
21         //
22         // Those are helper methods needed because `host` object (part of which `USBIPServer` is)
23         // is not fully supported in monitor/repl.
24         //
AttachUSBKeyboard(this USBIPServer usbController, int? port = null)25         public static void AttachUSBKeyboard(this USBIPServer usbController, int? port = null)
26         {
27             if(usbController.Children.Where(m => m.Peripheral.GetType() == typeof(USBKeyboard)).Count() != 0)
28             {
29                 throw new RecoverableException("There is already a USB keyboard connected to the USB/IP server");
30             }
31 
32             usbController.Register(new USBKeyboard(), port);
33         }
34 
KeyboardType(this USBIPServer usbController, string text)35         public static void KeyboardType(this USBIPServer usbController, string text)
36         {
37             var keyboard = usbController.Children.Where(m => m.Peripheral.GetType() == typeof(USBKeyboard)).Select(m => m.Peripheral).Cast<USBKeyboard>().FirstOrDefault();
38             if(keyboard == null)
39             {
40                 throw new RecoverableException("No USB keyboard attached to the host. Did you forget to call 'host AttachUSBKeyboard'?");
41             }
42 
43             keyboard.TypeText(text);
44         }
45 
TypeText(this USBKeyboard keyboard, string text)46         public static void TypeText(this USBKeyboard keyboard, string text)
47         {
48             var inEscapeMode = false;
49             foreach(var character in text)
50             {
51                 if(inEscapeMode)
52                 {
53                     inEscapeMode = false;
54                     if(character == 'n')
55                     {
56                         keyboard.Press(KeyScanCode.Enter);
57                         keyboard.Release(KeyScanCode.Enter);
58                         continue;
59                     }
60                     else if(character == '\\')
61                     {
62                         keyboard.Press(KeyScanCode.OemPipe);
63                         keyboard.Release(KeyScanCode.OemPipe);
64                         continue;
65                     }
66                     else
67                     {
68                         keyboard.Log(LogLevel.Warning, "Unexpected escaped character: {0}", character);
69                         keyboard.Press(KeyScanCode.OemPipe);
70                         keyboard.Release(KeyScanCode.OemPipe);
71                         // intentionally no continue - press the unexpected escaped character
72                     }
73                 }
74                 else
75                 {
76                     if(character == '\\')
77                     {
78                         inEscapeMode = true;
79                         continue;
80                     }
81                 }
82 
83                 var scanCodes = character.ToKeyScanCodes();
84                 foreach(var scanCode in scanCodes)
85                 {
86                     keyboard.Press(scanCode);
87                 }
88 
89                 foreach(var scanCode in scanCodes)
90                 {
91                     keyboard.Release(scanCode);
92                 }
93             }
94 
95             if(inEscapeMode)
96             {
97                 // handle dangling `\` at the end of string
98                 var scanCodes = '\\'.ToKeyScanCodes();
99                 foreach(var scanCode in scanCodes)
100                 {
101                     keyboard.Press(scanCode);
102                 }
103 
104                 foreach(var scanCode in scanCodes)
105                 {
106                     keyboard.Release(scanCode);
107                 }
108             }
109         }
110     }
111 
112     public class USBKeyboard : IUSBDevice, IKeyboard
113     {
USBKeyboard()114         public USBKeyboard()
115         {
116             USBCore = new USBDeviceCore(this)
117                 .WithConfiguration(configure: c =>
118                     c.WithInterface(new Core.USB.HID.Interface(this, 0,
119                         subClassCode: (byte)Core.USB.HID.SubclassCode.BootInterfaceSubclass,
120                         protocol: (byte)Core.USB.HID.Protocol.Keyboard,
121                         reportDescriptor: new Core.USB.HID.ReportDescriptor(ReportHidDescriptor)),
122                         configure: i =>
123                             i.WithEndpoint(
124                                 Direction.DeviceToHost,
125                                 EndpointTransferType.Interrupt,
126                                 maximumPacketSize: 0x4,
127                                 interval: 0xa,
128                                 createdEndpoint: out endpoint)));
129         }
130 
Reset()131         public void Reset()
132         {
133             USBCore.Reset();
134             modifiers = 0;
135             pressedKey = 0;
136         }
137 
Press(KeyScanCode scanCode)138         public void Press(KeyScanCode scanCode)
139         {
140             this.Log(LogLevel.Noisy, "Pressing {0}", scanCode);
141             if(!UpdateModifiers(scanCode, true))
142             {
143                 pressedKey = (byte)((int)scanCode & 0x7f);
144             }
145             SendPacket();
146         }
147 
Release(KeyScanCode scanCode)148         public void Release(KeyScanCode scanCode)
149         {
150             this.Log(LogLevel.Noisy, "Releasing {0}", scanCode);
151             if(!UpdateModifiers(scanCode, false))
152             {
153                 pressedKey = 0;
154             }
155             SendPacket();
156         }
157 
158         public USBDeviceCore USBCore { get; }
159 
UpdateModifiers(KeyScanCode scanCode, bool set)160         private bool UpdateModifiers(KeyScanCode scanCode, bool set)
161         {
162             if(scanCode >= KeyScanCode.CtrlL && scanCode <= KeyScanCode.WinR)
163             {
164                 BitHelper.SetBit(ref modifiers, (byte)((int)scanCode & 0x7), set);
165                 return true;
166             }
167             return false;
168         }
169 
SendPacket()170         private void SendPacket()
171         {
172             using(var p = endpoint.PreparePacket())
173             {
174                 p.Add(modifiers);
175                 p.Add(0); // reserved field
176                 p.Add(pressedKey);
177 
178                 // keypresses 2-6 are currently not supported
179                 p.Add(0);
180                 p.Add(0);
181                 p.Add(0);
182                 p.Add(0);
183                 p.Add(0);
184             }
185         }
186 
187         private byte modifiers;
188         private byte pressedKey;
189         private USBEndpoint endpoint;
190 
191         private readonly byte[] ReportHidDescriptor = new byte[]
192         {
193             0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x75, 0x01,
194             0x95, 0x08, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7,
195             0x15, 0x00, 0x25, 0x01, 0x81, 0x02, 0x95, 0x01,
196             0x75, 0x08, 0x81, 0x01, 0x95, 0x05, 0x75, 0x01,
197             0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02,
198             0x95, 0x01, 0x75, 0x03, 0x91, 0x01, 0x95, 0x06,
199             0x75, 0x08, 0x15, 0x00, 0x25, 0xff, 0x05, 0x07,
200             0x19, 0x00, 0x29, 0xff, 0x81, 0x00, 0xc0
201         };
202     }
203 }
204