├── .gitignore ├── package.json ├── source ├── player.js ├── polyfills.js ├── bookmarklet.js ├── keys.js └── preview.js ├── playRGBFile.js ├── preview.html ├── bookmarklet.min.js ├── LICENSE ├── installBookmarklet.html ├── readme.md └── recorder.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "keyboard", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "bookmarklet.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ws": "^0.7.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /source/player.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var os = require('os'); 3 | 4 | var stream = fs.createWriteStream('/dev/input/ckb1/cmd', { 5 | flags: 'w', 6 | encoding: 'ascii' 7 | }); 8 | 9 | function writeCommandToKeyboard(command) { 10 | stream.write(command + os.EOL); 11 | }; 12 | 13 | stream.on('error', function (error) { 14 | console.log(error); 15 | }); 16 | 17 | module.exports = { 18 | writeCommandToKeyboard: writeCommandToKeyboard 19 | }; -------------------------------------------------------------------------------- /playRGBFile.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var player = require('./source/player'); 3 | var fs = require('fs'); 4 | 5 | var rgbFile; 6 | 7 | if (process.argv.length < 3) { 8 | console.log('You need to pass a .rgb file as an argument to this program.'); 9 | console.log('node playRGBFile.js [filename.rgb]'); 10 | process.exit(); 11 | } else { 12 | rgbFile = process.argv[2]; 13 | } 14 | 15 | var commands = JSON.parse(fs.readFileSync(rgbFile)); 16 | 17 | var index = 0; 18 | setInterval(function () { 19 | index %= commands.length; 20 | player.writeCommandToKeyboard(commands[index]); 21 | index += 1; 22 | }, 1000/24); 23 | -------------------------------------------------------------------------------- /preview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Animation preview 6 | 22 | 23 | 24 |

Drag a .rgb file to preview the animation!

25 | 26 | 27 | -------------------------------------------------------------------------------- /bookmarklet.min.js: -------------------------------------------------------------------------------- 1 | javascript: var socket = new WebSocket("ws://localhost:8000"); var hovertarget; window.addEventListener("mousemove", function (event) {if (hovertarget) return; if (event.target.tagName === "CANVAS") {hovertarget = event.target; doCanvasThingy(hovertarget); } }); function doCanvasThingy(canvas) {var ctx = canvas.getContext("2d"); var renderTarget = document.createElement("canvas"); var renderTargetCtx = renderTarget.getContext("2d"); renderTarget.width = 26; renderTarget.height = 6; document.body.appendChild(renderTarget); renderTarget.setAttribute("style", "position: fixed; top: 0; left: 0; z-index: 9999;"); window.setInterval(function () {renderTargetCtx.drawImage(canvas, 0, 0, 26, 6); var data = renderTargetCtx.getImageData(0, 0, 26, 6); socket.send(JSON.stringify(Array.prototype.slice.call(data.data))); }, 1000/24); } -------------------------------------------------------------------------------- /source/polyfills.js: -------------------------------------------------------------------------------- 1 | Array.prototype.fill = function(value) { 2 | // Steps 1-2. 3 | if (this == null) { 4 | throw new TypeError('this is null or not defined'); 5 | } 6 | 7 | var O = Object(this); 8 | 9 | // Steps 3-5. 10 | var len = O.length >>> 0; 11 | 12 | // Steps 6-7. 13 | var start = arguments[1]; 14 | var relativeStart = start >> 0; 15 | 16 | // Step 8. 17 | var k = relativeStart < 0 ? 18 | Math.max(len + relativeStart, 0) : 19 | Math.min(relativeStart, len); 20 | 21 | // Steps 9-10. 22 | var end = arguments[2]; 23 | var relativeEnd = end === undefined ? 24 | len : end >> 0; 25 | 26 | // Step 11. 27 | var final = relativeEnd < 0 ? 28 | Math.max(len + relativeEnd, 0) : 29 | Math.min(relativeEnd, len); 30 | 31 | // Step 12. 32 | while (k < final) { 33 | O[k] = value; 34 | k++; 35 | } 36 | 37 | // Step 13. 38 | return O; 39 | }; -------------------------------------------------------------------------------- /source/bookmarklet.js: -------------------------------------------------------------------------------- 1 | var socket = new WebSocket("ws://localhost:8000"); 2 | 3 | var hovertarget; 4 | 5 | window.addEventListener("mousemove", function (event) { 6 | if (hovertarget) return; 7 | if (event.target.tagName === "CANVAS") { 8 | hovertarget = event.target; 9 | doCanvasThingy(hovertarget); 10 | } 11 | }); 12 | 13 | function doCanvasThingy(canvas) { 14 | var ctx = canvas.getContext("2d"); 15 | var renderTarget = document.createElement("canvas"); 16 | var renderTargetCtx = renderTarget.getContext("2d"); 17 | 18 | renderTarget.width = 26; 19 | renderTarget.height = 6; 20 | 21 | document.body.appendChild(renderTarget); 22 | renderTarget.setAttribute("style", "position: fixed; top: 0; left: 0; z-index: 9999;"); 23 | 24 | window.setInterval(function () { 25 | renderTargetCtx.drawImage(canvas, 0, 0, 26, 6); 26 | 27 | var data = renderTargetCtx.getImageData(0, 0, 26, 6); 28 | 29 | socket.send(JSON.stringify(Array.prototype.slice.call(data.data))); 30 | }, 1000/24); 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Martijn Brekelmans 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /installBookmarklet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Installing the bookmarklet 6 | 42 | 43 | 44 |
45 | rgbrecorder 46 | 47 |

Drag that huge circle to your bookmarks bar!

48 |
49 | 50 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Displaying shadertoy shaders on your corsair K70! 2 | 3 | ### LINUX ONLY, requires the ckb daemon to be running! 4 | 5 | There are multiple parts to this project. 6 | 7 | 1. .rgb animation files. These are saved shader animations, they can be played on your keyboard 8 | 2. Preview.html, this can be used to preview what animations will look like on your keyboard. Just drag and drop a .rgb file on the page 9 | 3. playRGBFile.js, a node.js script to play a .rgb animation file on your keyboard. The animation will loop when it's done, so they can be used as animated backgrounds for your k70 10 | `node playRGBFile.js ` 11 | 4. The bookmarklet, this is what's used to communicate a shader from the browser to the recorder, create a new bookmarklet with the code from `bookmarklet.min.js` and run it on any page with a canvas, then just hover over the canvas you want to record, and it'll connect to the recorded 12 | 5. recorder.js, run this to record a new animation. This needs to be running first to connect with the bookmarklet. `node recorder.js ` 13 | 14 | Steps to get this running: 15 | 16 | # 1. installation 17 | 18 | 1. Make sure you have node and npm installed 19 | 2. Make sure you are on Linux 20 | 3. Make sure you have [ckb](https://github.com/ccMSC/ckb) installed 21 | 4. Run `npm install` in this directory 22 | 5. Add `bookmarklet.min.js` as a bookmarklet on your browser. For firefox, you'll need to edit an option, see below 23 | 24 | Firefox configuration. There's a small issue with firefox and websockets on bookmarklets. This can easily be resolved by going to about:config, and toggling `network.websocket.allowInsecureFromHTTPS` to true. 25 | 26 | Additionally, you can make playRGBFile and the recorder executable using `chmod +x ./playRGBFile.js recorder.js`. Allowing you to omit `node` on the command line. `playRGBFile.js "example animations/forest.rgb" 27 | 28 | # 2. creating animations 29 | 30 | Visit shadertoy, and pick out a few of your favorite shaders. Then run `node recorder.js `. Click on the bookmarklet, and hover over the shader canvas. You can stop recording by pressing ctrl-c. 31 | 32 | # 3. Viewing and previewing animations 33 | 34 | An animation can be viewed on your keyboard using `node playRGBFile.js `. You can preview the animation with `preview.html` 35 | 36 | You can find example animations inside the aptly named, `example animations` folder. -------------------------------------------------------------------------------- /recorder.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require('fs'); 3 | var ws = require('ws'); 4 | var keys = require('./source/keys'); 5 | var player = require('./source/player'); 6 | require('./source/polyfills.js'); 7 | 8 | var options = { 9 | saveToFile: false, 10 | filterQuality: 5 11 | }; 12 | var channel = new ws.Server({port: 8000}); 13 | 14 | if (process.argv.length < 3) { 15 | console.log("Enter a filename for your animation save file if you want to save your animation"); 16 | console.log("node renderImage.js [filename]"); 17 | } else { 18 | var animationOutputFile = process.argv[2] + '.rgb'; 19 | options.saveToFile = true; 20 | } 21 | 22 | if (options.saveToFile) { 23 | // all keyboard frames get saved for export into a file 24 | var frames = []; 25 | } 26 | 27 | function hex(num) { 28 | var hexnum = num.toString(16); 29 | if (num < 16) 30 | hexnum = "0" + hexnum 31 | return hexnum; 32 | } 33 | 34 | function imageToCommand(imageBuffer) { 35 | var totalCommand = "rgb "; 36 | keys.forEach(function (key) { 37 | var pos = (Math.floor(key.x) + (Math.floor(key.y) * 26)) * 4; 38 | var r = imageBuffer[pos ]; 39 | var g = imageBuffer[pos + 1]; 40 | var b = imageBuffer[pos + 2]; 41 | var a = imageBuffer[pos + 3]; 42 | var hexval = hex(r) + hex(g) + hex(b); 43 | var command = key.ckbname + ":" + hexval; 44 | 45 | totalCommand += command + " "; 46 | }); 47 | return totalCommand; 48 | } 49 | 50 | function saveCommandToAnimationFile(command) { 51 | if (options.saveToFile) { 52 | frames.push(command); 53 | fs.writeFile(animationOutputFile, JSON.stringify(frames, null, 4), 'ascii', function (err) { 54 | if (err) console.log(err); 55 | }); 56 | } 57 | } 58 | 59 | // filtering using, I think what's called, a moving average filter. 60 | // Basically, it just takes `filterQuality` amount of previous frames 61 | // and averages them all out 62 | // a low filterquality means more noise 63 | // a higher filterquality means less noise, but is far less fit for fast moving animations 64 | var bufferHistory = new Array(options.filterQuality).fill(0); 65 | var count = 0; 66 | channel.on('connection', function (client) { 67 | console.log("connection!"); 68 | client.on('message', function (data) { 69 | var buffer = JSON.parse(data); 70 | 71 | // the count is a shitty hack to prevent filtering for the first `filterQuality` frames 72 | bufferHistory.shift() === 0? count++ : null; 73 | bufferHistory.push(buffer); 74 | 75 | var data = buffer; 76 | if (count === options.filterQuality) { 77 | data = buffer.map(function (color, i) { 78 | var average = color; 79 | for (var j = 0; j < options.filterQuality; j++) { 80 | average += bufferHistory[j][i]; 81 | } 82 | return Math.floor(average / (options.filterQuality + 1)); 83 | }); 84 | } 85 | 86 | var command = imageToCommand(data); 87 | 88 | saveCommandToAnimationFile(command); 89 | player.writeCommandToKeyboard(command); 90 | }); 91 | }); -------------------------------------------------------------------------------- /source/keys.js: -------------------------------------------------------------------------------- 1 | function key (name, x, y, ckbName, xsize, ysize) { 2 | var key = Object.create(null); 3 | 4 | key.name = name; 5 | key.ckbname = ckbName; 6 | key.x = x; 7 | key.y = y; 8 | key.xsize = xsize || 1; 9 | key.ysize = ysize || 1; 10 | key.color = "green"; 11 | 12 | return key; 13 | } 14 | 15 | module.exports = [ 16 | key("esc", 0, 0, "esc"), 17 | key("F1", 2, 0, "f1"), 18 | key("F2", 3, 0, "f2"), 19 | key("F3", 4, 0, "f3"), 20 | key("F4", 5, 0, "f4"), 21 | key("F5", 6.5, 0, "f5"), 22 | key("F6", 7.5, 0, "f6"), 23 | key("F7", 8.5, 0, "f7"), 24 | key("F8", 9.5, 0, "f8"), 25 | key("F9", 11, 0, "f9"), 26 | key("F10", 12, 0, "f10"), 27 | key("F11", 13, 0, "f11"), 28 | key("F12", 14, 0, "f12"), 29 | 30 | key("prs", 15.5, 0, "prtscn"), 31 | key("scrl", 16.5, 0, "scroll"), 32 | key("pause", 17.5, 0, "pause"), 33 | 34 | key("~", 0, 1.2, "grave"), 35 | key("1", 1, 1.2, "1"), 36 | key("2", 2, 1.2, "2"), 37 | key("3", 3, 1.2, "3"), 38 | key("4", 4, 1.2, "4"), 39 | key("5", 5, 1.2, "5"), 40 | key("6", 6, 1.2, "6"), 41 | key("7", 7, 1.2, "7"), 42 | key("8", 8, 1.2, "8"), 43 | key("9", 9, 1.2, "9"), 44 | key("0", 10, 1.2, "0"), 45 | key("_", 11, 1.2, "minus"), 46 | key("=", 12, 1.2, "equal"), 47 | key("back", 13, 1.2, "bspace", 2), 48 | 49 | key("ins", 15.5, 1.2, "ins"), 50 | key("home", 16.5, 1.2, "home"), 51 | key("p-up", 17.5, 1.2, "pgup"), 52 | 53 | key("nlock", 19, 1.2, "numlock"), 54 | key("/", 20, 1.2, "numslash"), 55 | key("*", 21, 1.2, "numstar"), 56 | key("-", 22, 1.2, "numminus"), 57 | 58 | key("tab", 0, 2.2, "tab", 1.5), 59 | key("q", 1.5, 2.2, "q"), 60 | key("w", 2.5, 2.2, "w"), 61 | key("e", 3.5, 2.2, "e"), 62 | key("r", 4.5, 2.2, "r"), 63 | key("t", 5.5, 2.2, "t"), 64 | key("y", 6.5, 2.2, "y"), 65 | key("u", 7.5, 2.2, "u"), 66 | key("i", 8.5, 2.2, "i"), 67 | key("o", 9.5, 2.2, "o"), 68 | key("p", 10.5, 2.2, "p"), 69 | key("[", 11.5, 2.2, "lbrace"), 70 | key("]", 12.5, 2.2, "rbrace"), 71 | key("|", 13.5, 2.2, "bslash", 1.5), 72 | 73 | key("delete", 15.5, 2.2, "del"), 74 | key("end", 16.5, 2.2, "end"), 75 | key("p-dwn", 17.5, 2.2, "pgdn"), 76 | 77 | key("n7", 19, 2.2, "num7"), 78 | key("n8", 20, 2.2, "num8"), 79 | key("n9", 21, 2.2, "num9"), 80 | key("n+", 22, 2.2, "numplus", 1, 2), 81 | 82 | key("caps", 0, 3.2, "caps", 2), 83 | key("a", 2, 3.2, "a"), 84 | key("s", 3, 3.2, "s"), 85 | key("d", 4, 3.2, "d"), 86 | key("f", 5, 3.2, "f"), 87 | key("g", 6, 3.2, "g"), 88 | key("h", 7, 3.2, "h"), 89 | key("j", 8, 3.2, "j"), 90 | key("k", 9, 3.2, "k"), 91 | key("l", 10, 3.2, "l"), 92 | key(";", 11, 3.2, "colon"), 93 | key("'", 12, 3.2, "quote"), 94 | key("enter", 13, 3.2, "enter", 2), 95 | 96 | key("n4", 19, 3.2, "num4"), 97 | key("n5", 20, 3.2, "num5"), 98 | key("n6", 21, 3.2, "num6"), 99 | 100 | key("lshift", 0, 4.2, "lshift", 2.3), 101 | key("z", 2.3, 4.2, "z"), 102 | key("x", 3.3, 4.2, "x"), 103 | key("c", 4.3, 4.2, "c"), 104 | key("v", 5.3, 4.2, "v"), 105 | key("b", 6.3, 4.2, "b"), 106 | key("n", 7.3, 4.2, "n"), 107 | key("m", 8.3, 4.2, "m"), 108 | key(",", 9.3, 4.2, "comma"), 109 | key(".", 10.3, 4.2, "dot"), 110 | key("/", 11.3, 4.2, "slash"), 111 | key("rshift", 12.3, 4.2, "rshift", 2.7), 112 | key("up", 16.5, 4.2, "up"), 113 | 114 | key("n1", 19, 4.2, "num1"), 115 | key("n2", 20, 4.2, "num2"), 116 | key("n3", 21, 4.2, "num3"), 117 | key("nenter", 22, 4.2, "numenter", 1, 2), 118 | 119 | key("lctrl", 0, 5.2, "lctrl", 1.5), 120 | key("lwin", 1.5, 5.2, "lwin", 1), 121 | key("lalt", 2.5, 5.2, "lalt", 1.2), 122 | key("space", 3.7, 5.2, "space", 6.6), 123 | key("ralt", 10.3, 5.2, "ralt", 1.2), 124 | key("rwin", 11.5, 5.2, "rwin"), 125 | key("ctx", 12.5, 5.2, "rmenu"), 126 | key("rctrl", 13.5, 5.2, "rctrl", 1.5), 127 | 128 | key("left", 15.5, 5.2, "left"), 129 | key("down", 16.5, 5.2, "down"), 130 | key("right", 17.5, 5.2, "right"), 131 | 132 | key("n0", 19, 5.2, "num0", 2), 133 | key("n.", 21, 5.2, "numdot") // why the hell is this key space? :o 134 | ]; -------------------------------------------------------------------------------- /source/preview.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function fullscreenCanvas() { 4 | "use strict"; 5 | var drawing = Object.create(null); 6 | var stylesheet = document.createElement("style"); 7 | var canvas = document.createElement("canvas"); 8 | var ctx = canvas.getContext("2d"); 9 | 10 | function resize() { 11 | var width = window.innerWidth; 12 | var height = window.innerHeight; 13 | 14 | canvas.width = width; 15 | canvas.height = height; 16 | } 17 | 18 | stylesheet.innerHTML = "* {margin: 0; padding: 0; overflow: hidden;}"; 19 | 20 | resize(); 21 | window.addEventListener("resize", resize); 22 | 23 | document.body.appendChild(stylesheet); 24 | document.body.appendChild(canvas); 25 | 26 | drawing.canvas = canvas; 27 | drawing.ctx = ctx; 28 | 29 | drawing.clear = function() { 30 | ctx.clearRect(0, 0, canvas.width, canvas.height); 31 | }; 32 | 33 | drawing.disableContextMenu = function() { 34 | window.addEventListener("contextmenu", function(e) { 35 | e.preventDefault(); 36 | return false; 37 | }); 38 | }; 39 | 40 | return drawing; 41 | } 42 | 43 | var _fullscreenCanvas = fullscreenCanvas(); 44 | 45 | var ctx = _fullscreenCanvas.ctx; 46 | var canvas = _fullscreenCanvas.canvas; 47 | 48 | var keySize = 44; 49 | var margin = 4; 50 | var gridsize = 20; 51 | var normalSize = 1; 52 | 53 | function pointInSquare(point, square) { 54 | return point.x > square.x2 && point.x < square.x1 && point.y > square.y2 && point.y < square.y1; 55 | } 56 | 57 | var keyboard = Object.create(null); 58 | 59 | function key(name, x, y, ckbName, xsize, ysize) { 60 | var key = Object.create(null); 61 | 62 | key.name = name; 63 | key.ckbname = ckbName; 64 | key.x = x; 65 | key.y = y; 66 | key.xsize = xsize || 1; 67 | key.ysize = ysize || 1; 68 | key.color = "black"; 69 | 70 | keyboard[ckbName] = key; 71 | 72 | return key; 73 | } 74 | 75 | key("esc", 0, 0, "esc"); 76 | key("F1", 2, 0, "f1"); 77 | key("F2", 3, 0, "f2"); 78 | key("F3", 4, 0, "f3"); 79 | key("F4", 5, 0, "f4"); 80 | key("F5", 6.5, 0, "f5"); 81 | key("F6", 7.5, 0, "f6"); 82 | key("F7", 8.5, 0, "f7"); 83 | key("F8", 9.5, 0, "f8"); 84 | key("F9", 11, 0, "f9"); 85 | key("F10", 12, 0, "f10"); 86 | key("F11", 13, 0, "f11"); 87 | key("F12", 14, 0, "f12"); 88 | 89 | key("prs", 15.5, 0, "prtscn"); 90 | key("scrl", 16.5, 0, "scroll"); 91 | key("pause", 17.5, 0, "pause"); 92 | 93 | key("~", 0, 1.2, "grave"); 94 | key("1", 1, 1.2, "1"); 95 | key("2", 2, 1.2, "2"); 96 | key("3", 3, 1.2, "3"); 97 | key("4", 4, 1.2, "4"); 98 | key("5", 5, 1.2, "5"); 99 | key("6", 6, 1.2, "6"); 100 | key("7", 7, 1.2, "7"); 101 | key("8", 8, 1.2, "8"); 102 | key("9", 9, 1.2, "9"); 103 | key("0", 10, 1.2, "0"); 104 | key("_", 11, 1.2, "minus"); 105 | key("=", 12, 1.2, "equal"); 106 | key("back", 13, 1.2, "bspace", 2); 107 | 108 | key("ins", 15.5, 1.2, "ins"); 109 | key("home", 16.5, 1.2, "home"); 110 | key("p-up", 17.5, 1.2, "pgup"); 111 | 112 | key("nlock", 19, 1.2, "numlock"); 113 | key("/", 20, 1.2, "numslash"); 114 | key("*", 21, 1.2, "numstar"); 115 | key("-", 22, 1.2, "numminus"); 116 | 117 | key("tab", 0, 2.2, "tab", 1.5); 118 | key("q", 1.5, 2.2, "q"); 119 | key("w", 2.5, 2.2, "w"); 120 | key("e", 3.5, 2.2, "e"); 121 | key("r", 4.5, 2.2, "r"); 122 | key("t", 5.5, 2.2, "t"); 123 | key("y", 6.5, 2.2, "y"); 124 | key("u", 7.5, 2.2, "u"); 125 | key("i", 8.5, 2.2, "i"); 126 | key("o", 9.5, 2.2, "o"); 127 | key("p", 10.5, 2.2, "p"); 128 | key("[", 11.5, 2.2, "lbrace"); 129 | key("]", 12.5, 2.2, "rbrace"); 130 | key("|", 13.5, 2.2, "bslash", 1.5); 131 | 132 | key("delete", 15.5, 2.2, "del"); 133 | key("end", 16.5, 2.2, "end"); 134 | key("p-dwn", 17.5, 2.2, "pgdn"); 135 | 136 | key("7", 19, 2.2, "num7"); 137 | key("8", 20, 2.2, "num8"); 138 | key("9", 21, 2.2, "num9"); 139 | key("+", 22, 2.2, "numplus", 1, 2); 140 | 141 | key("caps", 0, 3.2, "caps", 2); 142 | key("a", 2, 3.2, "a"); 143 | key("s", 3, 3.2, "s"); 144 | key("d", 4, 3.2, "d"); 145 | key("f", 5, 3.2, "f"); 146 | key("g", 6, 3.2, "g"); 147 | key("h", 7, 3.2, "h"); 148 | key("j", 8, 3.2, "j"); 149 | key("k", 9, 3.2, "k"); 150 | key("l", 10, 3.2, "l"); 151 | key(";", 11, 3.2, "colon"); 152 | key("'", 12, 3.2, "quote"); 153 | key("enter", 13, 3.2, "enter", 2); 154 | 155 | key("4", 19, 3.2, "num4"); 156 | key("5", 20, 3.2, "num5"); 157 | key("6", 21, 3.2, "num6"); 158 | 159 | key("lshift", 0, 4.2, "lshift", 2.3); 160 | key("z", 2.3, 4.2, "z"); 161 | key("x", 3.3, 4.2, "x"); 162 | key("c", 4.3, 4.2, "c"); 163 | key("v", 5.3, 4.2, "v"); 164 | key("b", 6.3, 4.2, "b"); 165 | key("n", 7.3, 4.2, "n"); 166 | key("m", 8.3, 4.2, "m"); 167 | key(",", 9.3, 4.2, "comma"); 168 | key(".", 10.3, 4.2, "dot"); 169 | key("/", 11.3, 4.2, "slash"); 170 | key("rshift", 12.3, 4.2, "rshift", 2.7); 171 | key("up", 16.5, 4.2, "up"); 172 | 173 | key("1", 19, 4.2, "num1"); 174 | key("2", 20, 4.2, "num2"); 175 | key("3", 21, 4.2, "num3"); 176 | key("enter", 22, 4.2, "numenter", 1, 2); 177 | 178 | key("lctrl", 0, 5.2, "lctrl", 1.5); 179 | key("lwin", 1.5, 5.2, "lwin", 1); 180 | key("lalt", 2.5, 5.2, "lalt", 1.2); 181 | key("space", 3.7, 5.2, "space", 6.6); 182 | key("ralt", 10.3, 5.2, "ralt", 1.2); 183 | key("rwin", 11.5, 5.2, "rwin"); 184 | key("ctx", 12.5, 5.2, "rmenu"); 185 | key("rctrl", 13.5, 5.2, "rctrl", 1.5); 186 | 187 | key("left", 15.5, 5.2, "left"); 188 | key("down", 16.5, 5.2, "down"); 189 | key("right", 17.5, 5.2, "right"); 190 | 191 | key("0", 19, 5.2, "num0", 2); 192 | key(".", 21, 5.2, "numdot"); 193 | 194 | keyboard.render = function () { 195 | ctx.clearRect(0, 0, canvas.width, canvas.height); 196 | Object.keys(keyboard).forEach(function (property, index, obj) { 197 | var key = keyboard[property]; 198 | drawKey(key); 199 | }); 200 | }; 201 | 202 | function disc(position, radius, ctx) { 203 | ctx.beginPath(); 204 | ctx.arc(position.x, position.y, radius, 0, Math.PI * 2); 205 | ctx.fill(); 206 | ctx.closePath(); 207 | } 208 | 209 | ctx.lineWidth = 2; 210 | 211 | function drawKey(key) { 212 | var keyWidth = keySize * key.xsize; 213 | var keyHeight = keySize * key.ysize; 214 | var x = key.x * keySize + 8; 215 | var y = key.y * keySize + 8; 216 | var textX = x + (keyWidth - ctx.measureText(key.name).width) / 2; 217 | var textY = y + (keyHeight - 14) / 2; 218 | 219 | ctx.fillStyle = key.color; 220 | ctx.strokeStyle = key.color; 221 | ctx.strokeRect(x, y, keyWidth, keyHeight); 222 | ctx.font = "14px Helvetica"; 223 | 224 | ctx.fillText(key.name, textX, textY); 225 | } 226 | 227 | var destroyOldAnimation = false; 228 | var animationRunning = false; 229 | 230 | function doAnimation(animationData) { 231 | animationRunning = true; 232 | function doFrame(frame) { 233 | // .rgb files are json arrays filled with commands that would get sent 234 | // to /dev/input/cbk1/cmd 235 | // they look like 236 | // "rgb : : ", including the space at the end 237 | // remove " rgb" from the command 238 | var data = frame.substr(4, frame.length - 1); 239 | var keydata = data.split(" "); 240 | keydata.pop(); // the last element is empty, get rid of it. 241 | keydata.forEach(function (keydatum) { 242 | var temp = keydatum.split(":"); 243 | var keyname = temp[0]; 244 | var color = temp[1]; 245 | keyboard[keyname].color = "#" + color; 246 | }); 247 | keyboard.render(); 248 | } 249 | 250 | var index = 0; 251 | var intervalId = window.setInterval(function () { 252 | // when a new .rgb file gets dropped in, get rid of the old animation, and play the new one. 253 | if (destroyOldAnimation) { 254 | window.clearInterval(intervalId); 255 | destroyOldAnimation = false; 256 | } 257 | index %= animationData.length; 258 | var frame = animationData[index]; 259 | doFrame(frame); 260 | index += 1; 261 | }, 1000 / 24); 262 | } 263 | 264 | keyboard.render(); 265 | 266 | window.addEventListener("dragenter", function (event) { 267 | document.body.setAttribute("class", "hovering"); 268 | event.stopPropagation(); 269 | event.preventDefault(); 270 | }); 271 | 272 | window.addEventListener("dragleave", function (event) { 273 | document.body.setAttribute("class", ""); 274 | event.stopPropagation(); 275 | event.preventDefault(); 276 | }); 277 | 278 | window.addEventListener("dragover", function (event) { 279 | event.stopPropagation(); 280 | event.preventDefault(); 281 | }); 282 | 283 | window.addEventListener("drop", function (event) { 284 | document.body.setAttribute("class", ""); 285 | event.stopPropagation(); 286 | event.preventDefault(); 287 | 288 | 289 | var colorAnimation = event.dataTransfer.files[0]; 290 | 291 | var reader = new FileReader(); 292 | reader.onload = function (element) { 293 | var data = JSON.parse(element.target.result); 294 | if (animationRunning) { 295 | destroyOldAnimation = true; 296 | } 297 | doAnimation(data); 298 | }; 299 | 300 | reader.readAsText(colorAnimation); 301 | }); --------------------------------------------------------------------------------