Hacking a BMD Speed Editor keypad into an X11 macropad
![Hacking a BMD Speed Editor keypad into an X11 macropad Hacking a BMD Speed Editor keypad into an X11 macropad](/pic/hacking_a_bmd_speed_editor_keypad_into_an_x11_macropad/VW9JbHd6ZTV4cDQ_.jpeg)
I have made enough money from my YouTube channel in the last ever to be able to afford a proper license for D Vinci resolve so thank you to everyone who's watched my videos I do appreciate it I appreciate all the nice things people have said in the comments I do intend to keep making them at semi-regular intervals I do intend them to be just as odd as before if possibly slightly better edited and remember the secret is always use an ad blocker but that's not the important bit the important bit is the DCI resolve came with one of these this is a speed editor it is a dedicated controller for the video editor it's got buttons LEDs and a nice spinny thing it's really rather nice the only problem is it only works in Da Vinci resolve it doesn't work in any other application let's see if there's something I can do about that it's actually a really nice thing it is plastic but it's good plastic and it's reassuringly heavy and stays put when you're working with it the keys are not double shot but they are well made and silk screened and a lot of them have Windows to make the underlying LED is visible a lot of these features are like toggle things that you select and having an indicator directly on the key is very handy the key switches themselves are cherry red linear which I like the spinny thing here is heavy metal with a rubber Friction band around the outside meaning that moving it rapidly but precisely is very easy and the indented thing on the top is also very useful because this is intended to be used with d Vinci resolve and nothing else all the features on the keys are hardcoded to various bits of editor functionality so that this Matrix here marked cam 1 cam 2 Etc selects a particular camera when using a multicam clip I can scroll through the timeline of video I'm editing with this thing and if I want to switch to a different camera I simply hold down the button and move the dial painted likewise I can trim the front and end of a clip by holding down Trim in moving the dial and letting go a lot of the keys have alternate functions which are labeled on the front of the keys which you get at through double tapping rather than modifiers so you know Escape undo very easy the downside is that all the functions on the keys are hardwired to features in the editor and nothing else so I mentioned the camera buttons thing is I don't do multicamera if youve seen my videos I tend to do picture and picture a lot this means I have to deal with multiple video tracks and it'd be really nice to be able to select a video track using one of the camera buttons but you can't and of of course it doesn't work in anything other than Da Vinci resolve the keyboard actually reports itself as being a usb hid keyboard except it won't produce events until it's authenticated itself with da Vinci resolve and likewise Da Vinci resolve won't accept keyboard input until it's authenticated itself with the keyboard which is really annoying I can think of places where it would be rather nice to be able to use this thing you know audio editing for example or even just as a general purpose macroad I mean the keyboard I use has lots of extra keys but it's not as nice as this okay let's start waffling and actually plug it in so I have this attached next to my real computer I can insert the plug lsusb and there it is so now I can do USB HD dump device is 1 E DB D AO e and I want to show the event stream so this is showing events that come from the device and if I press some keys nothing happens cuz the device is not awake yet okay now I know that running D Vinci resolve will wake the device up so I am going to use wi shark to sniff the USB conversation as that happens and see what it does so let me just set that up we are ready to go I have wire shark running let's fire up resolve now I haven't done this before Oh resolve is talking to the device I haven't done this before cuz I wanted capture the very first conversation in case it did a firmware upgrade so let's load a project and there we go it wants to do the upgrade so let's tell it to continue it's doing a thing do I need to do something here what's this button do ah okay do the upgrade right it's uploading with the USB dfu protocol which is a standard USB interface for doing firmware upgrades this is a good thing I should be able to pull the firmware image from this dump and it's finished close that now what does the knob do it is indeed winding back through the timeline and you can see the LEDs on the device have lit up let's put into shuttle mode it's a more sluggish than I was really hoping but you can see here in wire shark it's getting lots of communications from the device indicating that I did something on the knob that's what these things are here okay let's put that into the cut page which is where this is really supposed to work um a that's better so let's try trimming the output of this right that's not doing anything because I am not I'm not turning the knob while I'm pressing the button but I can see button press packets coming in when I press the key I get a packet with this value in it o for the first bite of the array and then when I release the key I get another event with a zero okay good that looks like a decent set of data so let's save that so now let's have a look at pulling it apart so I'm looking at the packet dump and I can scroll back to where the dfu protocol starts and here you can see it uploading well downloading by dfu terminology blocks to the keyboard Each of which is numbered and contains data if you look at the first ones we can see here the actual data it's 512 bytes per block this does not look like a embedded device firmware image this looks like a tough file the ustar root root is very suggestive that's very odd and for the second block I can see here this must be the the Manifest file described here so these are probably check sums of the files within the tar file block two yeah this is the signature for the Manifest manifest. Sig there's the data keyboard. in there's the data there's lots of data for that that's very very strange exporting all the data as a CSV file the dfu data is this big Fiel full of hex I can then do a bit of scripting to produce a output binary which turns out to be a tar file which is extremely interesting so I can decode it like so and now we can look at the different files the fact that this contains two big big binaries makes me wonder that this thing may have two microcontrollers in it however that does not look like the usual firware image you see on one of these things so I think that's compressed or encrypted yeah and this other one looks the same so I think we've got the firmware image but it's not actually going to do us any good so this is the Manifest file these are the lengths of the individual files and I bet this is a signature that looks like a load address so if they are encrypted the keys already built into the device so having done some searching around I believe that these files are in fact encrypted and decrypting them is basically going to be beyond my abilities unless I get access to the Keys it might be possible to read the firmware off the device I believe that this keyboard's based on an stm32 and they have pretty lousy firmware protection but hopefully I won't need to do that so I'm going to save that for later or hopefully never and move on so back into wire shark I have done a second dump this time focusing on the USB H stuff and you can definitely see patterns if I here we go here you can see the host which is the PC talking to the keyboard it is looking for report id1 and then we get the response back from the keyboard with the report ID one data now W shark is actually doing a fairly poor job of pausing the usb hid packet so the actual data is the bottom line here but you can see request response request response these which are side Channel asynchronous data packet thingies coming from the keyboard and then here we have the host talking to the keyboard it is sending a report of value six again the data is down here in the bottom line This is 10 bytes 60 all zeros and we get the response back basically that it worked the host then requests a report typ six back from the keyboard and we get a response and the keyboard responds with this and this repeats several times with weird random numbers so we get a host to keyboard of this keyboard to host 0602 followed by some random junk host to keyboard 0603 some random junk keyboard to host 0604 5802 and some zeros now this is the magic authentication sequence this is how the keyboard figures out that it's talking to a copy of Da Vinci resolve and how Da Vinci resolve figures out that it's talking to the keyboard the post starts with all zeros and then fetches back a random number from the keyboard the host then munges the magic number it's got and sends it back again in this one meanwhile the host sends a random magic number to the keyboard which is this one and the keyboard responds with a munged copy of the value this way the keyboard can verify that the host is perturbing its number correctly and the host can verify that the keyboard is perturbing its number correctly so when you reach the end the keyboard responds with this fixed value this means that both sides are happy and and the keyboard wakes up and then we start getting keyboard events via the asynchronous interrupt mechanism although weirdly immediately afterwards and there are some time stamps here so that's like 2 seconds apart the host authenticates again it's possible that this was me double clicking a project but I I don't think I did so in order to make the keyboard wake up we need to somehow fake enough of this to make the keyboard happy we don't actually care about the other way around we don't want to be able to verify that we're talking to a real Blackmagic speed editor we only want the speed Editor to think that it's talking to resolve which does simplify things slightly now there are two ways of dealing with this we can either write a program to send random numbers to the keyboard log all the responses we get and eventually we should have enough data to reverse engineer the keys or we go look on the internet to see if someone has already done this and that brings me to this because it seems that someone has already done this including deciphering what all the key codes are what various packets that you get from the keyboard look like where the LEDs are set and most importantly of all how the authentication challenge works and these are the keys which is slightly disappointing because then I don't get to do all this but only slightly because this looks really nasty I do not know where the original author got the keys from possibly they managed to dump the firmware maybe they just knew about Crypt analysis and reverse engineered them from probing the device I don't know but it does save me a great deal of time what you're looking at here is a boilerplate hid API setup in C++ I've taken one of their standard test programs and made it build it doesn't do anything currently because it's looking for the wrong device so let's see what I can do with this so the device is 1 EB for black magic and da o e for the speed editor um I do not want to actually write anything just yet so let's comment all that lot out okay it builds what happens when I run [Music] it excellent we have successfully connected to the device don't want that so now let's add a a loop where we read some data from the device you want to dump the result do want to know how much data we've read uh so it returns minus one on error otherwise the number of bytes red so we change that to that R equals minus1 the read should not fail because it should block should just wait for something to show up but let's try that so it builds we run it and I forgotten to turn the camera on but when I touch the keyboard nothing happens that is exactly what we expected okay now I want to do the authentication code that is over here in the python code I think the first thing I want to do is to send the blank packet and get the response back so I think that is going to be right 6 n 1 2 3 4 5 6 7 8 10 bytes and read back the result actually the results should be read by that Loop so we don't need that bit just yet can this actually work yes it can okay so what does this do nothing so what should be happening is that after sending this packet the keyboard should respond with its challenge so let's pull up wire shark again and run this and see what happens okay well it did something so I am not sure that's right I think we should be sending a feature report rather than just raw data so let me take a look at the hid API API for that yes there is actually a special function for doing this hid send feature report so H send feature report and there's change that to hid read feature report H get feature report it's called okay run the program interesting so this returned minus one and it exited let's just do a put that in yeah so why is that it should be blocking minus one on error I bet that we are in non-blocking mode doesn't say what what the default is but H set [Music] nonblocking false run it interesting okay so here is our feature report going out six and lots of zeros response here is our request for the feature get report going out and here is the keyboard's response and the the keyboard has not returned any data in fact the response we get back from the device has got status broken pipe which I think is the keyboard telling us there's nothing is listening on the other end okay so this is a wire shark dump of the real traffic so here goes the all the zeros going out the host immediately requests a report six oh I know what I'm doing we need to prefill the buffer to tell the keyboard which report we want so this is going to be not equals 6 1 = not let's try that huh fire up a new instance of wire shark this is a live capture we run our thing stop all right now we get to compare this with what DiVinci resolve tried the reason why the address has changed from 314 to 315 is because I unplugged the keyboard and plugged it back in again here we have the outgoing request for a report sorry outgoing request to set a report and here is the data so it looks like the same data for both programs okay so we then request the report get report 1 W Val 306 report type 6 report type feature that looks right the only difference is that the buffer length is wrong so so then this is returning with an error while this is saying success okay let's try changing this to a [Music] 10 see what happens there you go right it wanted the length of the buffer to be correct so we are retrieving the same report over and over again 61 uh the first bite the six IND IND Ates this is part of the handshake the one is how far through the handshake we've got so looking at this one we start the conversation with an outgoing 60 the keyboard replies with a 60 and its challenge we reply with a 61 the keyboard replies with a 62 we reply with a 63 key replies with a 64 so this fact that this is one is strange let me try that look at the beginning of the conversation yeah there we go the first time round we get a 60 the second time round we get a 61 because we should have replied to the get feature Report with a set feature report of our own okay let's get feature report handle challenge one 10 uh [Applause] just a little very brief error reporting error checking rather so change one right we have reset the O conversation although that doesn't seem to be working terribly well we then request The Challenge from the keyboard so looking at the python code we have done this now we want to send our challenge which we don't care about because we are not going to authenticate the keyboard so this is we get back a zero we want to send a one which we don't care about the keyboard will then respond with its response to our challenge let's just do this let's make that clear [Applause] so we now want to get [Music] the uh keyboard responses the wrong phrase [Applause] but it may be possible we can do do this in a different order cuz this order doesn't really make very much sense so that's worked this is the challenge the keyboard gave us we then send our Challenge and the keyboard responds with this we don't care about that we now need to calculate our response to the keyboards challenge that's this and therefore this code and the way it's doing this is It's paing the value as a little Indian long which makes perfect sense so that's [Music] so where is it here that's the python code we change this to this take 40 or OD mask okay so this code should be trivial to change uh should be 6040 roll left roll 8 and here we go so roll 8 here moves the entire word left by 56 bits that puts the bottom bite to the top this looks like a raw not roll I think this is rolling right by one bite and this just repeats the role by a certain number of items okay anyway we can just copy these directly that's straightforward enough t4t roll 8 [Music] 64 n like that wow I actually more and more glad I didn't have to figure this out myself I mean the way I would have done it would be to dump the firmware load it into gidra and then just copy the code there meaning that I wouldn't add have to understand what it all meant so this [Applause] should be relatively easy okay there is our code I mean it doesn't work doesn't compile but that's not declared that needs another set of parentheses that needs to be well formed there we go these need square brackets okay so now I have the routine that does the orth stuff I need to read the challenge the keyboard gave us calculate the response and send it so believe that's a two at this point keyboard response 60 we send 61 keyboard send 62 we send 63 so we want to write [Music] to the computed response of the challenge that the keyboard gave us and I've used these two functions which I will just go away and Implement okay so then we want to send feature report handle our response 10 and then we want to read the keyboard response and and print it and I'm well aware that this this code is suck I'm going to refactor it into something a bit nicer in a moment there we go okay now what happens when when we run it 64882 that looks promising because 6 for hex 58 is 88 2 yep the keyboard has responded saying that it's authenticated and we should be good to go so let's uncomment this this wants to be a hid [Music] read4 and let me just turn the other camera on okay and if I press a key on the keyboard woohoo we get some stuff out I mean it's not happy it's just failed but it worked what happens if I turn the knob yep knob turns so we're getting a report for for a report three a report three and smoon out here has very helpfully documented what the various reports do so key presses are a report for setting the LEDs we do a set Report with two this is four this one pause report three that's the jog dial pause report four is the keyboard PW report 7 is the battery status which as I intend to use this thing always plugged into to USB I don't really care about okay that's excellent we have the keyboard waking up not entirely sure why that failed but as this works so now would be a good time to rewrite a chunk of this and I have the nice shiny C++ version with the hi stuff pulled out into a class the authentication stuff pulled out into a nice small function so the main Loop just looks like this open the device authenticate the device repeatedly read packets from the device and print them the result looks like this it actually works very much like a USB keyboard so if I hold down like cam 1 you see we get a using my left hand to move the mouse we get a 04 packet with the key code 33 in it let go we get an 04 packet with nothing hold down cam 1 and cam 2 and we get a 33 and 34 33 34 and 35 let go cam 1 34 35 35 Etc so you get up to six keys of rollover so if I were to do that that gives us four okay come on go yeah pressing a seventh key causes a new event to arrive but without an actual key code in it spinning the wheel produces 03 packets with a 4 byte payload which is the relative position of the wheel it's actually possible to configure the keyboard into absolute mode and I believe you do that by writing out a o03 packet hang on I can actually try that so if I do device.
write and we want to send a three and a one and five zeros uh should be send Using read and write as names in CN C++ programs is a Teensy bit problematic because occasionally they are defined as macros and the standard headers okay well I've done something to it it's certainly not behaving as it did ah it's showing negative Z positive only that's interesting so we change that to mode two and we get this is the relative mode we were in previously I think so mode three interesting that's the same as mode one I wonder if these values have anything to do with it no okay that will require a little bit more experiment mentation but what I actually want is relative mode so not setting this value works that didn't reset hang on let's put this back the way it was one two three four five 3 four five 6 7 2 three yeah let's try that there we go back to normal that's a relief all right so we successfully talking to the keyboard the next step is to do something with all these keyboard values we're getting I have taken a break which is why the window layout slightly different and in the process I've realized I've made a bit of a mistake the API we're using to talk to the keyboard hid API only allows one program to have access to the device at a time this means that when my program is running which it currently is you can see the events coming in then D Vinci resolve which is the other program that wants access to it can't get at the keyboard therefore my original plan of using my program here to add missing functionality to the video editor just isn't going to work I can think of a few ways around this but they're all kind of complicated D Vinci resolve does have a public API to allow it to be scripted remotely so it might be possible to Simply deny access to the keyboard using my program here to D Vinci resolve and then I basically reimplement everything the video editor does using my program so that when the user presses a key my code here sends a command to the video editor and it does it I don't really want to do that the other approach is to create a fake USB device and my program here proxies between the video editor and the real keyboard this would allow me to inject key event which will be picked up by the video editor I think this is much more plausible but I don't actually know how to create virtual USB devices and I think that's getting a bit complicated so I'm actually going to take the third option which is to pretend that I was never intending to make this work in the video editor entirely and instead just treat it as a simple macroad because it's much easier and achieves quite a lot of what I actually wanted so let's start work on pausing the incoming packets we're going to deal with the keyboard first here are some incoming keyboard packets and each one is a 4 byte followed by up to six key presses as little Indian ins and each packet tells us the current state of the keyboard so this packet says that Keys 36 37 and 38 are pressed this packet says that keys 36 and 37 are pressed therefore we know that 38 must have been released we actually need to keep track of the state of each key in order to tell whether they've been pressed or released so that's actually not too hard let's do some code [Applause] [Music] so for each key press we want to get 16bit integer from the incoming packet data actually we want to get the set of keys which are currently pressed we are going to put together the current state of the keyboard so this is that that's not going to build cuz I haven't included set and we need to implement get in 16 so now when we run it and I press a key we get nothing the jog wheel still produces three packets so we are putting together this the new state of the keyboard we want to keep a global map that will be here somewhere this will be the previous state of the keyboard so now we can compare the two sets any key which is in the new state must have just gone down any key which is in the Old State but not the new state must have just come up so we want to do a set difference let me go and look up how this works in C++ again here it is it's pretty ugly because it's C++ I really hate all these STL iterator based functions but anyway we're going to do oh yeah we need to [Music] do algorithm set different between the first which is the Old State [Music] begin keep see end and the new keyboard State begin and no that's not right this doesn't just return an iterator it actually fills in another Vector so in this example is compute the set difference between first and second here and puts the result in this Vector that's foul I wonder if there's a better way of doing this using Rangers yes there is let's do that instead okay so we want a output set use press we are going [Music] to compute the set difference between the current state and the new state and put the result into our keys press State our keys pressed set okay is that going to build uh stood Rangers has not been declared yeah I forgot to tell the build system that this is a C++ 20 file and this needs to be probably stood inserter no having looked it up I forgot to put a begin in here wait what okay like that so now I should just be able to do for 6t press key down like that and I run it and of course it doesn't work yeah um I also forgot to do current keyboard State equals new keyboard state it is reporting key UPS not key Downs therefore these parameters are backwards so this wants to be new keyboard State current key state right good that's working uh we did get that zero at the top this is because I forgot to check that the key code might be zero so let's just pull that out like so press a key so we've got key downs and then then we do exactly the same thing but the other way around for released keys this was the thing that we' figured out earlier press release press release press release yep that is working let's try multiple Keys press press press press release release release release excellent the next step is to actually do something the simplest thing is to just map each of the speed editor keys to a x keyboard key you know like function keys or something and the simplest way of doing that is with a library called lib fake key that I am actually using for my narcissus cording keyboard program so let me just find the code like this and put that there much better okay and I also need to change the build script to include to link against lib fake key and then we can start using it now lib fake key works with X so we need to set up a x connection so we you're going to need this stuff as well and we need to do the ritual of actually opening the X connection so look at the main function here you go main open display null this code over here is in c not C++ we don't care about the X input selection but we do want to open a window and set up a fake key instance and this is important because as we are faking key up and key down events if the program quits with a key held down that key will never be released but we're going to do that here yeah you can't use a Lambda in the place of a function pointer if it captures something so that will work but I now cannot access these variables so I actually just need to make these globals including the current keyboard state so is [Applause] okay so our atexit code here now has access to the set that tells us which keys are press so that we can release them and in fact there seems to be a function that does exactly that in Li fake key so all we need is that okay so here we can actually start pressing and releasing keys and that's pretty straightforward we just do fake key press key Sim fake key modifier mask is zero currently no wait that should have the key Sim in it so let's just do this for now uh how do you release keys that releases all keys actually it looks like fake key release Here releases all pressed Keys that's not really what I want okay I'm going to look this up from looking at the implementation it seems that lib fake key only supports having one pressed key at a time which is not what I wanted so I think in order to fix this we're just going to do with go with if the keys release set is not empty release all the keys okay so run it and press a key get repeat Keys it works however with only one key press at a time rollover works very badly as you can see so I don't think that FIB fake key is going to work for me I think I'm going to have to do things the hard way which means basically reimplementing a lot of what this has got that's annoying let me look to see how this works turns out that lib fake key does actually have a way of doing this which is we're using the function fake key send key event you get to specify whether it's pressed or not but it's annoying because it works with key codes rather than with key Sims cu the way that X handles keyboards is really annoying okay so what we want to do is let's add a function press release key and this is going to take a key sim a pressed flag and the modifier Flags so what this needs to do is it needs to convert the key Sim to a key code and this is the display like that and if there is a matching key code for that key Sim we can then call fake lib what's it called press key send key send key event key send key event fake key code pressed Flags so here this becomes press release key k + 65 true zero and this becomes press release key k+ 65 false Z and that need to be Keys released and we need to change this because we're no longer using that bit of fake key so this wants to bek in current keyboard State okay let's see what this does press press yeah much better it's doing roll over correctly good of course it's not very useful because it's just sending random keys for a decent macroad you want to be able to send either function keys that can be trapped by particular applications for example I'm using KDE here so that I can add Global shortcuts or you want to run commands I could run commands directly from this program but it actually be easier to have this send special function key sequences and then the mapping from that to a command happens elsewhere so all this program does is it makes the keyboard work okay so we're going to change this from Key Sim to a Unicode code Point these get mapped to X key Sims by just adding a value onto the Unicode code point and there is actually here we go this value so we actually want to Pim equals uni code or with that and this should actually work in exactly the same way as before for except it's not okay it's not working because my current keyboard mapping does not have any mapping for these Unicode values lib fake key works around this by deliberately remapping part of the keyboard on the fly so that the key that gets pressed the virtual key that gets pressed has the desired mapping to the Unicode value it would be nice if we could use fake Keys implementation of this but we can't so we're going to have to copy this so we need to read the X keyboard mapping which happens here in fact we can probably use the implementation inside fake key no we can't because the fake key state is opaque which means we can't get access to any of this stuff so we're just going to have to do this the hard way great here is the actual implementation why can't I select that okay so this is fetching the the range of key codes the key codes are the platform specific codes that describe the keyboard then we fetch the key Sims the key Sims are the platform independent mappings so what we end up with is an array indexed by key code of which key Sim is defined for each key [Applause] code code okay so the my keyboard here the mapping has 255 minus 8 keys and we want a last modified key Zer we going to use the last 10 keys of the key map this is what lib fake key does here is the remapping code this this actually wants to go here so not that means that these variables all need to be global [Applause] and I'm going to rename those later to be to match our coding style in main this wants to be Q [Music] Sims this wants to be last modified keys so this will cycle around the last 10 Keys uh rather the last 10 slots in the keyboard map we then change the keyboard mapping so that the key that we want has that particular key Sim and then we update the keyboard map sync and our new key code is going to be index plus min ke code okay so now let's try it uh I'm not sure that's working b c d e no F press it again I get an F this suggests that this is not correct so in fact let's just do this again okay so that is now working once more and but now we're sending arbitrary uni code values so if I were to change this to 256 we want to change that as well otherwise we won't do we won't generate the right key up events and then press things yep there we go actual uni code stuff why have I done that well I want to send uni code because the standard X key Sim table that is the set of keys that X expects to be on the keyboard does have function keys it's got 35 my speed editor here has got 47 keys and I don't want to map them to function keys because my real keyboard already has function keys instead I am going to send special uni code values in the user defined area with luck I should be able to use these as keyboard shortcuts inside kwin and I know that they won't overlap with the real function keys so rather than have this take a uni code value I'm actually going to have a key num so key num will be the key that's going to be an INT the key number according to the speed editor and then we're going to add on the start of the uni code user area which I shall have to go and look up there are three user areas we're going to use this one so that's going to be o uh it's going to be Ox EO so now the reason why we put the mapping here is so that I don't have to change all these three all these three things in log step to that builds we run it and what happens when I press a key we get weird stuff that's good let me just fire up XF this displays X event uh nothing's happening oh that's cuz I just control C the program that's why okay let's run XV in this terminal and now try it there we go so so here's one of the events you can see here's the key Sim that we computed in our code up here there is the uni code value good good and huh uh I think that's all there was to it okay let's try go up to let's go up to the KDE shortcuts editor find kwin uh actually no let's find custom shortcuts see I create a console if I press shift and and this tools is actually one of the special keys on my real keyboard so let's add a trigger for U rxvt so oh blast these keys aren't working in the with this well I figured it out and it was really stupid system settings here will will not let you set printable characters to be shortcuts so I can't set a I can't set shift a but I can set out a so we apply and it works and because my keypad here is generating printable Unicode characters system settings was not accepting them what I can do is hold down out on the keyboard and then the main key and look it's bound a shortcut it looks weird because there's just some random glyph attached but it's worked there's only one slight problem doesn't work it looks like the extra keys are mapped straight to text entry and not going through whatever layer is doing the global shortcut stuff which is a shame so for this to be at all useful for keyboard shortcuts I need to make sure that this like works and also that the keys being generated by the macroad have a modifier set let's actually let's change this back to uh let's actually just change this to our 65 and take off the uni code modifier uh oh yeah in the process of figuring out what was going wrong I cleaned up a lot of this code we don't need to keep the entire key mapping because this function will let us change one key at a time okay so uh key code code X SIM key code not code yeah I did actually have this logic in there I am just putting it back again and this wants to be X key code e code with a Capital C okay so that will now a quck that that will now generate alphabetic keys so interesting why is that generating lower case oh yeah I haven't set any modifiers here all right so let's fire up system settings again shortcuts custom shortcuts console trigger right let's bind out B that's has detected it correctly apply alt B okay so it looks like you just can't bind keyboard shortcuts to keys with high uni code values so I am going to have to change this to generate alphabetic keys but because in order for the shortcuts to work they have to have modifiers anyway that's actually not a problem because they won't collide with any existing Keys provided we can generate the right set of modifiers of course luckily X has a lot of modifiers so we have shift shift lock is special control mod one which is Alton meta mod two which is numlock mod three which is composed keys and such as is Mod Five and mod four is super so I should be able to easily generate keys with more modifiers than makes sense so I can bind to control come on control shift super out a no same person is going to want to type that but then if I do control shift meta out a it works so let's just do that and luckily lib fake key will generate the modifiers for me so I just have to look up what those are the results are horrifyingly simple the way it does it is before typing any Keys it just fakes key presses to the modifiers which is something okay what can we do about this we need to press the modifiers before any key press happens and then release them after any key UPS happen so if Keys press is not empty let's actually do this the other way around if if the current keyboard state is empty no keys pressed but some keys are being being pressed press the modifiers then press or release the keys then if the current keyboard state is not empty and uh this actually wants to be new keyboard State and the new keyboard state is is empty release the keys right and the way we're going to do that is fake key is the key to the handle this is the key code key to key code we want to press contrl L true zero and this wants to be the same thing but release why is that not oh yeah so that works so now when we press something let me see b c d e f g h i j contrl j yep that works CR K L M N O P QR good so now let's do the same with out and why does that see out you see control [Applause] out meta [Applause] so what's this going to do that has in fact horribly screwed up my keyboard see if I press contrl shift windows out does that put it back yes it does good okay we can clean this up cont um XK not shift contrl L XK out L XK meta l so I'm actually a bit concerned this might not work work lib fake key has lots of weird codes that go and look at the modifier table I think it's trying to figure out which modifier Keys the map to which modifiers here it is cu if you happen to have is there a super there is a super yeah let's just try that and this wants to be true so that will just be B press and then we do four or key s in modifiers and then one of [Applause] these and this wants to be be key Sim and this in fact does not want to be an X so far up our tool stick in the background bring up X evev and then press the key and see what happens okay so I see that super got pressed then Zed got pressed then Super got released I think I need to do a flush because this code is doing that actually I think send key event does that yes let's put it in any way and see what happens and that wants to be display [Applause] press key there you go you can see if I move the mouse that will all scroll away you can see that control alt and super have all been released with key release events excellent so let's let's bring up this again custom shortcuts console trigger let's sign no modifiers just pressing a key meta controlb good so apply that press the button and there comes my console brilliant it's working now I didn't press shift for a reason because we've got 47 keys there aren't enough alphabetic keys so I'm going to have to use shift to distinguish between them the actual key numbers go 1 2 3 4 5 6 down this way then there's a gap then they start again from here and then to here I'll actually make this print the numbers again uh go if press print so this is key one oh I'm starting lots of consoles two that's not good right what that's done is that shift Control Alt C is not a known modifier and it's showing up in the console as a contrl c so I need to press control shift meta out and my keyboard goes back to normal um really I want these things to be ignored if they don't have a mapping now I thought I had meta C map to close the window apparently I don't it hasn't printed the C control meta C right control seems to take priority so C with no modifiers out C does nothing that did just close the window that was meta C so that's a bit problematic I've done some experimenting it looks like using combinations of meta and out with alphabetic keys Place Havoc with terminals so that's just not going to work so either I have to go back to using the Unicode stuff which I also know won't work or I'm going to have to use the function keys that's annoying I don't want to do that because the function keys are already used for various shortcuts does here we go restart debugging control shift F5 I think I can probably get away without meta and the function keys I think that's the only thing that will work anyway I also need to change my attex here to press release modifiers false release all the modifiers so this wants to be xkf is there an F not there is not XP XK F1 and the modifiers being set are basically all of them see how that goes press the key oh yeah you probably didn't see what happened there I'll need to check up on the video but it turns out that pressing control shift out and function key in Linux changes Virtual Console so let's just change our modifiers not to hold down control let's try that again all right press key receive number so there you can see the gibberish that's being produced on the terminal for the function keys plus the key numbers so this goes down to 17 over here and then this skips up to 49 31 okay clearly black magic have rearranged some of the keys this stes not a great deal of sense and these numbers are low as well here is smoot's table of what the keys actually mean and you can see that they go up monotonically there's a gap between smooth cut that's this one and then source timeline which are up here then shuttle Jog and scroll then syn bin here and the highest key is stop play at [Music] 3C AKA 60 so that is too many so I'll need to do some kind of clever mapping right key mapping actually this is easier than that this is a map between key number and the key Sim we want to generate and whether it shifted or not so key one is smart insert and this becomes a XK F1 false good and that compiles so I just need to go and add lots of entries to this be back in a moment here is the table and it even compiles so now we just need to do something with it so let's go down to press release key and I need [Music] to fetch the appropriate value out of the keymap if the key doesn't have a mapping ignore it so the key Sim is going to B first and if second is set press shift XK shift l pressed yes Flags actually I can use the flags we're not using here at all so I can just [Music] do second fake key mod shift why zero so get rid of that get rid of that get rid of that we should be ready to go no we're not uh should be [Music] second first yeah that's not great that could be clearer I should have made an actual structure for this okay that builds let's fire it up press a key did a thing fire up quick settings shortcuts custom shortcuts console trigger meta out F1 that's just what you wanted meta out shift F22 that's just what I wanted so I hit apply now if I hit stop play we get a console brilliant it now works as a macroad I haven't done the twiddly thing it's unclear what to map that to actually I'll have to think about that but it's now pretty late so I am going to take a break and come back I was vaguely thinking of trying to map the spinny thing to a pointer axis but it looks like X test doesn't support this you can fake motion and you can fake relative motion but you can only Supply X and Y AIS so I think I'm just going to go for the simple approach and simulate the mouse scroll wheel this works by just sending button four and button five for up and down so scrolling up button four scrolling down button five which is honestly not the smoothest but it should work all right so the way the spinny thing works is it sends signed relative deltas if I spin it clockwise you get positive numbers in these four bytes if I spin it anticlockwise I get negative numbers therefore you want to read keyboard packets uh Mouse packet wheel packets and the value is a signed in 32 which comes from 32 bits in starting from the from bite number two 0 1 2 don't know what this trailing one is but this zero appears to be the mouse I keep saying Mouse wheel mode so zero is relative so let's just print that [Applause] and spin the thing there we go negative numbers positive numbers the numbers are all quite big but no matter right what I'm going to do is keep track of the current wheel [Applause] position and update it every time we get a packet [Applause] in so now I spin it the right the number keeps going up I spin it to the left the number goes down again let me do this what I'm going to do is accumulate data until the wheel has moved a certain distance in either direction and then send a button event so [Music] um you position sent real position okay so if the total Delta is greater than a certain value the total Delta here is the difference between the last time we sent a button event and we are going to do real step let's call that thousand to start with uh where is it here we go then I want to figure out which button it is um so figuring out which is the larger number is actually not as easy as it might be because we're expecting this to roll over so we might start with a very large number and our number here is signed so let's go with this this is large so this is plus let's call it seven we add on [Applause] one what we end up with is let's make that two for purposes of example we end up with this which is actually a negative number I think that's minus 7 but it doesn't really matter so just comparing before and after this one and this one will not give us the right results so I want to get get rid of the ABS here because I need to preserve the sign we put the ABS in there so the button is going to be for Mouse up if the wheel is rotated anticlockwise if it's rotated anticlockwise then this means the wheel position is going to be smaller than scent wheel position therefore total Delta will be negative otherwise we want button five which is scroll down we also want to adjust sent wheel position accordingly so if total Delta is negative we're just sending one one button so we want to adjust that by that otherwise by that okay so now we can send the button event X test fake button event display button uh that's button it is pressed zero delay make sure that gets flushed and release the button however the wheel might rotate more than one wheel step in a packet so we want to do this in a loop I will actually flip this if it's less than wheel step just stop otherwise send the event so we simply loop around this sending button UPS or button Downs until the scent wheel position is close enough to wheel position and then stop let's give this a try um I will actually insert some tracing here I think okay let's give this a go scrolling the wheel and well my my console is scrolling that's that's actually working just fine the tracing is not very useful because it's printing the tracing into the window that is scrolling so let's move the mouse over here hang on I need to make sure right that's interest make sure the focus is at the end now I can scroll you can see that that's working it's it's very abrupt I need to increase wheel step and it's also doing weird things to my mouse Focus H okay let's change that to factor of 10 no hang on I want to make this smaller no I want to make it bigger go if you've been watching my channel for a while you'll know what I'm like with numbers so scroll that's better in fact it doesn't need to be very quick because this thing is so easy to spin rapidly let's get rid of the tracing yeah see the focus is not moving between Windows anymore I have to click no I don't I had to press a key before the focus would move [Music] um I think this is not working quite right I think the button is staying down for some reason's put that in let see if that makes a difference and let's slow this down again scroll okay that is too slow but it is working what about the mouse Focus yes that is better okay clearly I needed the that xync so let's just change that to that that's probably reasonable compromise yeah that feels good and let's try it in the web browser right the web browser Scrolls much more quickly now the interesting thing is that I actually use a x extension if I can find it X start here we go I use a feature called button scrolling what this does is it emulates the mouse Wheel by holding down a mouse button in this case I have it mapped to the right Mouse button so I can hold that down and drag and I get very natural scrolling it's much easier than actually using the mouse wheel this is scrolling with the mouse wheel this is just a single Mo motion of my hand and if I do it here I remember this as being slower possibly it's because I have the font size cranked up for the recording but that is clearly doing exactly the same thing as the wheel which I killed here so I reckon that's normal let me put that back to normal size yeah that's better okay so I was thinking that button scrolling might not have the problem where different applications would scroll different amounts but it clearly does good so let me just format that and you know what I think it's done no wait it's not done there are in fact two very important things I need to do the first one of which is of course to make the LEDs work that's done by sending a two report from the host to the device where is it set report output report to takes a single 32-bit bit field so that should [Music] be send to f f f and right after trying that again but this time with the keyboard actually plugged in you can see that all the LEDs light up so good the LEDs being handled the other important thing is that even though we've authenticated the device here the keyboard will actually forget its authentication periodically and you have to reauthenticate so we actually need to repeat this every 60 seconds or so which is fantastically annoying because now we have to do receive here with a timeout okay how does this work we calling hid read we want to put uh let me go look this up okay there is a hid read timeout that takes a millisecond parameter and it returns zero if nothing got R so we want to put time out milliseconds uh let's actually do that milliseconds time out we want to put the time out here if re is zero that means that no data is available so we want to error out but we're returning the data packet as our error so let's just add an exception head read timeout so time out exception okay so this now need to take the timeout and the thing is we need to accumulate the timeout because we can't just wait for 60 seconds because we may get multiple events within our minute window and if we're going to reset the timer after each one that's just not going to work so we're going to have to do int milliseconds timeout start that is Time Out milliseconds is 60 seconds time th uh time about millisecond yes I have to spell that correctly so this becomes that and we have to wrap this in a exception Handler exception e so if we get data this will return and we go through this code path if we get a timeout then we want to reset the [Applause] timer authenticate and go around again why does that want a parameter device authenticate device and let's just stick some tracing in here print indicated okay now this is not going to work because there's another bit we need that is after doing the receive we need to know how long we waited so we can adjust the timeout correspondingly and in fact let's actually do this differently if Ms time out is less than zero do that so if we get a timeout we just clear the timer to zero we clear the timer out to zero thus forcing this code path to be taken which both resets our timer and does the authentication and up here here we want to measure oops measure how long this receive took and subtract that from the timer now how to get the current time in C is tricky you've got to fiddle around with CPU clocks and stuff like that but there maybe a c new C++ standard way of doing it let me check yes there is it's part of the new Chrono Library so let's include that so what we need to do is and it's pretty annoying we need to fetch the current time before and after the operation and subtract them and the current time in the Chrono library is this ridiculous type but luckily this is C+ plus 20 so we can just do stood Chrono steady clock now if I remember correctly so corono steady clock now yep wrong button so that then lets us put this afterwards which means that we can then do this ridiculous thing to convert the two times into a microsc value which of course is not an INT it's a stood Chrono duration so I believe the way it works is it counts ticks and the stood Chrono microsc type uses a microsc as a tick and in fact we didn't want microsc we wanted milliseconds so I now need to get the number of ticks out of the representation so that's count so let's actually do int delay that [Applause] print delay for so then I can do Ms timeout minus equals delay okay so that will slowly count down this until it reaches zero and then it will reauthenticate so it builds we run so now if I do a thing we see over here I forgot to change the size of that terminal there we go we see over here that there was delay of 3,000 milliseconds then the key down happened then a delay of 56 millisecs and the key up happened and I will put that in as well run that again so oops in fact so you can see Ms timeout slowly going down you can see that when I spin the wheel we get events that are 24 and 15 milliseconds apart which is interesting seems to be very regular so now just have to wait until that times out and there it re authenticates excellent so now I can get rid of that and put that up here okay right now it's done in fact I'm not very satisfied with it there are too many compromises I don't like the way it uses function keys I don't like the way that the wheel doesn't map to a proper axis in the process of writing it I discovered U input which which is a Linux API for creating low-level input devices from software so that with this I can just you know create my own base level device inject event directly into it and then X or whand or whatever you're using will just pick up events from that it gets added as a x input device one of these so that would I think be rather more satisfactory I bet there will still be edge cases that we'll need dealing with but I think it would work better than what I currently have here in particular having to inject modifier Keys is just vile I do have a computer that is missing a meta key that's the Windows key and because I use meta a lot for my everyday work I have that mapped to something else because because we're injecting physical key code this doesn't know about that mapping so it's going to be pressing the wrong key but I'm not going to be using this program on that computer I'm only going to be using it on this desktop and it works fine here and there you have it Linux software for turning a Blackmagic speed editor into a macroad rather badly written at some point I am going to rewrite that use U input it won't work in any other systems but oh well incidentally all the lights are on because I've just had it plugged into m
2024-07-18 21:16